def _verify_package_yaml_structure(self):
        '''Verify package.yaml has the expected structure'''
        # https://developer.ubuntu.com/en/snappy/guides/packaging-format-apps/
        # lp:click doc/file-format.rst
        yp = yaml.dump(self.pkg_yaml, default_flow_style=False, indent=4)
        if not isinstance(self.pkg_yaml, dict):
            error("package yaml malformed:\n%s" % self.pkg_yaml)

        for f in self.snappy_required:
            if f not in self.pkg_yaml:
                error("could not find required '%s' in package.yaml:\n%s" %
                      (f, yp))
            elif f in ['name', 'version']:
                # make sure this is a string for other tests since
                # yaml.safe_load may make it an int, float or str
                self.pkg_yaml[f] = str(self.pkg_yaml[f])

        for f in self.snappy_optional:
            if f in self.pkg_yaml:
                if f in ["architecture", "frameworks"] and not \
                    (isinstance(self.pkg_yaml[f], str) or
                     isinstance(self.pkg_yaml[f], list)):
                    error("yaml malformed: '%s' is not str or list:\n%s" %
                          (f, yp))
                elif f in ["binaries", "services"] and not \
                        isinstance(self.pkg_yaml[f], list):
                    error("yaml malformed: '%s' is not list:\n%s" % (f, yp))
                elif f in ["icon", "source", "type", "vendor"] and not \
                        isinstance(self.pkg_yaml[f], str):
                    error("yaml malformed: '%s' is not str:\n%s" % (f, yp))
    def _has_policy_version(self, vendor, version):
        '''Determine if has specified policy version'''
        if not self.aa_policy:
            return None

        if vendor not in self.aa_policy:
            error("Could not find vendor '%s'" % vendor, do_exit=False)
            return False

        if str(version) not in self.aa_policy[vendor]:
            return False
        return True
    def _get_policy_versions(self, vendor):
        '''Get the supported AppArmor policy versions'''
        if not self.aa_policy:
            return None

        if vendor not in self.aa_policy:
            error("Could not find vendor '%s'" % vendor, do_exit=False)
            return None

        supported_policy_versions = []
        for i in self.aa_policy[vendor].keys():
            supported_policy_versions.append("%.1f" % float(i))

        return sorted(supported_policy_versions)
 def _create_dict(self, lst, topkey='name'):
     '''Converts list of dicts into dict[topkey][<the rest>]. Useful for
        conversions from yaml list to json dict'''
     d = dict()
     for entry in lst:
         if topkey not in entry:
             error("required field '%s' not present: %s" % (topkey, entry))
         name = entry[topkey]
         d[name] = dict()
         for key in entry:
             if key == topkey:
                 continue
             d[name][key] = entry[key]
     return d
    def _get_policy_groups(self, vendor, version, aa_type="all"):
        '''Get policy groups by type'''
        if not self.aa_policy:
            return None

        groups = []
        if vendor not in self.aa_policy:
            error("Could not find vendor '%s'" % vendor, do_exit=False)
            return groups

        if not self._has_policy_version(vendor, version):
            error("Could not find version '%s'" % version, do_exit=False)
            return groups

        v = str(version)
        if aa_type == "all":
            for k in self.aa_policy[vendor][v]['policy_groups'].keys():
                groups += self.aa_policy[vendor][v]['policy_groups'][k]
        else:
            groups = self.aa_policy[vendor][v]['policy_groups'][aa_type]

        return sorted(groups)
    def __init__(self,
                 fn,
                 review_type,
                 peer_hooks=None,
                 overrides=None,
                 peer_hooks_link=None):
        Review.__init__(self, fn, review_type, overrides=overrides)

        # The cr_* scripts only support 15.04 snaps (v1). Use sr_* scripts for
        # 16.04 (v2) or higher
        if not self.is_click and not self.is_snap1:
            return

        if not self.pkg_filename.endswith(".click") and \
                not self.pkg_filename.endswith(".snap"):
            if self.pkg_filename.endswith(".deb"):
                error("filename does not end with '.click', but '.deb' "
                      "instead. See http://askubuntu.com/a/485544/94326 for "
                      "how click packages are different.")
            error("filename does not end with '.click'")

        self.manifest = None
        self.click_pkgname = None
        self.click_version = None
        self.pkg_arch = []
        self.is_snap_oem = False
        self.peer_hooks = peer_hooks
        self.peer_hooks_link = peer_hooks_link

        if self.is_snap1:
            pkg_yaml = self._extract_package_yaml()
            if pkg_yaml:
                try:
                    self.pkg_yaml = yaml.safe_load(pkg_yaml)
                except Exception:
                    error(
                        "Could not load package.yaml. Is it properly formatted?"
                    )
                self._verify_package_yaml_structure()
            else:
                error("Could not load package.yaml.")

            #  default to 'app'
            if 'type' not in self.pkg_yaml:
                self.pkg_yaml['type'] = 'app'

            if 'architectures' in self.pkg_yaml:
                self.pkg_arch = self.pkg_yaml['architectures']
            elif 'architecture' in self.pkg_yaml:
                if isinstance(self.pkg_yaml['architecture'], str):
                    self.pkg_arch = [self.pkg_yaml['architecture']]
                elif isinstance(self.pkg_yaml['architecture'], list):
                    self.pkg_arch = self.pkg_yaml['architecture']
                else:
                    error(
                        "Could not load package.yaml: invalid 'architecture'")
            else:
                self.pkg_arch = ['all']

            if 'type' in self.pkg_yaml and self.pkg_yaml['type'] == 'oem':
                self.is_snap_oem = True

        if self.is_click or self.is_snap1:
            # Get some basic information from the control file
            control_file = self._extract_control_file()
            tmp = list(Deb822.iter_paragraphs(control_file))
            if len(tmp) != 1:
                error("malformed control file: too many paragraphs")
            control = tmp[0]
            self.click_pkgname = control['Package']
            self.click_version = control['Version']
            if self.is_click:
                if control['Architecture'] not in self.pkg_arch:
                    self.pkg_arch.append(control['Architecture'])
                self.pkgfmt["version"] = str(control['Click-Version'])

            # Parse and store the manifest
            manifest_json = self._extract_manifest_file()
            try:
                self.manifest = json.load(manifest_json)
            except Exception:
                error(
                    "Could not load manifest file. Is it properly formatted?")
            self._verify_manifest_structure()

            self.valid_frameworks = self._extract_click_frameworks()
    def _verify_manifest_structure(self):
        '''Verify manifest has the expected structure'''
        # lp:click doc/file-format.rst
        mp = pprint.pformat(self.manifest)
        if not isinstance(self.manifest, dict):
            error("manifest malformed:\n%s" % self.manifest)

        required = ["name", "version", "framework"]  # click required
        for f in required:
            if f not in self.manifest:
                error("could not find required '%s' in manifest:\n%s" %
                      (f, mp))
            elif not isinstance(self.manifest[f], str):
                error("manifest malformed: '%s' is not str:\n%s" % (f, mp))

        # optional click fields here (may be required by appstore)
        # http://click.readthedocs.org/en/latest/file-format.html
        optional = [
            "title", "description", "maintainer", "architecture",
            "installed-size", "icon"
        ]

        # https://developer.ubuntu.com/snappy/guides/packaging-format-apps/
        snappy_optional = ["ports", "source", "type"]

        for f in optional:
            if f in self.manifest:
                if f != "architecture" and \
                   not isinstance(self.manifest[f], str):
                    error("manifest malformed: '%s' is not str:\n%s" % (f, mp))
                elif f == "architecture" and not \
                    (isinstance(self.manifest[f], str) or
                     isinstance(self.manifest[f], list)):
                    error("manifest malformed: '%s' is not str or list:\n%s" %
                          (f, mp))

        # FIXME: this is kinda gross but the best we can do while we are trying
        # to support clicks and native snaps
        if 'type' in self.manifest and self.manifest['type'] == 'oem':
            if 'hooks' in self.manifest:
                error("'hooks' present in manifest with type 'oem'")
            # mock up something for other tests
            self.manifest['hooks'] = {'oem': {'reviewtools': True}}

        # Not required by click, but required by appstore. 'hooks' is assumed
        # to be present in other checks
        if 'hooks' not in self.manifest:
            error("could not find required 'hooks' in manifest:\n%s" % mp)
        if not isinstance(self.manifest['hooks'], dict):
            error("manifest malformed: 'hooks' is not dict:\n%s" % mp)
        # 'hooks' is assumed to be present and non-empty in other checks
        if len(self.manifest['hooks']) < 1:
            error("manifest malformed: 'hooks' is empty:\n%s" % mp)
        for app in self.manifest['hooks']:
            if not isinstance(self.manifest['hooks'][app], dict):
                error("manifest malformed: hooks/%s is not dict:\n%s" %
                      (app, mp))
            # let cr_lint.py handle required hooks
            if len(self.manifest['hooks'][app]) < 1:
                error("manifest malformed: hooks/%s is empty:\n%s" % (app, mp))

        for k in sorted(self.manifest):
            if k not in required + optional + snappy_optional + ['hooks']:
                # click supports local extensions via 'x-...', ignore those
                # here but report in lint
                if k.startswith('x-'):
                    continue
                error("manifest malformed: unsupported field '%s':\n%s" %
                      (k, mp))
 def _extract_manifest_file(self):
     '''Extract and read the manifest file'''
     m = os.path.join(self.unpack_dir, "DEBIAN/manifest")
     if not os.path.isfile(m):
         error("Could not find manifest file")
     return open_file_read(m)
