Implement tripleo container image push command

This patch implements the tripleo container image push command.

Change-Id: Ifc2588e9e62582530f19c852715dea3ff68d9fa4
Co-Authored-By: Alex Schultz <aschultz@redhat.com>
This commit is contained in:
David J Peacock 2019-08-06 08:49:35 -04:00 committed by Alex Schultz
parent 5a66fe5446
commit bdfd3bfb1b
4 changed files with 316 additions and 1 deletions

View File

@ -0,0 +1,8 @@
---
features:
- |
With the new podman container setup comes an Apache served local image
registry.
`openstack tripleo container image push` allows you to maintain those
images, and add new images as required.

View File

@ -107,6 +107,7 @@ openstack.tripleoclient.v1 =
tripleo_container_image_delete = tripleoclient.v1.container_image:TripleOContainerImageDelete
tripleo_container_image_list = tripleoclient.v1.container_image:TripleOContainerImageList
tripleo_container_image_show = tripleoclient.v1.container_image:TripleOContainerImageShow
tripleo_container_image_push = tripleoclient.v1.container_image:TripleOContainerImagePush
tripleo_container_image_prepare = tripleoclient.v1.container_image:TripleOImagePrepare
tripleo_container_image_prepare_default = tripleoclient.v1.container_image:TripleOImagePrepareDefault
undercloud_install = tripleoclient.v1.undercloud:InstallUndercloud

View File

