def validate(self, data): """Supplement jujubundlelib validation with some extra checks. """ validate_display_name(data, self) if 'series' not in data and 'inherits' not in data: self.info("No series defined") if 'services' in data: app_key = 'services' else: app_key = 'applications' if app_key in data: for svc, sdata in data[app_key].items(): if 'annotations' not in sdata: self.warn('%s: No annotations found, will render ' 'poorly in GUI' % svc) if ('charm' in sdata and not charm_url_includes_id(sdata['charm'] or '')): self.warn( '%s: charm URL should include a revision' % svc) else: if 'inherits' not in data: self.err("No applications defined")
def validate(self, data): """Supplement jujubundlelib validation with some extra checks. """ validate_display_name(data, self) if 'series' not in data and 'inherits' not in data: self.info("No series defined") if 'services' in data: app_key = 'services' else: app_key = 'applications' if app_key in data: for svc, sdata in data[app_key].items(): if 'annotations' not in sdata: self.warn('%s: No annotations found, will render ' 'poorly in GUI' % svc) if ('charm' in sdata and not charm_url_includes_id(sdata['charm'] or '')): self.warn('%s: charm URL should include a revision' % svc) else: if 'inherits' not in data: self.err("No applications defined")
def proof(self): lint = CharmLinter() charm_name = self.charm_path if os.path.isdir(charm_name): charm_path = charm_name else: charm_home = os.getenv('CHARM_HOME', '.') charm_path = os.path.join(charm_home, charm_name) if not os.path.isdir(charm_path): lint.crit("%s is not a directory, Aborting" % charm_path) return lint.lint, lint.exit_code hooks_path = os.path.join(charm_path, 'hooks') actions_path = os.path.join(charm_path, 'actions') yaml_path = os.path.join(charm_path, 'metadata.yaml') actions_yaml_file = os.path.join(charm_path, 'actions.yaml') try: yamlfile = open(yaml_path, 'r') try: charm = yaml.safe_load(yamlfile) except Exception as e: lint.crit('cannot parse ' + yaml_path + ":" + str(e)) return lint.lint, lint.exit_code yamlfile.close() for key in charm.keys(): if key not in KNOWN_METADATA_KEYS: lint.err("Unknown root metadata field (%s)" % key) charm_basename = os.path.basename(charm_path) if charm['name'] != charm_basename: msg = ("metadata name (%s) must match directory name (%s)" " exactly for local deployment.") % (charm['name'], charm_basename) lint.info(msg) # summary should be short if len(charm['summary']) > 72: lint.warn('summary should be less than 72') validate_display_name(charm, lint) validate_maintainer(charm, lint) validate_categories_and_tags(charm, lint) validate_storage(charm, lint) validate_devices(charm, lint) validate_series(charm, lint) validate_min_juju_version(charm, lint) validate_extra_bindings(charm, lint) validate_payloads(charm, lint) validate_terms(charm, lint) validate_resources(charm, lint) if not os.path.exists(os.path.join(charm_path, 'icon.svg')): lint.info("No icon.svg file.") else: # should have an icon.svg template_sha1 = hashlib.sha1() icon_sha1 = hashlib.sha1() try: with open(TEMPLATE_ICON, 'rb') as ti: template_sha1.update(ti.read()) icon_file = os.path.join(charm_path, 'icon.svg') with open(icon_file, 'rb') as ci: icon_sha1.update(ci.read()) if template_sha1.hexdigest() == icon_sha1.hexdigest(): lint.info("Includes template icon.svg file.") except IOError as e: lint.info("Error while opening %s (%s)" % (e.filename, e.strerror)) # Must have a hooks dir if not os.path.exists(hooks_path): lint.info("no hooks directory") # Must have a copyright file if not os.path.exists(os.path.join(charm_path, 'copyright')): lint.warn("no copyright file") # should have a readme root_files = os.listdir(charm_path) found_readmes = set() for filename in root_files: if filename.upper().find('README') != -1: found_readmes.add(filename) if len(found_readmes): if 'README.ex' in found_readmes: lint.warn("Includes template README.ex file") try: with open(TEMPLATE_README) as tr: bad_lines = [] for line in tr: if len(line) >= 40: bad_lines.append(line.strip()) for readme in found_readmes: readme_path = os.path.join(charm_path, readme) with open(readme_path) as r: readme_content = r.read() lc = 0 for l in bad_lines: if not len(l): continue lc += 1 if l in readme_content: err_msg = ('%s includes boilerplate: ' '%s') lint.warn(err_msg % (readme, l)) except IOError as e: lint.warn("Error while opening %s (%s)" % (e.filename, e.strerror)) else: lint.warn("no README file") subordinate = charm.get('subordinate', False) if type(subordinate) != bool: lint.err("subordinate must be a boolean value") # All charms should provide at least one thing provides = charm.get('provides') if provides is not None: lint.check_relation_hooks(provides, subordinate, hooks_path) else: if not subordinate: lint.info("all charms should provide at least one thing") if subordinate: try: requires = charm.get('requires') if requires is not None: found_scope_container = False for rel in six.itervalues(requires): if 'scope' in rel: if rel['scope'] == 'container': found_scope_container = True break if not found_scope_container: raise RelationError else: raise RelationError except RelationError: lint.err("subordinates must have at least one scope: " "container relation") else: requires = charm.get('requires') if requires is not None: lint.check_relation_hooks(requires, subordinate, hooks_path) peers = charm.get('peers') if peers is not None: lint.check_relation_hooks(peers, subordinate, hooks_path) if 'revision' in charm: lint.warn("Revision should not be stored in metadata.yaml " "anymore. Move it to the revision file") # revision must be an integer try: x = int(charm['revision']) if x < 0: raise ValueError except (TypeError, ValueError): lint.warn("revision should be a positive integer") lint.check_hook('install', hooks_path, recommended=True) lint.check_hook('start', hooks_path, recommended=True) lint.check_hook('stop', hooks_path, recommended=True) if os.path.exists(os.path.join(charm_path, 'config.yaml')): lint.check_hook('config-changed', hooks_path, recommended=True) else: lint.check_hook('config-changed', hooks_path) if os.path.exists(actions_yaml_file): with open(actions_yaml_file) as f: try: actions = yaml.safe_load(f.read()) except Exception as e: lint.crit('cannot parse {}: {}'.format( actions_yaml_file, e)) validate_actions(actions, actions_path, lint) except IOError: lint.err("could not find metadata file for " + charm_name) lint.exit_code = -1 # Should not have autogen test if os.path.exists(os.path.join(charm_path, 'tests', '00-autogen')): lint.warn('Includes template test file, tests/00-autogen') rev_path = os.path.join(charm_path, 'revision') if os.path.exists(rev_path): with open(rev_path, 'r') as rev_file: content = rev_file.read().rstrip() try: int(content) except ValueError: lint.err("revision file contains non-numeric data") lint.check_config_file(charm_path) return lint.lint, lint.exit_code
def proof(self): lint = self.linter charm_name = self.charm_path if os.path.isdir(charm_name): charm_path = charm_name else: charm_home = os.getenv('CHARM_HOME', '.') charm_path = os.path.join(charm_home, charm_name) if not os.path.isdir(charm_path): lint.crit("%s is not a directory, Aborting" % charm_path) return lint.lint, lint.exit_code hooks_path = os.path.join(charm_path, 'hooks') actions_path = os.path.join(charm_path, 'actions') yaml_path = os.path.join(charm_path, 'metadata.yaml') actions_yaml_file = os.path.join(charm_path, 'actions.yaml') layer_yaml_file = os.path.join(charm_path, 'layer.yaml') try: yamlfile = open(yaml_path, "rb") try: charm = yaml.safe_load(yamlfile) except Exception as e: lint.crit('cannot parse ' + yaml_path + ":" + str(e)) return lint.lint, lint.exit_code yamlfile.close() proof_extensions = {} try: with open(layer_yaml_file, "rb") as yamlfile: try: layer_data = yaml.safe_load(yamlfile) proof_extensions.update(layer_data.get('proof', {})) except Exception as e: lint.warn('cannot parse {}: {}'.format(layer_yaml_file, str(e))) except Exception: # not all charms have a layer.yaml pass for key in charm.keys(): if key not in KNOWN_METADATA_KEYS: lint.err("Unknown root metadata field (%s)" % key) for key in REQUIRED_METADATA_KEYS: if key not in charm: lint.err("Missing required metadata field (%s)" % key) charm_basename = os.path.basename(charm_path) if charm.get('name') != charm_basename: msg = ( "metadata name (%s) must match directory name (%s)" " exactly for local deployment.") % (charm.get('name'), charm_basename) lint.info(msg) # summary should be short if len(charm.get('summary', '')) > 72: lint.warn('summary should be less than 72') validate_display_name(charm, lint) validate_maintainer(charm, lint) validate_categories_and_tags(charm, lint) validate_storage(charm, lint, proof_extensions.get('storage')) validate_devices(charm, lint, proof_extensions.get('devices')) validate_series(charm, lint) validate_min_juju_version(charm, lint) validate_extra_bindings(charm, lint) validate_payloads(charm, lint, proof_extensions.get('payloads')) validate_terms(charm, lint) validate_resources(charm, lint, proof_extensions.get('resources')) if not os.path.exists(os.path.join(charm_path, 'icon.svg')): lint.info("No icon.svg file.") else: # should have an icon.svg template_sha1 = hashlib.sha1() icon_sha1 = hashlib.sha1() try: with open(TEMPLATE_ICON, 'rb') as ti: template_sha1.update(ti.read()) icon_file = os.path.join(charm_path, 'icon.svg') with open(icon_file, 'rb') as ci: icon_sha1.update(ci.read()) if template_sha1.hexdigest() == icon_sha1.hexdigest(): lint.info("Includes template icon.svg file.") except IOError as e: lint.info( "Error while opening %s (%s)" % (e.filename, e.strerror)) # Must have a hooks dir if not os.path.exists(hooks_path): lint.info("no hooks directory") # Must have a copyright file if not os.path.exists(os.path.join(charm_path, 'copyright')): lint.warn("no copyright file") # should have a readme root_files = os.listdir(charm_path) found_readmes = set() for filename in root_files: if filename.upper().find('README') != -1: found_readmes.add(filename) if len(found_readmes): if 'README.ex' in found_readmes: lint.warn("Includes template README.ex file") try: with open(TEMPLATE_README) as tr: bad_lines = [] for line in tr: if len(line) >= 40: bad_lines.append(line.strip()) for readme in found_readmes: readme_path = os.path.join(charm_path, readme) with open(readme_path) as r: readme_content = r.read() lc = 0 for l in bad_lines: if not len(l): continue lc += 1 if l in readme_content: err_msg = ('%s includes boilerplate: ' '%s') lint.warn(err_msg % (readme, l)) except IOError as e: lint.warn( "Error while opening %s (%s)" % (e.filename, e.strerror)) else: lint.warn("no README file") subordinate = charm.get('subordinate', False) if type(subordinate) != bool: lint.err("subordinate must be a boolean value") # All charms should provide at least one thing provides = charm.get('provides') if provides is not None: lint.check_relation_hooks(provides, subordinate, hooks_path) else: if not subordinate: lint.info("all charms should provide at least one thing") if subordinate: try: requires = charm.get('requires') if requires is not None: found_scope_container = False for rel in six.itervalues(requires): if 'scope' in rel: if rel['scope'] == 'container': found_scope_container = True break if not found_scope_container: raise RelationError else: raise RelationError except RelationError: lint.err("subordinates must have at least one scope: " "container relation") else: requires = charm.get('requires') if requires is not None: lint.check_relation_hooks(requires, subordinate, hooks_path) peers = charm.get('peers') if peers is not None: lint.check_relation_hooks(peers, subordinate, hooks_path) if 'revision' in charm: lint.warn("Revision should not be stored in metadata.yaml " "anymore. Move it to the revision file") # revision must be an integer try: x = int(charm['revision']) if x < 0: raise ValueError except (TypeError, ValueError): lint.warn("revision should be a positive integer") lint.check_hook('install', hooks_path, recommended=True) lint.check_hook('start', hooks_path, recommended=True) lint.check_hook('stop', hooks_path, recommended=True) if os.path.exists(os.path.join(charm_path, 'config.yaml')): lint.check_hook('config-changed', hooks_path, recommended=True) else: lint.check_hook('config-changed', hooks_path) if os.path.exists(actions_yaml_file): with open(actions_yaml_file, "rb") as f: try: actions = yaml.safe_load(f.read()) validate_actions(actions, actions_path, lint) except Exception as e: lint.crit('cannot parse {}: {}'.format( actions_yaml_file, e)) except IOError: lint.err("could not find metadata file for " + charm_name) lint.exit_code = -1 # Should not have autogen test if os.path.exists(os.path.join(charm_path, 'tests', '00-autogen')): lint.warn('Includes template test file, tests/00-autogen') rev_path = os.path.join(charm_path, 'revision') if os.path.exists(rev_path): with open(rev_path, 'r') as rev_file: content = rev_file.read().rstrip() try: int(content) except ValueError: lint.err("revision file contains non-numeric data") lint.check_config_file(charm_path) return lint.lint, lint.exit_code