ebcli/operations/createops.py (222 lines of code) (raw):
# Copyright 2015 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.
import os
import re
from zipfile import ZipFile
from cement.utils.misc import minimal_logger
from ebcli.operations import gitops, buildspecops, commonops, statusops
from ebcli.operations.tagops import tagops
from ebcli.operations.tagops.taglist import TagList
from ebcli.lib import cloudformation, elasticbeanstalk, heuristics, iam, utils
from ebcli.lib.aws import InvalidParameterValueError
from ebcli.core import io, fileoperations
from ebcli.objects.exceptions import NotAuthorizedError
from ebcli.resources.strings import strings, responses, prompts
from ebcli.resources.statics import iam_attributes
import json
LOG = minimal_logger(__name__)
DEFAULT_ROLE_NAME = 'aws-elasticbeanstalk-ec2-role'
DEFAULT_SERVICE_ROLE_NAME = 'aws-elasticbeanstalk-service-role'
DEFAULT_SERVICE_ROLE_POLICIES = [
'arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth',
'arn:aws:iam::aws:policy/AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy'
]
def make_new_env(
env_request,
branch_default=False,
process_app_version=False,
nohang=False,
interactive=True,
timeout=None,
source=None,
):
resolve_roles(env_request, interactive)
build_config = None
if fileoperations.build_spec_exists():
build_config = fileoperations.get_build_configuration()
LOG.debug("Retrieved build configuration from buildspec: {0}".format(build_config.__str__()))
codecommit_setup = gitops.git_management_enabled()
if not env_request.sample_application and not env_request.version_label:
if source is not None:
io.log_info('Creating new application version using remote source')
io.echo("Starting environment deployment via remote source")
env_request.version_label = commonops.create_app_version_from_source(
env_request.app_name, source, process=process_app_version, label=env_request.version_label,
build_config=build_config)
process_app_version = True
elif codecommit_setup:
io.log_info('Creating new application version using CodeCommit')
io.echo("Starting environment deployment via CodeCommit")
env_request.version_label = \
commonops.create_codecommit_app_version(env_request.app_name, process=process_app_version,
build_config=build_config)
process_app_version = True
else:
io.log_info('Creating new application version using project code')
env_request.version_label = \
commonops.create_app_version(env_request.app_name, process=process_app_version,
build_config=build_config)
if build_config is not None:
buildspecops.stream_build_configuration_app_version_creation(
env_request.app_name,
env_request.version_label,
build_config
)
elif process_app_version is True:
success = commonops.wait_for_processed_app_versions(
env_request.app_name,
[env_request.version_label],
timeout=timeout or 5
)
if not success:
return
if env_request.version_label is None or env_request.sample_application:
env_request.version_label = \
commonops.create_dummy_app_version(env_request.app_name)
if env_request.key_name:
commonops.upload_keypair_if_needed(env_request.key_name)
download_sample_app = None
if interactive:
download_sample_app = should_download_sample_app()
io.log_info('Creating new environment')
result, request_id = create_env(env_request,
interactive=interactive)
env_name = result.name
default_env = commonops.get_current_branch_environment()
if not default_env or branch_default:
commonops.set_environment_for_current_branch(env_name)
if codecommit_setup:
io.echo("Setting up default branch")
gitops.set_branch_default_for_current_environment(gitops.get_default_branch())
gitops.set_repo_default_for_current_environment(gitops.get_default_repository())
if download_sample_app:
download_and_extract_sample_app(env_name)
result.print_env_details(
io.echo,
elasticbeanstalk.get_environments,
elasticbeanstalk.get_environment_resources,
health=False
)
statusops.alert_environment_status(result)
if nohang:
return
io.echo('Printing Status:')
commonops.wait_for_success_events(request_id,
timeout_in_minutes=timeout)
def should_download_sample_app():
"""
Method determines whether the present directory is empty. If yes, it allows the user
to choose to download the sample application that the environment will be launched
with.
:return: User's choice of whether the sample application should be downloaded
"""
if heuristics.directory_is_empty():
return io.get_boolean_response(
text=strings['create.sample_application_download_option'],
default=True)
return False
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 download_application_version(url, zip_file_location):
"""
Method downloads the application version from the URL, 'url', and
writes them at the location specified by `zip_file_location`
:param url: the URL of the application version.
:param zip_file_location: path on the user's system to write the application version ZIP file to.
:return: None
"""
data = utils.get_data_from_url(url, timeout=30)
fileoperations.write_to_data_file(zip_file_location, data)
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 create_env(env_request, interactive=True):
if env_request.template_name:
platform = env_request.platform
env_request.platform = None
else:
platform = None
while True:
try:
return elasticbeanstalk.create_environment(env_request)
except InvalidParameterValueError as e:
if e.message == responses['app.notexists'].replace(
'{app-name}', '\'' + env_request.app_name + '\''):
commonops.create_app(env_request.app_name)
elif e.message == responses['create.noplatform']:
if platform:
env_request.platform = platform
else:
raise
elif interactive:
LOG.debug('creating env returned error: ' + e.message)
if re.match(responses['env.cnamenotavailable'], e.message):
io.echo(prompts['cname.unavailable'])
io.prompt_for_cname()
elif re.match(responses['env.nameexists'], e.message):
io.echo(strings['env.exists'])
current_environments = elasticbeanstalk.get_all_environment_names()
unique_name = utils.get_unique_name(env_request.env_name,
current_environments)
env_request.env_name = io.prompt_for_environment_name(
default_name=unique_name)
elif e.message == responses['app.notexists'].replace(
'{app-name}', '\'' + env_request.app_name + '\''):
commonops.create_app(env_request.app_name)
else:
raise
else:
raise
def get_service_role():
try:
roles = iam.get_role_names()
if DEFAULT_SERVICE_ROLE_NAME not in roles:
return None
except NotAuthorizedError:
pass
return DEFAULT_SERVICE_ROLE_NAME
def create_default_service_role():
"""
Create the default service role
"""
io.log_info('Creating service role {} with default permissions.'
.format(DEFAULT_SERVICE_ROLE_NAME))
trust_document = _get_default_service_trust_document()
role_name = DEFAULT_SERVICE_ROLE_NAME
try:
iam.create_role_with_policy(role_name, trust_document,
DEFAULT_SERVICE_ROLE_POLICIES)
except NotAuthorizedError as e:
raise NotAuthorizedError(prompts['create.servicerole.nopermissions']
.format(DEFAULT_SERVICE_ROLE_NAME, e))
return DEFAULT_SERVICE_ROLE_NAME
def resolve_roles(env_request, interactive):
"""
Resolves instance-profile and service-role
:param env_request: environment request
:param interactive: boolean
"""
LOG.debug('Resolving roles')
if (
not env_request.instance_profile or
env_request.instance_profile == iam_attributes.DEFAULT_ROLE_NAME
) and not env_request.template_name:
env_request.instance_profile = commonops.create_default_instance_profile()
if (
env_request.platform and
env_request.platform.has_healthd_support and
not env_request.service_role and
not env_request.template_name
):
role = get_service_role()
if role is None:
if interactive:
io.echo()
io.echo(prompts['create.servicerole.info'])
input = io.get_input(prompts['create.servicerole.view'],
default='')
if input.strip('"').lower() == 'view':
for policy_arn in DEFAULT_SERVICE_ROLE_POLICIES:
document = iam.get_managed_policy_document(policy_arn)
io.echo(json.dumps(document, indent=4))
io.get_input(prompts['general.pressenter'])
role = create_default_service_role()
env_request.service_role = role
def _get_default_service_trust_document():
"""
Just a string representing the service role policy.
Includes newlines for pretty printing :)
"""
return """{
"Version": "2012-10-17",
"Statement": [{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "elasticbeanstalk.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "elasticbeanstalk"
}
}
}]
}"""