def stream_build_configuration_app_version_creation(app_name, app_version_label): # Get the CloudWatch logs link successfully_generated = wait_for_app_version_attribute( app_name, [app_version_label], 'BuildArn', timeout=1) app_version_response = elasticbeanstalk.get_application_versions( app_name, version_labels=[app_version_label])['ApplicationVersions'] build_response = codebuild.batch_get_builds([app_version_response[0]['BuildArn']]) \ if successfully_generated else None if build_response is not None and 'logs' in build_response['builds'][0]: log_link_text = strings['codebuild.buildlogs'].replace( '{logs_link}', build_response['builds'][0]['logs']['deepLink']) io.echo(log_link_text) else: io.log_warning("Could not retrieve CloudWatch link for CodeBuild logs") # Wait for the success events try: from ebcli.operations.commonops import wait_for_success_events wait_for_success_events(None, timeout_in_minutes=5, can_abort=False, version_label=app_version_label) except ServiceError as ex: LOG.debug( "Caught service error while creating application version '{0}' " "deleting the created applicaiton version as it is useless now.") elasticbeanstalk.delete_application_version(app_name, app_version_label) raise ex
def test_log_warning__cement_application_not_defined_yet( self, echo_mock, ebglobals_mock ): io.log_warning('hello, world!') echo_mock.assert_called_once_with('WARN: hello, world!')
def interactive_update_lifcycle_policy(app_name): # Get current application settings api_model = elasticbeanstalk.describe_application(app_name) # Convert into yaml format from raw API lifecycle_config = LifecycleConfiguration(api_model) usr_model = lifecycle_config.convert_api_to_usr_model() # Save yaml file into temp file file_location = fileoperations.save_app_file(usr_model) fileoperations.open_file_for_editing(file_location) # Update and delete file try: usr_model = fileoperations.get_application_from_file(app_name) config_changes = lifecycle_config.collect_changes(usr_model) fileoperations.delete_app_file(app_name) except InvalidSyntaxError: io.log_error(strings['lifecycle.invalidsyntax']) fileoperations.delete_app_file(app_name) return if not config_changes: # no changes made, exit io.log_warning(strings['lifecycle.updatenochanges']) return elasticbeanstalk.update_application_resource_lifecycle(app_name, config_changes) io.echo(strings['lifecycle.success'])
def setup_new_codecommit_branch(self, branch_name): LOG.debug("Setting up CodeCommit branch") self.fetch_remote_branches(self.codecommit_remote_name) self.checkout_branch(branch_name, create_branch=True) stdout, stderr, exitcode = self._run_cmd( ['git', 'push', '-u', self.codecommit_remote_name, branch_name], handle_exitcode=False) if exitcode == 1: io.log_warning( 'Git is not able to push code: {0}'.format(exitcode)) io.log_warning(stderr) if stderr: LOG.debug('git push error: ' + stderr) LOG.debug('git push result: ' + stdout) self.fetch_remote_branches(self.codecommit_remote_name) stdout, stderr, exitcode = self._run_cmd([ 'git', 'branch', '--set-upstream-to', '{0}/{1}'.format( self.codecommit_remote_name, branch_name) ], handle_exitcode=False) if stderr: LOG.debug('git branch --set-upstream-to error: ' + stderr) LOG.debug('git branch result: ' + stdout)
def validate_build_config(build_config): if build_config.service_role is not None: # Verify that the service role exists in the customers account from ebcli.lib.iam import get_roles role = build_config.service_role validated_role = None existing_roles = get_roles() for existing_role in existing_roles: if role == existing_role['Arn'] or role == existing_role[ 'RoleName']: validated_role = existing_role['Arn'] if validated_role is None: LOG.debug( "Role '{0}' not found in retrieved list of roles".format(role)) raise ValidationError("Role '{0}' does not exist.".format(role)) build_config.service_role = validated_role else: io.log_warning( "To learn more about creating a service role for CodeBuild, see Docs:" " https://docs-aws.amazon.com/codebuild/latest/userguide/setting-up.html#setting-up-service-role" ) raise ValidationError( "No service role specified in buildspec; this is a required argument." ) # Fail because the service role is required if build_config.image is None: # Fail because the image is required raise ValidationError( "No image specified in buildspec; this is a required argument.")
def interactive_update_lifcycle_policy(app_name): # Get current application settings api_model = elasticbeanstalk.describe_application(app_name) # Convert into yaml format from raw API lifecycle_config = LifecycleConfiguration(api_model) usr_model = lifecycle_config.convert_api_to_usr_model() # Save yaml file into temp file file_location = fileoperations.save_app_file(usr_model) fileoperations.open_file_for_editing(file_location) # Update and delete file try: usr_model = fileoperations.get_application_from_file(app_name) config_changes = lifecycle_config.collect_changes(usr_model) fileoperations.delete_app_file(app_name) except InvalidSyntaxError: io.log_error(strings['lifecycle.invalidsyntax']) fileoperations.delete_app_file(app_name) return if not config_changes: # no changes made, exit io.log_warning(strings['lifecycle.updatenochanges']) return elasticbeanstalk.update_application_resource_lifecycle( app_name, config_changes) io.echo(strings['lifecycle.success'])
def interactive_update_lifcycle_policy(app_name): api_model = elasticbeanstalk.describe_application(app_name) lifecycle_config = LifecycleConfiguration(api_model) usr_model = lifecycle_config.convert_api_to_usr_model() file_location = fileoperations.save_app_file(usr_model) fileoperations.open_file_for_editing(file_location) try: usr_model = fileoperations.get_application_from_file(app_name) config_changes = lifecycle_config.collect_changes(usr_model) fileoperations.delete_app_file(app_name) except InvalidSyntaxError: io.log_error(strings['lifecycle.invalidsyntax']) fileoperations.delete_app_file(app_name) return if not config_changes: io.log_warning(strings['lifecycle.updatenochanges']) return elasticbeanstalk.update_application_resource_lifecycle( app_name, config_changes) io.echo(strings['lifecycle.success'])
def do_upgrade(env_name, add_rolling, timeout, solution_stack_name, health_based=False, platform_arn=None): if add_rolling: if health_based: roll_type = 'Health' else: roll_type = 'Time' changes = [ elasticbeanstalk.create_option_setting( namespaces.ROLLING_UPDATES, option_names.ROLLING_UPDATE_ENABLED, 'true'), elasticbeanstalk.create_option_setting( namespaces.ROLLING_UPDATES, option_names.ROLLING_UPDATE_TYPE, roll_type) ] io.log_warning(prompts['upgrade.applyrolling'].format(roll_type)) else: changes = None if PlatformVersion.is_valid_arn(platform_arn): commonops.update_environment(env_name, changes, None, timeout=timeout, platform_arn=platform_arn) else: commonops.update_environment(env_name, changes, None, timeout=timeout, solution_stack_name=solution_stack_name)
def test_log_warning( self, echo_mock, ebglobals_mock ): io.log_warning('hello, world!') echo_mock.assert_not_called()
def remove_cwl_extensions(cwfile_dir, ebextension_dir): for file_name in os.listdir(cwfile_dir): try: os.remove(os.path.join(ebextension_dir, file_name)) except: io.log_warning('Ebextension {} already removed'.format(file_name)) io.echo(strings['cloudwatch-setup.removetext'])
def create_codecommit_app_version(app_name, process=False, label=None, message=None, build_config=None): fileoperations.ProjectRoot.traverse() source_control = SourceControl.get_source_control() if source_control.get_current_commit() is None: io.log_warning( 'There are no commits for the current branch, attempting ' 'to create an empty commit and launching with the sample ' 'application') source_control.create_initial_commit() if source_control.untracked_changes_exist(): io.log_warning(strings['sc.unstagedchanges']) if label: version_label = label else: version_label = source_control.get_version_label() if message: description = message else: description = source_control.get_message() if len(description) > 200: description = description[:195] + '...' try: source_control.push_codecommit_code() except CommandError as e: io.echo("Could not push code to the CodeCommit repository:") raise e from ebcli.operations import gitops repository = gitops.get_default_repository() commit_id = source_control.get_current_commit() if repository is None or commit_id is None: raise ServiceError( "Could not find repository or commit id to create an application version" ) io.log_info('Creating AppVersion ' + version_label) return _create_application_version(app_name, version_label, description, None, None, process, repository=repository, commit_id=commit_id, build_config=build_config)
def push_codecommit_code(self): io.log_info('Pushing local code to codecommit with git-push') stdout, stderr, exitcode = self._run_cmd(['git', 'push', self.get_current_repository(), self.get_current_branch()]) if exitcode != 0: io.log_warning('Git is not able to push code: {0}'.format(exitcode)) io.log_warning(stderr) else: LOG.debug('git push result: {0}'.format(stdout)) self._handle_exitcode(exitcode, stderr)
def do_command(self): app_name = self.get_app_name() num_to_leave = self.app.pargs.num_to_leave older_than = self.app.pargs.older_than force = self.app.pargs.force envs = elasticbeanstalk.get_app_environments(app_name) versions_in_use = [e.version_label for e in envs] app_versions = elasticbeanstalk.get_application_versions( app_name)['ApplicationVersions'] app_versions.sort(key=itemgetter('DateUpdated'), reverse=True) # Filter out versions currently being used app_versions = [ v for v in app_versions if v['VersionLabel'] not in versions_in_use ] total_num_unused_versions = len(app_versions) if total_num_unused_versions < num_to_leave: io.echo( 'Not enough unused application version to leave behind {0}; No application versions to delete.' .format(num_to_leave)) return # Filter out versions newer than filter date app_versions = [ v for v in app_versions if utils.get_delta_from_now_and_datetime( v['DateUpdated']).days > older_than ] # dont include most recent app_versions = app_versions[num_to_leave:] if app_versions: if not force: response = io.get_boolean_response( '{} application versions will be deleted. ' 'Continue?'.format(len(app_versions))) if not response: return else: io.echo('No application versions to delete.') return for version in app_versions: label = version['VersionLabel'] try: elasticbeanstalk.delete_application_version(app_name, label) except ServiceError as e: io.log_warning('Error deleting version {0}. Error: {1}'.format( label, e.message))
def get_instance_profile(self): # Check to see if it was specified on the command line profile = self.app.pargs.instance_profile if profile is None: try: # Check to see if it is associated with the workspace profile = fileoperations.get_instance_profile() except NotInitializedError: pass if profile is None: # Check to see if the default instance profile already exists try: existing_profiles = iam.get_instance_profile_names() if iam_attributes.DEFAULT_PLATFORM_BUILDER_ROLE in existing_profiles: profile = iam_attributes.DEFAULT_PLATFORM_BUILDER_ROLE except NotAuthorizedError: io.log_warning(strings['platformcreateiamdescribeerror.info']) if profile is None: # We will now create the default role for the customer try: profile = iam_attributes.DEFAULT_PLATFORM_BUILDER_ROLE try: iam.create_instance_profile(profile) io.log_info(strings['platformcreateiamcreated.info']) except AlreadyExistsError: pass document = iam_documents.EC2_ASSUME_ROLE_PERMISSION try: # Create a role with the same name iam.create_role(profile, document) # Attach required custom platform builder permissions iam.put_role_policy( profile, iam_attributes.PLATFORM_BUILDER_INLINE_POLICY_NAME, iam_documents.CUSTOM_PLATFORM_BUILDER_INLINE_POLICY) # Associate instance profile with the required role iam.add_role_to_profile(profile, profile) io.log_info(strings['platformcreateiampolicyadded.info']) except AlreadyExistsError: # If the role exists then we leave it as is, we do not try to add or modify its policies pass except NotAuthorizedError: io.log_warning(strings['platformcreateiamcreateerror.info']) # Save to disk write_config_setting('global', 'instance_profile', profile)
def get_current_branch(self): stdout, stderr, exitcode = self._run_cmd( ['git', 'rev-parse', '--abbrev-ref', 'HEAD'], handle_exitcode=False) if stdout.strip() == 'HEAD': io.log_warning( 'Git is in a detached head state. Using branch "default".') return 'default' else: self._handle_exitcode(exitcode, stderr) return stdout
def stream_build_configuration_app_version_creation(app_name, app_version_label, build_spec): # Get the CloudWatch logs link successfully_generated = wait_for_app_version_attribute( app_name, [app_version_label], timeout=1 ) app_version_response = elasticbeanstalk.get_application_versions( app_name, version_labels=[app_version_label] )['ApplicationVersions'] build_response = codebuild.batch_get_builds([app_version_response[0]['BuildArn']]) \ if successfully_generated else None codebuild_timeout = build_spec.timeout or 60 if build_response is not None and 'logs' in build_response['builds'][0]: log_link_text = strings['codebuild.buildlogs'].replace( '{logs_link}', build_response['builds'][0]['logs']['deepLink'] ) io.echo(log_link_text) io.echo( "NOTE: The CodeBuild timeout is set to {0} minutes, so this " "operation may take upto '{0}' minutes to complete.".format(codebuild_timeout) ) else: io.log_warning("Could not retrieve CloudWatch link for CodeBuild logs") try: # Need to lazy-import `ebcli.lib.commonops` because `pytest` is unable to load it # at module load-time using Python 2.7 and Python 3.4 from ebcli.operations import commonops timeout_error_message = ' '.join([ 'The CodeBuild build timed out after {} minute(s).'.format(codebuild_timeout), "To increase the time limit, use the 'Timeout' option in the 'buildspec.yml' file." ]) commonops.wait_for_success_events( app_name=app_name, can_abort=False, request_id=None, timeout_error_message=timeout_error_message, timeout_in_minutes=codebuild_timeout, version_label=app_version_label ) except ServiceError as exception: LOG.debug("Caught service error while creating application version '{0}' " "deleting the created application version as it is useless now.".format(app_version_label)) elasticbeanstalk.delete_application_version(app_name, app_version_label) raise exception
def get_build_configuration(): # Values expected in the eb config section in BuildSpec service_role_key = 'CodeBuildServiceRole' image_key = 'Image' compute_key = 'ComputeType' timeout_key = 'Timeout' # get setting from global if it exists cwd = os.getcwd() # save working directory try: _traverse_to_project_root() build_spec = _get_yaml_dict(buildspec_name) # Assert that special beanstalk section exists if build_spec is None or buildspec_config_header not in build_spec.keys( ): LOG.debug("Buildspec Keys: {0}".format(build_spec.keys())) io.log_warning(strings['codebuild.noheader'].replace( '{header}', buildspec_config_header)) return None build_configuration = BuildConfiguration() beanstalk_build_configs = build_spec[buildspec_config_header] if beanstalk_build_configs is None: LOG.debug("No values for EB header in buildspec file") return build_configuration LOG.debug("EB Config Keys: {0}".format(beanstalk_build_configs.keys())) if service_role_key in beanstalk_build_configs.keys(): build_configuration.service_role = beanstalk_build_configs[ service_role_key] if image_key in beanstalk_build_configs.keys(): build_configuration.image = beanstalk_build_configs[image_key] if compute_key in beanstalk_build_configs.keys(): build_configuration.compute_type = beanstalk_build_configs[ compute_key] if timeout_key in beanstalk_build_configs.keys(): build_configuration.timeout = beanstalk_build_configs[timeout_key] finally: os.chdir(cwd) # move back to working directory return build_configuration
def setup_ssh(env_name, keyname, timeout=None): io.log_warning(prompts['ssh.setupwarn'].replace('{env-name}', env_name)) keyname = prompt_for_ec2_keyname(env_name=env_name, keyname=keyname) if keyname: options = [{ 'Namespace': 'aws:autoscaling:launchconfiguration', 'OptionName': 'EC2KeyName', 'Value': keyname }] commonops.update_environment(env_name, options, False, timeout=timeout or 5)
def get_current_branch(self): revparse_command = ['git', 'rev-parse', '--abbrev-ref', 'HEAD'] LOG.debug('Getting current branch name by performing `{0}`'.format(' '.join(revparse_command))) stdout, stderr, exitcode = self._run_cmd(revparse_command, handle_exitcode=False) if stdout.strip() == 'HEAD': io.log_warning('Git is in a detached head state. Using branch "default".') return 'default' else: self._handle_exitcode(exitcode, stderr) LOG.debug(stdout) return stdout
def get_platform(solution_string, iprofile=None): """ Set a PlatformVersion or a SolutionStack based on the `solution_string`. :param solution_string: The value of the `--platform` argument input by the customer :param iprofile: The instance profile, if any, the customer passed as argument :return: a PlatformVersion or a SolutionStack object depending on whether the match was against an ARN of a Solution Stack name. """ solution = solution_stack_ops.find_solution_stack_from_string(solution_string) solution = solution or solution_stack_ops.get_solution_stack_from_customer() if isinstance(solution, SolutionStack): if solution.language_name == 'Multi-container Docker' and not iprofile: io.log_warning(prompts['ecs.permissions']) return solution
def create_platform_version( version, major_increment, minor_increment, patch_increment, instance_type, vpc=None, staged=False, timeout=None, tags=None, ): _raise_if_directory_is_empty() _raise_if_platform_definition_file_is_missing() version and _raise_if_version_format_is_invalid(version) platform_name = fileoperations.get_platform_name() instance_profile = fileoperations.get_instance_profile(None) key_name = commonops.get_default_keyname() version = version or _resolve_version_number( platform_name, major_increment, minor_increment, patch_increment) tags = tagops.get_and_validate_tags(tags) source_control = SourceControl.get_source_control() io.log_warning(strings['sc.unstagedchanges'] ) if source_control.untracked_changes_exist() else None version_label = _resolve_version_label(source_control, staged) bucket, key, file_path = _resolve_s3_bucket_and_key( platform_name, version_label, source_control, staged) _upload_platform_version_to_s3_if_necessary(bucket, key, file_path) io.log_info('Creating Platform Version ' + version_label) response = elasticbeanstalk.create_platform_version( platform_name, version, bucket, key, instance_profile, key_name, instance_type, tags, vpc) environment_name = 'eb-custom-platform-builder-packer' io.echo( colored( strings['platformbuildercreation.info'].format(environment_name), attrs=['reverse'])) fileoperations.update_platform_version(version) commonops.set_environment_for_current_branch(environment_name) stream_platform_logs(response, platform_name, version, timeout)
def download_and_extract_sample_app(env_name): """ Method orchestrates the retrieval, and extraction of application version. :param env_name: The name of the environment whose application version will be downloaded. :return: None """ try: url = retrieve_application_version_url(env_name) zip_file_location = '.elasticbeanstalk/.sample_app_download.zip' io.echo('INFO: {}'.format(strings['create.downloading_sample_application'])) download_application_version(url, zip_file_location) ZipFile(zip_file_location, 'r', allowZip64=True).extractall() os.remove(zip_file_location) io.echo('INFO: {}'.format(strings['create.sample_application_download_complete'])) except NotAuthorizedError as e: io.log_warning('{} Continuing environment creation.'.format(e.message)) except cloudformation.CFNTemplateNotFound as e: io.log_warning('{} Continuing environment creation.'.format(e.message))
def cleanup_application_versions(app_name): io.echo('Removing application versions from s3.') versions = elasticbeanstalk.get_application_versions( app_name)['ApplicationVersions'] buckets = defaultdict(list) for version in versions: bundle = version.get('SourceBundle', {}) bucket = bundle.get('S3Bucket') key = bundle.get('S3Key') if bucket and key: buckets[bucket].append(key) for bucket, keys in six.iteritems(buckets): try: s3.delete_objects(bucket, keys) except NotAuthorizedError: io.log_warning( 'Error deleting application versions from bucket "{0}"'.format( bucket))
def setup_new_codecommit_branch(self, branch_name): LOG.debug("Setting up CodeCommit branch") # Get fetch to ensure the remote repository is up to date self.fetch_remote_branches(self.codecommit_remote_name) # Attempt to check out the desired branch, if it doesn't exist create it from the current HEAD self.checkout_branch(branch_name, create_branch=True) # Push the current code and set the remote as the current working remote stdout, stderr, exitcode = self._run_cmd( ['git', 'push', '-u', self.codecommit_remote_name, branch_name], handle_exitcode=False ) if exitcode == 1: io.log_warning('Git is not able to push code: {0}'.format(exitcode)) io.log_warning(stderr) if stderr: LOG.debug('git push error: ' + stderr) LOG.debug('git push result: ' + stdout) # Get fetch to ensure the remote repository is up to date because we just pushed a new branch self.fetch_remote_branches(self.codecommit_remote_name) # Set the remote branch up so it's not using the presigned remote OR if the push failed. stdout, stderr, exitcode = self._run_cmd( [ 'git', 'branch', '--set-upstream-to', '{0}/{1}'.format(self.codecommit_remote_name, branch_name) ], handle_exitcode=False ) if stderr: LOG.debug('git branch --set-upstream-to error: ' + stderr) LOG.debug('git branch result: ' + stdout)
def update_environment_configuration(app_name, env_name, nohang, timeout=None): # get environment setting api_model = elasticbeanstalk.describe_configuration_settings( app_name, env_name) # Convert the raw api return to yaml format env_settings = EnvironmentSettings(api_model) usr_model = env_settings.convert_api_to_usr_model() # Save the yaml in a temp file file_location = fileoperations.save_env_file(usr_model) fileoperations.open_file_for_editing(file_location) platform_arn = None # Update and delete file try: usr_model = fileoperations.get_environment_from_file(env_name) changes, remove = env_settings.collect_changes(usr_model) if api_model['PlatformArn'] != usr_model['PlatformArn']: platform_arn = usr_model['PlatformArn'] fileoperations.delete_env_file(env_name) except InvalidSyntaxError: io.log_error(prompts['update.invalidsyntax']) return if not changes and not remove and not platform_arn: # no changes made, exit io.log_warning('No changes made. Exiting.') return if fileoperations.env_yaml_exists(): io.echo(strings['config.envyamlexists']) commonops.update_environment(env_name, changes, nohang, remove=remove, timeout=timeout, solution_stack_name=None, platform_arn=platform_arn)
def upgrade_env(app_name, env_name, timeout, confirm, noroll): env = elasticbeanstalk.get_environment_settings(app_name, env_name) latest = solution_stack_ops.find_solution_stack_from_string( env.platform.name, find_newer=True) if latest.name == env.platform.name: io.echo(prompts['upgrade.alreadylatest']) return else: single = elasticbeanstalk.get_option_setting( env.option_settings, namespaces.ENVIRONMENT, 'EnvironmentType') == 'SingleInstance' rolling_enabled = elasticbeanstalk.get_option_setting( env.option_settings, namespaces.ROLLING_UPDATES, option_names.ROLLING_UPDATE_ENABLED) == 'true' webserver = env.tier.name.lower() == 'webserver' io.echo() io.echo(prompts['upgrade.infodialog'].format(env_name)) io.echo('Current platform:', env.platform) io.echo('Latest platform: ', latest.name) io.echo() warning = _get_warning_message(confirm, single, rolling_enabled, webserver, noroll) if warning: io.log_warning(warning) io.echo(prompts['upgrade.altmessage']) io.echo() if not confirm: io.validate_action(prompts['upgrade.validate'], env.name) add_rolling = _should_add_rolling(single, rolling_enabled, noroll) do_upgrade(env_name, add_rolling, timeout, latest.name, health_based=webserver, platform_arn=latest.name)
def scale(app_name, env_name, number, confirm, timeout=None): options = [] # get environment env = elasticbeanstalk.describe_configuration_settings( app_name, env_name)['OptionSettings'] # if single instance, offer to switch to load-balanced namespace = 'aws:elasticbeanstalk:environment' setting = next((n for n in env if n["Namespace"] == namespace), None) value = setting['Value'] if value == 'SingleInstance': if not confirm: ## prompt to switch to LoadBalanced environment type io.echo(prompts['scale.switchtoloadbalance']) io.log_warning(prompts['scale.switchtoloadbalancewarn']) switch = io.get_boolean_response() if not switch: return options.append({ 'Namespace': namespace, 'OptionName': 'EnvironmentType', 'Value': 'LoadBalanced' }) # change autoscaling min AND max to number namespace = 'aws:autoscaling:asg' max = 'MaxSize' min = 'MinSize' for name in [max, min]: options.append({ 'Namespace': namespace, 'OptionName': name, 'Value': str(number) }) request_id = elasticbeanstalk.update_environment(env_name, options) commonops.wait_for_success_events(request_id, timeout_in_minutes=timeout or 5, can_abort=True)
def update_environment_configuration(app_name, env_name, nohang, timeout=None): # get environment setting api_model = elasticbeanstalk.describe_configuration_settings( app_name, env_name ) # Convert the raw api return to yaml format env_settings = EnvironmentSettings(api_model) usr_model = env_settings.convert_api_to_usr_model() # Save the yaml in a temp file file_location = fileoperations.save_env_file(usr_model) fileoperations.open_file_for_editing(file_location) platform_arn = None # Update and delete file try: usr_model = fileoperations.get_environment_from_file(env_name) changes, remove = env_settings.collect_changes(usr_model) if api_model['PlatformArn'] != usr_model['PlatformArn']: platform_arn = usr_model['PlatformArn'] fileoperations.delete_env_file(env_name) except InvalidSyntaxError: io.log_error(prompts['update.invalidsyntax']) return if not changes and not remove and not platform_arn: # no changes made, exit io.log_warning('No changes made. Exiting.') return if fileoperations.env_yaml_exists(): io.echo(strings['config.envyamlexists']) commonops.update_environment(env_name, changes, nohang, remove=remove, timeout=timeout, solution_stack_name=None, platform_arn=platform_arn)
def stream_build_configuration_app_version_creation(app_name, app_version_label, build_spec): # Get the CloudWatch logs link successfully_generated = wait_for_app_version_attribute(app_name, [app_version_label], 'BuildArn', timeout=1) app_version_response = elasticbeanstalk.get_application_versions(app_name, version_labels=[app_version_label])['ApplicationVersions'] build_response = codebuild.batch_get_builds([app_version_response[0]['BuildArn']]) \ if successfully_generated else None codebuild_timeout = build_spec.timeout or 60 if build_response is not None and 'logs' in build_response['builds'][0]: log_link_text = strings['codebuild.buildlogs'].replace('{logs_link}', build_response['builds'][0]['logs']['deepLink']) io.echo(log_link_text) io.echo("NOTE: The CodeBuild timeout is set to {0} minutes, so this operation may take upto '{0}' minutes to complete.".format(codebuild_timeout)) else: io.log_warning("Could not retrieve CloudWatch link for CodeBuild logs") # Wait for the success events try: # Need to lazy-import `ebcli.lib.commonops` because `pytest` is unable to load it # at module load-time using Python 2.7 and Python 3.4 from ebcli.operations import commonops timeout_error_message = ' '.join([ 'The CodeBuild build timed out after {} minute(s).'.format(codebuild_timeout), "To increase the time limit, use the 'Timeout' option in the 'buildspec.yml' file." ]) commonops.wait_for_success_events( app_name=app_name, can_abort=False, request_id=None, timeout_error_message=timeout_error_message, timeout_in_minutes=codebuild_timeout, version_label=app_version_label ) except ServiceError as exception: LOG.debug("Caught service error while creating application version '{0}' " "deleting the created application version as it is useless now.".format(app_version_label)) elasticbeanstalk.delete_application_version(app_name, app_version_label) raise exception
def get_build_configuration(): # Values expected in the eb config section in BuildSpec service_role_key = 'CodeBuildServiceRole' image_key = 'Image' compute_key = 'ComputeType' timeout_key = 'Timeout' # get setting from global if it exists cwd = os.getcwd() # save working directory try: _traverse_to_project_root() build_spec = _get_yaml_dict(buildspec_name) # Assert that special beanstalk section exists if build_spec is None or buildspec_config_header not in build_spec.keys(): LOG.debug("Buildspec Keys: {0}".format(build_spec.keys())) io.log_warning(strings['codebuild.noheader'].replace('{header}', buildspec_config_header)) return None beanstalk_build_configs = build_spec[buildspec_config_header] if beanstalk_build_configs is None: LOG.debug("No values for EB header in buildspec file") return BuildConfiguration() LOG.debug("EB Config Keys: {0}".format(beanstalk_build_configs.keys())) build_configuration = BuildConfiguration( compute_type=beanstalk_build_configs.get(compute_key), image=beanstalk_build_configs.get(image_key), service_role=beanstalk_build_configs.get(service_role_key), timeout=beanstalk_build_configs.get(timeout_key) ) finally: os.chdir(cwd) # move back to working directory return build_configuration
def retrieve_application_version_url(env_name): """ Method retrieves the URL of the application version of the environment, 'env_name', for the CLI to download from. The method waits for the CloudFormation stack associated with `env_name` to come into existence, after which, it retrieves the 'url' of the application version. :param env_name: Name of the environment that launched with the sample application :return: The URL of the application version. """ env = elasticbeanstalk.get_environment(env_name=env_name) cloudformation_stack_name = 'awseb-' + env.id + '-stack' cloudformation.wait_until_stack_exists(cloudformation_stack_name) template = cloudformation.get_template(cloudformation_stack_name) url = None try: url = template['TemplateBody']['Parameters']['AppSource']['Default'] except KeyError: io.log_warning('{}. '.format(strings['cloudformation.cannot_find_app_source_for_environment'])) return url
def validate_build_config(build_config): if build_config.service_role is not None: # Verify that the service role exists in the customers account from ebcli.lib.iam import get_roles role = build_config.service_role validated_role = None existing_roles = get_roles() for existing_role in existing_roles: if role == existing_role['Arn'] or role == existing_role['RoleName']: validated_role = existing_role['Arn'] if validated_role is None: LOG.debug("Role '{0}' not found in retrieved list of roles".format(role)) raise ValidationError("Role '{0}' does not exist.".format(role)) build_config.service_role = validated_role else: io.log_warning("To learn more about creating a service role for CodeBuild, see Docs:" " https://docs-aws.amazon.com/codebuild/latest/userguide/setting-up.html#setting-up-service-role") raise ValidationError("No service role specified in buildspec; this is a required argument.") # Fail because the service role is required if build_config.image is None: # Fail because the image is required raise ValidationError("No image specified in buildspec; this is a required argument.")
def do_command(self): # get arguments self.interactive = self.app.pargs.interactive self.region = self.app.pargs.region self.noverify = self.app.pargs.no_verify_ssl self.force_non_interactive = False # Determine if the customer is avoiding interactive mode by setting the platform flag if self.app.pargs.platform: self.force_non_interactive = True # Code Commit integration self.source = self.app.pargs.source source_location = None branch = None repository = None if self.source is not None: source_location, repository, branch = utils.parse_source(self.source) # The user specifies directories to initialize self.modules = self.app.pargs.modules if self.modules and len(self.modules) > 0: self.initialize_multiple_directories() return default_env = self.get_old_values() fileoperations.touch_config_folder() if self.interactive: self.region = get_region(self.region, self.interactive, self.force_non_interactive) else: self.region = get_region_from_inputs(self.region) aws.set_region(self.region) self.region = set_up_credentials(self.app.pargs.profile, self.region, self.interactive) self.solution = self.get_solution_stack() self.app_name = self.get_app_name() if self.noverify: fileoperations.write_config_setting('global', 'no-verify-ssl', True) if not default_env and not self.interactive: # try to get default env from config file if exists try: default_env = commonops.get_current_branch_environment() except NotInitializedError: default_env = None elif self.interactive: default_env = None if self.force_non_interactive: default_env = '/ni' # Create application sstack, key = commonops.pull_down_app_info(self.app_name, default_env=default_env) if elasticbeanstalk.application_exist(self.app_name) \ else commonops.create_app(self.app_name, default_env=default_env) if not self.solution: self.solution = sstack platform_set = False if not self.solution or \ (self.interactive and not self.app.pargs.platform): if fileoperations.env_yaml_exists(): env_yaml_platform = fileoperations.get_platform_from_env_yaml() if env_yaml_platform: platform = solutionstack.SolutionStack(env_yaml_platform).platform_shorthand self.solution = platform platform_set = True if not platform_set: self.solution = solution_stack_ops.get_solution_stack_from_customer().platform_shorthand # Select CodeBuild image if BuildSpec is present do not prompt or show if we are non-interactive if fileoperations.build_spec_exists() and not self.force_non_interactive: build_spec = fileoperations.get_build_configuration() if build_spec is not None and build_spec.image is None: LOG.debug("Buildspec file is present but image is does not exist. Attempting to fill best guess.") platform_image = initializeops.get_codebuild_image_from_platform(self.solution) # If the return is a dictionary then it must be a single image and we can use that automatically if type(platform_image) is dict: io.echo('codebuild.latestplatform'.replace('{platform}', self.solution)) else: # Otherwise we have an array for images which we must prompt the customer to pick from io.echo(prompts['codebuild.getplatform'].replace('{platform}', self.solution)) selected = utils.prompt_for_index_in_list(map(lambda image: image['description'], platform_image)) platform_image = platform_image[selected] platform_image['name'] = utils.decode_bytes(platform_image['name']) # Finally write the CodeBuild image back to the buildspec file fileoperations.write_config_setting(fileoperations.buildspec_config_header, 'Image', platform_image['name'], file=fileoperations.buildspec_name) # Setup code commit integration # Ensure that git is setup source_control = SourceControl.get_source_control() try: source_control_setup = source_control.is_setup() if source_control_setup is None: source_control_setup = False except CommandError: source_control_setup = False default_branch_exists = False if gitops.git_management_enabled() and not self.interactive: default_branch_exists = True # Warn the customer if they picked a region that CodeCommit is not supported codecommit_region_supported = codecommit.region_supported(self.region) if self.source is not None and not codecommit_region_supported: io.log_warning(strings['codecommit.badregion']) # Prompt customer to opt into CodeCommit unless one of the follows holds: if self.force_non_interactive: prompt_codecommit = False elif not codecommit.region_supported(self.region): prompt_codecommit = False elif self.source and source_location.lower() != 'codecommit': # Do not prompt if customer has already specified a code source to # associate the EB workspace with prompt_codecommit = False elif default_branch_exists: # Do not prompt if customer has already configured the EB application # in the present working directory with Git prompt_codecommit = False else: prompt_codecommit = True # Prompt for interactive CodeCommit if prompt_codecommit: if not source_control_setup: io.echo(strings['codecommit.nosc']) else: io.echo(strings['codecommit.ccwarning']) try: if not self.source: io.validate_action(prompts['codecommit.usecc'], "y") # Setup git config settings for code commit credentials source_control.setup_codecommit_cred_config() # Get user specified repository remote_url = None if repository is None: repository = get_repository_interactive() else: try: setup_codecommit_remote_repo(repository, source_control) except ServiceError as ex: if self.source: create_codecommit_repository(repository) setup_codecommit_remote_repo(repository, source_control) else: io.log_error(strings['codecommit.norepo']) raise ex # Get user specified branch if branch is None: branch = get_branch_interactive(repository) else: try: codecommit.get_branch(repository, branch) except ServiceError as ex: if self.source: create_codecommit_branch(source_control, branch) else: io.log_error(strings['codecommit.nobranch']) raise ex source_control.setup_existing_codecommit_branch(branch, remote_url) except ValidationError: LOG.debug("Denied option to use CodeCommit, continuing initialization") # Initialize the whole setup initializeops.setup(self.app_name, self.region, self.solution, dir_path=None, repository=repository, branch=branch) if 'IIS' not in self.solution: self.keyname = self.get_keyname(default=key) if self.keyname == -1: self.keyname = None fileoperations.write_config_setting('global', 'default_ec2_keyname', self.keyname) # Default to including git submodules when creating zip files through `eb create`/`eb deploy`. fileoperations.write_config_setting('global', 'include_git_submodules', True)
def create_platform_version( version, major_increment, minor_increment, patch_increment, instance_type, vpc = None, staged=False, timeout=None): platform_name = fileoperations.get_platform_name() instance_profile = fileoperations.get_instance_profile(None) key_name = commonops.get_default_keyname() if version is None: version = _get_latest_version(platform_name=platform_name, owner=Constants.OWNED_BY_SELF, ignored_states=[]) if version is None: version = '1.0.0' else: major, minor, patch = version.split('.', 3) if major_increment: major = str(int(major) + 1) minor = '0' patch = '0' if minor_increment: minor = str(int(minor) + 1) patch = '0' if patch_increment or not(major_increment or minor_increment): patch = str(int(patch) + 1) version = "%s.%s.%s" % (major, minor, patch) if not VALID_PLATFORM_VERSION_FORMAT.match(version): raise InvalidPlatformVersionError(strings['exit.invalidversion']) cwd = os.getcwd() fileoperations._traverse_to_project_root() try: if heuristics.directory_is_empty(): raise PlatformWorkspaceEmptyError(strings['exit.platformworkspaceempty']) finally: os.chdir(cwd) if not heuristics.has_platform_definition_file(): raise PlatformWorkspaceEmptyError(strings['exit.no_pdf_file']) source_control = SourceControl.get_source_control() if source_control.untracked_changes_exist(): io.log_warning(strings['sc.unstagedchanges']) version_label = source_control.get_version_label() if staged: # Make a unique version label timestamp = datetime.now().strftime("%y%m%d_%H%M%S") version_label = version_label + '-stage-' + timestamp file_descriptor, original_platform_yaml = tempfile.mkstemp() os.close(file_descriptor) copyfile('platform.yaml', original_platform_yaml) s3_bucket = None s3_key = None try: # Add option settings to platform.yaml _enable_healthd() s3_bucket, s3_key = get_app_version_s3_location(platform_name, version_label) # Create zip file if the application version doesn't exist if s3_bucket is None and s3_key is None: file_name, file_path = _zip_up_project(version_label, source_control, staged=staged) else: file_name = None file_path = None finally: # Restore original platform.yaml move(original_platform_yaml, 'platform.yaml') # Use existing bucket if it exists bucket = elasticbeanstalk.get_storage_location() if s3_bucket is None else s3_bucket # Use existing key if it exists key = platform_name + '/' + file_name if s3_key is None else s3_key try: s3.get_object_info(bucket, key) io.log_info('S3 Object already exists. Skipping upload.') except NotFoundError: io.log_info('Uploading archive to s3 location: ' + key) s3.upload_platform_version(bucket, key, file_path) # Just deletes the local zip fileoperations.delete_app_versions() io.log_info('Creating Platform Version ' + version_label) response = elasticbeanstalk.create_platform_version( platform_name, version, bucket, key, instance_profile, key_name, instance_type, vpc) # TODO: Enable this once the API returns the name of the environment associated with a # CreatePlatformRequest, and remove hard coded value. There is currently only one type # of platform builder, we may support additional builders in the future. #environment_name = response['PlatformSummary']['EnvironmentName'] environment_name = 'eb-custom-platform-builder-packer' io.echo(colored( strings['platformbuildercreation.info'].format(environment_name), attrs=['reverse'])) fileoperations.update_platform_version(version) commonops.set_environment_for_current_branch(environment_name) arn = response['PlatformSummary']['PlatformArn'] request_id = response['ResponseMetadata']['RequestId'] if not timeout: timeout = 30 # Share streamer for platform events and builder events streamer = io.get_event_streamer() builder_events = threading.Thread( target=logsops.stream_platform_logs, args=(platform_name, version, streamer, 5, None, PackerStreamFormatter())) builder_events.daemon = True # Watch events from builder logs builder_events.start() commonops.wait_for_success_events( request_id, platform_arn=arn, streamer=streamer, timeout_in_minutes=timeout )