Exemple #9
0
 def _extract_snap_yaml(self):  # pragma: nocover
     '''Extract and read the snappy 16.04 snap.yaml'''
     y = os.path.join(self.unpack_dir, "meta/snap.yaml")
     if not os.path.isfile(y):
         error("Could not find snap.yaml.")
     return open_file_read(y)
Exemple #10
0
    def __init__(self, fn, review_type, overrides=None):
        Review.__init__(self, fn, review_type, overrides=overrides)

        if not self.is_snap2:
            return

        snap_yaml = self._extract_snap_yaml()
        try:
            self.snap_yaml = yaml.safe_load(snap_yaml)
        except Exception:  # pragma: nocover
            error("Could not load snap.yaml. Is it properly formatted?")

        # If local_copy is None, then this will check the server to see if
        # we are up to date. However, if we are working within the development
        # tree, use it unconditionally.
        local_copy = None
        branch_fn = os.path.join(os.path.dirname(__file__),
                                 '../data/snapd-base-declaration.yaml')
        if os.path.exists(branch_fn):
            local_copy = branch_fn
        p = snapd_base_declaration.SnapdBaseDeclaration(local_copy)
        # FIXME: don't hardcode series
        self.base_declaration_series = "16"
        self.base_declaration = p.decl[self.base_declaration_series]

        # Add in-progress interfaces
        if self.base_declaration_series in self.inprogress_interfaces:
            rel = self.base_declaration_series
            for side in ['plugs', 'slots']:
                if side not in self.base_declaration or \
                        side not in self.inprogress_interfaces[rel]:
                    continue

                if side == 'plugs':
                    oside = 'slots'
                else:
                    oside = 'plugs'

                for iface in self.inprogress_interfaces[rel][side]:
                    if iface in self.base_declaration[side] or \
                            iface in self.base_declaration[oside]:
                        # don't override anything in the base declaration
                        continue
                    self.base_declaration[side][
                        iface] = self.inprogress_interfaces[rel][side][iface]

        # to simplify checks, gather up all the interfaces into one dict()
        for side in ['plugs', 'slots']:
            for k in self.base_declaration[side]:
                if k in self.interfaces_attribs:
                    self.interfaces[k] = self.interfaces_attribs[k]
                else:
                    self.interfaces[k] = {}

        # default to 'app'
        if 'type' not in self.snap_yaml:
            self.snap_yaml['type'] = 'app'

        if 'architectures' in self.snap_yaml:
            self.pkg_arch = self.snap_yaml['architectures']
        else:
            self.pkg_arch = ['all']

        self.is_snap_gadget = False
        if 'type' in self.snap_yaml and self.snap_yaml['type'] == 'gadget':
            self.is_snap_gadget = True

        # snapd understands:
        #   plugs:
        #     foo: null
        # but yaml.safe_load() treats 'null' as 'None', but we need a {}, so
        # we need to account for that.
        for k in ['plugs', 'slots']:
            if k not in self.snap_yaml:
                continue
            for iface in self.snap_yaml[k]:
                if not isinstance(self.snap_yaml[k], dict):
                    # eg, top-level "plugs: [ content ]"
                    error("Invalid top-level '%s' (not a dict)" %
                          k)  # pragma: nocover
                if self.snap_yaml[k][iface] is None:
                    self.snap_yaml[k][iface] = {}