Beispiel #1
0
    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")
Beispiel #2
0
    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")
Beispiel #3
0
    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
Beispiel #4
0
    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