Merge "Implement image customization during upload"
This commit is contained in:
commit
e4becc4545
@ -21,10 +21,13 @@ import logging
|
|||||||
import netifaces
|
import netifaces
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
import shutil
|
||||||
import six
|
import six
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import tenacity
|
import tenacity
|
||||||
|
import yaml
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
try:
|
try:
|
||||||
@ -32,6 +35,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from docker import Client
|
from docker import Client
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
|
from tripleo_common.actions import ansible
|
||||||
from tripleo_common.image.base import BaseImageManager
|
from tripleo_common.image.base import BaseImageManager
|
||||||
from tripleo_common.image.exception import ImageUploaderException
|
from tripleo_common.image.exception import ImageUploaderException
|
||||||
|
|
||||||
@ -96,9 +100,13 @@ class ImageUploadManager(BaseImageManager):
|
|||||||
|
|
||||||
# This updates the parsed upload_images dict with real values
|
# This updates the parsed upload_images dict with real values
|
||||||
item['push_destination'] = push_destination
|
item['push_destination'] = push_destination
|
||||||
|
append_tag = item.get('append_tag')
|
||||||
|
modify_role = item.get('modify_role')
|
||||||
|
modify_vars = item.get('modify_vars')
|
||||||
|
|
||||||
self.uploader(uploader).add_upload_task(
|
self.uploader(uploader).add_upload_task(
|
||||||
image_name, pull_source, push_destination)
|
image_name, pull_source, push_destination,
|
||||||
|
append_tag, modify_role, modify_vars)
|
||||||
|
|
||||||
for uploader in self.uploaders.values():
|
for uploader in self.uploaders.values():
|
||||||
uploader.run_tasks()
|
uploader.run_tasks()
|
||||||
@ -122,7 +130,8 @@ class ImageUploader(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def add_upload_task(self, image_name, pull_source, push_destination):
|
def add_upload_task(self, image_name, pull_source, push_destination,
|
||||||
|
append_tag, modify_role, modify_vars):
|
||||||
"""Add an upload task to be executed later"""
|
"""Add an upload task to be executed later"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -150,41 +159,90 @@ class DockerImageUploader(ImageUploader):
|
|||||||
self.secure_registries = set(SECURE_REGISTRIES)
|
self.secure_registries = set(SECURE_REGISTRIES)
|
||||||
self.insecure_registries = set()
|
self.insecure_registries = set()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_modify_playbook(modify_role, modify_vars,
|
||||||
|
source_image, target_image, append_tag):
|
||||||
|
vars = {}
|
||||||
|
if modify_vars:
|
||||||
|
vars.update(modify_vars)
|
||||||
|
vars['source_image'] = source_image
|
||||||
|
vars['target_image'] = target_image
|
||||||
|
vars['modified_append_tag'] = append_tag
|
||||||
|
LOG.debug('Playbook variables: \n%s' % yaml.safe_dump(
|
||||||
|
vars, default_flow_style=False))
|
||||||
|
playbook = [{
|
||||||
|
'hosts': 'localhost',
|
||||||
|
'tasks': [{
|
||||||
|
'name': 'Import role %s' % modify_role,
|
||||||
|
'import_role': {
|
||||||
|
'name': modify_role
|
||||||
|
},
|
||||||
|
'vars': vars
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
LOG.debug('Playbook: \n%s' % yaml.safe_dump(
|
||||||
|
playbook, default_flow_style=False))
|
||||||
|
work_dir = tempfile.mkdtemp(prefix='tripleo-modify-image-playbook-')
|
||||||
|
try:
|
||||||
|
action = ansible.AnsiblePlaybookAction(
|
||||||
|
playbook=playbook,
|
||||||
|
work_dir=work_dir
|
||||||
|
)
|
||||||
|
result = action.run(None)
|
||||||
|
LOG.debug(result.get('stdout', ''))
|
||||||
|
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(work_dir)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def upload_image(image_name, pull_source, push_destination,
|
def upload_image(image_name, pull_source, push_destination,
|
||||||
insecure_registries):
|
insecure_registries, append_tag, modify_role,
|
||||||
|
modify_vars):
|
||||||
LOG.info('imagename: %s' % image_name)
|
LOG.info('imagename: %s' % image_name)
|
||||||
dockerc = Client(base_url='unix://var/run/docker.sock', version='auto')
|
dockerc = Client(base_url='unix://var/run/docker.sock', version='auto')
|
||||||
if ':' in image_name:
|
if ':' in image_name:
|
||||||
image = image_name.rpartition(':')[0]
|
image = image_name.rpartition(':')[0]
|
||||||
tag = image_name.rpartition(':')[2]
|
source_tag = image_name.rpartition(':')[2]
|
||||||
else:
|
else:
|
||||||
image = image_name
|
image = image_name
|
||||||
tag = 'latest'
|
source_tag = 'latest'
|
||||||
if pull_source:
|
if pull_source:
|
||||||
repo = pull_source + '/' + image
|
repo = pull_source + '/' + image
|
||||||
else:
|
else:
|
||||||
repo = image
|
repo = image
|
||||||
|
|
||||||
full_image = repo + ':' + tag
|
source_image = repo + ':' + source_tag
|
||||||
new_repo = push_destination + '/' + repo.partition('/')[2]
|
target_image_no_tag = push_destination + '/' + repo.partition('/')[2]
|
||||||
full_new_repo = new_repo + ':' + tag
|
append_tag = append_tag or ''
|
||||||
|
target_tag = source_tag + append_tag
|
||||||
|
target_image_source_tag = target_image_no_tag + ':' + source_tag
|
||||||
|
target_image = target_image_no_tag + ':' + target_tag
|
||||||
|
|
||||||
if DockerImageUploader._images_match(full_image, full_new_repo,
|
if DockerImageUploader._images_match(source_image, target_image,
|
||||||
insecure_registries):
|
insecure_registries):
|
||||||
LOG.info('Skipping upload for image %s' % image_name)
|
LOG.info('Skipping upload for image %s' % image_name)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
DockerImageUploader._pull(dockerc, repo, tag=tag)
|
DockerImageUploader._pull(dockerc, repo, tag=source_tag)
|
||||||
|
|
||||||
response = dockerc.tag(image=full_image, repository=new_repo,
|
if modify_role:
|
||||||
tag=tag, force=True)
|
DockerImageUploader.run_modify_playbook(
|
||||||
LOG.debug(response)
|
modify_role, modify_vars, source_image,
|
||||||
|
target_image_source_tag, append_tag)
|
||||||
|
# raise an exception if the playbook didn't tag
|
||||||
|
# the expected target image
|
||||||
|
dockerc.inspect_image(target_image)
|
||||||
|
else:
|
||||||
|
response = dockerc.tag(
|
||||||
|
image=source_image, repository=target_image_no_tag,
|
||||||
|
tag=target_tag, force=True
|
||||||
|
)
|
||||||
|
LOG.debug(response)
|
||||||
|
|
||||||
DockerImageUploader._push(dockerc, new_repo, tag=tag)
|
DockerImageUploader._push(dockerc, target_image_no_tag, tag=target_tag)
|
||||||
|
|
||||||
LOG.info('Completed upload for image %s' % image_name)
|
LOG.info('Completed upload for image %s' % image_name)
|
||||||
return full_image, full_new_repo
|
return source_image, target_image
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@tenacity.retry( # Retry up to 5 times with jittered exponential backoff
|
@tenacity.retry( # Retry up to 5 times with jittered exponential backoff
|
||||||
@ -353,7 +411,8 @@ class DockerImageUploader(ImageUploader):
|
|||||||
else:
|
else:
|
||||||
LOG.warning(e)
|
LOG.warning(e)
|
||||||
|
|
||||||
def add_upload_task(self, image_name, pull_source, push_destination):
|
def add_upload_task(self, image_name, pull_source, push_destination,
|
||||||
|
append_tag, modify_role, modify_vars):
|
||||||
# prime self.insecure_registries
|
# prime self.insecure_registries
|
||||||
if pull_source:
|
if pull_source:
|
||||||
self.is_insecure_registry(self._image_to_url(pull_source).netloc)
|
self.is_insecure_registry(self._image_to_url(pull_source).netloc)
|
||||||
@ -361,7 +420,8 @@ class DockerImageUploader(ImageUploader):
|
|||||||
self.is_insecure_registry(self._image_to_url(image_name).netloc)
|
self.is_insecure_registry(self._image_to_url(image_name).netloc)
|
||||||
self.is_insecure_registry(self._image_to_url(push_destination).netloc)
|
self.is_insecure_registry(self._image_to_url(push_destination).netloc)
|
||||||
self.upload_tasks.append((image_name, pull_source, push_destination,
|
self.upload_tasks.append((image_name, pull_source, push_destination,
|
||||||
self.insecure_registries))
|
self.insecure_registries, append_tag,
|
||||||
|
modify_role, modify_vars))
|
||||||
|
|
||||||
def run_tasks(self):
|
def run_tasks(self):
|
||||||
if not self.upload_tasks:
|
if not self.upload_tasks:
|
||||||
|
@ -21,6 +21,7 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import time
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from tripleo_common.image import base
|
from tripleo_common.image import base
|
||||||
@ -114,11 +115,18 @@ def container_images_prepare_multi(environment, roles_data):
|
|||||||
|
|
||||||
env_params = {}
|
env_params = {}
|
||||||
service_filter = build_service_filter(environment, roles_data)
|
service_filter = build_service_filter(environment, roles_data)
|
||||||
|
modified_timestamp = time.strftime('-modified-%Y%m%d%H%M%S')
|
||||||
|
|
||||||
for cip_entry in cip:
|
for cip_entry in cip:
|
||||||
mapping_args = cip_entry.get('set')
|
mapping_args = cip_entry.get('set')
|
||||||
push_destination = cip_entry.get('push_destination')
|
push_destination = cip_entry.get('push_destination')
|
||||||
pull_source = cip_entry.get('pull_source')
|
pull_source = cip_entry.get('pull_source')
|
||||||
|
modify_role = cip_entry.get('modify_role')
|
||||||
|
modify_vars = cip_entry.get('modify_vars')
|
||||||
|
if modify_role:
|
||||||
|
append_tag = modified_timestamp
|
||||||
|
else:
|
||||||
|
append_tag = None
|
||||||
|
|
||||||
prepare_data = container_images_prepare(
|
prepare_data = container_images_prepare(
|
||||||
excludes=cip_entry.get('excludes'),
|
excludes=cip_entry.get('excludes'),
|
||||||
@ -129,16 +137,21 @@ def container_images_prepare_multi(environment, roles_data):
|
|||||||
output_env_file='image_params',
|
output_env_file='image_params',
|
||||||
output_images_file='upload_data',
|
output_images_file='upload_data',
|
||||||
tag_from_label=cip_entry.get('tag_from_label'),
|
tag_from_label=cip_entry.get('tag_from_label'),
|
||||||
|
append_tag=append_tag,
|
||||||
|
modify_role=modify_role,
|
||||||
|
modify_vars=modify_vars
|
||||||
)
|
)
|
||||||
env_params.update(prepare_data['image_params'])
|
env_params.update(prepare_data['image_params'])
|
||||||
|
|
||||||
if push_destination or pull_source:
|
if push_destination or pull_source or modify_role:
|
||||||
with tempfile.NamedTemporaryFile(mode='w') as f:
|
with tempfile.NamedTemporaryFile(mode='w') as f:
|
||||||
yaml.safe_dump({
|
yaml.safe_dump({
|
||||||
'container_images': prepare_data['upload_data']
|
'container_images': prepare_data['upload_data']
|
||||||
}, f)
|
}, f)
|
||||||
uploader = image_uploader.ImageUploadManager(
|
uploader = image_uploader.ImageUploadManager(
|
||||||
[f.name], verbose=True)
|
[f.name],
|
||||||
|
verbose=True,
|
||||||
|
)
|
||||||
uploader.upload()
|
uploader.upload()
|
||||||
return env_params
|
return env_params
|
||||||
|
|
||||||
@ -157,7 +170,9 @@ def container_images_prepare(template_file=DEFAULT_TEMPLATE_FILE,
|
|||||||
excludes=None, service_filter=None,
|
excludes=None, service_filter=None,
|
||||||
pull_source=None, push_destination=None,
|
pull_source=None, push_destination=None,
|
||||||
mapping_args=None, output_env_file=None,
|
mapping_args=None, output_env_file=None,
|
||||||
output_images_file=None, tag_from_label=None):
|
output_images_file=None, tag_from_label=None,
|
||||||
|
append_tag=None, modify_role=None,
|
||||||
|
modify_vars=None):
|
||||||
"""Perform container image preparation
|
"""Perform container image preparation
|
||||||
|
|
||||||
:param template_file: path to Jinja2 file containing all image entries
|
:param template_file: path to Jinja2 file containing all image entries
|
||||||
@ -174,12 +189,18 @@ def container_images_prepare(template_file=DEFAULT_TEMPLATE_FILE,
|
|||||||
:param output_images_file: key to use for image upload data
|
:param output_images_file: key to use for image upload data
|
||||||
:param tag_from_label: string when set will trigger tag discovery on every
|
:param tag_from_label: string when set will trigger tag discovery on every
|
||||||
image
|
image
|
||||||
|
:param append_tag: string to append to the tag for the destination image
|
||||||
|
:param modify_role: string of ansible role name to run during upload before
|
||||||
|
the push to destination
|
||||||
|
:param modify_vars: dict of variables to pass to modify_role
|
||||||
:returns: dict with entries for the supplied output_env_file or
|
:returns: dict with entries for the supplied output_env_file or
|
||||||
output_images_file
|
output_images_file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if mapping_args is None:
|
if mapping_args is None:
|
||||||
mapping_args = {}
|
mapping_args = {}
|
||||||
|
if not append_tag:
|
||||||
|
append_tag = ''
|
||||||
|
|
||||||
if service_filter:
|
if service_filter:
|
||||||
if 'OS::TripleO::Services::OpenDaylightApi' in service_filter:
|
if 'OS::TripleO::Services::OpenDaylightApi' in service_filter:
|
||||||
@ -227,9 +248,15 @@ def container_images_prepare(template_file=DEFAULT_TEMPLATE_FILE,
|
|||||||
# push_destination, since that is where they will be uploaded to
|
# push_destination, since that is where they will be uploaded to
|
||||||
image = imagename.partition('/')[2]
|
image = imagename.partition('/')[2]
|
||||||
imagename = '/'.join((push_destination, image))
|
imagename = '/'.join((push_destination, image))
|
||||||
|
if append_tag:
|
||||||
|
entry['append_tag'] = append_tag
|
||||||
|
if modify_role:
|
||||||
|
entry['modify_role'] = modify_role
|
||||||
|
if modify_vars:
|
||||||
|
entry['modify_vars'] = modify_vars
|
||||||
if 'params' in entry:
|
if 'params' in entry:
|
||||||
for p in entry.pop('params'):
|
for p in entry.pop('params'):
|
||||||
params[p] = imagename
|
params[p] = imagename + append_tag
|
||||||
if 'services' in entry:
|
if 'services' in entry:
|
||||||
del(entry['services'])
|
del(entry['services'])
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import requests
|
|||||||
import six
|
import six
|
||||||
import urllib3
|
import urllib3
|
||||||
|
|
||||||
|
from oslo_concurrency import processutils
|
||||||
from tripleo_common.image.exception import ImageUploaderException
|
from tripleo_common.image.exception import ImageUploaderException
|
||||||
from tripleo_common.image import image_uploader
|
from tripleo_common.image import image_uploader
|
||||||
from tripleo_common.tests import base
|
from tripleo_common.tests import base
|
||||||
@ -164,7 +165,10 @@ class TestDockerImageUploader(base.TestCase):
|
|||||||
self.uploader.upload_image(image + ':' + tag,
|
self.uploader.upload_image(image + ':' + tag,
|
||||||
None,
|
None,
|
||||||
push_destination,
|
push_destination,
|
||||||
set())
|
set(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None)
|
||||||
|
|
||||||
self.dockermock.assert_called_once_with(
|
self.dockermock.assert_called_once_with(
|
||||||
base_url='unix://var/run/docker.sock', version='auto')
|
base_url='unix://var/run/docker.sock', version='auto')
|
||||||
@ -189,7 +193,10 @@ class TestDockerImageUploader(base.TestCase):
|
|||||||
self.uploader.upload_image(image,
|
self.uploader.upload_image(image,
|
||||||
None,
|
None,
|
||||||
push_destination,
|
push_destination,
|
||||||
set())
|
set(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None)
|
||||||
|
|
||||||
self.dockermock.assert_called_once_with(
|
self.dockermock.assert_called_once_with(
|
||||||
base_url='unix://var/run/docker.sock', version='auto')
|
base_url='unix://var/run/docker.sock', version='auto')
|
||||||
@ -220,7 +227,10 @@ class TestDockerImageUploader(base.TestCase):
|
|||||||
self.uploader.upload_image(image + ':' + tag,
|
self.uploader.upload_image(image + ':' + tag,
|
||||||
None,
|
None,
|
||||||
push_destination,
|
push_destination,
|
||||||
set())
|
set(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None)
|
||||||
|
|
||||||
self.dockermock.assert_called_once_with(
|
self.dockermock.assert_called_once_with(
|
||||||
base_url='unix://var/run/docker.sock', version='auto')
|
base_url='unix://var/run/docker.sock', version='auto')
|
||||||
@ -230,6 +240,109 @@ class TestDockerImageUploader(base.TestCase):
|
|||||||
self.dockermock.return_value.tag.assert_not_called()
|
self.dockermock.return_value.tag.assert_not_called()
|
||||||
self.dockermock.return_value.push.assert_not_called()
|
self.dockermock.return_value.push.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch('subprocess.Popen')
|
||||||
|
@mock.patch('tripleo_common.actions.'
|
||||||
|
'ansible.AnsiblePlaybookAction', autospec=True)
|
||||||
|
def test_modify_upload_image(self, mock_ansible, mock_popen):
|
||||||
|
result1 = {
|
||||||
|
'Digest': 'a'
|
||||||
|
}
|
||||||
|
result2 = {
|
||||||
|
'Digest': 'b'
|
||||||
|
}
|
||||||
|
mock_process = mock.Mock()
|
||||||
|
mock_process.communicate.side_effect = [
|
||||||
|
(json.dumps(result1), ''),
|
||||||
|
(json.dumps(result2), ''),
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_process.returncode = 0
|
||||||
|
mock_popen.return_value = mock_process
|
||||||
|
|
||||||
|
image = 'docker.io/tripleomaster/heat-docker-agents-centos'
|
||||||
|
tag = 'latest'
|
||||||
|
append_tag = 'modify-123'
|
||||||
|
push_destination = 'localhost:8787'
|
||||||
|
push_image = 'localhost:8787/tripleomaster/heat-docker-agents-centos'
|
||||||
|
playbook = [{
|
||||||
|
'tasks': [{
|
||||||
|
'import_role': {
|
||||||
|
'name': 'add-foo-plugin'
|
||||||
|
},
|
||||||
|
'name': 'Import role add-foo-plugin',
|
||||||
|
'vars': {
|
||||||
|
'target_image': '%s:%s' % (push_image, tag),
|
||||||
|
'modified_append_tag': append_tag,
|
||||||
|
'source_image': '%s:%s' % (image, tag),
|
||||||
|
'foo_version': '1.0.1'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
'hosts': 'localhost'
|
||||||
|
}]
|
||||||
|
|
||||||
|
self.uploader.upload_image(image + ':' + tag,
|
||||||
|
None,
|
||||||
|
push_destination,
|
||||||
|
set(),
|
||||||
|
append_tag,
|
||||||
|
'add-foo-plugin',
|
||||||
|
{'foo_version': '1.0.1'})
|
||||||
|
|
||||||
|
self.dockermock.assert_called_once_with(
|
||||||
|
base_url='unix://var/run/docker.sock', version='auto')
|
||||||
|
|
||||||
|
self.dockermock.return_value.pull.assert_called_once_with(
|
||||||
|
image, tag=tag, stream=True)
|
||||||
|
mock_ansible.assert_called_once_with(
|
||||||
|
playbook=playbook, work_dir=mock.ANY)
|
||||||
|
self.dockermock.return_value.tag.assert_not_called()
|
||||||
|
self.dockermock.return_value.push.assert_called_once_with(
|
||||||
|
push_image,
|
||||||
|
tag=tag + append_tag,
|
||||||
|
stream=True)
|
||||||
|
|
||||||
|
@mock.patch('subprocess.Popen')
|
||||||
|
@mock.patch('tripleo_common.actions.'
|
||||||
|
'ansible.AnsiblePlaybookAction', autospec=True)
|
||||||
|
def test_modify_image_failed(self, mock_ansible, mock_popen):
|
||||||
|
result1 = {
|
||||||
|
'Digest': 'a'
|
||||||
|
}
|
||||||
|
result2 = {
|
||||||
|
'Digest': 'b'
|
||||||
|
}
|
||||||
|
mock_process = mock.Mock()
|
||||||
|
mock_process.communicate.side_effect = [
|
||||||
|
(json.dumps(result1), ''),
|
||||||
|
(json.dumps(result2), ''),
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_process.returncode = 0
|
||||||
|
mock_popen.return_value = mock_process
|
||||||
|
|
||||||
|
image = 'docker.io/tripleomaster/heat-docker-agents-centos'
|
||||||
|
tag = 'latest'
|
||||||
|
append_tag = 'modify-123'
|
||||||
|
push_destination = 'localhost:8787'
|
||||||
|
error = processutils.ProcessExecutionError(
|
||||||
|
'', 'ouch', -1, 'ansible-playbook')
|
||||||
|
mock_ansible.return_value.run.side_effect = error
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
processutils.ProcessExecutionError,
|
||||||
|
self.uploader.upload_image,
|
||||||
|
image + ':' + tag, None, push_destination, set(), append_tag,
|
||||||
|
'add-foo-plugin', {'foo_version': '1.0.1'}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.dockermock.assert_called_once_with(
|
||||||
|
base_url='unix://var/run/docker.sock', version='auto')
|
||||||
|
|
||||||
|
self.dockermock.return_value.pull.assert_called_once_with(
|
||||||
|
image, tag=tag, stream=True)
|
||||||
|
self.dockermock.return_value.tag.assert_not_called()
|
||||||
|
self.dockermock.return_value.push.assert_not_called()
|
||||||
|
|
||||||
@mock.patch('requests.get')
|
@mock.patch('requests.get')
|
||||||
def test_is_insecure_registry_known(self, mock_get):
|
def test_is_insecure_registry_known(self, mock_get):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
@ -669,7 +669,9 @@ class TestPrepare(base.TestCase):
|
|||||||
'set': mapping_args,
|
'set': mapping_args,
|
||||||
'tag_from_label': 'bar',
|
'tag_from_label': 'bar',
|
||||||
'excludes': ['nova', 'neutron'],
|
'excludes': ['nova', 'neutron'],
|
||||||
'push_destination': '192.0.2.1:8787'
|
'push_destination': '192.0.2.1:8787',
|
||||||
|
'modify_role': 'add-foo-plugin',
|
||||||
|
'modify_vars': {'foo_version': '1.0.1'}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -709,7 +711,10 @@ class TestPrepare(base.TestCase):
|
|||||||
pull_source=None,
|
pull_source=None,
|
||||||
push_destination=None,
|
push_destination=None,
|
||||||
service_filter=None,
|
service_filter=None,
|
||||||
tag_from_label='foo'
|
tag_from_label='foo',
|
||||||
|
append_tag=None,
|
||||||
|
modify_role=None,
|
||||||
|
modify_vars=None
|
||||||
),
|
),
|
||||||
mock.call(
|
mock.call(
|
||||||
excludes=['nova', 'neutron'],
|
excludes=['nova', 'neutron'],
|
||||||
@ -719,7 +724,10 @@ class TestPrepare(base.TestCase):
|
|||||||
pull_source=None,
|
pull_source=None,
|
||||||
push_destination='192.0.2.1:8787',
|
push_destination='192.0.2.1:8787',
|
||||||
service_filter=None,
|
service_filter=None,
|
||||||
tag_from_label='bar'
|
tag_from_label='bar',
|
||||||
|
append_tag=mock.ANY,
|
||||||
|
modify_role='add-foo-plugin',
|
||||||
|
modify_vars={'foo_version': '1.0.1'}
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user