From 223a01589817bc6287369a0aef305b2b5287d801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubom=C3=ADr=20Sedl=C3=A1=C5=99?= Date: Tue, 30 Aug 2016 09:51:36 +0200 Subject: [PATCH] Automatically generate missing image version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the configuration does not specify version for images or live media, Pungi will create a default value based on `release_version`. If label is used for the compose, the milestone from it will be appended to the version (unless it's RC). This change is backwards compatible: nothing changes when version is set in configuration. If the version was missing before, building the artifacts would fail. With this patch, default values will be supplied. Signed-off-by: Lubomír Sedlář --- doc/configuration.rst | 7 ++-- pungi/compose.py | 14 ++++++++ pungi/phases/base.py | 7 ++++ pungi/phases/image_build.py | 2 +- pungi/phases/live_images.py | 2 +- pungi/phases/livemedia_phase.py | 2 +- tests/helpers.py | 1 + tests/test_compose.py | 46 +++++++++++++++++++++++-- tests/test_imagebuildphase.py | 59 +++++++++++++++++++++++++++++++++ tests/test_liveimagesphase.py | 14 ++++---- tests/test_livemediaphase.py | 3 +- 11 files changed, 141 insertions(+), 16 deletions(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index a4bbffc7..ab848b54 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -810,14 +810,17 @@ Target is specified by these settings. For live images refer to ``live_target``. * ``live_media_target`` * ``image_build_target`` -Version is specified by these options. +.. _auto_version: + +Version is specified by these options. If no version is set, a default value +will be provided based on product version. If label is used (and is not +``RC``), the milestone will be appended to the version with an underscore. * ``global_version`` -- global fallback setting * ``live_media_version`` * ``image_build_version`` * ``live_images_version`` - .. _auto_release: Release is specified by these options. If set explicitly to ``None``, a value diff --git a/pungi/compose.py b/pungi/compose.py index 6bcdfa3b..e51fdf14 100644 --- a/pungi/compose.py +++ b/pungi/compose.py @@ -355,3 +355,17 @@ class Compose(kobo.log.LoggingBase): return '%s%s.%s' % (self.compose_date, self.ci_base.compose.type_suffix, self.compose_respin) + + @property + def image_version(self): + """Generate a value to pass to Koji as image version. + + The value is based on release version. If compose has a label, the + milestone from it is appended to the version (unless it is RC). + """ + version = self.ci_base.release.version + if self.compose_label and not self.compose_label.startswith('RC-'): + milestone, release = self.compose_label.split('-') + return '%s_%s' % (version, milestone) + + return version diff --git a/pungi/phases/base.py b/pungi/phases/base.py index 8e8fe639..06bda27d 100644 --- a/pungi/phases/base.py +++ b/pungi/phases/base.py @@ -106,6 +106,13 @@ class ImageConfigMixin(object): '%s_%s' % (self.name, opt), self.compose.conf.get( 'global_%s' % opt))) + def get_version(self, cfg): + """ + Get version from configuration hierarchy or fall back to release + version. + """ + return self.get_config(cfg, 'version') or self.compose.image_version + def get_release(self, cfg): """ If release is set explicitly to None, replace it with date and respin. diff --git a/pungi/phases/image_build.py b/pungi/phases/image_build.py index 25b1363a..2129d5b7 100644 --- a/pungi/phases/image_build.py +++ b/pungi/phases/image_build.py @@ -134,7 +134,7 @@ class ImageBuildPhase(base.ImageConfigMixin, base.ConfigGuardedPhase): if release: image_conf['image-build']['release'] = release - image_conf['image-build']['version'] = self.get_config(image_conf['image-build'], 'version') + image_conf['image-build']['version'] = self.get_version(image_conf['image-build']) image_conf['image-build']['target'] = self.get_config(image_conf['image-build'], 'target') # transform format into right 'format' for image-build diff --git a/pungi/phases/live_images.py b/pungi/phases/live_images.py index ebd46b57..7d90c8da 100644 --- a/pungi/phases/live_images.py +++ b/pungi/phases/live_images.py @@ -140,7 +140,7 @@ class LiveImagesPhase(base.ImageConfigMixin, base.ConfigGuardedPhase): cmd = { "name": data.get('name'), - "version": self.get_config(data, 'version'), + "version": self.get_version(data), "release": self.get_release(data), "dest_dir": dest_dir, "build_arch": arch, diff --git a/pungi/phases/livemedia_phase.py b/pungi/phases/livemedia_phase.py index 48279e03..3e10a6d7 100644 --- a/pungi/phases/livemedia_phase.py +++ b/pungi/phases/livemedia_phase.py @@ -115,7 +115,7 @@ class LiveMediaPhase(ImageConfigMixin, ConfigGuardedPhase): 'title': image_conf.get('title'), 'repo': self._get_repos(image_conf, variant), 'install_tree': self._get_install_tree(image_conf, variant), - 'version': self.get_config(image_conf, 'version'), + 'version': self.get_version(image_conf), 'failable_arches': image_conf.get('failable', []), } self.pool.add(LiveMediaThread(self.pool)) diff --git a/tests/helpers.py b/tests/helpers.py index e52090f8..4ec53737 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -37,6 +37,7 @@ class DummyCompose(object): self.compose_label = None self.compose_label_major_version = None self.image_release = '20151203.t.0' + self.image_version = '25' self.ci_base = mock.Mock( release_id='Test-1.0', release=mock.Mock( diff --git a/tests/test_compose.py b/tests/test_compose.py index cc7bb939..5dd2ddb5 100755 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -127,12 +127,12 @@ class ComposeTestCase(unittest.TestCase): ci.return_value.compose.respin = 2 ci.return_value.compose.date = '20160107' ci.return_value.compose.type = 'production' - ci.return_value.compose.type_suffix = '.n' + ci.return_value.compose.type_suffix = '' ci.return_value.compose.label = None compose = Compose(conf, self.tmp_dir) - self.assertEqual(compose.image_release, '20160107.n.2') + self.assertEqual(compose.image_release, '20160107.2') @mock.patch('pungi.compose.ComposeInfo') def test_image_release_from_label(self, ci): @@ -147,6 +147,48 @@ class ComposeTestCase(unittest.TestCase): self.assertEqual(compose.image_release, '1.2') + @mock.patch('pungi.compose.ComposeInfo') + def test_image_version_without_label(self, ci): + conf = {} + ci.return_value.compose.respin = 2 + ci.return_value.compose.date = '20160107' + ci.return_value.compose.type = 'nightly' + ci.return_value.compose.type_suffix = '.n' + ci.return_value.compose.label = None + ci.return_value.release.version = '25' + + compose = Compose(conf, self.tmp_dir) + + self.assertEqual(compose.image_version, '25') + + @mock.patch('pungi.compose.ComposeInfo') + def test_image_version_with_label(self, ci): + conf = {} + ci.return_value.compose.respin = 2 + ci.return_value.compose.date = '20160107' + ci.return_value.compose.type = 'nightly' + ci.return_value.compose.type_suffix = '.n' + ci.return_value.compose.label = 'Alpha-1.2' + ci.return_value.release.version = '25' + + compose = Compose(conf, self.tmp_dir) + + self.assertEqual(compose.image_version, '25_Alpha') + + @mock.patch('pungi.compose.ComposeInfo') + def test_image_version_with_label_rc(self, ci): + conf = {} + ci.return_value.compose.respin = 2 + ci.return_value.compose.date = '20160107' + ci.return_value.compose.type = 'nightly' + ci.return_value.compose.type_suffix = '.n' + ci.return_value.compose.label = 'RC-1.2' + ci.return_value.release.version = '25' + + compose = Compose(conf, self.tmp_dir) + + self.assertEqual(compose.image_version, '25') + @mock.patch('pungi.compose.ComposeInfo') def test_get_variant_arches_without_filter(self, ci): conf = ConfigWrapper( diff --git a/tests/test_imagebuildphase.py b/tests/test_imagebuildphase.py index 1a5e2b12..9f9fc2b6 100755 --- a/tests/test_imagebuildphase.py +++ b/tests/test_imagebuildphase.py @@ -163,6 +163,65 @@ class TestImageBuildPhase(PungiTestCase): self.assertItemsEqual(phase.pool.queue_put.mock_calls, [mock.call((compose, server_args))]) + @mock.patch('pungi.phases.image_build.ThreadPool') + def test_image_build_phase_missing_version(self, ThreadPool): + compose = DummyCompose(self.topdir, { + 'image_build_ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git', + 'image_build_release': None, + 'image_build_target': 'f24', + 'image_build': { + '^Server$': [ + { + 'image-build': { + 'format': [('docker', 'tar.xz')], + 'name': 'Fedora-Docker-Base', + 'kickstart': "fedora-docker-base.ks", + 'distro': 'Fedora-20', + 'disk_size': 3 + } + } + ] + }, + 'koji_profile': 'koji', + }) + + phase = ImageBuildPhase(compose) + + phase.run() + self.maxDiff = None + + # assert at least one thread was started + self.assertTrue(phase.pool.add.called) + server_args = { + "format": [('docker', 'tar.xz')], + "image_conf": { + 'image-build': { + 'install_tree': self.topdir + '/compose/Server/$arch/os', + 'kickstart': 'fedora-docker-base.ks', + 'format': 'docker', + 'repo': self.topdir + '/compose/Server/$arch/os', + 'variant': compose.variants['Server'], + 'target': 'f24', + 'disk_size': 3, + 'name': 'Fedora-Docker-Base', + 'arches': 'amd64,x86_64', + 'version': '25', + 'ksurl': 'git://git.fedorahosted.org/git/spin-kickstarts.git', + 'distro': 'Fedora-20', + 'release': '20151203.t.0', + } + }, + "conf_file": self.topdir + '/work/image-build/Server/docker_Fedora-Docker-Base.cfg', + "image_dir": self.topdir + '/compose/Server/%(arch)s/images', + "relative_image_dir": 'Server/%(arch)s/images', + "link_type": 'hardlink-or-copy', + "scratch": False, + "failable_arches": [], + } + self.maxDiff = None + self.assertItemsEqual(phase.pool.queue_put.mock_calls, + [mock.call((compose, server_args))]) + @mock.patch('pungi.phases.image_build.ThreadPool') def test_image_build_filter_all_variants(self, ThreadPool): compose = DummyCompose(self.topdir, { diff --git a/tests/test_liveimagesphase.py b/tests/test_liveimagesphase.py index 8a00acec..89fe9f12 100755 --- a/tests/test_liveimagesphase.py +++ b/tests/test_liveimagesphase.py @@ -50,7 +50,7 @@ class TestLiveImagesPhase(PungiTestCase): 'label': '', 'name': None, 'filename': 'image-name', - 'version': None, + 'version': '25', 'specfile': None, 'sign': False, 'type': 'live', @@ -99,7 +99,7 @@ class TestLiveImagesPhase(PungiTestCase): 'label': '', 'name': None, 'filename': 'image-name', - 'version': None, + 'version': '25', 'specfile': None, 'sign': False, 'type': 'live', @@ -145,7 +145,7 @@ class TestLiveImagesPhase(PungiTestCase): 'label': '', 'name': None, 'filename': None, - 'version': None, + 'version': '25', 'specfile': None, 'sign': False, 'type': 'live', @@ -193,7 +193,7 @@ class TestLiveImagesPhase(PungiTestCase): 'label': '', 'name': None, 'filename': 'image-name', - 'version': None, + 'version': '25', 'specfile': None, 'sign': False, 'type': 'live', @@ -214,7 +214,7 @@ class TestLiveImagesPhase(PungiTestCase): 'label': '', 'name': None, 'filename': 'image-name', - 'version': None, + 'version': '25', 'specfile': None, 'sign': False, 'type': 'live', @@ -263,7 +263,7 @@ class TestLiveImagesPhase(PungiTestCase): 'label': '', 'name': None, 'filename': 'image-name', - 'version': None, + 'version': '25', 'specfile': None, 'sign': False, 'type': 'appliance', @@ -417,7 +417,7 @@ class TestLiveImagesPhase(PungiTestCase): 'label': '', 'name': None, 'filename': 'image-name', - 'version': None, + 'version': '25', 'specfile': None, 'sign': False, 'type': 'live', diff --git a/tests/test_livemediaphase.py b/tests/test_livemediaphase.py index dee7afc4..b22bef3b 100755 --- a/tests/test_livemediaphase.py +++ b/tests/test_livemediaphase.py @@ -339,7 +339,6 @@ class TestLiveMediaPhase(PungiTestCase): 'arches': ['x86_64'], 'ksversion': '24', 'release': None, - 'version': 'Rawhide', 'install_tree_from': 'Everything', 'subvariant': 'Something', 'failable': ['*'], @@ -372,7 +371,7 @@ class TestLiveMediaPhase(PungiTestCase): 'target': 'f24', 'title': 'Custom Title', 'install_tree': self.topdir + '/compose/Everything/$basearch/os', - 'version': 'Rawhide', + 'version': '25', 'subvariant': 'Something', 'failable_arches': ['*'], }))])