appengine/runtime_builders/verify_manifest.py (85 lines of code) (raw):

#!/usr/bin/python # Copyright 2017 Google Inc. All rights reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import logging import sys import yaml import builder_util def main(): logging.getLogger().setLevel(logging.INFO) parser = argparse.ArgumentParser() parser.add_argument('--manifest', '-m', help='path to runtime.yaml manifest', required=True) args = parser.parse_args() verify_manifest(args.manifest) def verify_manifest(manifest_file): """Verify that the provided runtime manifest is valid before publishing. Aliases are provided for runtime 'names' that can be included in users' application configuration files: this method ensures that all the aliases can resolve to actual builder files. All builders and aliases are turned into nodes in a graph, which is then traversed to be sure that all nodes lead down to a builder node. Example formatting of the manifest, showing both an 'alias' and an actual builder file: runtimes: java: target: runtime: java-openjdk java-openjdk: target: file: gs://runtimes/java-openjdk-1234.yaml deprecation: message: "openjdk is deprecated." """ with open(manifest_file) as f: manifest = yaml.load(f) _verify_manifest_formatting(manifest) node_graph = _build_manifest_graph(manifest) _verify_manifest_graph(node_graph) def _verify_manifest_formatting(manifest): try: if 'schema_version' not in manifest: logging.error('Manifest does not contain schema_version!') sys.exit(1) for key, val in manifest.get('runtimes').iteritems(): file = val.get('target').get('file', '') if not file: continue if file.startswith('gs://'): logging.error('Builder file {0} should NOT be prefixed with ' 'GCS bucket prefix or bucket name!'.format(file)) sys.exit(1) file = builder_util.RUNTIME_BUCKET_PREFIX + file if not builder_util.file_exists(file): logging.error('File {0} not found in GCS!' .format(file)) sys.exit(1) except KeyError as ke: logging.error('Error encountered when verifying manifest: %s', ke) sys.exit(1) def _verify_manifest_graph(node_graph): for _, node in node_graph.items(): seen = set() child = node while True: seen.add(child) if not child.child: break elif child.child not in node_graph.keys(): logging.error('Non-existent alias provided for {0}: {1}' .format(child.name, child.child)) sys.exit(1) child = node_graph[child.child] if child in seen: logging.error('Circular dependency found in manifest! ' 'Check node {0}'.format(child)) sys.exit(1) if not child.isBuilder: logging.error('No terminating builder for alias {0}' .format(node.name)) sys.exit(1) def _build_manifest_graph(manifest): try: node_graph = {} for key, val in manifest.get('runtimes').iteritems(): target = val.get('target', {}) if not target: if 'deprecation' not in val: logging.error('No target or deprecation specified for ' 'runtime: %s', key) sys.exit(1) continue child = None isBuilder = 'file' in target.keys() if not isBuilder: child = target['runtime'] node = node_graph.get(key, {}) if not node: node_graph[key] = builder_util.Node(key, isBuilder, child) return node_graph except (KeyError, AttributeError) as ke: logging.error('Error encountered when verifying manifest: %s', ke) sys.exit(1) if __name__ == '__main__': main()