From 7734ddf57eb8868b9cfb9389d9afb526c48665c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubom=C3=ADr=20Sedl=C3=A1=C5=99?= Date: Mon, 22 Feb 2016 11:19:11 +0100 Subject: [PATCH] [live-images] Add images to manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type is either "live" or "appliance", the format is set based on filename extension. Signed-off-by: Lubomír Sedlář --- pungi/phases/live_images.py | 26 ++++++++ tests/test_liveimagesphase.py | 119 ++++++++++++++++++++++++---------- 2 files changed, 111 insertions(+), 34 deletions(-) diff --git a/pungi/phases/live_images.py b/pungi/phases/live_images.py index a4433af2..31c82797 100644 --- a/pungi/phases/live_images.py +++ b/pungi/phases/live_images.py @@ -23,6 +23,7 @@ import shutil from kobo.threads import ThreadPool, WorkerThread from kobo.shortcuts import run, save_to_file +from productmd.images import Image from pungi.wrappers.kojiwrapper import KojiWrapper from pungi.wrappers.iso import IsoWrapper @@ -269,15 +270,40 @@ class CreateLiveImageThread(WorkerThread): shutil.copy2(rpm_path, cmd["dest_dir"]) self._write_manifest(destination) + self._add_to_images(compose, variant, arch, cmd['type'], self._get_format(image_path), destination) self.pool.log_info("[DONE ] %s" % msg) + def _add_to_images(self, compose, variant, arch, type, format, path): + """Adds the image to images.json""" + img = Image(compose.im) + img.type = type + img.format = format + img.path = os.path.relpath(path, compose.paths.compose.topdir()) + img.mtime = int(os.stat(path).st_mtime) + img.size = os.path.getsize(path) + img.arch = arch + img.disc_number = 1 # We don't expect multiple disks + img.disc_count = 1 + img.bootable = True + compose.im.add(variant=variant.uid, arch=arch, image=img) + def _is_image(self, path): for ext in ('.iso', '.raw.xz'): if path.endswith(ext): return True return False + def _get_format(self, path): + """Extract all extensions from the path.""" + exts = [] + while True: + path, ext = os.path.splitext(path) + if not ext: + break + exts.append(ext.lstrip('.')) + return '.'.join(reversed(exts)) + def _write_manifest(self, iso_path): """Generate manifest for ISO at given path. diff --git a/tests/test_liveimagesphase.py b/tests/test_liveimagesphase.py index 8eb69a33..abf4a29e 100755 --- a/tests/test_liveimagesphase.py +++ b/tests/test_liveimagesphase.py @@ -22,24 +22,20 @@ class _DummyCompose(object): self.conf = config self.paths = mock.Mock( compose=mock.Mock( + topdir=mock.Mock(return_value='/top'), repository=mock.Mock( side_effect=lambda arch, variant, create_dir=False: os.path.join('/repo', arch, variant.uid) ), iso_dir=mock.Mock( side_effect=lambda arch, variant, symlink_to: os.path.join( - '/iso_dir', arch, variant.uid + '/top/iso_dir', arch, variant.uid ) ), image_dir=mock.Mock( side_effect=lambda variant, symlink_to: os.path.join( - '/image_dir/%(arch)s', variant.uid + '/top/image_dir/%(arch)s', variant.uid ) ), - iso_path=mock.Mock( - side_effect=lambda arch, variant, filename, symlink_to: os.path.join( - '/iso_dir', arch, variant.uid, filename - ) - ) ), log=mock.Mock( log_file=mock.Mock(return_value='/a/b/log/log_file') @@ -53,6 +49,7 @@ class _DummyCompose(object): } self.log_error = mock.Mock() self.get_image_name = mock.Mock(return_value='image-name') + self.im = mock.Mock() def get_arches(self): return ['x86_64', 'amd64'] @@ -93,7 +90,7 @@ class TestLiveImagesPhase(unittest.TestCase): [mock.call((compose, {'ks_file': 'test.ks', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -137,7 +134,7 @@ class TestLiveImagesPhase(unittest.TestCase): [mock.call((compose, {'ks_file': 'test.ks', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -183,7 +180,7 @@ class TestLiveImagesPhase(unittest.TestCase): [mock.call((compose, {'ks_file': 'test.ks', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -202,7 +199,7 @@ class TestLiveImagesPhase(unittest.TestCase): mock.call((compose, {'ks_file': 'another.ks', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -249,7 +246,7 @@ class TestLiveImagesPhase(unittest.TestCase): [mock.call((compose, {'ks_file': 'test.ks', 'build_arch': 'amd64', - 'dest_dir': '/image_dir/amd64/Client', + 'dest_dir': '/top/image_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -271,16 +268,17 @@ class TestLiveImagesPhase(unittest.TestCase): class TestCreateLiveImageThread(unittest.TestCase): + @mock.patch('pungi.phases.live_images.Image') @mock.patch('shutil.copy2') @mock.patch('pungi.phases.live_images.run') @mock.patch('pungi.phases.live_images.KojiWrapper') - def test_process(self, KojiWrapper, run, copy2): + def test_process(self, KojiWrapper, run, copy2, Image): compose = _DummyCompose({'koji_profile': 'koji'}) pool = mock.Mock() cmd = { 'ks_file': '/path/to/ks_file', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -305,17 +303,21 @@ class TestCreateLiveImageThread(unittest.TestCase): koji_wrapper.get_image_path.return_value = ['/path/to/image.iso'] t = CreateLiveImageThread(pool) - with mock.patch('time.sleep'): - t.process((compose, cmd, compose.variants['Client'], 'amd64'), 1) + with mock.patch('os.stat') as stat: + with mock.patch('os.path.getsize') as getsize: + with mock.patch('time.sleep'): + getsize.return_value = 1024 + stat.return_value.st_mtime = 13579 + t.process((compose, cmd, compose.variants['Client'], 'amd64'), 1) self.assertEqual(koji_wrapper.run_blocking_cmd.mock_calls, [mock.call('koji spin-livecd ...', log_file='/a/b/log/log_file')]) self.assertEqual(koji_wrapper.get_image_path.mock_calls, [mock.call(123)]) self.assertEqual(copy2.mock_calls, - [mock.call('/path/to/image.iso', '/iso_dir/amd64/Client/image-name')]) + [mock.call('/path/to/image.iso', '/top/iso_dir/amd64/Client/image-name')]) write_manifest_cmd = ' && '.join([ - 'cd /iso_dir/amd64/Client', + 'cd /top/iso_dir/amd64/Client', 'isoinfo -R -f -i image-name | grep -v \'/TRANS.TBL$\' | sort >> image-name.manifest' ]) self.assertEqual(run.mock_calls, [mock.call(write_manifest_cmd)]) @@ -331,17 +333,29 @@ class TestCreateLiveImageThread(unittest.TestCase): wait=True, release=None, ksurl='https://git.example.com/kickstarts.git?#CAFEBABE')]) + self.assertEqual(Image.return_value.type, 'live') + self.assertEqual(Image.return_value.format, 'iso') + self.assertEqual(Image.return_value.path, 'iso_dir/amd64/Client/image-name') + self.assertEqual(Image.return_value.size, 1024) + self.assertEqual(Image.return_value.mtime, 13579) + self.assertEqual(Image.return_value.arch, 'amd64') + self.assertEqual(Image.return_value.disc_number, 1) + self.assertEqual(Image.return_value.disc_count, 1) + self.assertTrue(Image.return_value.bootable) + self.assertEqual(compose.im.add.mock_calls, + [mock.call(variant='Client', arch='amd64', image=Image.return_value)]) + @mock.patch('pungi.phases.live_images.Image') @mock.patch('shutil.copy2') @mock.patch('pungi.phases.live_images.run') @mock.patch('pungi.phases.live_images.KojiWrapper') - def test_process_no_rename(self, KojiWrapper, run, copy2): + def test_process_no_rename(self, KojiWrapper, run, copy2, Image): compose = _DummyCompose({'koji_profile': 'koji'}) pool = mock.Mock() cmd = { 'ks_file': '/path/to/ks_file', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -366,17 +380,25 @@ class TestCreateLiveImageThread(unittest.TestCase): koji_wrapper.get_image_path.return_value = ['/path/to/image.iso'] t = CreateLiveImageThread(pool) - with mock.patch('time.sleep'): - t.process((compose, cmd, compose.variants['Client'], 'amd64'), 1) + with mock.patch('os.stat') as stat: + with mock.patch('os.path.getsize') as getsize: + getsize.return_value = 1024 + getsize.return_value = 1024 + getsize.return_value = 1024 + getsize.return_value = 1024 + with mock.patch('time.sleep'): + getsize.return_value = 1024 + stat.return_value.st_mtime = 13579 + t.process((compose, cmd, compose.variants['Client'], 'amd64'), 1) self.assertEqual(koji_wrapper.run_blocking_cmd.mock_calls, [mock.call('koji spin-livecd ...', log_file='/a/b/log/log_file')]) self.assertEqual(koji_wrapper.get_image_path.mock_calls, [mock.call(123)]) self.assertEqual(copy2.mock_calls, - [mock.call('/path/to/image.iso', '/iso_dir/amd64/Client/image.iso')]) + [mock.call('/path/to/image.iso', '/top/iso_dir/amd64/Client/image.iso')]) write_manifest_cmd = ' && '.join([ - 'cd /iso_dir/amd64/Client', + 'cd /top/iso_dir/amd64/Client', 'isoinfo -R -f -i image.iso | grep -v \'/TRANS.TBL$\' | sort >> image.iso.manifest' ]) self.assertEqual(run.mock_calls, [mock.call(write_manifest_cmd)]) @@ -393,16 +415,29 @@ class TestCreateLiveImageThread(unittest.TestCase): release=None, ksurl='https://git.example.com/kickstarts.git?#CAFEBABE')]) + self.assertEqual(Image.return_value.type, 'live') + self.assertEqual(Image.return_value.format, 'iso') + self.assertEqual(Image.return_value.path, 'iso_dir/amd64/Client/image.iso') + self.assertEqual(Image.return_value.size, 1024) + self.assertEqual(Image.return_value.mtime, 13579) + self.assertEqual(Image.return_value.arch, 'amd64') + self.assertEqual(Image.return_value.disc_number, 1) + self.assertEqual(Image.return_value.disc_count, 1) + self.assertTrue(Image.return_value.bootable) + self.assertEqual(compose.im.add.mock_calls, + [mock.call(variant='Client', arch='amd64', image=Image.return_value)]) + + @mock.patch('pungi.phases.live_images.Image') @mock.patch('shutil.copy2') @mock.patch('pungi.phases.live_images.run') @mock.patch('pungi.phases.live_images.KojiWrapper') - def test_process_applicance(self, KojiWrapper, run, copy2): + def test_process_applicance(self, KojiWrapper, run, copy2, Image): compose = _DummyCompose({'koji_profile': 'koji'}) pool = mock.Mock() cmd = { 'ks_file': '/path/to/ks_file', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -424,20 +459,24 @@ class TestCreateLiveImageThread(unittest.TestCase): 'output': 'some output', 'task_id': 123 } - koji_wrapper.get_image_path.return_value = ['/path/to/image.iso'] + koji_wrapper.get_image_path.return_value = ['/path/to/image.raw.xz'] t = CreateLiveImageThread(pool) - with mock.patch('time.sleep'): - t.process((compose, cmd, compose.variants['Client'], 'amd64'), 1) + with mock.patch('os.stat') as stat: + with mock.patch('os.path.getsize') as getsize: + with mock.patch('time.sleep'): + getsize.return_value = 1024 + stat.return_value.st_mtime = 13579 + t.process((compose, cmd, compose.variants['Client'], 'amd64'), 1) self.assertEqual(koji_wrapper.run_blocking_cmd.mock_calls, [mock.call('koji spin-livecd ...', log_file='/a/b/log/log_file')]) self.assertEqual(koji_wrapper.get_image_path.mock_calls, [mock.call(123)]) self.assertEqual(copy2.mock_calls, - [mock.call('/path/to/image.iso', '/iso_dir/amd64/Client/image-name')]) + [mock.call('/path/to/image.raw.xz', '/top/iso_dir/amd64/Client/image-name')]) write_manifest_cmd = ' && '.join([ - 'cd /iso_dir/amd64/Client', + 'cd /top/iso_dir/amd64/Client', 'isoinfo -R -f -i image-name | grep -v \'/TRANS.TBL$\' | sort >> image-name.manifest' ]) self.assertEqual(run.mock_calls, [mock.call(write_manifest_cmd)]) @@ -454,6 +493,18 @@ class TestCreateLiveImageThread(unittest.TestCase): release=None, ksurl=None)]) + self.assertEqual(Image.return_value.type, 'appliance') + self.assertEqual(Image.return_value.format, 'raw.xz') + self.assertEqual(Image.return_value.path, 'iso_dir/amd64/Client/image-name') + self.assertEqual(Image.return_value.size, 1024) + self.assertEqual(Image.return_value.mtime, 13579) + self.assertEqual(Image.return_value.arch, 'amd64') + self.assertEqual(Image.return_value.disc_number, 1) + self.assertEqual(Image.return_value.disc_count, 1) + self.assertTrue(Image.return_value.bootable) + self.assertEqual(compose.im.add.mock_calls, + [mock.call(variant='Client', arch='amd64', image=Image.return_value)]) + @mock.patch('shutil.copy2') @mock.patch('pungi.phases.live_images.run') @mock.patch('pungi.phases.live_images.KojiWrapper') @@ -466,7 +517,7 @@ class TestCreateLiveImageThread(unittest.TestCase): cmd = { 'ks_file': '/path/to/ks_file', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', @@ -503,14 +554,14 @@ class TestCreateLiveImageThread(unittest.TestCase): cmd = { 'ks_file': '/path/to/ks_file', 'build_arch': 'amd64', - 'dest_dir': '/iso_dir/amd64/Client', + 'dest_dir': '/top/iso_dir/amd64/Client', 'scratch': False, 'repos': ['/repo/amd64/Client', 'http://example.com/repo/', '/repo/amd64/Everything'], 'label': '', 'name': None, - 'iso_path': '/iso_dir/amd64/Client/image-name', + 'filename': 'image-name', 'version': None, 'specfile': None, 'ksurl': None,