ebcli/controllers/deploy.py (174 lines of code) (raw):

# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file 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. from os import path, chdir, getcwd, makedirs import zipfile import datetime from typing import Optional from cement.utils.misc import minimal_logger from ebcli.core import io, hooks, fileoperations from ebcli.core.abstractcontroller import AbstractBaseController from ebcli.lib import elasticbeanstalk from ebcli.objects.environment import Environment from ebcli.objects.exceptions import InvalidOptionsError, NotInitializedError from ebcli.operations import commonops, deployops, composeops, statusops from ebcli.resources.strings import strings, flag_text LOG = minimal_logger(__name__) class DeployController(AbstractBaseController): class Meta(AbstractBaseController.Meta): label = 'deploy' description = strings['deploy.info'] arguments = [ (['environment_name'], dict( action='store', nargs='?', default=[], help=flag_text['deploy.env'])), (['--modules'], dict(help=flag_text['deploy.modules'], nargs='*')), (['-g', '--env-group-suffix'], dict(help=flag_text['deploy.group_suffix'])), (['--version'], dict(help=flag_text['deploy.version'])), (['-l', '--label'], dict(help=flag_text['deploy.label'])), (['-m', '--message'], dict(help=flag_text['deploy.message'])), (['-nh', '--nohang'], dict( action='store_true', help=flag_text['deploy.nohang'])), (['--staged'], dict( action='store_true', help=flag_text['deploy.staged'])), (['--timeout'], dict(type=int, help=flag_text['general.timeout'])), (['--source'], dict(help=flag_text['deploy.source'])), (['-p', '--process'], dict( action='store_true', help=flag_text['deploy.process'])), (['--archive'], dict(help=flag_text['deploy.archive'])),] usage = AbstractBaseController.Meta.usage.replace('{cmd}', label) def do_command(self): self.nohang = self.app.pargs.nohang self.timeout = self.app.pargs.timeout if self.nohang: self.timeout = 0 if self.app.pargs.modules: self.multiple_app_deploy() return self.message = self.app.pargs.message self.staged = self.app.pargs.staged self.source = self.app.pargs.source self.archive = self.app.pargs.archive if self.archive and not self.app.pargs.region: raise InvalidOptionsError(strings['deploy.archivewithoutregion']) if self.source and self.archive: raise InvalidOptionsError(strings['deploy.archivewithsource']) self.env_name = self.app.pargs.environment_name if self.archive and not self.env_name: raise InvalidOptionsError(strings['deploy.archivewithoutenvname']) elif not self.archive: self.env_name = self.env_name or self.get_env_name() if self.archive: environment = elasticbeanstalk.get_environment(env_name=self.env_name) self.app_name = environment.app_name else: self.app_name = self.get_app_name() self.version = self.app.pargs.version if self.version and self.archive: raise InvalidOptionsError(strings['deploy.archivewithversion']) self.label = self.app.pargs.label self.process = self.app.pargs.process group_name = self.app.pargs.env_group_suffix _check_env_lifecycle_state(self.env_name) if self.version and (self.message or self.label): raise InvalidOptionsError(strings['deploy.invalidoptions']) process_app_versions = fileoperations.env_yaml_exists() or self.process source_bundle_zip = None if self.archive: source_bundle_zip = get_or_create_source_bundle(archive=self.archive, label=self.label) self.label = self.label or source_bundle_zip.split(path.sep)[-1] deployops.deploy(self.app_name, self.env_name, self.version, self.label, self.message, group_name=group_name, process_app_versions=process_app_versions, staged=self.staged, timeout=self.timeout, source=self.source, source_bundle=source_bundle_zip) def multiple_app_deploy(self): missing_env_yaml = [] top_dir = getcwd() for module in self.app.pargs.modules: if not path.isdir(path.join(top_dir, module)): continue chdir(path.join(top_dir, module)) if not fileoperations.env_yaml_exists(): missing_env_yaml.append(module) chdir(top_dir) if len(missing_env_yaml) > 0: module_list = '' for module_name in missing_env_yaml: module_list = module_list + module_name + ', ' io.echo(strings['deploy.modulemissingenvyaml'].replace('{modules}', module_list[:-2])) return self.compose_deploy() def compose_deploy(self): app_name = None modules = self.app.pargs.modules group_name = self.app.pargs.env_group_suffix env_names = [] stages_version_labels = {} stages_env_names = {} top_dir = getcwd() for module in modules: if not path.isdir(path.join(top_dir, module)): io.log_error(strings['deploy.notadirectory'].replace('{module}', module)) continue chdir(path.join(top_dir, module)) if not group_name: group_name = commonops.get_current_branch_group_suffix() if group_name not in stages_version_labels.keys(): stages_version_labels[group_name] = [] stages_env_names[group_name] = [] if not app_name: app_name = self.get_app_name() io.echo('--- Creating application version for module: {0} ---'.format(module)) hooks.set_region(None) hooks.set_ssl(None) hooks.set_profile(None) if not app_name: app_name = self.get_app_name() process_app_version = fileoperations.env_yaml_exists() version_label = commonops.create_app_version(app_name, process=process_app_version) stages_version_labels[group_name].append(version_label) environment_name = fileoperations.get_env_name_from_env_yaml() if environment_name is not None: commonops.set_environment_for_current_branch(environment_name. replace('+', '-{0}'. format(group_name))) env_name = commonops.get_current_branch_environment() stages_env_names[group_name].append(env_name) env_names.append(env_name) else: io.echo(strings['deploy.noenvname'].replace('{module}', module)) stages_version_labels[group_name] = [ v for v in stages_version_labels[group_name] if v != version_label ] chdir(top_dir) if len(stages_version_labels) > 0: for stage in stages_version_labels.keys(): request_id = composeops.compose_no_events(app_name, stages_version_labels[stage], group_name=stage) if request_id is None: io.log_error("Unable to compose modules.") return commonops.wait_for_compose_events(request_id, app_name, env_names, self.timeout) else: io.log_warning(strings['compose.novalidmodules']) def _check_env_lifecycle_state(env_name): env = elasticbeanstalk.get_environment(env_name=env_name) statusops.alert_environment_status(env) def get_or_create_source_bundle(archive: str, label: str=None) -> Optional[str]: if archive and zipfile.is_zipfile(archive): source_bundle_zip = archive elif archive and path.isdir(archive): upload_target_dir = archive utcnow = str(datetime.datetime.now(datetime.UTC).timestamp()) migrations_path = path.join(path.expanduser('~'), '.ebartifacts') if label: zip_file_name = f"{label}.zip" else: zip_file_name = f"archives-{utcnow}.zip" source_bundle_zip = path.join(migrations_path, 'archives', zip_file_name) makedirs(path.join(migrations_path, 'archives'), exist_ok=True) fileoperations.zip_up_folder( upload_target_dir, source_bundle_zip ) else: raise InvalidOptionsError(strings['deploy.archive_must_be_dir_or_zip']) return source_bundle_zip