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)
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)
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] = {}