@ -24,9 +24,10 @@ import tempfile
import yaml
from osc_lib import exceptions as oscexc
from six.moves.urllib import parse
from tripleo_common.image import image_uploader
from tripleo_common.image import kolla_builder
from tripleoclient import exceptions as tcexc
from tripleoclient.tests.v1.test_plugin import TestPluginV1
from tripleoclient.v1 import container_image
@ -82,6 +83,194 @@ class TestContainerImageUpload(TestPluginV1):
mock_manager.return_value.upload.assert_called_once_with()
class TestContainerImagePush(TestPluginV1):
def setUp(self):
super(TestContainerImagePush, self).setUp()
self.cmd = container_image.TripleOContainerImagePush(self.app, None)
@mock.patch('tripleo_common.image.image_uploader.UploadTask')
@mock.patch('tripleo_common.image.image_uploader.ImageUploadManager')
def test_take_action(self, mock_manager, mock_task):
arglist = ['docker.io/namespace/foo']
verifylist = [('image_to_push', 'docker.io/namespace/foo')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# mock manager object
mock_mgr = mock.Mock()
mock_manager.return_value = mock_mgr
# mock uploader object
mock_uploader = mock.Mock()
mock_mgr.uploader.return_value = mock_uploader
# mock return url object from uploader._image_to_url
mock_url = mock.Mock()
container_url = parse.urlparse("docker://docker.io/namespace/foo")
registry_url = parse.urlparse("docker://127.0.0.1:8787")
mock_url.side_effect = [container_url, registry_url]
mock_uploader._image_to_url = mock_url
# mock return session object from uploader.authenticate
mock_session = mock.Mock()
mock_uploader.authenticate.return_value = mock_session
# mock upload task
mock_uploadtask = mock.Mock()
mock_task.return_value = mock_uploadtask
# mock add upload task action
mock_add_upload = mock.Mock()
data = []
mock_add_upload.return_value = data
mock_uploader.add_upload_task = mock_add_upload
# mock run tasks action
mock_run_tasks = mock.Mock()
mock_uploader.run_tasks = mock_run_tasks
self.cmd.take_action(parsed_args)
mock_task.assert_called_once_with(
image_name='namespace/foo',
pull_source='docker.io',
push_destination=parsed_args.registry_url,
append_tag=parsed_args.append_tag,
modify_role=None,
modify_vars=None,
dry_run=parsed_args.dry_run,
cleanup=False,
multi_arch=parsed_args.multi_arch)
mock_add_upload.assert_called_once_with(mock_uploadtask)
mock_run_tasks.assert_called_once()
def test_take_action_local(self):
arglist = ['docker.io/namespace/foo', '--local']
verifylist = [('image_to_push', 'docker.io/namespace/foo'),
('local', True)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(tcexc.NotFound, self.cmd.take_action, parsed_args)
@mock.patch('tripleo_common.image.image_uploader.UploadTask')
@mock.patch('tripleo_common.image.image_uploader.ImageUploadManager')
def test_take_action_oserror(self, mock_manager, mock_task):
arglist = ['docker.io/namespace/foo']
verifylist = [('image_to_push', 'docker.io/namespace/foo')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# mock manager object
mock_mgr = mock.Mock()
mock_manager.return_value = mock_mgr
# mock uploader object
mock_uploader = mock.Mock()
mock_mgr.uploader.return_value = mock_uploader
# mock return url object from uploader._image_to_url
mock_url = mock.Mock()
container_url = parse.urlparse("docker://docker.io/namespace/foo")
registry_url = parse.urlparse("docker://127.0.0.1:8787")
mock_url.side_effect = [container_url, registry_url]
mock_uploader._image_to_url = mock_url
# mock return session object from uploader.authenticate
mock_session = mock.Mock()
mock_uploader.authenticate.return_value = mock_session
# mock upload task
mock_uploadtask = mock.Mock()
mock_task.return_value = mock_uploadtask
# mock add upload task action
mock_add_upload = mock.Mock()
data = []
mock_add_upload.return_value = data
mock_uploader.add_upload_task = mock_add_upload
# mock run tasks action
mock_run_tasks = mock.Mock()
mock_run_tasks.side_effect = OSError('Fail')
mock_uploader.run_tasks = mock_run_tasks
self.assertRaises(oscexc.CommandError,
self.cmd.take_action,
parsed_args)
@mock.patch('tripleo_common.image.image_uploader.UploadTask')
@mock.patch('tripleo_common.image.image_uploader.ImageUploadManager')
def test_take_action_all_options(self, mock_manager, mock_task):
arglist = ['--registry-url', '127.0.0.1:8787',
'--append-tag', 'test',
'--username', 'user',
'--password', 'password',
'--dry-run',
'--multi-arch',
'--cleanup',
'docker.io/namespace/foo:tag']
verifylist = [('registry_url', '127.0.0.1:8787'),
('append_tag', 'test'),
('username', 'user'),
('password', 'password'),
('dry_run', True),
('multi_arch', True),
('cleanup', True),
('image_to_push', 'docker.io/namespace/foo:tag')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# mock manager object
mock_mgr = mock.Mock()
mock_manager.return_value = mock_mgr
# mock uploader object
mock_uploader = mock.Mock()
mock_mgr.uploader.return_value = mock_uploader
# mock return url object from uploader._image_to_url
mock_url = mock.Mock()
container_url = parse.urlparse("docker://docker.io/namespace/foo:tag")
registry_url = parse.urlparse("docker://127.0.0.1:8787")
mock_url.side_effect = [container_url, registry_url]
mock_uploader._image_to_url = mock_url
# mock return session object from uploader.authenticate
mock_session = mock.Mock()
mock_uploader.authenticate.return_value = mock_session
# mock upload task
mock_uploadtask = mock.Mock()
mock_task.return_value = mock_uploadtask
# mock add upload task action
mock_add_upload = mock.Mock()
data = []
mock_add_upload.return_value = data
mock_uploader.add_upload_task = mock_add_upload
# mock run tasks action
mock_run_tasks = mock.Mock()
mock_uploader.run_tasks = mock_run_tasks
self.cmd.take_action(parsed_args)
mock_uploader.authenticate.assert_called_once_with(
registry_url, parsed_args.username, parsed_args.password)
mock_task.assert_called_once_with(
image_name='namespace/foo:tag',
pull_source='docker.io',
push_destination=parsed_args.registry_url,
append_tag=parsed_args.append_tag,
modify_role=None,
modify_vars=None,
dry_run=parsed_args.dry_run,
cleanup=True,
multi_arch=parsed_args.multi_arch)
mock_add_upload.assert_called_once_with(mock_uploadtask)
mock_run_tasks.assert_called_once()
class TestContainerImageDelete(TestPluginV1):
def setUp(self):

View File

@ -513,6 +513,123 @@ class DiscoverImageTag(command.Command):
))
class TripleOContainerImagePush(command.Command):
"""Push specified image to registry."""
auth_required = False
log = logging.getLogger(__name__ + ".TripleoContainerImagePush")
def get_parser(self, prog_name):
parser = super(TripleOContainerImagePush, self).get_parser(prog_name)
parser.add_argument(
"--local",
dest="local",
default=False,
action="store_true",
help=_("Use this flag if the container image is already on the "
"current system and does not need to be pulled from a "
"remote registry.")
)
parser.add_argument(
"--registry-url",
dest="registry_url",
metavar='<registry url>',
default=image_uploader.get_undercloud_registry(),
help=_("URL of the destination registry in the form "
"<fqdn>:<port>.")
)
parser.add_argument(
"--append-tag",
dest="append_tag",
default='',
help=_("Tag to append to the existing tag when pushing the "
"container. ")
)
parser.add_argument(
"--username",
dest="username",
metavar='<username>',
help=_("Username for the destination image registry.")
)
parser.add_argument(
"--password",
dest="password",
metavar='<password>',
help=_("Password for the destination image registry.")
)
parser.add_argument(
"--dry-run",
dest="dry_run",
action="store_true",
help=_("Perform a dry run upload. The upload action is not "
"performed, but the authentication process is attempted.")
)
parser.add_argument(
"--multi-arch",
dest="multi_arch",
action="store_true",
help=_("Enable multi arch support for the upload.")
)
parser.add_argument(
"--cleanup",
dest="cleanup",
action="store_true",
default=False,
help=_("Remove local copy of the image after uploading")
)
parser.add_argument(
dest="image_to_push",
metavar='<image to push>',
help=_("Container image to upload. Should be in the form of "
"<registry>/<namespace>/<name>:<tag>. If tag is "
"not provided, then latest will be used.")
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % parsed_args)
# TODO(aschultz): need to fix upload to be able to handle local source
if parsed_args.local:
raise exceptions.NotFound('--local is currently not implemented')
manager = image_uploader.ImageUploadManager()
uploader = manager.uploader('python')
source_url = uploader._image_to_url(parsed_args.image_to_push)
image_name = source_url.path[1:]
if len(image_name.split('/')) != 2:
raise exceptions.DownloadError('Invalid container. Provided '
'container image should be '
'<registry>/<namespace>/<name>:'
'<tag>')
image_source = source_url.netloc
reg_url = uploader._image_to_url(parsed_args.registry_url)
uploader.authenticate(reg_url,
parsed_args.username,
parsed_args.password)
task = image_uploader.UploadTask(
image_name=image_name,
pull_source=image_source,
push_destination=parsed_args.registry_url,
append_tag=parsed_args.append_tag,
modify_role=None,
modify_vars=None,
dry_run=parsed_args.dry_run,
cleanup=parsed_args.cleanup,
multi_arch=parsed_args.multi_arch)
try:
uploader.add_upload_task(task)
uploader.run_tasks()
except OSError as e:
self.log.error("Unable to upload due to permissions. "
"Please prefix command with sudo.")
raise oscexc.CommandError(e)
class TripleOContainerImageDelete(command.Command):
"""Delete specified image from registry."""