# Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy import datetime import errno import json import logging import os import shutil import sys import tempfile import time import uuid from osc_lib import exceptions as oscexc from osc_lib.i18n import _ import six from six.moves.urllib import parse import yaml from tripleo_common.image.builder import buildah from tripleo_common.image import image_uploader from tripleo_common.image import kolla_builder from tripleo_common.utils.locks import processlock from tripleoclient import utils as oooutils from tripleoclient import command from tripleoclient import constants from tripleoclient import exceptions from tripleoclient import utils def build_env_file(params, command_options): f = six.StringIO() f.write('# Generated with the following on %s\n#\n' % datetime.datetime.now().isoformat()) f.write('# openstack %s\n#\n\n' % ' '.join(command_options)) yaml.safe_dump({'parameter_defaults': params}, f, default_flow_style=False) return f.getvalue() class UploadImage(command.Command): """Push overcloud container images to registries.""" auth_required = False log = logging.getLogger(__name__ + ".UploadImage") def get_parser(self, prog_name): parser = super(UploadImage, self).get_parser(prog_name) parser.add_argument( "--config-file", dest="config_files", metavar='', default=[], action="append", required=True, help=_("YAML config file specifying the image build. May be " "specified multiple times. Order is preserved, and later " "files will override some options in previous files. " "Other options will append."), ) parser.add_argument( "--cleanup", dest="cleanup", metavar='', default=image_uploader.CLEANUP_FULL, help=_("Cleanup behavior for local images left after upload. " "The default 'full' will attempt to delete all local " "images. 'partial' will leave images required for " "deployment on this host. 'none' will do no cleanup.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) if parsed_args.cleanup not in image_uploader.CLEANUP: raise oscexc.CommandError('--cleanup must be one of: %s' % ', '.join(image_uploader.CLEANUP)) lock = processlock.ProcessLock() uploader = image_uploader.ImageUploadManager( parsed_args.config_files, cleanup=parsed_args.cleanup, lock=lock) try: uploader.upload() except KeyboardInterrupt: # ctrl-c self.log.warning('Upload was interrupted by ctrl-c.') class BuildImage(command.Command): """Build overcloud container images with kolla-build.""" 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): default_kolla_conf = os.path.join( sys.prefix, 'share', 'tripleo-common', 'container-images', 'tripleo_kolla_config_overrides.conf') parser = super(BuildImage, self).get_parser(prog_name) parser.add_argument( "--config-file", dest="config_files", metavar='', default=[], action="append", help=_("YAML config file specifying the images to build. May be " "specified multiple times. Order is preserved, and later " "files will override some options in previous files. " "Other options will append. If not specified, the default " "set of containers will be built."), ) parser.add_argument( "--kolla-config-file", dest="kolla_config_files", metavar='', default=[default_kolla_conf], action="append", required=True, help=_("Path to a Kolla config file to use. Multiple config files " "can be specified, with values in later files taking " "precedence. By default, tripleo kolla conf file {conf} " "is added.").format(conf=default_kolla_conf), ) 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.') ) parser.add_argument( "--exclude", dest="excludes", metavar='', default=[], action="append", help=_("Name of a container to match against the list of " "containers to be built to skip. Can be specified multiple " "times."), ) parser.add_argument( '--use-buildah', dest='use_buildah', action='store_true', default=False, help=_('Use Buildah instead of Docker to build the images ' 'with Kolla.') ) parser.add_argument( "--work-dir", dest="work_dir", default='/tmp/container-builds', metavar='', help=_("TripleO container builds directory, storing configs and " "logs for each image and its dependencies.") ) parser.add_argument( "--build-timeout", dest="build_timeout", default=None, type=int, metavar="", help=_("Build timeout in seconds.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) self.log.warning('This command is deprecated. Please use "openstack ' 'tripleo container image build" instead.') 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) # Generate an unique work directory so we can keep configs and logs # each time we run the command; they'll be stored in work_dir. kolla_work_dir = os.path.join(parsed_args.work_dir, str(uuid.uuid4())) # Make sure the unique work directory exists if not os.path.exists(kolla_work_dir): self.log.debug("Creating container builds " "workspace in: %s" % kolla_work_dir) os.makedirs(kolla_work_dir) builder = kolla_builder.KollaImageBuilder(parsed_args.config_files) result = builder.build_images(kolla_config_files, parsed_args.excludes, parsed_args.use_buildah, kolla_work_dir) if parsed_args.use_buildah: deps = json.loads(result) kolla_cfg = utils.get_read_config(kolla_config_files) bb = buildah.BuildahBuilder( kolla_work_dir, deps, utils.get_from_cfg(kolla_cfg, "base"), utils.get_from_cfg(kolla_cfg, "type"), utils.get_from_cfg(kolla_cfg, "tag"), utils.get_from_cfg(kolla_cfg, "namespace"), utils.get_from_cfg(kolla_cfg, "registry"), utils.getboolean_from_cfg(kolla_cfg, "push"), parsed_args.build_timeout, self.app.options.debug) bb.build_all() elif 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) class PrepareImageFiles(command.Command): """Generate files defining the images, tags and registry.""" auth_required = False log = logging.getLogger(__name__ + ".PrepareImageFiles") def get_parser(self, prog_name): parser = super(PrepareImageFiles, self).get_parser(prog_name) try: roles_file = utils.rel_or_abs_path( constants.OVERCLOUD_ROLES_FILE, constants.TRIPLEO_HEAT_TEMPLATES) except exceptions.DeploymentError: roles_file = None defaults = kolla_builder.container_images_prepare_defaults() parser.add_argument( "--template-file", dest="template_file", default=kolla_builder.DEFAULT_TEMPLATE_FILE, metavar='', help=_("YAML template file which the images config file will be " "built from.\n" "Default: %s") % kolla_builder.DEFAULT_TEMPLATE_FILE, ) parser.add_argument( "--push-destination", dest="push_destination", metavar='', help=_("Location of image registry to push images to. " "If specified, a push_destination will be set for every " "image entry."), ) parser.add_argument( "--tag", dest="tag", default=defaults['tag'], metavar='', help=_("Override the default tag substitution. " "If --tag-from-label is specified, " "start discovery with this tag.\n" "Default: %s") % defaults['tag'], ) parser.add_argument( "--tag-from-label", dest="tag_from_label", metavar='', help=_("Use the value of the specified label(s) to discover the " "tag. Labels can be combined in a template format, " "for example: {version}-{release}"), ) parser.add_argument( "--namespace", dest="namespace", default=defaults['namespace'], metavar='', help=_("Override the default namespace substitution.\n" "Default: %s") % defaults['namespace'], ) parser.add_argument( "--prefix", dest="prefix", default=defaults['name_prefix'], metavar='', help=_("Override the default name prefix substitution.\n" "Default: %s") % defaults['name_prefix'], ) parser.add_argument( "--suffix", dest="suffix", default=defaults['name_suffix'], metavar='', help=_("Override the default name suffix substitution.\n" "Default: %s") % defaults['name_suffix'], ) parser.add_argument( '--set', metavar='', action='append', help=_('Set the value of a variable in the template, even if it ' 'has no dedicated argument such as "--suffix".') ) parser.add_argument( "--exclude", dest="excludes", metavar='', default=[], action="append", help=_("Pattern to match against resulting imagename entries to " "exclude from the final output. Can be specified multiple " "times."), ) parser.add_argument( "--include", dest="includes", metavar='', default=[], action="append", help=_("Pattern to match against resulting imagename entries to " "include in final output. Can be specified multiple " "times, entries not matching any --include will be " "excluded. --exclude is ignored if --include is used."), ) parser.add_argument( "--output-images-file", dest="output_images_file", metavar='', help=_("File to write resulting image entries to, as well as " "stdout. Any existing file will be overwritten."), ) parser.add_argument( '--environment-file', '-e', metavar='', action='append', dest='environment_files', help=_('Environment files specifying which services are ' 'containerized. Entries will be filtered to only contain ' 'images used by containerized services. (Can be specified ' 'more than once.)') ) parser.add_argument( '--environment-directory', metavar='', action='append', dest='environment_directories', default=[os.path.expanduser(constants.DEFAULT_ENV_DIRECTORY)], help=_('Environment file directories that are automatically ' 'added to the update command. Entries will be filtered ' 'to only contain images used by containerized services. ' 'Can be specified more than once. Files in directories are ' 'loaded in ascending sort order.') ) parser.add_argument( "--output-env-file", dest="output_env_file", metavar='', help=_("File to write heat environment file which specifies all " "image parameters. Any existing file will be overwritten."), ) parser.add_argument( '--roles-file', '-r', dest='roles_file', default=roles_file, help=_( 'Roles file, overrides the default %s in the t-h-t templates ' 'directory used for deployment. May be an ' 'absolute path or the path relative to the templates dir.' ) % constants.OVERCLOUD_ROLES_FILE ) parser.add_argument( '--modify-role', dest='modify_role', help=_('Name of ansible role to run between every image upload ' 'pull and push.') ) parser.add_argument( '--modify-vars', dest='modify_vars', help=_('Ansible variable file containing variables to use when ' 'invoking the role --modify-role.') ) return parser def parse_set_values(self, subs, set_values): if not set_values: return for s in set_values: try: (n, v) = s.split(('='), 1) subs[n] = v except ValueError: msg = _('Malformed --set(%s). ' 'Use the variable=value format.') % s raise oscexc.CommandError(msg) def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) self.log.warning("[DEPRECATED] This command has been deprecated and " "replaced by the 'openstack tripleo container image " "prepare' command.") roles_data = utils.fetch_roles_file(parsed_args.roles_file) or set() env = utils.build_prepare_env( parsed_args.environment_files, parsed_args.environment_directories ) if roles_data: service_filter = kolla_builder.build_service_filter( env, roles_data) else: service_filter = None mapping_args = { 'tag': parsed_args.tag, 'namespace': parsed_args.namespace, 'name_prefix': parsed_args.prefix, 'name_suffix': parsed_args.suffix, } self.parse_set_values(mapping_args, parsed_args.set) pd = env.get('parameter_defaults', {}) kolla_builder.set_neutron_driver(pd, mapping_args) output_images_file = (parsed_args.output_images_file or 'container_images.yaml') modify_role = None modify_vars = None append_tag = None if parsed_args.modify_role: modify_role = parsed_args.modify_role append_tag = time.strftime('-modified-%Y%m%d%H%M%S') if parsed_args.modify_vars: with open(parsed_args.modify_vars) as m: modify_vars = yaml.safe_load(m.read()) prepare_data = kolla_builder.container_images_prepare( excludes=parsed_args.excludes, includes=parsed_args.includes, service_filter=service_filter, push_destination=parsed_args.push_destination, mapping_args=mapping_args, output_env_file=parsed_args.output_env_file, output_images_file=output_images_file, tag_from_label=parsed_args.tag_from_label, modify_role=modify_role, modify_vars=modify_vars, append_tag=append_tag, template_file=parsed_args.template_file ) if parsed_args.output_env_file: params = prepare_data[parsed_args.output_env_file] output_env_file_expanded = os.path.expanduser( parsed_args.output_env_file) if os.path.exists(output_env_file_expanded): self.log.warning("Output env file exists, " "moving it to backup.") shutil.move(output_env_file_expanded, output_env_file_expanded + ".backup") utils.safe_write(output_env_file_expanded, build_env_file(params, self.app.command_options)) result = prepare_data[output_images_file] result_str = yaml.safe_dump({'container_images': result}, default_flow_style=False) sys.stdout.write(result_str) if parsed_args.output_images_file: utils.safe_write(parsed_args.output_images_file, result_str) class DiscoverImageTag(command.Command): """Discover the versioned tag for an image.""" auth_required = False log = logging.getLogger(__name__ + ".DiscoverImageTag") def get_parser(self, prog_name): parser = super(DiscoverImageTag, self).get_parser(prog_name) parser.add_argument( "--image", dest="image", metavar='', required=True, help=_("Fully qualified name of the image to discover the tag for " "(Including registry and stable tag)."), ) parser.add_argument( "--tag-from-label", dest="tag_from_label", metavar='', help=_("Use the value of the specified label(s) to discover the " "tag. Labels can be combined in a template format, " "for example: {version}-{release}"), ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) self.log.warning("[DEPRECATED] This command has been deprecated and " "replaced by the 'openstack tripleo container image " "prepare' command.") lock = processlock.ProcessLock() uploader = image_uploader.ImageUploadManager([], lock=lock) print(uploader.discover_image_tag( image=parsed_args.image, tag_from_label=parsed_args.tag_from_label )) 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='', default=None, help=_("URL of the destination registry in the form " ":.") ) 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='', help=_("Username for the destination image registry.") ) parser.add_argument( "--password", dest="password", metavar='', help=_("Password for the destination image registry.") ) parser.add_argument( "--source-username", dest="source_username", metavar='', help=_("Username for the source image registry.") ) parser.add_argument( "--source-password", dest="source_password", metavar='', help=_("Password for the source 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='', help=_("Container image to upload. Should be in the form of " "//:. 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) lock = processlock.ProcessLock() manager = image_uploader.ImageUploadManager(lock=lock) uploader = manager.uploader('python') source_image = parsed_args.image_to_push if parsed_args.local or source_image.startswith('containers-storage:'): storage = 'containers-storage:' if not source_image.startswith(storage): source_image = storage + source_image.replace('docker://', '') elif not parsed_args.local: self.log.warning('Assuming local container based on provided ' 'container path. (e.g. starts with ' 'containers-storage:)') source_url = parse.urlparse(source_image) image_name = source_url.geturl() image_source = None if parsed_args.source_username or parsed_args.source_password: self.log.warning('Source credentials ignored for local images') else: storage = 'docker://' if not source_image.startswith(storage): source_image = storage + source_image source_url = parse.urlparse(source_image) image_source = source_url.netloc image_name = source_url.path[1:] if len(image_name.split('/')) != 2: raise exceptions.DownloadError('Invalid container. Provided ' 'container image should be ' '//:' '') if parsed_args.source_username or parsed_args.source_password: if not parsed_args.source_username: self.log.warning('Skipping authentication - missing source' ' username') elif not parsed_args.source_password: self.log.warning('Skipping authentication - missing source' ' password') else: uploader.authenticate(source_url, parsed_args.source_username, parsed_args.source_password) registry_url_arg = parsed_args.registry_url if registry_url_arg is None: registry_url_arg = image_uploader.get_undercloud_registry() if not registry_url_arg.startswith('docker://'): registry_url = 'docker://%s' % registry_url_arg else: registry_url = registry_url_arg reg_url = parse.urlparse(registry_url) session = uploader.authenticate(reg_url, parsed_args.username, parsed_args.password) try: if not parsed_args.dry_run: task = image_uploader.UploadTask( image_name=image_name, pull_source=image_source, push_destination=registry_url_arg, append_tag=parsed_args.append_tag, modify_role=None, modify_vars=None, cleanup=parsed_args.cleanup, multi_arch=parsed_args.multi_arch) uploader.add_upload_task(task) uploader.run_tasks() except OSError as e: if e.errno == errno.EACCES: self.log.error("Unable to upload due to permissions. " "Please prefix command with sudo.") raise oscexc.CommandError(e) finally: session.close() class TripleOContainerImageDelete(command.Command): """Delete specified image from registry.""" auth_required = False log = logging.getLogger(__name__ + ".TripleoContainerImageDelete") def get_parser(self, prog_name): parser = super(TripleOContainerImageDelete, self).get_parser(prog_name) parser.add_argument( "--registry-url", dest="registry_url", metavar='', default=None, help=_("URL of registry images are to be listed from in the " "form :.") ) parser.add_argument( dest="image_to_delete", metavar='', help=_("Full URL of image to be deleted in the " "form :/path/to/image") ) parser.add_argument( "--username", dest="username", metavar='', help=_("Username for image registry.") ) parser.add_argument( "--password", dest="password", metavar='', help=_("Password for image registry.") ) parser.add_argument( '-y', '--yes', help=_('Skip yes/no prompt (assume yes).'), default=False, action="store_true") return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) if not parsed_args.yes: confirm = utils.prompt_user_for_confirmation( message=_("Are you sure you want to delete this image " "[y/N]? "), logger=self.log) if not confirm: raise oscexc.CommandError("Action not confirmed, exiting.") lock = processlock.ProcessLock() manager = image_uploader.ImageUploadManager(lock=lock) uploader = manager.uploader('python') registry_url_arg = parsed_args.registry_url if registry_url_arg is None: registry_url_arg = image_uploader.get_undercloud_registry() url = uploader._image_to_url(registry_url_arg) session = uploader.authenticate(url, parsed_args.username, parsed_args.password) try: uploader.delete(parsed_args.image_to_delete, session=session) except OSError as e: if e.errno == errno.EACCES: self.log.error("Unable to remove due to permissions. " "Please prefix command with sudo.") raise oscexc.CommandError(e) finally: session.close() class TripleOContainerImageList(command.Lister): """List images discovered in registry.""" auth_required = False log = logging.getLogger(__name__ + ".TripleoContainerImageList") def get_parser(self, prog_name): parser = super(TripleOContainerImageList, self).get_parser(prog_name) parser.add_argument( "--registry-url", dest="registry_url", metavar='', default=None, help=_("URL of registry images are to be listed from in the " "form :.") ) parser.add_argument( "--username", dest="username", metavar='', help=_("Username for image registry.") ) parser.add_argument( "--password", dest="password", metavar='', help=_("Password for image registry.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) lock = processlock.ProcessLock() manager = image_uploader.ImageUploadManager(lock=lock) uploader = manager.uploader('python') registry_url_arg = parsed_args.registry_url if registry_url_arg is None: registry_url_arg = image_uploader.get_undercloud_registry() url = uploader._image_to_url(registry_url_arg) session = uploader.authenticate(url, parsed_args.username, parsed_args.password) try: results = uploader.list(url.geturl(), session=session) finally: session.close() cliff_results = [] for r in results: cliff_results.append((r,)) return (("Image Name",), cliff_results) class TripleOContainerImageShow(command.ShowOne): """Show image selected from the registry.""" auth_required = False log = logging.getLogger(__name__ + ".TripleoContainerImageShow") @property def formatter_default(self): return 'json' def get_parser(self, prog_name): parser = super(TripleOContainerImageShow, self).get_parser(prog_name) parser.add_argument( "--username", dest="username", metavar='', help=_("Username for image registry.") ) parser.add_argument( "--password", dest="password", metavar='', help=_("Password for image registry.") ) parser.add_argument( dest="image_to_inspect", metavar='', help=_( "Image to be inspected, for example: " "docker.io/library/centos:7 or " "docker://docker.io/library/centos:7") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) lock = processlock.ProcessLock() manager = image_uploader.ImageUploadManager(lock=lock) uploader = manager.uploader('python') url = uploader._image_to_url(parsed_args.image_to_inspect) session = uploader.authenticate(url, parsed_args.username, parsed_args.password) try: image_inspect_result = uploader.inspect( parsed_args.image_to_inspect, session=session) finally: session.close() return self.format_image_inspect(image_inspect_result) def format_image_inspect(self, image_inspect_result): column_names = ['Name'] data = [image_inspect_result.pop('Name')] result_fields = list(image_inspect_result.keys()) result_fields.sort() for field in result_fields: column_names.append(field) data.append(image_inspect_result[field]) return column_names, data class TripleOImagePrepareDefault(command.Command): """Generate a default ContainerImagePrepare parameter.""" auth_required = False log = logging.getLogger(__name__ + ".TripleoImagePrepare") def get_parser(self, prog_name): parser = super(TripleOImagePrepareDefault, self).get_parser(prog_name) parser.add_argument( "--output-env-file", dest="output_env_file", metavar='', help=_("File to write environment file containing default " "ContainerImagePrepare value."), ) parser.add_argument( '--local-push-destination', dest='push_destination', action='store_true', default=False, help=_('Include a push_destination to trigger upload to a local ' 'registry.') ) parser.add_argument( '--enable-registry-login', dest='registry_login', action='store_true', default=False, help=_('Use this flag to enable the flag to have systems attempt ' 'to login to a remote registry prior to pulling their ' 'containers. This flag should be used when ' '--local-push-destination is *NOT* used and the target ' 'systems will have network connectivity to the remote ' 'registries. Do not use this for an overcloud that ' 'may not have network connectivity to a remote registry.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) cip = copy.deepcopy(kolla_builder.CONTAINER_IMAGE_PREPARE_PARAM) if parsed_args.push_destination: for entry in cip: entry['push_destination'] = True params = { 'ContainerImagePrepare': cip } if parsed_args.registry_login: if parsed_args.push_destination: self.log.warning('[WARNING] --local-push-destination was used ' 'with --enable-registry-login. Please make ' 'sure you understand the use of these ' 'parameters together as they can cause ' 'deployment failures.') self.log.warning('[NOTE] Make sure to update the paramter_defaults' ' with ContainerImageRegistryCredentials for the ' 'registries requiring authentication.') params['ContainerImageRegistryLogin'] = True env_data = build_env_file(params, self.app.command_options) self.app.stdout.write(env_data) if parsed_args.output_env_file: if os.path.exists(parsed_args.output_env_file): self.log.warning("Output env file exists, " "moving it to backup.") shutil.move(parsed_args.output_env_file, parsed_args.output_env_file + ".backup") utils.safe_write(parsed_args.output_env_file, env_data) class TripleOImagePrepare(command.Command): """Prepare and upload containers from a single command.""" auth_required = False log = logging.getLogger(__name__ + ".TripleoImagePrepare") def get_parser(self, prog_name): parser = super(TripleOImagePrepare, self).get_parser(prog_name) try: roles_file = utils.rel_or_abs_path( constants.OVERCLOUD_ROLES_FILE, constants.TRIPLEO_HEAT_TEMPLATES) except exceptions.DeploymentError: roles_file = None parser.add_argument( '--environment-file', '-e', metavar='', action='append', dest='environment_files', help=_('Environment file containing the ContainerImagePrepare ' 'parameter which specifies all prepare actions. ' 'Also, environment files specifying which services are ' 'containerized. Entries will be filtered to only contain ' 'images used by containerized services. (Can be specified ' 'more than once.)') ) parser.add_argument( '--environment-directory', metavar='', action='append', dest='environment_directories', default=[os.path.expanduser(constants.DEFAULT_ENV_DIRECTORY)], help=_('Environment file directories that are automatically ' 'added to the environment. ' 'Can be specified more than once. Files in directories are ' 'loaded in ascending sort order.') ) parser.add_argument( '--roles-file', '-r', dest='roles_file', default=roles_file, help=_( 'Roles file, overrides the default %s in the t-h-t templates ' 'directory used for deployment. May be an ' 'absolute path or the path relative to the templates dir.' ) % constants.OVERCLOUD_ROLES_FILE ) parser.add_argument( "--output-env-file", dest="output_env_file", metavar='', help=_("File to write heat environment file which specifies all " "image parameters. Any existing file will be overwritten."), ) parser.add_argument( '--dry-run', dest='dry_run', action='store_true', default=False, help=_('Do not perform any pull, modify, or push operations. ' 'The environment file will still be populated as if these ' 'operations were performed.') ) parser.add_argument( "--cleanup", dest="cleanup", metavar='', default=image_uploader.CLEANUP_FULL, help=_("Cleanup behavior for local images left after upload. " "The default 'full' will attempt to delete all local " "images. 'partial' will leave images required for " "deployment on this host. 'none' will do no cleanup.") ) parser.add_argument( "--log-file", dest="log_file", default=constants.CONTAINER_IMAGE_PREPARE_LOG_FILE, help=_("Log file to be used for python logging. " "By default it would be logged to " "$HOME/container_image_prepare.log.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) if parsed_args.cleanup not in image_uploader.CLEANUP: raise oscexc.CommandError('--cleanup must be one of: %s' % ', '.join(image_uploader.CLEANUP)) role_file = None if parsed_args.roles_file: role_file = utils.rel_or_abs_path(parsed_args.roles_file, constants.TRIPLEO_HEAT_TEMPLATES) env_dirs = [os.path.abspath(x) for x in parsed_args.environment_directories] env_files = [os.path.abspath(x) for x in parsed_args.environment_files] extra_vars = { "roles_file": role_file, "environment_directories": env_dirs, "environment_files": env_files, "cleanup": parsed_args.cleanup, "dry_run": parsed_args.dry_run, "log_file": parsed_args.log_file} if self.app_args.verbose_level >= 3: extra_vars["debug"] = True if parsed_args.output_env_file: extra_vars["output_env_file"] = os.path.abspath( parsed_args.output_env_file) with oooutils.TempDirs() as tmp: oooutils.run_ansible_playbook( playbook='cli-container-image-prepare.yaml', inventory='localhost,', workdir=tmp, playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, verbosity=oooutils.playbook_verbosity(self=self), extra_vars=extra_vars)