def test_deserialize(self): hdr = Header(None) data = { "header": { "version": "1.0", } } hdr.deserialize(data) self.assertEqual(hdr.version, "1.0")
def test_deserialize(self): hdr = Header(None, "productmd.header") data = { "header": { "type": "productmd.header", "version": "1.0", } } hdr.deserialize(data) self.assertEqual(hdr.version, "1.0")
class Images(productmd.common.MetadataBase): def __init__(self): super(Images, self).__init__() self.header = Header(self) self.compose = Compose(self) self.images = {} def __getitem__(self, variant): return self.images[variant] def __delitem__(self, variant): del self.images[variant] def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} data["payload"]["images"] = {} self.compose.serialize(data["payload"]) for variant in self.images: for arch in self.images[variant]: for image_obj in self.images[variant][arch]: images = data["payload"]["images"].setdefault( variant, {}).setdefault(arch, []) image_obj.serialize(images) images.sort(key=lambda x: x["path"]) return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) for variant in data["payload"]["images"]: for arch in data["payload"]["images"][variant]: for image in data["payload"]["images"][variant][arch]: image_obj = Image(self) image_obj.deserialize(image) self.add(variant, arch, image_obj) self.header.set_current_version() def add(self, variant, arch, image): """ Assign an :class:`.Image` object to variant and arch. :param variant: compose variant UID :type variant: str :param arch: compose architecture :type arch: str :param image: image :type image: :class:`.Image` """ if arch not in productmd.common.RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) self.images.setdefault(variant, {}).setdefault(arch, set()).add(image)
class Images(productmd.common.MetadataBase): def __init__(self): super(Images, self).__init__() self.header = Header(self) self.compose = Compose(self) self.images = {} def __getitem__(self, variant): return self.images[variant] def __delitem__(self, variant): del self.images[variant] def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} data["payload"]["images"] = {} self.compose.serialize(data["payload"]) for variant in self.images: for arch in self.images[variant]: for image_obj in self.images[variant][arch]: images = data["payload"]["images"].setdefault(variant, {}).setdefault(arch, []) image_obj.serialize(images) images.sort(key=lambda x: x["path"]) return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) for variant in data["payload"]["images"]: for arch in data["payload"]["images"][variant]: for image in data["payload"]["images"][variant][arch]: image_obj = Image(self) image_obj.deserialize(image) self.add(variant, arch, image_obj) self.header.set_current_version() def add(self, variant, arch, image): """ Assign an :class:`.Image` object to variant and arch. :param variant: compose variant UID :type variant: str :param arch: compose architecture :type arch: str :param image: image :type image: :class:`.Image` """ if arch not in productmd.common.RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) self.images.setdefault(variant, {}).setdefault(arch, set()).add(image)
class Images(productmd.common.MetadataBase): def __init__(self): super(Images, self).__init__() self.header = Header(self, "productmd.images") self.compose = Compose(self) self.images = {} def __getitem__(self, variant): return self.images[variant] def __delitem__(self, variant): del self.images[variant] def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} data["payload"]["images"] = {} self.compose.serialize(data["payload"]) for variant in self.images: for arch in self.images[variant]: for image_obj in self.images[variant][arch]: images = data["payload"]["images"].setdefault( variant, {}).setdefault(arch, []) image_obj.serialize(images) images.sort(key=lambda x: x["path"]) return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) for variant in data["payload"]["images"]: for arch in data["payload"]["images"][variant]: for image in data["payload"]["images"][variant][arch]: image_obj = Image(self) image_obj.deserialize(image) if self.header.version_tuple <= (1, 1): self._add_1_1(data, variant, arch, image_obj) else: self.add(variant, arch, image_obj) self.header.set_current_version() def _add_1_1(self, data, variant, arch, image): if arch == "src": # move src under binary arches for variant_arch in data["payload"]["images"][variant]: if variant_arch == "src": continue self.add(variant, variant_arch, image) else: self.add(variant, arch, image) def add(self, variant, arch, image): """ Assign an :class:`.Image` object to variant and arch. :param variant: compose variant UID :type variant: str :param arch: compose architecture :type arch: str :param image: image :type image: :class:`.Image` """ if arch not in productmd.common.RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) if arch in ["src", "nosrc"]: raise ValueError( "Source arch is not allowed. Map source files under binary arches." ) if self.header.version_tuple >= (1, 1): # disallow adding a different image with same 'unique' # attributes. can't do this pre-1.1 as we couldn't truly # identify images before subvariant for checkvar in self.images: for checkarch in self.images[checkvar]: for curimg in self.images[checkvar][checkarch]: if identify_image(curimg) == identify_image( image) and curimg.checksums != image.checksums: raise ValueError( "Image {0} shares all UNIQUE_IMAGE_ATTRIBUTES with " "image {1}! This is forbidden.".format( image, curimg)) self.images.setdefault(variant, {}).setdefault(arch, set()).add(image)
class ComposeInfo(productmd.common.MetadataBase): """ This class only encapsulates other classes with actual data. """ def __init__(self): super(ComposeInfo, self).__init__() self.header = Header( self, "productmd.composeinfo") #: (:class:`.Header`) -- Metadata header self.compose = Compose(self) #: (:class:`.Compose`) -- Compose details self.release = Release(self) #: (:class:`.Release`) -- Release details self.base_product = BaseProduct( self ) #: (:class:`.BaseProduct`) -- Base product details (optional) self.variants = Variants( self) #: (:class:`.Variants`) -- release variants self.validate() self.header.set_current_version() def __str__(self): result = self.release_id if self.compose.label: result += " (%s)" % self.compose.label return result def __cmp__(self, other): result = cmp(self.release, other.release) if result != 0: return result result = cmp(self.base_product, other.base_product) if result != 0: return result result = cmp(self.compose, other.compose) if result != 0: return result return 0 def get_release_id(self, major_version=False): if major_version: result = "%s-%s" % (self.release.short, self.release.major_version) else: result = "%s-%s" % (self.release.short, self.release.version) if self.release.is_layered: result += "-%s-%s" % (self.base_product.short, self.base_product.version) return result @property def release_id(self): return self.get_release_id() def create_compose_id(self): result = "%s-%s%s" % (self.release.short, self.release.version, self.release.type_suffix) if self.release.is_layered: result += "-%s-%s%s" % (self.base_product.short, self.base_product.version, self.base_product.type_suffix) rhel5 = (self.release.short == "RHEL" and self.release.major_version == "5") rhel5 &= (self.base_product.short == "RHEL" and self.base_product.major_version == "5") if rhel5: # HACK: there are 2 RHEL 5 composes -> need to add Server or Client variant to compose ID if self.variants.variants: variant = sorted(self.variants.variants)[0] if variant in ("Client", "Server"): result += "-%s" % variant result += "-%s%s.%s" % (self.compose.date, self.compose.type_suffix, self.compose.respin) return result def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} self.compose.serialize(data["payload"]) self.release.serialize(data["payload"]) if self.release.is_layered: self.base_product.serialize(data["payload"]) self.variants.serialize(data["payload"]) return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) self.release.deserialize(data["payload"]) if self.release.is_layered: self.base_product.deserialize(data["payload"]) self.variants.deserialize(data["payload"]) self.header.set_current_version() def __getitem__(self, name): return self.variants[name] def get_variants(self, *args, **kwargs): return self.variants.get_variants(*args, **kwargs)
class ComposeInfo(productmd.common.MetadataBase): """ This class only encapsulates other classes with actual data. """ def __init__(self): super(ComposeInfo, self).__init__() self.header = Header(self) #: (:class:`.Header`) -- Metadata header self.compose = Compose(self) #: (:class:`.Compose`) -- Compose details self.release = Release(self) #: (:class:`.Release`) -- Release details self.base_product = BaseProduct(self) #: (:class:`.BaseProduct`) -- Base product details (optional) self.variants = Variants(self) #: (:class:`.Variants`) -- release variants self.validate() self.header.set_current_version() def __str__(self): result = self.release_id if self.compose.label: result += " (%s)" % self.compose.label return result def __cmp__(self, other): result = cmp(self.release, other.product) if result != 0: return result result = cmp(self.base_product, other.base_product) if result != 0: return result result = cmp(self.compose, other.compose) if result != 0: return result return 0 def get_release_id(self, major_version=False): if major_version: result = "%s-%s" % (self.release.short, self.release.major_version) else: result = "%s-%s" % (self.release.short, self.release.version) if self.release.is_layered: result += "-%s-%s" % (self.base_product.short, self.base_product.version) return result @property def release_id(self): return self.get_release_id() def create_compose_id(self): result = "%s-%s" % (self.release.short, self.release.version) if self.release.is_layered: result += "-%s-%s" % (self.base_product.short, self.base_product.version) rhel5 = (self.release.short == "RHEL" and self.release.major_version == "5") rhel5 &= (self.base_product.short == "RHEL" and self.base_product.major_version == "5") if rhel5: # HACK: there are 2 RHEL 5 composes -> need to add Server or Client variant to compose ID if self.variants.variants: variant = sorted(self.variants.variants)[0] if variant in ("Client", "Server"): result += "-%s" % variant result += "-%s%s.%s" % (self.compose.date, self.compose.type_suffix, self.compose.respin) return result def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} self.compose.serialize(data["payload"]) self.release.serialize(data["payload"]) if self.release.is_layered: self.base_product.serialize(data["payload"]) self.variants.serialize(data["payload"]) return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) self.release.deserialize(data["payload"]) if self.release.is_layered: self.base_product.deserialize(data["payload"]) self.variants.deserialize(data["payload"]) self.header.set_current_version() def __getitem__(self, name): return self.variants[name] def get_variants(self, *args, **kwargs): return self.variants.get_variants(*args, **kwargs)
class Modules(productmd.common.MetadataBase): def __init__(self): super(Modules, self).__init__() self.header = Header(self, "productmd.modules") self.compose = Compose(self) self.modules = {} def __getitem__(self, variant): return self.modules[variant] def __delitem__(self, variant): del self.modules[variant] @staticmethod def parse_uid(uid): if not isinstance(uid, six.string_types): raise ValueError("Uid has to be string: %s" % uid) # pattern to parse uid MODULE_NAME:STREAM[:VERSION[:CONTEXT]] UID_RE = re.compile( r"^(.*/)?(?P<module_name>[^:]+):(?P<stream>[^:]+)(:(?P<version>[^:]+))?(:(?P<context>[^:]+))?$" ) matched = UID_RE.match(uid) if matched: uid_dict = matched.groupdict() else: raise ValueError("Invalid uid: %s" % uid) if uid_dict["version"] is None: uid_dict["version"] = "" if uid_dict["context"] is None: uid_dict["context"] = "" return uid_dict def _check_uid(self, uid): if not isinstance(uid, six.string_types): raise ValueError("Uid has to be string: %s" % uid) if ":" not in uid: raise ValueError("Missing stream in uid: %s" % uid) try: uid_dict = self.parse_uid(uid) except ValueError: raise ValueError("Invalid uid format: %s" % uid) uid = "%(module_name)s:%(stream)s" % uid_dict uid += ":%s" % uid_dict['version'] if uid_dict['version'] else "" uid += ":%s" % uid_dict['context'] if uid_dict['context'] else "" return uid, uid_dict def serialize(self, parser): self.validate() data = parser self.header.serialize(data) data["payload"] = {} self.compose.serialize(data["payload"]) data["payload"]["modules"] = self.modules return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) self.modules = data["payload"]["modules"] self.validate() def add(self, variant, arch, uid, koji_tag, modulemd_path, category, rpms): if not variant: raise ValueError("Non-empty variant is expected") if arch not in RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) if category not in SUPPORTED_CATEGORIES: raise ValueError("Invalid category value: %s" % category) uid, uid_dict = self._check_uid(uid) name = uid_dict["module_name"] stream = uid_dict["stream"] version = uid_dict["version"] context = uid_dict["context"] if modulemd_path.startswith("/"): raise ValueError("Relative path expected: %s" % modulemd_path) if not koji_tag: raise ValueError("Non-empty 'koji_tag' is expected") for param_name, param in { "variant": variant, "koji_tag": koji_tag, "modulemd_path": modulemd_path }.items(): if not param: raise ValueError("Non-empty '%s' is expected" % param_name) if not isinstance(rpms, (list, tuple)): raise ValueError("Wrong type of 'rpms'") arches = self.modules.setdefault(variant, {}) uids = arches.setdefault(arch, {}) metadata = uids.setdefault(uid, {}) metadata["metadata"] = { "uid": uid, "name": name, "stream": stream, "version": version, "context": context, "koji_tag": koji_tag, } metadata.setdefault("modulemd_path", {})[category] = modulemd_path metadata.setdefault("rpms", []).extend(list(rpms))
class Rpms(productmd.common.MetadataBase): def __init__(self): super(Rpms, self).__init__() self.header = Header(self, "productmd.rpms") self.compose = Compose(self) self.rpms = {} def __getitem__(self, variant): return self.rpms[variant] def __delitem__(self, variant): del self.rpms[variant] def _check_nevra(self, nevra): if ":" not in nevra: raise ValueError("Missing epoch in N-E:V-R.A: %s" % nevra) try: nevra_dict = productmd.common.parse_nvra(nevra) except ValueError: raise ValueError("Invalid N-E:V-R.A: %s" % nevra) nevra_dict["epoch"] = nevra_dict["epoch"] or 0 nevra = "%(name)s-%(epoch)s:%(version)s-%(release)s.%(arch)s" % nevra_dict return nevra, nevra_dict def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} data["payload"]["rpms"] = {} self.compose.serialize(data["payload"]) data["payload"]["rpms"] = self.rpms return data def deserialize(self, data): self.header.deserialize(data) if self.header.version_tuple <= (0, 3): self.deserialize_0_3(data) else: self.deserialize_1_0(data) self.validate() self.header.set_current_version() def deserialize_0_3(self, data): self.compose.deserialize(data["payload"]) payload = data["payload"]["manifest"] self.rpms = {} for variant in payload: for arch in payload[variant]: if arch == "src": continue for srpm_nevra, rpms in payload[variant][arch].items(): srpm_data = payload[variant].get("src", {}).get(srpm_nevra, None) for rpm_nevra, rpm_data in rpms.items(): category = rpm_data["type"] if category == "package": category = "binary" self.add(variant, arch, rpm_nevra, rpm_data["path"], rpm_data["sigkey"], category, srpm_nevra) if srpm_data is not None: self.add(variant, arch, srpm_nevra, srpm_data["path"], srpm_data["sigkey"], "source") def deserialize_1_0(self, data): self.compose.deserialize(data["payload"]) self.rpms = data["payload"]["rpms"] def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None): """ Map RPM to to variant and arch. :param variant: compose variant UID :type variant: str :param arch: compose architecture :type arch: str :param nevra: name-epoch:version-release.arch :type nevra: str :param sigkey: sigkey hash :type sigkey: str or None :param category: RPM category, one of binary, debug, source :type category: str :param srpm_nevra: name-epoch:version-release.arch of RPM's SRPM :type srpm_nevra: str """ if arch not in productmd.common.RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) if arch in ["src", "nosrc"]: raise ValueError( "Source arch is not allowed. Map source files under binary arches." ) if category not in SUPPORTED_CATEGORIES: raise ValueError("Invalid category value: %s" % category) if path.startswith("/"): raise ValueError("Relative path expected: %s" % path) nevra, nevra_dict = self._check_nevra(nevra) if category == "source" and srpm_nevra is not None: raise ValueError( "Expected blank srpm_nevra for source package: %s" % nevra) if category != "source" and srpm_nevra is None: raise ValueError("Missing srpm_nevra for package: %s" % nevra) if (category == "source") != (nevra_dict["arch"] in ("src", "nosrc")): raise ValueError("Invalid category/arch combination: %s/%s" % (category, nevra)) if sigkey is not None: sigkey = sigkey.lower() if srpm_nevra: srpm_nevra, _ = self._check_nevra(srpm_nevra) else: srpm_nevra = nevra arches = self.rpms.setdefault(variant, {}) srpms = arches.setdefault(arch, {}) rpms = srpms.setdefault(srpm_nevra, {}) rpms[nevra] = {"sigkey": sigkey, "path": path, "category": category}
class Images(productmd.common.MetadataBase): def __init__(self): super(Images, self).__init__() self.header = Header(self, "productmd.images") self.compose = Compose(self) self.images = {} def __getitem__(self, variant): return self.images[variant] def __delitem__(self, variant): del self.images[variant] def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} data["payload"]["images"] = {} self.compose.serialize(data["payload"]) for variant in self.images: for arch in self.images[variant]: for image_obj in self.images[variant][arch]: images = data["payload"]["images"].setdefault(variant, {}).setdefault(arch, []) image_obj.serialize(images) images.sort(key=lambda x: x["path"]) return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) for variant in data["payload"]["images"]: for arch in data["payload"]["images"][variant]: for image in data["payload"]["images"][variant][arch]: image_obj = Image(self) image_obj.deserialize(image) if self.header.version_tuple <= (1, 1): self._add_1_1(data, variant, arch, image_obj) else: self.add(variant, arch, image_obj) self.header.set_current_version() def _add_1_1(self, data, variant, arch, image): if arch == "src": # move src under binary arches for variant_arch in data["payload"]["images"][variant]: if variant_arch == "src": continue self.add(variant, variant_arch, image) else: self.add(variant, arch, image) def add(self, variant, arch, image): """ Assign an :class:`.Image` object to variant and arch. :param variant: compose variant UID :type variant: str :param arch: compose architecture :type arch: str :param image: image :type image: :class:`.Image` """ if arch not in productmd.common.RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) if arch in ["src", "nosrc"]: raise ValueError("Source arch is not allowed. Map source files under binary arches.") self.images.setdefault(variant, {}).setdefault(arch, set()).add(image)
class Rpms(productmd.common.MetadataBase): def __init__(self): super(Rpms, self).__init__() self.header = Header(self) self.compose = Compose(self) self.rpms = {} def __getitem__(self, variant): return self.rpms[variant] def __delitem__(self, variant): del self.rpms[variant] def _check_nevra(self, nevra): if ":" not in nevra: raise ValueError("Missing epoch in N-E:V-R.A: %s" % nevra) try: nevra_dict = productmd.common.parse_nvra(nevra) except ValueError: raise ValueError("Invalid N-E:V-R.A: %s" % nevra) nevra_dict["epoch"] = nevra_dict["epoch"] or 0 nevra = "%(name)s-%(epoch)s:%(version)s-%(release)s.%(arch)s" % nevra_dict return nevra, nevra_dict def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} data["payload"]["rpms"] = {} self.compose.serialize(data["payload"]) data["payload"]["rpms"] = self.rpms return data def deserialize(self, data): self.header.deserialize(data) if self.header.version_tuple <= (0, 3): self.deserialize_0_3(data) else: self.deserialize_1_0(data) self.validate() self.header.set_current_version() def deserialize_0_3(self, data): self.compose.deserialize(data["payload"]) payload = data["payload"]["manifest"] self.rpms = {} for variant in payload: for arch in payload[variant]: if arch == "src": continue for srpm_nevra, rpms in payload[variant][arch].items(): srpm_data = payload[variant].get("src", {}).get(srpm_nevra, None) for rpm_nevra, rpm_data in rpms.items(): category = rpm_data["type"] if category == "package": category = "binary" self.add(variant, arch, rpm_nevra, rpm_data["path"], rpm_data["sigkey"], category, srpm_nevra) if srpm_data is not None: self.add(variant, arch, srpm_nevra, srpm_data["path"], srpm_data["sigkey"], "source") def deserialize_1_0(self, data): self.compose.deserialize(data["payload"]) self.rpms = data["payload"]["rpms"] def add(self, variant, arch, nevra, path, sigkey, category, srpm_nevra=None): """ Map RPM to to variant and arch. :param variant: compose variant UID :type variant: str :param arch: compose architecture :type arch: str :param nevra: name-epoch:version-release.arch :type nevra: str :param sigkey: sigkey hash :type sigkey: str or None :param category: RPM category, one of binary, debug, source :type category: str :param srpm_nevra: name-epoch:version-release.arch of RPM's SRPM :type srpm_nevra: str """ if arch not in productmd.common.RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) if category not in SUPPORTED_CATEGORIES: raise ValueError("Invalid category value: %s" % category) if path.startswith("/"): raise ValueError("Relative path expected: %s" % path) nevra, nevra_dict = self._check_nevra(nevra) if category == "source" and srpm_nevra is not None: raise ValueError("Expected blank srpm_nevra for source package: %s" % nevra) if category != "source" and srpm_nevra is None: raise ValueError("Missing srpm_nevra for package: %s" % nevra) if (category == "source") != (nevra_dict["arch"] in ("src", "nosrc")): raise ValueError("Invalid category/arch combination: %s/%s" % (category, nevra)) if sigkey is not None: sigkey = sigkey.lower() if srpm_nevra: srpm_nevra, _ = self._check_nevra(srpm_nevra) else: srpm_nevra = nevra arches = self.rpms.setdefault(variant, {}) srpms = arches.setdefault(arch, {}) rpms = srpms.setdefault(srpm_nevra, {}) rpms[nevra] = {"sigkey": sigkey, "path": path, "category": category}
class ExtraFiles(productmd.common.MetadataBase): def __init__(self): super(ExtraFiles, self).__init__() self.header = Header(self, "productmd.extra_files") self.compose = Compose(self) self.extra_files = {} def __getitem__(self, variant): return self.extra_files[variant] def __delitem__(self, variant): del self.extra_files[variant] def serialize(self, parser): self.validate() data = parser self.header.serialize(data) data["payload"] = {} self.compose.serialize(data["payload"]) data["payload"]["extra_files"] = self.extra_files return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) self.extra_files = data["payload"]["extra_files"] self.validate() def add(self, variant, arch, path, size, checksums): if not variant: raise ValueError("Non-empty variant is expected") if arch not in RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) if not path: raise ValueError("Path can not be empty.") if path.startswith("/"): raise ValueError("Relative path expected: %s" % path) if not isinstance(checksums, dict): raise TypeError("Checksums must be a dict.") metadata = self.extra_files.setdefault(variant, {}).setdefault(arch, []) metadata.append({"file": path, "size": size, "checksums": checksums}) def dump_for_tree(self, output, variant, arch, basepath): """Dump the serialized metadata for given tree. The basepath is stripped from all paths. """ metadata = {"header": {"version": "1.0"}, "data": []} for item in self.extra_files[variant][arch]: metadata["data"].append({ "file": _relative_to(item["file"], basepath), "size": item["size"], "checksums": item["checksums"], }) json.dump(metadata, output, sort_keys=True, indent=4, separators=(",", ": "))
class Images(productmd.common.MetadataBase): def __init__(self): super(Images, self).__init__() self.header = Header(self, "productmd.images") self.compose = Compose(self) self.images = {} def __getitem__(self, variant): return self.images[variant] def __delitem__(self, variant): del self.images[variant] def serialize(self, parser): data = parser self.header.serialize(data) data["payload"] = {} data["payload"]["images"] = {} self.compose.serialize(data["payload"]) for variant in self.images: for arch in self.images[variant]: for image_obj in self.images[variant][arch]: images = data["payload"]["images"].setdefault(variant, {}).setdefault(arch, []) image_obj.serialize(images) images.sort(key=lambda x: x["path"]) return data def deserialize(self, data): self.header.deserialize(data) self.compose.deserialize(data["payload"]) for variant in data["payload"]["images"]: for arch in data["payload"]["images"][variant]: for image in data["payload"]["images"][variant][arch]: image_obj = Image(self) image_obj.deserialize(image) if self.header.version_tuple <= (1, 1): self._add_1_1(data, variant, arch, image_obj) else: self.add(variant, arch, image_obj) self.header.set_current_version() def _add_1_1(self, data, variant, arch, image): if arch == "src": # move src under binary arches for variant_arch in data["payload"]["images"][variant]: if variant_arch == "src": continue self.add(variant, variant_arch, image) else: self.add(variant, arch, image) def add(self, variant, arch, image): """ Assign an :class:`.Image` object to variant and arch. :param variant: compose variant UID :type variant: str :param arch: compose architecture :type arch: str :param image: image :type image: :class:`.Image` """ if arch not in productmd.common.RPM_ARCHES: raise ValueError("Arch not found in RPM_ARCHES: %s" % arch) if arch in ["src", "nosrc"]: raise ValueError("Source arch is not allowed. Map source files under binary arches.") if self.header.version_tuple >= (1, 1): # disallow adding a different image with same 'unique' # attributes. can't do this pre-1.1 as we couldn't truly # identify images before subvariant for checkvar in self.images: for checkarch in self.images[checkvar]: for curimg in self.images[checkvar][checkarch]: if identify_image(curimg) == identify_image(image) and curimg.checksums != image.checksums: raise ValueError("Image {0} shares all UNIQUE_IMAGE_ATTRIBUTES with " "image {1}! This is forbidden.".format(image, curimg)) self.images.setdefault(variant, {}).setdefault(arch, set()).add(image)
class TreeInfo(productmd.common.MetadataBase): def __init__(self): super(productmd.common.MetadataBase, self) self.header = Header(self) #: (:class:`productmd.common.Header`) -- Metadata header self.release = Release(self) #: (:class:`.Release`) -- Release details self.base_product = BaseProduct(self) #: (:class:`.BaseProduct`) -- Base product details (optional) self.tree = Tree(self) #: (:class:`.Tree`) -- Tree details self.variants = Variants(self) #: (:class:`.Variants`) -- Release variants self.checksums = Checksums(self) #: (:class:`.Checksums`) -- Checksums of images included in a tree self.images = Images(self) #: (:class:`.Images`) -- Paths to images included in a tree self.stage2 = Stage2(self) #: (:class:`.Stage2`) -- Stage 2 image path (for Anaconda installer) self.media = Media(self) #: (:class:`.Media`) -- Media set information (optional) def __str__(self): result = "%s-%s" % (self.release.short, self.release.version) if self.release.is_layered: result += "-%s-%s" % (self.base_product.short, self.base_product.version) variant = sorted(self.variants)[0] result += " %s.%s" % (variant, self.tree.arch) return result def __getitem__(self, name): return self.variants[name] def __delitem__(self, name): del self.variants[name] def _get_parser(self): return productmd.common.SortedConfigParser() def parse_file(self, f): # parse file, return parser or dict with data f.seek(0) parser = productmd.common.SortedConfigParser() parser.read_file(f) return parser def build_file(self, parser, f): # build file from parser or dict with data parser.write(f) def serialize(self, parser): self.validate() self.header.serialize(parser) self.release.serialize(parser) if self.release.is_layered: self.base_product.serialize(parser) self.tree.serialize(parser) self.variants.serialize(parser) self.checksums.serialize(parser) self.images.serialize(parser) self.stage2.serialize(parser) self.media.serialize(parser) # HACK: generate [general] section for compatibility general = General(self) general.serialize(parser) def deserialize(self, parser): self.header.deserialize(parser) self.release.deserialize(parser) if self.release.is_layered: self.base_product.deserialize(parser) self.tree.deserialize(parser) self.variants.deserialize(parser) self.checksums.deserialize(parser) self.images.deserialize(parser) self.stage2.deserialize(parser) self.media.deserialize(parser) self.validate() self.header.set_current_version() return parser