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:
parent
5a66fe5446
commit
bdfd3bfb1b
@ -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.
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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."""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user