Mini Shell
# -*- coding: utf-8 -*-
# Copyright (C) 2015 Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
"""
This module provides classes for manipulating .treeinfo files.
Treeinfo files provide details about installable trees in Fedora composes and media.
"""
import os
import hashlib
import re
import six
import productmd.common
import productmd.composeinfo
__all__ = (
"TreeInfo",
"Variant",
"VARIANT_TYPES",
)
#: supported variant types
VARIANT_TYPES = [
"variant",
"optional",
"addon",
]
def compute_checksum(path, checksum_type):
checksum = hashlib.new(checksum_type)
with open(path, "rb") as fo:
while True:
chunk = fo.read(1024**2)
if not chunk:
break
checksum.update(chunk)
return checksum.hexdigest().lower()
class TreeInfo(productmd.common.MetadataBase):
def __init__(self):
super(productmd.common.MetadataBase, self)
self.header = Header(self, "productmd.treeinfo") #: (: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
class Header(productmd.common.Header):
def serialize(self, parser):
self.validate()
parser.add_section(self._section)
# write *current* version, because format gets converted on save
parser.set(self._section, "version", ".".join([str(i) for i in productmd.common.VERSION]))
parser.set(self._section, "type", self.metadata_type)
def deserialize(self, parser):
if parser.has_option(self._section, "version"):
self.version = parser.get(self._section, "version")
if self.version_tuple >= (1, 1):
metadata_type = parser.get(self._section, "type")
if metadata_type != self.metadata_type:
raise ValueError("Invalid metadata type '%s', expected '%s'" % (metadata_type, self.metadata_type))
self.validate()
class BaseProduct(productmd.common.MetadataBase):
"""
:class:`.BaseProduct` provides information about operating system a :class:`.Release` runs on.
"""
def __init__(self, metadata):
super(BaseProduct, self).__init__()
self._section = "base_product"
self._metadata = metadata
self.name = None #: (*str*) -- base product name, for example: "Fedora", "Red Hat Enterprise Linux"
self.short = None #: (*str*) -- base product short name, for example: "F", "RHEL"
self.version = None #: (*str*) -- base product *major* version, for example: "21", "7"
def _validate_name(self):
self._assert_type("name", list(six.string_types))
def _validate_version(self):
self._assert_type("version", list(six.string_types))
if re.match(r'^\d', self.version):
self._assert_matches_re("version", [r"^\d+(\.\d+)*$"])
def _validate_short(self):
self._assert_type("short", list(six.string_types))
def serialize(self, parser):
self.validate()
parser.add_section(self._section)
parser.set(self._section, "name", self.name)
parser.set(self._section, "version", self.version)
parser.set(self._section, "short", self.short)
def deserialize(self, parser):
self.name = parser.get(self._section, "name")
self.version = parser.get(self._section, "version")
self.short = parser.get(self._section, "short")
self.validate()
class Release(BaseProduct):
def __init__(self, metadata):
super(Release, self).__init__(metadata)
self._section = "release"
self.name = None #: (*str*) -- release name, for example: "Fedora", "Red Hat Enterprise Linux", "Spacewalk"
self.short = None #: (*str*) -- release short name, for example: "F", "RHEL", "Spacewalk"
self.version = None #: (*str*) -- release version, for example: "21", "7.0", "2.1"
self.is_layered = False #: (*bool*) -- typically False for an operating system, True otherwise
def _validate_is_layered(self):
self._assert_type("is_layered", [bool])
def serialize(self, parser):
self.validate()
parser.add_section(self._section)
parser.set(self._section, "name", self.name)
parser.set(self._section, "version", self.version)
parser.set(self._section, "short", self.short)
if self.is_layered:
parser.set(self._section, "is_layered", "true")
def deserialize(self, parser):
if self._metadata.header.version_tuple == (0, 0):
self.deserialize_0_0(parser)
elif self._metadata.header.version_tuple <= (0, 3):
self.deserialize_0_3(parser)
else:
self.deserialize_1_0(parser)
self.validate()
# pre-productmd treeinfos
def deserialize_0_0(self, parser):
self.name = parser.get("general", "family")
self.version = parser.get("general", "version")
for i in re.split(r"[-_]", self.version):
if re.match(r"^\d+(\.\d+)*$", i):
self.version = i
if self.name.startswith("Red Hat Enterprise Linux"):
self.name = "Red Hat Enterprise Linux"
self.short = "RHEL"
elif self.name == "Subscription Asset Manager":
self.short = "SAM"
elif self.name == "Red Hat Storage":
self.short = "RHS"
elif self.name == "JBEAP":
self.short = "JBEAP"
elif self.name == "Red Hat Storage Software Appliance":
self.short = "SSA"
elif self.name.startswith("Fedora"):
self.name = "Fedora"
self.short = "Fedora"
elif self.name.startswith("CentOS"):
self.name = "CentOS"
self.short = "CentOS"
else:
self.short = None
def deserialize_0_3(self, parser):
self.name = parser.get("product", "name")
self.version = parser.get("product", "version")
self.short = parser.get("product", "short")
if parser.has_option("product", "is_layered"):
self.is_layered = parser.getboolean("product", "is_layered")
def deserialize_1_0(self, parser):
self.name = parser.get(self._section, "name")
self.version = parser.get(self._section, "version")
self.short = parser.get(self._section, "short")
if parser.has_option(self._section, "is_layered"):
self.is_layered = parser.getboolean(self._section, "is_layered")
@property
def major_version(self):
"""
Version string without the last part.
For example: version == 1.2.0 -> major_version == 1.2
"""
if self.version is None:
return None
return productmd.common.get_major_version(self.version)
@property
def minor_version(self):
"""
Last part of the version string.
For example: version == 1.2.0 -> minor_version == 0
"""
if self.version is None:
return None
return productmd.common.get_minor_version(self.version)
# Note: [tree]/variants is read/written in the Variants class
class Tree(productmd.common.MetadataBase):
def __init__(self, _metadata):
super(Tree, self).__init__()
self._section = "tree"
self._metadata = _metadata
self.arch = None #: (*str*) -- tree architecture, for example x86_64
self.build_timestamp = None #: (*int*, *float*) -- tree build time timestamp; format: unix time
self.platforms = set() #: (*set(str)*), supported platforms; for example x86_64,xen
def _validate_arch(self):
self._assert_type("arch", list(six.string_types))
self._assert_not_blank("arch")
def _validate_build_timestamp(self):
self._assert_type("build_timestamp", list(six.integer_types) + [float])
self._assert_not_blank("build_timestamp")
def serialize(self, parser):
self.validate()
parser.add_section(self._section)
parser.set(self._section, "arch", self.arch)
parser.set(self._section, "platforms", ",".join(sorted(self.platforms | set([self.arch]))))
parser.set(self._section, "build_timestamp", str(self.build_timestamp))
def deserialize(self, parser):
if self._metadata.header.version_tuple == (0, 0):
self.deserialize_0_0(parser)
else:
self.deserialize_1_0(parser)
self.validate()
def deserialize_0_0(self, parser):
self.arch = parser.get("general", "arch")
self.platforms.add(self.arch)
if self.arch in parser.sections():
self.platforms.update([i for i in parser.get(self.arch, "platforms").split(",") if i])
for i in parser.sections():
if not i.startswith("images-"):
continue
i = i[7:]
if i != self.arch and i.endswith("-%s" % self.arch):
i = i[:-len(self.arch)-1]
self.platforms.add(i)
if parser.has_option("general", "timestamp"):
self.build_timestamp = int(parser.getfloat("general", "timestamp"))
else:
self.build_timestamp = -1
def deserialize_1_0(self, parser):
self.arch = parser.get(self._section, "arch")
self.platforms = set([i for i in parser.get(self._section, "platforms").split(",") if i])
self.build_timestamp = parser.getint(self._section, "build_timestamp")
class Variants(productmd.composeinfo.VariantBase):
def __init__(self, metadata):
super(Variants, self).__init__(metadata)
self._metadata = metadata
def __len__(self):
return len(self.variants)
def _validate_variants(self):
self._assert_not_blank("variants")
def serialize(self, parser):
self.validate()
# variant UIDs *should* be identical to IDs at the top level,
# but sometimes they can differ (Server-optional)
variant_ids = sorted([i.uid for i in self.variants.values()])
parser.set("tree", "variants", ",".join(sorted(variant_ids)))
for variant in self.variants.values():
variant.serialize(parser)
def deserialize(self, parser):
# variant UIDs should be identical to IDs at the top level
if self._metadata.header.version_tuple == (0, 0):
variant_ids = self.deserialize_0_0(parser)
else:
variant_ids = self.deserialize_1_0(parser)
for variant_id in variant_ids:
variant = Variant(self._metadata)
variant.deserialize(parser, variant_id)
# handle cases when top-level ID != UID, like $variant-optional
self.add(variant, variant_id=variant.uid)
self.validate()
def deserialize_0_0(self, parser):
if not parser.has_option("general", "variant") and parser.get("general", "family") == "Red Hat Enterprise Linux Server":
variant_ids = "Server"
elif not parser.has_option("general", "variant") and parser.get("general", "family") == "Red Hat Enterprise Linux Client":
variant_ids = "Client"
elif not parser.has_option("general", "variant") and parser.get("general", "family") == "CentOS":
variant_ids = "CentOS"
else:
variant_ids = parser.get("general", "variant")
if variant_ids:
variant_ids = [variant_ids]
else:
variant_ids = [i[8:] for i in parser.sections() if i.startswith("variant-")]
variant_ids = [i for i in variant_ids if "-" not in i] # we want only top-level variants
if not variant_ids:
variant_ids = [self._metadata.release.short]
return variant_ids
def deserialize_1_0(self, parser):
variant_ids = [i for i in parser.get("tree", "variants").split(",")]
return variant_ids
class VariantPaths(productmd.common.MetadataBase):
"""
This class stores paths for a variant in a tree.
All paths are relative to .treeinfo location.
**Binary**
* **packages** -- directory with binary RPMs
* **repository** -- YUM repository with binary RPMs
**Source**
* **source_packages** -- directory with source RPMs
* **source_repository** -- YUM repository with source RPMs
**Debug**
* **debug_packages** -- directory with debug RPMs
* **debug_repository** -- YUM repository with debug RPMs
**Others**
* **identity** -- path to a pem file which identifies a product
Example::
variant = ...
variant.paths.packages = "Packages"
variant.paths.repository = "."
"""
def __init__(self, variant):
self._variant = variant
self._metadata = self._variant._metadata
self._fields = [
# binary
"packages",
"repository",
# source
"source_packages",
"source_repository",
# debug
"debug_packages",
"debug_repository",
# others
"identity",
]
for name in self._fields:
setattr(self, name, None)
def deserialize(self, parser):
if self._metadata.header.version_tuple == (0, 0):
self.deserialize_0_0(parser)
elif self._metadata.header.version_tuple <= (0, 3):
self.deserialize_0_3(parser)
else:
self.deserialize_1_0(parser)
self.validate()
# pre-productmd treeinfos
def deserialize_0_0(self, parser):
# repository
lookup = [
("variant-%s" % self._variant.id, "repository"),
("addon-%s" % self._variant.id, "repository"),
("general", "repository"),
]
self.repository = parser.option_lookup(lookup, ".")
# remove /repodata from repository path
self.repository = self.repository.rstrip("/") or "."
if self.repository.endswith("/repodata"):
self.repository = self.repository[:-9]
if self.repository == ".":
if self._metadata.release.short == "RHEL" and self._metadata.release.major_version in ("5", "6"):
# HACK: repo dirs named by variants on RHEL 5, 6
self.repository = self._variant.id
if self._metadata.release.short == "RHEL" and self._metadata.release.major_version in ("3", "4"):
# HACK: no repos on RHEL 3, 4
self.repository = None
# packages
lookup = [
("variant-%s" % self._variant.uid, "packages"),
("variant-%s" % self._variant.uid, "packagedir"),
("addon-%s" % self._variant.uid, "packages"),
("addon-%s" % self._variant.uid, "packagedir"),
("variant-%s" % self._variant.id, "packages"),
("variant-%s" % self._variant.id, "packagedir"),
("addon-%s" % self._variant.id, "packages"),
("addon-%s" % self._variant.id, "packagedir"),
("general", "packages"),
("general", "packagedir"),
("general", "packagedirs"),
]
self.packages = parser.option_lookup(lookup, self.repository) or ""
self.packages = self.packages.rstrip("/") or "."
if self._metadata.release.short == "RHEL" and self._metadata.release.major_version == "5":
# HACK: RHEL 5
self.packages = self._variant.id
elif self._metadata.release.short == "RHEL" and self._metadata.release.major_version in ("3", "4"):
# HACK: RHEL 3, 4
self.packages = "RedHat/RPMS"
elif self._metadata.release.short == "Fedora":
# HACK: replace empty packagedir with "Packages" on Fedora
if self.packages == ".":
self.packages = "Packages"
if self._metadata.tree.arch == "src":
self.source_packages = self.packages
self.source_repository = self.repository
self.packages = None
self.repository = None
# identity
lookup = [
("variant-%s" % self._variant.uid, "identity"),
("addon-%s" % self._variant.uid, "identity"),
("variant-%s" % self._variant.id, "identity"),
("addon-%s" % self._variant.id, "identity"),
("general", "identity"),
]
self.identity = parser.option_lookup(lookup, None)
def deserialize_0_3(self, parser):
for field in self._fields:
lookup = [
("variant-%s" % self._variant.uid, field),
("variant-%s" % self._variant.id, field),
("addon-%s" % self._variant.uid, field),
("addon-%s" % self._variant.id, field),
]
value = parser.option_lookup(lookup, None)
setattr(self, field, value)
if self._metadata.tree.arch == "src":
self.source_packages = self.packages
self.source_repository = self.repository
self.packages = None
self.repository = None
def deserialize_1_0(self, parser):
for field in self._fields:
if parser.has_option(self._variant._section, field):
value = parser.get(self._variant._section, field)
else:
value = None
setattr(self, field, value)
def serialize(self, parser):
self.validate()
for field in self._fields:
value = getattr(self, field, None)
if value is not None:
parser.set(self._variant._section, field, value)
class Variant(productmd.composeinfo.VariantBase):
def __init__(self, metadata):
super(Variant, self).__init__(metadata)
# variant details
self.id = None #: (*str*) -- variant ID, for example "Server", "optional"
self.uid = None #: (*str*) -- variant UID ($parent_UID.$ID), for example "Server", "Server-optional"
self.name = None #: (*str*) -- variant name, for example "Server"
self.type = None #: (*str*) -- "variant", "addon", "optional"
self.parent = None #: (:class:`.Variant` or *None*) -- reference to parent :class:`.Variant`
self.variants = {} #: (*dict*) :class:`.Variant`
self.paths = VariantPaths(self) #: (:class:`.VariantPaths`) -- relative paths to repositories, packages, etc.
def __str__(self):
return self.uid
def __delitem__(self, name):
# remove repomd.xml from checksums (but only if exists)
repository = self[name].paths.repository + "/repodata/repomd.xml"
super(Variant, self).__delitem__(name)
if repository in self._metadata.checksums.checksums.keys():
del self._metadata.checksums.checksums[repository]
@property
def arch(self):
return self._metadata.tree.arch
@property
def _section(self):
if self.type == "addon":
section = "addon-" + self.uid
else:
section = "variant-" + self.uid
return section
def _validate_id(self):
self._assert_type("id", [str])
if "-" in self.id:
raise ValueError("Invalid character '-' in variant ID: %s" % self.id)
def _validate_uid(self):
if self.parent:
uid = "%s-%s" % (self.parent.uid, self.id)
else:
uid = self.uid
if self.uid != uid:
raise ValueError("UID '%s' doesn't align with parent UID '%s'" % (self.uid, uid))
def _validate_type(self):
self._assert_value("type", VARIANT_TYPES)
def deserialize(self, parser, uid, addon=False):
if not uid:
raise ValueError("Invalid variant UID value: %s" % uid)
self.uid = uid
if addon:
self.type = "addon"
# variant details
if self._metadata.header.version_tuple == (0, 0):
self.deserialize_0_0(parser, uid, addon=addon)
elif self._metadata.header.version_tuple <= (0, 3):
self.deserialize_0_3(parser, uid, addon=addon)
else:
self.deserialize_1_0(parser, uid, addon=addon)
self.paths.deserialize(parser)
# pre-productmd treeinfo
def deserialize_0_0(self, parser, uid, addon=False):
# id, uid
self.id = uid.split("-")[-1]
self.uid = uid
# variant type and section
sections = [
"addon-" + self.uid,
"addon-" + self.id,
"variant-" + self.uid,
"variant-" + self.id,
]
for section in sections:
if parser.has_option(section, "type"):
self.type = parser.get(section, "type")
break
if parser.has_section(section):
if "addon" in section:
self.type = "addon"
elif "optional" in self.id:
self.type = "optional"
else:
self.type = "variant"
break
if not self.type:
# no variant/addon section, fallback to general
if addon:
self.type = "addon"
else:
self.type = "variant"
self.name = self._metadata.release.name
self.uid = uid
self.id = uid.rsplit("-")[-1]
# name
if parser.has_option(section, "name"):
self.name = parser.get(section, "name")
else:
self.name = self.id
if self.type == "variant":
lookup = [
(section, "addons"),
(section, "variants"),
("general", "addons"),
]
addons = parser.option_lookup(lookup, "").split(",")
addons = [i for i in addons if i]
if self._metadata.release.short == "RHEL" and self._metadata.release.major_version == "5":
# workaround for RHEL 5 - add addons
if self.uid == "Client":
addons = ["VT", "Workstation"]
if self.uid == "Server":
if self.arch in ("i386", "ia64", "x86_64"):
addons = ["Cluster", "ClusterStorage", "VT"]
elif self.arch == "ppc":
if self._metadata.release.minor_version == "0":
# no addons on RHEL 5.0 Server.ppc
addons = []
else:
addons = ["Cluster", "ClusterStorage"]
elif self.arch == "s390x":
addons = []
for addon_id in addons:
addon_uid = addon_id
if not addon_uid.startswith("%s-" % self.uid):
addon_uid = "%s-%s" % (self.uid, addon_uid)
addon = Variant(self._metadata)
addon.deserialize(parser, addon_uid)
# HACK: for RHEL 5 addons
addon.type = "addon"
self.add(addon)
def deserialize_0_3(self, parser, uid, addon=False):
section = "variant-%s" % uid
if not parser.has_section(section):
section = "addon-%s" % uid
self.id = parser.get(section, "id")
self.uid = parser.get(section, "uid")
self.name = parser.get(section, "name")
self.type = parser.get(section, "type")
# child addons
addons = ""
if parser.has_option(section, "addons"):
addons = parser.get(section, "addons")
elif parser.has_option(section, "variants"):
addons = parser.get(section, "variants")
if addons:
variant_uids = [i for i in addons.split(",") if i]
for variant_uid in variant_uids:
variant = Variant(self._metadata)
variant.deserialize(parser, variant_uid, addon=True)
self.add(variant)
def deserialize_1_0(self, parser, uid, addon=False):
self.id = parser.get(self._section, "id")
self.uid = parser.get(self._section, "uid")
self.name = parser.get(self._section, "name")
self.type = parser.get(self._section, "type")
# child addons
if parser.has_option(self._section, "addons"):
variant_uids = [i for i in parser.get(self._section, "addons").split(",") if i]
for variant_uid in variant_uids:
variant = Variant(self._metadata)
variant.deserialize(parser, variant_uid, addon=True)
self.add(variant)
def serialize(self, parser):
self.validate()
# print "SERIALIZE", self._section, self.type
parser.add_section(self._section)
# variant details
parser.set(self._section, "id", self.id)
parser.set(self._section, "uid", self.uid)
parser.set(self._section, "name", self.name)
parser.set(self._section, "type", self.type)
# paths
self.paths.serialize(parser)
# parent
if self.parent:
parser.set(self._section, "parent", self.parent.uid)
# child variants
variant_uids = set()
for variant in self.variants.values():
variant.serialize(parser)
variant_uids.add(variant.uid)
if variant_uids:
parser.set(self._section, "addons", ",".join(sorted(variant_uids)))
class Images(productmd.common.MetadataBase):
def __init__(self, metadata):
super(Images, self).__init__()
self._metadata = metadata
self.images = {}
def __getitem__(self, platform):
return self.images[platform]
def _fix_path(self, path):
if self._metadata.header.version_tuple == (0, 0):
if path.startswith("/"):
if "/os/" in path:
path = path[path.find("/os/")+4:]
else:
path = path.lstrip("/")
return path
@property
def platforms(self):
"""Return all platforms with available images"""
return sorted(self.images.keys())
def serialize(self, parser):
if not self.images:
return
self.validate()
for platform in self.images:
section = "images-%s" % platform
parser.add_section(section)
for image, path in self.images[platform].items():
parser.set(section, image, path)
def deserialize(self, parser):
for section in parser.sections():
if not section.startswith("images-"):
continue
platform = section[7:]
if platform != self._metadata.tree.arch and platform.endswith("-%s" % self._metadata.tree.arch):
platform = platform[:-len(self._metadata.tree.arch)-1]
self.images[platform] = {}
for image, path in parser.items(section):
path = parser.get(section, image) # re-read path to populate 'seen' records in tests
self.images[platform][image] = self._fix_path(path)
self.validate()
def _validate_image_paths(self):
for platform in self.images:
for image, path in self.images[platform].items():
if path.startswith("/"):
raise ValueError("Only relative paths are allowed for images: %s" % path)
def _validate_platforms(self):
for platform in self.platforms:
if platform not in self._metadata.tree.platforms:
raise ValueError("Platform has images but is not referenced in platform list: %s, %s"
% (platform, self._metadata.tree.platforms))
class Stage2(productmd.common.MetadataBase):
def __init__(self, metadata):
super(Stage2, self).__init__()
self._section = "stage2"
self._metadata = metadata
self.mainimage = None #: (*str*) -- relative path to Anaconda stage2 image
self.instimage = None #: (*str*) -- relative path to Anaconda instimage (obsolete)
def __getitem__(self, name):
getattr(self, name)
def _fix_path(self, path):
if self._metadata.header.version_tuple == (0, 0):
if path.startswith("/"):
if "/os/" in path:
path = path[path.find("/os/")+4:]
else:
path = path.lstrip("/")
return path
def serialize(self, parser):
if not self.mainimage and not self.instimage:
return
self.validate()
parser.add_section(self._section)
if self.mainimage:
parser.set(self._section, "mainimage", self.mainimage)
if self.instimage:
parser.set(self._section, "instimage", self.instimage)
def deserialize(self, parser):
if parser.has_option(self._section, "mainimage"):
self.mainimage = self._fix_path(parser.get(self._section, "mainimage"))
if parser.has_option(self._section, "instimage"):
self.instimage = self._fix_path(parser.get(self._section, "instimage"))
self.validate()
def _validate_mainimage(self):
if self.mainimage:
self._assert_type("mainimage", list(six.string_types))
if self.mainimage.startswith("/"):
raise ValueError("Only relative paths are allowed for images: %s" % self.mainimage)
def _validate_platforms(self):
pass
class Checksums(productmd.common.MetadataBase):
def __init__(self, metadata):
super(Checksums, self).__init__()
self._section = "checksums"
self._metadata = metadata
self.checksums = {}
def __getitem__(self, name):
return self.checksums[name]
def _fix_path(self, path):
if self._metadata.header.version_tuple == (0, 0):
if path.startswith("/"):
if "/os/" in path:
path = path[path.find("/os/")+4:]
else:
path = path.lstrip("/")
return path
def serialize(self, parser):
self.validate()
if not self.checksums:
return
parser.add_section(self._section)
for path, (checksum_type, checksum) in self.checksums.items():
parser.set(self._section, path, "%s:%s" % (checksum_type, checksum))
def deserialize(self, parser):
if parser.has_section(self._section):
for path, value in parser.items(self._section):
path = self._fix_path(path)
if ":" not in value:
if len(value) == 32:
checksum_type, checksum = "md5", value
elif len(value) == 40:
checksum_type, checksum = "sha1", value
elif len(value) == 64:
checksum_type, checksum = "sha256", value
else:
checksum_type, checksum = value.split(":")
self.checksums[path] = (checksum_type, checksum)
self.validate()
def _check_checksum_paths(self):
for path in self.checksums:
if path.startswith("/"):
raise ValueError("Only relative paths are allowed for checksums: %s" % path)
def add(self, relative_path, checksum_type, checksum_value=None, root_dir=None):
if relative_path.startswith("/"):
raise ValueError("Relative path expected: %s" % relative_path)
relative_path = os.path.normpath(relative_path)
if not checksum_value:
absolute_path = os.path.join(root_dir, relative_path)
checksum_value = compute_checksum(absolute_path, checksum_type)
self.checksums[relative_path] = [checksum_type, checksum_value]
class Media(productmd.common.MetadataBase):
def __init__(self, metadata):
super(Media, self).__init__()
self._section = "media"
self._metadata = metadata
self.discnum = None #: disc number
self.totaldiscs = None #: number of discs in media set
def _validate_discnum(self):
self._assert_type("discnum", list(six.integer_types) + [type(None)])
def _validate_totaldiscs(self):
self._assert_type("totaldiscs", list(six.integer_types) + [type(None)])
def serialize(self, parser):
if not self.discnum and not self.totaldiscs:
return
self.validate()
parser.add_section(self._section)
parser.set(self._section, "discnum", str(int(self.discnum)))
parser.set(self._section, "totaldiscs", str(int(self.totaldiscs)))
def deserialize(self, parser):
if self._metadata.header.version_tuple == (0, 0):
self.deserialize_0_0(parser)
else:
self.deserialize_1_0(parser)
self.validate()
def deserialize_0_0(self, parser):
if parser.has_option("general", "discnum") or parser.has_option("general", "totaldiscs"):
if parser.has_option("general", "discnum"):
self.discnum = parser.getint("general", "discnum")
else:
self.discnum = 1
if parser.has_option("general", "totaldiscs"):
self.totaldiscs = parser.getint("general", "totaldiscs")
else:
self.totaldiscs = self.discnum
def deserialize_1_0(self, parser):
if parser.has_section(self._section):
self.discnum = parser.getint(self._section, "discnum")
self.totaldiscs = parser.getint(self._section, "totaldiscs")
class General(productmd.common.MetadataBase):
def __init__(self, metadata):
super(General, self).__init__()
self._section = "general"
self._metadata = metadata
def serialize(self, parser):
parser.add_section(self._section)
parser.set(self._section, "; WARNING.0", "This section provides compatibility with pre-productmd treeinfos.")
parser.set(self._section, "; WARNING.1", "Read productmd documentation for details about new format.")
parser.set(self._section, "name", "%s %s" % (self._metadata.release.name, self._metadata.release.version))
parser.set(self._section, "family", self._metadata.release.name)
parser.set(self._section, "version", self._metadata.release.version)
parser.set(self._section, "arch", self._metadata.tree.arch)
parser.set(self._section, "platforms", ",".join(sorted(self._metadata.tree.platforms | set([self._metadata.tree.arch]))))
parser.set(self._section, "timestamp", str(int(self._metadata.tree.build_timestamp)))
variants = list(self._metadata.variants)
variants.sort()
parser.set(self._section, "variants", ",".join(variants))
# HACK: if there are more variants, use the first variant
variant = variants[0]
parser.set(self._section, "variant", variant)
# packages
if self._metadata.variants[variant].paths.packages is not None:
parser.set(self._section, "packagedir", self._metadata.variants[variant].paths.packages)
elif self._metadata.tree.arch == "src" and self._metadata.variants[variant].paths.source_packages is not None:
parser.set(self._section, "packagedir", self._metadata.variants[variant].paths.source_packages)
# repository
if self._metadata.variants[variant].paths.repository is not None:
parser.set(self._section, "repository", self._metadata.variants[variant].paths.repository)
elif self._metadata.tree.arch == "src" and self._metadata.variants[variant].paths.source_repository is not None:
parser.set(self._section, "repository", self._metadata.variants[variant].paths.source_repository)