Add list options to overcloud container image build

This change adds the options --list-images and --list-dependencies to
the "overcloud container image build" command.

With these options, images will not be built, but the command will
return structured data for what images would be built.

This provides parsable data to allow other standard image building
pipelines to do the actual image building.

The order of --list-images is determined by doing a depth-first
traversal of the dependencies structure, which provides a reasonable
order for image building.

Change-Id: Ibabbb487465566dc2550a9b89e76800dfcae8257
This commit is contained in:
Steve Baker 2017-06-23 13:54:42 +12:00
parent daf0f04bcf
commit 16cfbd2b97
2 changed files with 179 additions and 7 deletions

View File

@ -13,9 +13,11 @@
# under the License.
#
import fixtures
import mock
import os
import shutil
import six
import tempfile
import yaml
@ -171,6 +173,8 @@ class TestContainerImageBuild(TestPluginV1):
# Get the command object to test
self.cmd = container_image.BuildImage(self.app, None)
self.cmd.app.stdout = six.StringIO()
self.temp_dir = self.useFixture(fixtures.TempDir()).join()
@mock.patch('sys.exit')
@mock.patch('tripleo_common.image.kolla_builder.KollaImageBuilder',
@ -178,12 +182,16 @@ class TestContainerImageBuild(TestPluginV1):
def test_container_image_build_noargs(self, mock_builder, exit_mock):
arglist = []
verifylist = []
mock_builder.return_value.build_images.return_value = 'done'
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# argparse will complain that --config-file and --kolla-config are
# missing exit with 2
f, path = tempfile.mkstemp(dir=self.temp_dir)
with mock.patch('tempfile.mkstemp') as mock_mkstemp:
mock_mkstemp.return_value = f, path
self.cmd.take_action(parsed_args)
# argparse will complain that --config-file is missing and exit with 2
exit_mock.assert_called_with(2)
@mock.patch('tripleo_common.image.kolla_builder.KollaImageBuilder',
@ -198,13 +206,122 @@ class TestContainerImageBuild(TestPluginV1):
'/tmp/kolla.conf'
]
verifylist = []
mock_builder.return_value.build_images.return_value = 'done'
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
f, path = tempfile.mkstemp(dir=self.temp_dir)
with mock.patch('tempfile.mkstemp') as mock_mkstemp:
mock_mkstemp.return_value = f, path
self.cmd.take_action(parsed_args)
mock_builder.assert_called_once_with([
'/tmp/foo.yaml', '/tmp/bar.yaml'])
mock_builder.return_value.build_images.assert_called_once_with([
'/tmp/kolla.conf'
'/tmp/kolla.conf',
path
])
@mock.patch('tripleo_common.image.kolla_builder.KollaImageBuilder',
autospec=True)
@mock.patch('os.remove')
def test_container_image_build_list_images(self, mock_remove,
mock_builder):
arglist = [
'--list-images',
'--config-file',
'/tmp/bar.yaml',
'--kolla-config-file',
'/tmp/kolla.conf'
]
parsed_args = self.check_parser(self.cmd, arglist, [])
deps = '{"base": ["qrouterd"]}'
mock_builder.return_value.build_images.return_value = deps
f, path = tempfile.mkstemp(dir=self.temp_dir)
with mock.patch('tempfile.mkstemp') as mock_mkstemp:
mock_mkstemp.return_value = f, path
self.cmd.take_action(parsed_args)
with open(path, 'r') as conf_file:
self.assertEqual(
conf_file.readlines(),
['[DEFAULT]\n', 'list_dependencies=true'])
self.assertEqual('- base\n- qrouterd\n',
self.cmd.app.stdout.getvalue())
@mock.patch('tripleo_common.image.kolla_builder.KollaImageBuilder',
autospec=True)
@mock.patch('os.remove')
def test_container_image_build_list_deps(self, mock_remove, mock_builder):
arglist = [
'--config-file',
'/tmp/bar.yaml',
'--kolla-config-file',
'/tmp/kolla.conf',
'--list-dependencies',
]
parsed_args = self.check_parser(self.cmd, arglist, [])
deps = '{"base": ["qrouterd"]}'
mock_builder.return_value.build_images.return_value = deps
f, path = tempfile.mkstemp(dir=self.temp_dir)
with mock.patch('tempfile.mkstemp') as mock_mkstemp:
mock_mkstemp.return_value = f, path
self.cmd.take_action(parsed_args)
with open(path, 'r') as conf_file:
self.assertEqual(
conf_file.readlines(),
['[DEFAULT]\n', 'list_dependencies=true'])
self.assertEqual('base:\n- qrouterd\n',
self.cmd.app.stdout.getvalue())
def test_images_from_deps(self):
deps = yaml.safe_load('''base:
- qdrouterd
- cron
- ceph-base:
- ceph-osd
- ceph-rgw
- ceph-mon
- cephfs-fuse
- ceph-mds
- redis
- etcd
- kubernetes-entrypoint
- kolla-toolbox
- telegraf
- openstack-base:
- swift-base:
- swift-proxy-server
- swift-account
- swift-container
- swift-object-expirer
- swift-rsyncd
- swift-object''')
images_yaml = '''- base
- qdrouterd
- cron
- ceph-base
- ceph-osd
- ceph-rgw
- ceph-mon
- cephfs-fuse
- ceph-mds
- redis
- etcd
- kubernetes-entrypoint
- kolla-toolbox
- telegraf
- openstack-base
- swift-base
- swift-proxy-server
- swift-account
- swift-container
- swift-object-expirer
- swift-rsyncd
- swift-object
'''
images = []
self.cmd.images_from_deps(images, deps)
self.assertEqual(yaml.safe_load(images_yaml), images)

View File

@ -14,10 +14,12 @@
#
import datetime
import json
import logging
import os
import re
import sys
import tempfile
from osc_lib.command import command
from osc_lib.i18n import _
@ -62,6 +64,19 @@ class BuildImage(command.Command):
auth_required = False
log = logging.getLogger(__name__ + ".BuildImage")
@staticmethod
def images_from_deps(images, dep):
'''Builds a list from the dependencies depth-first. '''
if isinstance(dep, list):
for v in dep:
BuildImage.images_from_deps(images, v)
elif isinstance(dep, dict):
for k, v in dep.items():
images.append(k)
BuildImage.images_from_deps(images, v)
else:
images.append(dep)
def get_parser(self, prog_name):
parser = super(BuildImage, self).get_parser(prog_name)
parser.add_argument(
@ -87,12 +102,52 @@ class BuildImage(command.Command):
"can be specified, with values in later files taking "
"precedence."),
)
parser.add_argument(
'--list-images',
dest='list_images',
action='store_true',
default=False,
help=_('Show the images which would be built instead of '
'building them.')
)
parser.add_argument(
'--list-dependencies',
dest='list_dependencies',
action='store_true',
default=False,
help=_('Show the image build dependencies instead of '
'building them.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
builder = kolla_builder.KollaImageBuilder(parsed_args.config_files)
builder.build_images(parsed_args.kolla_config_files)
fd, path = tempfile.mkstemp(prefix='kolla_conf_')
with os.fdopen(fd, 'w') as tmp:
tmp.write('[DEFAULT]\n')
if parsed_args.list_images or parsed_args.list_dependencies:
tmp.write('list_dependencies=true')
kolla_config_files = list(parsed_args.kolla_config_files)
kolla_config_files.append(path)
try:
builder = kolla_builder.KollaImageBuilder(parsed_args.config_files)
result = builder.build_images(kolla_config_files)
if parsed_args.list_dependencies:
deps = json.loads(result)
yaml.safe_dump(deps, self.app.stdout, indent=2,
default_flow_style=False)
elif parsed_args.list_images:
deps = json.loads(result)
images = []
BuildImage.images_from_deps(images, deps)
yaml.safe_dump(images, self.app.stdout,
default_flow_style=False)
elif result:
self.app.stdout.write(result)
finally:
os.remove(path)
class PrepareImageFiles(command.Command):