From f067549aaa1f7c65fbd578a0e9d6ce754e62d68a Mon Sep 17 00:00:00 2001 From: David Cassany Date: Tue, 6 Nov 2018 16:31:37 +0100 Subject: [PATCH] Add history metadata for container builds This commit adds the history section in contianerconfig. With it 'author', 'created_by' and 'comment' can be customized. In addition 'created' is always included with the image creation date time. 'created_by' entry is set to 'KIWI __version__' by default if nothing is provided. Fixes #852 --- kiwi/container/oci.py | 17 +++- kiwi/defaults.py | 16 +++- kiwi/oci_tools/base.py | 7 +- kiwi/oci_tools/umoci.py | 44 ++++++--- kiwi/schema/kiwi.rnc | 103 +++++++++++++-------- kiwi/schema/kiwi.rng | 57 ++++++++++-- kiwi/xml_parse.py | 125 +++++++++++++++++++++++++- kiwi/xml_state.py | 20 +++++ test/data/example_config.xml | 1 + test/unit/container_image_oci_test.py | 20 +++-- test/unit/oci_tools_umoci_test.py | 65 +++++++++----- test/unit/xml_state_test.py | 12 ++- 12 files changed, 382 insertions(+), 105 deletions(-) diff --git a/kiwi/container/oci.py b/kiwi/container/oci.py index f5a3870c460..fdb2897b594 100644 --- a/kiwi/container/oci.py +++ b/kiwi/container/oci.py @@ -52,7 +52,12 @@ class ContainerImageOCI(object): 'expose_ports': ['80', '42'], 'volumes': ['/var/log', '/tmp'], 'environment': {'PATH': '/bin'}, - 'labels': {'name': 'value'} + 'labels': {'name': 'value'}, + 'history': { + 'created_by': 'some explanation here', + 'comment': 'some comment here', + 'author': 'tux' + } } """ def __init__(self, root_dir, custom_args=None): @@ -95,6 +100,12 @@ def __init__(self, root_dir, custom_args=None): self.oci_config['entry_subcommand'] = \ Defaults.get_default_container_subcommand() + if 'history' not in self.oci_config: + self.oci_config['history'] = {} + if 'created_by' not in self.oci_config['history']: + self.oci_config['history']['created_by'] = \ + Defaults.get_default_container_created_by() + self.oci = OCI(self.oci_config['container_tag']) def create(self, filename, base_image): @@ -117,7 +128,7 @@ def create(self, filename, base_image): image_tar = ArchiveTar(base_image) image_tar.extract(self.oci.container_dir) - self.oci.init_layout(base_image) + self.oci.init_layout(True if base_image else False) self.oci.unpack(self.oci_root_dir) oci_root = DataSync( @@ -133,7 +144,7 @@ def create(self, filename, base_image): for tag in self.oci_config['additional_tags']: self.oci.add_tag(tag) - self.oci.set_config(self.oci_config) + self.oci.set_config(self.oci_config, True if base_image else False) self.oci.garbage_collect() diff --git a/kiwi/defaults.py b/kiwi/defaults.py index 125730cfbdf..3edb7862724 100644 --- a/kiwi/defaults.py +++ b/kiwi/defaults.py @@ -25,7 +25,10 @@ # project from .path import Path -from .version import __githash__ +from .version import ( + __githash__, + __version__ +) class Defaults(object): @@ -1173,6 +1176,17 @@ def get_default_container_subcommand(self): """ return ['/bin/bash'] + @classmethod + def get_default_container_created_by(self): + """ + Provides the default 'created by' history entry for containers. + + :return: the specific kiwi version used for the build + + :rtype: str + """ + return 'KIWI {0}'.format(__version__) + @classmethod def set_python_default_encoding_to_utf8(self): """ diff --git a/kiwi/oci_tools/base.py b/kiwi/oci_tools/base.py index 0869d9b9dda..f5d654ef552 100644 --- a/kiwi/oci_tools/base.py +++ b/kiwi/oci_tools/base.py @@ -17,6 +17,7 @@ # import os from tempfile import mkdtemp +from datetime import datetime # project from kiwi.path import Path @@ -51,6 +52,9 @@ def __init__(self, container_tag, container_dir=None): self.container_name = ':'.join( [self.container_dir, self.container_tag] ) + self.creation_date = datetime.utcnow().strftime( + '%Y-%m-%dT%H:%M:%S+00:00' + ) def init_layout(self, base_image=False): """ @@ -81,7 +85,6 @@ def repack(self, oci_root_dir): Implementation in specialized tool class :param string oci_root_dir: root data directory - :param string container_name: custom container_dir:tag specifier """ raise NotImplementedError @@ -96,7 +99,7 @@ def add_tag(self, tag_name): """ raise NotImplementedError - def set_config(self, oci_config): + def set_config(self, oci_config, base_image=False): """ Set list of meta data information such as entry_point, maintainer, etc... to the container. The validation of diff --git a/kiwi/oci_tools/umoci.py b/kiwi/oci_tools/umoci.py index a0efb62f747..10b0521d4b6 100644 --- a/kiwi/oci_tools/umoci.py +++ b/kiwi/oci_tools/umoci.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with kiwi. If not, see # -from datetime import datetime # project from kiwi.oci_tools.base import OCIBase @@ -36,16 +35,10 @@ def init_layout(self, base_image=False): The import and unpack of the base image is not a responsibility of this class and done beforehead - :param string base_image: True|False + :param bool base_image: True|False """ if base_image: - Command.run( - [ - 'umoci', 'config', '--image', - '{0}:base_layer'.format(self.container_dir), - '--tag', self.container_tag - ] - ) + self.container_name = '{0}:base_layer'.format(self.container_dir) else: Command.run( ['umoci', 'init', '--layout', self.container_dir] @@ -87,24 +80,30 @@ def add_tag(self, tag_name): ] ) - def set_config(self, oci_config): + def set_config(self, oci_config, base_image=False): """ Set list of meta data information such as entry_point, maintainer, etc... to the container. :param list oci_config: meta data list + :param bool base_image: True|False """ config_args = self._process_oci_config_to_arguments(oci_config) Command.run( [ 'umoci', 'config' ] + config_args + [ + '--history.created', self.creation_date, '--image', self.container_name, - '--created', datetime.utcnow().strftime( - '%Y-%m-%dT%H:%M:%S+00:00' - ) + '--tag', self.container_tag, + '--created', self.creation_date ] ) + if base_image: + Command.run(['umoci', 'rm', '--image', self.container_name]) + self.container_name = self.container_name = ':'.join( + [self.container_dir, self.container_tag] + ) @classmethod # noqa:C091 def _process_oci_config_to_arguments(self, oci_config): @@ -168,8 +167,27 @@ def _process_oci_config_to_arguments(self, oci_config): name, oci_config['labels'][name] )) + arguments.extend(self._process_oci_history_to_arguments(oci_config)) return arguments + @classmethod + def _process_oci_history_to_arguments(self, oci_config): + history_args = [] + if 'history' in oci_config: + if 'comment' in oci_config['history']: + history_args.append('--history.comment={0}'.format( + oci_config['history']['comment'] + )) + if 'created_by' in oci_config['history']: + history_args.append('--history.created_by={0}'.format( + oci_config['history']['created_by'] + )) + if 'author' in oci_config['history']: + history_args.append('--history.author={0}'.format( + oci_config['history']['author'] + )) + return history_args + def garbage_collect(self): """ Cleanup unused data from operations diff --git a/kiwi/schema/kiwi.rnc b/kiwi/schema/kiwi.rnc index 479fcc1010f..58af3b5bde6 100644 --- a/kiwi/schema/kiwi.rnc +++ b/kiwi/schema/kiwi.rnc @@ -13,7 +13,7 @@ # : configuration files. The schema is maintained # : in the relax compact syntax. Any changes should # : made in !! *** kiwi.rnc *** !! -# : +# : # : # STATUS : Development #**************** @@ -46,7 +46,7 @@ start = # main block: # div { - k.image.name.attribute = k.imagename.attribute + k.image.name.attribute = k.imagename.attribute k.image.displayname.attribute = k.displayname.attribute k.image.noNamespaceSchemaLocation.attribute = ## The location of the XSD Schema (not relevant for RELAX NG or DTD) @@ -63,7 +63,7 @@ div { ## An identification number which is represented in a file ## named /etc/ImageID attribute id { xsd:string {length="10"} } - k.image.attlist = k.image.name.attribute + k.image.attlist = k.image.name.attribute & k.image.displayname.attribute? & k.image.id? & k.image.schemaversion.attribute @@ -71,7 +71,7 @@ div { | k.image.schemaLocation.attribute? )? k.image = - ## The root element of the configuration file + ## The root element of the configuration file element image { k.image.attlist & k.description & @@ -247,7 +247,7 @@ div { k.configuration.source.attribute = k.source.attribute k.configuration.dest.attribute = k.dest.attribute k.configuration.arch.attribute = k.arch.attribute - k.configuration.attlist = + k.configuration.attlist = k.configuration.source.attribute & k.configuration.dest.attribute & k.configuration.arch.attribute? @@ -267,7 +267,7 @@ div { # div { k.contact.attlist = empty - k.contact = + k.contact = ## Contact Information from the Author, like Email etc. element contact { k.contact.attlist, @@ -301,7 +301,7 @@ div { k.ignore.attlist = k.ignore.name.attribute & k.ignore.arch.attribute? - k.ignore = + k.ignore = ## Ignores a Package element ignore { k.ignore.attlist, @@ -571,7 +571,7 @@ div { k.oem-shutdown.content = xsd:boolean k.oem-shutdown.attlist = empty k.oem-shutdown = - ## For oemboot driven images: shutdown after first deployment + ## For oemboot driven images: shutdown after first deployment ## true/false element oem-shutdown { k.oem-shutdown.attlist, @@ -586,7 +586,7 @@ div { k.oem-shutdown-interactive.content = xsd:boolean k.oem-shutdown-interactive.attlist = empty k.oem-shutdown-interactive = - ## For oemboot driven images: shutdown after first deployment + ## For oemboot driven images: shutdown after first deployment ## true/false element oem-shutdown-interactive { k.oem-shutdown-interactive.attlist, @@ -799,7 +799,7 @@ div { # common element # div { - k.packagemanager.content = + k.packagemanager.content = "apt-get" | "zypper" | "yum" | "dnf" k.packagemanager.attlist = empty k.packagemanager = @@ -821,7 +821,7 @@ div { ## Partition ID attribute number { text } k.partition.size.attribute = k.size.attribute - k.partition.mountpoint.attribute = + k.partition.mountpoint.attribute = ## Mount path for this partition attribute mountpoint { text } k.partition.target.attribute = @@ -830,7 +830,7 @@ div { attribute target { xsd:boolean } k.partition.attlist = k.partition.type.attribute & - k.partition.number.attribute & + k.partition.number.attribute & k.partition.size.attribute? & k.partition.mountpoint.attribute? & k.partition.target.attribute? @@ -853,8 +853,8 @@ div { k.partitions.attlist = k.partitions.device.attribute? k.partitions = ## A List of Partitions - element partitions { - k.partitions.attlist, + element partitions { + k.partitions.attlist, k.partition+ } } @@ -892,7 +892,7 @@ div { # common element # div { - k.requires.profile.attribute = + k.requires.profile.attribute = ## The profile name required as part of the current profile ## definition. attribute profile {text} @@ -1012,7 +1012,7 @@ div { k.rpm-check-signatures.content = xsd:boolean k.rpm-check-signatures.attlist = empty k.rpm-check-signatures = - ## Sets the used package manager to validate, or not, the + ## Sets the used package manager to validate, or not, the ## repository and/or package signatures. The behavior can be ## slightly different depending on the used package manager. ## This is a system wide package manager option, so that, this @@ -1136,7 +1136,7 @@ div { # div { k.timeout.attlist = empty - k.timeout = + k.timeout = ## Specifies an ATFTP Download Timeout element timeout { k.timeout.attlist, @@ -1149,7 +1149,7 @@ div { # div { k.timezone.attlist = empty - k.timezone = + k.timezone = ## Setup Image Timezone setup element timezone { k.timezone.attlist, @@ -1327,7 +1327,7 @@ div { ] k.type.efiparttable.attribute = ## For images with an EFI firmware specifies the partition - ## table type to use. If not set defaults to gpt partition + ## table type to use. If not set defaults to gpt partition ## table type. attribute efiparttable { "msdos" | "gpt" } >> sch:pattern [ id = "efiparttable" is-a = "image_type" @@ -1386,7 +1386,7 @@ div { sch:param [ name = "attr" value = "editbootinstall" ] sch:param [ name = "types" value = "vmx oem" ] ] - k.type.filesystem.attribute = + k.type.filesystem.attribute = ## Specifies the root filesystem type attribute filesystem { "btrfs" | "ext2" | "ext3" | "ext4" | "squashfs" | "xfs" @@ -1651,7 +1651,7 @@ div { >> sch:pattern [ id = "rootfs_label" is-a = "image_type" sch:param [ name = "attr" value = "rootfs_label" ] sch:param [ name = "types" value = "oem vmx pxe docker" ] - ] + ] k.type.vga.attribute = ## Specifies the kernel framebuffer mode. More information ## about the possible values can be found by calling @@ -1779,7 +1779,7 @@ div { k.type.disk_start_sector.attribute? k.type = ## The Image Type of the Logical Extend - element type { + element type { k.type.attlist & k.containerconfig? & k.machine? & @@ -1796,7 +1796,7 @@ div { # div { k.union.ro.attribute = - ## Device only for read-only + ## Device only for read-only attribute ro { text } k.union.rw.attribute = ## Device for Read-Write @@ -1808,8 +1808,8 @@ div { k.union.ro.attribute & k.union.rw.attribute & k.union.type.attribute - - k.union = + + k.union = ## As part of the network deploy configuration this section ## specifies the overlay filesystem setup if required by the ## filesystem type of the system image.An overlay setup is @@ -1826,7 +1826,7 @@ div { # div { k.user.name.attribute = k.name.attribute - k.user.id.attribute = + k.user.id.attribute = ## The user ID for this user attribute id { xsd:nonNegativeInteger } k.user.groups.attribute = @@ -1871,7 +1871,7 @@ div { div { k.version.attlist = empty k.version = - ## A Version Number for the Image, Consists of Major.Minor.Release + ## A Version Number for the Image, Consists of Major.Minor.Release element version { k.version.attlist & text @@ -1915,7 +1915,7 @@ div { k.vmdisk.device.attribute = ## The disk device to appear in the guest (xen only) attribute device { text } - k.vmdisk.diskmode.attribute = + k.vmdisk.diskmode.attribute = ## The disk mode (vmdk only) attribute diskmode { "monolithicSparse" | @@ -2042,7 +2042,7 @@ div { k.pxedeploy.server.attribute = ## Name or IP Address of server for downloading the data attribute server { text } - k.pxedeploy.blocksize.attribute = + k.pxedeploy.blocksize.attribute = ## Blocksize value used for atftp downloads attribute blocksize { xsd:nonNegativeInteger } k.pxedeploy.attlist = @@ -2065,7 +2065,7 @@ div { # main block: # div { - k.description.type.attribute = + k.description.type.attribute = ## Kiwi distinguishes between two basic image description types ## which uses the same format but one is created and provided by ## the kiwi developers and the other is created by the users of @@ -2074,7 +2074,7 @@ div { ## a standard image description created by a kiwi user. attribute type { "boot" | "system" } k.description.attlist = k.description.type.attribute - k.description = + k.description = ## A Short Description element description { k.description.attlist & @@ -2090,11 +2090,11 @@ div { # div { k.drivers.profiles.attribute = k.profiles.attribute - k.drivers.attlist = + k.drivers.attlist = k.drivers.profiles.attribute? - + k.drivers = - ## A Collection of Driver Files + ## A Collection of Driver Files element drivers { k.drivers.attlist & k.file+ @@ -2207,7 +2207,8 @@ div { k.expose? & k.volumes? & k.environment? & - k.labels? + k.labels? & + k.history? } } @@ -2400,6 +2401,32 @@ div { } } +#========================================== +# main block: +# +div { + k.history.created_by.attribute = + ## Specifies the 'created by' history record. By default set to 'KIWI' + attribute created_by { text } + + k.history.author.attribute = + ## Specifies the 'author' history record. + attribute author { text } + + k.history.attlist = + k.history.created_by.attribute? & + k.history.author.attribute? + + k.history = + ## Provides details about the container history. Includes the + ## 'created by', 'author' as attributes and its content represents + ## the 'comment' entry. + element history { + k.history.attlist, + text + } +} + #========================================== # main block: # @@ -2587,7 +2614,7 @@ div { k.preferences.profiles.attribute = k.profiles.attribute k.preferences.attlist = k.preferences.profiles.attribute? - k.preferences = + k.preferences = ## Configuration Information Needed for Logical Extend ## All elements are optional since the combination of appropriate ## preference sections based on profiles combine to create on vaild @@ -2613,7 +2640,7 @@ div { # div { k.profiles.attlist = empty - k.profiles = + k.profiles = ## Namespace section which creates a namespace and the ## drivers can bind itself to one of the listed namespaces. element profiles { @@ -2629,7 +2656,7 @@ div { k.users.profiles.attribute = k.profiles.attribute k.users.attlist = k.users.profiles.attribute? - k.users = + k.users = ## A List of Users element users { k.users.attlist & diff --git a/kiwi/schema/kiwi.rng b/kiwi/schema/kiwi.rng index 5575d9db8ed..78549e4a061 100644 --- a/kiwi/schema/kiwi.rng +++ b/kiwi/schema/kiwi.rng @@ -15,7 +15,7 @@ : configuration files. The schema is maintained : in the relax compact syntax. Any changes should : made in !! *** kiwi.rnc *** !! - : + : : STATUS : Development **************** @@ -146,7 +146,7 @@ named /etc/ImageID - The root element of the configuration file + The root element of the configuration file @@ -896,7 +896,7 @@ recovery partition. Value is interpreted as MB - For oemboot driven images: shutdown after first deployment + For oemboot driven images: shutdown after first deployment true/false @@ -917,7 +917,7 @@ true/false - For oemboot driven images: shutdown after first deployment + For oemboot driven images: shutdown after first deployment true/false @@ -1577,7 +1577,7 @@ as well as a path specification - Sets the used package manager to validate, or not, the + Sets the used package manager to validate, or not, the repository and/or package signatures. The behavior can be slightly different depending on the used package manager. This is a system wide package manager option, so that, this @@ -1985,7 +1985,7 @@ size is set to 20 MB For images with an EFI firmware specifies the partition -table type to use. If not set defaults to gpt partition +table type to use. If not set defaults to gpt partition table type. msdos @@ -2750,7 +2750,7 @@ default.
- Device only for read-only + Device only for read-only @@ -2879,7 +2879,7 @@ to the user according to he specifing toolchain behaviour. - A Version Number for the Image, Consists of Major.Minor.Release + A Version Number for the Image, Consists of Major.Minor.Release @@ -3258,7 +3258,7 @@ a standard image description created by a kiwi user. - A Collection of Driver Files + A Collection of Driver Files @@ -3427,6 +3427,9 @@ section provides globally useful container information. + + + @@ -3699,6 +3702,42 @@ At least one label must be configured
+ +
+ + + Specifies the 'created by' history record. By default set to 'KIWI' + + + + + Specifies the 'author' history record. + + + + + + + + + + + + + + + Provides details about the container history. Includes the +'created by', 'author' as attributes and its content represents +the 'comment' entry. + + + + +