Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 62 additions & 11 deletions ebcli/controllers/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@
# 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
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, utils
from ebcli.objects.exceptions import InvalidOptionsError
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, alerts
from ebcli.resources.statics import platform_branch_lifecycle_states
from ebcli.resources.strings import strings, flag_text

LOG = minimal_logger(__name__)

Expand All @@ -47,24 +49,45 @@ class Meta(AbstractBaseController.Meta):
(['--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.timeout = self.app.pargs.timeout
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.app_name = self.get_app_name()
self.env_name = self.get_env_name()
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
Expand All @@ -76,9 +99,15 @@ def do_command(self):

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)
staged=self.staged, timeout=self.timeout, source=self.source,
source_bundle=source_bundle_zip)

def multiple_app_deploy(self):
missing_env_yaml = []
Expand Down Expand Up @@ -181,3 +210,25 @@ def compose_deploy(self):
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
9 changes: 5 additions & 4 deletions ebcli/lib/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ def delete_objects(bucket, keys):
return result


def upload_workspace_version(bucket, key, file_path, workspace_type='Application'):
def upload_workspace_version(bucket, key, file_path, workspace_type='Application', relative_to_project_root=True):
cwd = os.getcwd()
try:
fileoperations.ProjectRoot.traverse()
if relative_to_project_root:
fileoperations.ProjectRoot.traverse()
size = os.path.getsize(file_path)
except OSError as err:
if err.errno == 2:
Expand All @@ -144,8 +145,8 @@ def upload_workspace_version(bucket, key, file_path, workspace_type='Application
return result


def upload_application_version(bucket, key, file_path):
upload_workspace_version(bucket, key, file_path, 'Application')
def upload_application_version(bucket, key, file_path, relative_to_project_root=True):
upload_workspace_version(bucket, key, file_path, 'Application', relative_to_project_root=relative_to_project_root)


def upload_platform_version(bucket, key, file_path):
Expand Down
66 changes: 53 additions & 13 deletions ebcli/operations/commonops.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import time
from datetime import datetime, timedelta
import platform
import zipfile

from ebcli.core.fileoperations import _marker

Expand Down Expand Up @@ -486,8 +487,9 @@ def create_dummy_app_version(app_name):
None, None, warning=False)


def create_app_version(app_name, process=False, label=None, message=None, staged=False, build_config=None):
def create_app_version(app_name, process=False, label=None, message=None, staged=False, build_config=None, source_bundle=None):
cwd = os.getcwd()

fileoperations.ProjectRoot.traverse()
try:
if heuristics.directory_is_empty():
Expand All @@ -514,7 +516,6 @@ def create_app_version(app_name, process=False, label=None, message=None, staged

if len(description) > 200:
description = description[:195] + '...'

artifact = fileoperations.get_config_setting('deploy', 'artifact')
if artifact:
file_name, file_extension = os.path.splitext(artifact)
Expand All @@ -525,16 +526,41 @@ def create_app_version(app_name, process=False, label=None, message=None, staged
else:
s3_bucket, s3_key = get_app_version_s3_location(app_name, version_label)

file_name, file_path = None, None
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
if not source_bundle:
file_name, file_path = _zip_up_project(
version_label, source_control, staged=staged)
elif zipfile.is_zipfile(source_bundle):
file_name, file_path = label, source_bundle

return handle_upload_target(app_name,
s3_bucket,
s3_key,
file_name,
file_path,
version_label,
description,
process,
build_config,
)


def handle_upload_target(
app_name,
s3_bucket,
s3_key,
file_name,
file_path,
version_label,
description,
process,
build_config,
relative_to_project_root=True
):
bucket = elasticbeanstalk.get_storage_location() if s3_bucket is None else s3_bucket
key = app_name + '/' + file_name if s3_key is None else s3_key

key = app_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.')
Expand All @@ -544,12 +570,17 @@ def create_app_version(app_name, process=False, label=None, message=None, staged
' Try uploading the Application Version again.')

io.log_info('Uploading archive to s3 location: ' + key)
s3.upload_application_version(bucket, key, file_path)
if relative_to_project_root:
s3.upload_application_version(bucket, key, file_path)
else:
s3.upload_application_version(bucket, key, file_path, relative_to_project_root=False)

fileoperations.delete_app_versions()
if not relative_to_project_root:
fileoperations.delete_app_versions()
io.log_info('Creating AppVersion ' + version_label)
return _create_application_version(app_name, version_label, description,
bucket, key, process, build_config=build_config)
bucket, key, process, build_config=build_config,
relative_to_project_root=relative_to_project_root)


def create_codecommit_app_version(app_name, process=False, label=None, message=None, build_config=None):
Expand Down Expand Up @@ -672,15 +703,15 @@ def create_app_version_from_source(
def _create_application_version(app_name, version_label, description,
bucket, key, process=False, warning=True,
repository=None, commit_id=None,
build_config=None):
build_config=None, relative_to_project_root=True):
"""
A wrapper around elasticbeanstalk.create_application_version that
handles certain error cases:
* application doesnt exist
* version already exists
* validates BuildSpec files for CodeBuild
"""
if build_config is not None:
if relative_to_project_root and build_config is not None:
buildspecops.validate_build_config(build_config)
while True:
try:
Expand Down Expand Up @@ -725,6 +756,15 @@ def _zip_up_project(version_label, source_control, staged=False):
return file_name, file_path


def _zip_up_project_at_location(version_label, upload_target_dir, zip_output_path):
file_name = version_label + '.zip'
fileoperations.zip_up_folder(
upload_target_dir,
zip_output_path,
)
return file_name, zip_output_path


def update_environment(env_name, changes, nohang, remove=None,
template=None, timeout=None, template_body=None,
solution_stack_name=None, platform_arn=None):
Expand Down
27 changes: 21 additions & 6 deletions ebcli/operations/deployops.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,28 @@


def deploy(app_name, env_name, version, label, message, group_name=None,
process_app_versions=False, staged=False, timeout=5, source=None):
process_app_versions=False, staged=False, timeout=5, source=None,
source_bundle=None):
region_name = aws.get_region_name()

build_config = None
if fileoperations.build_spec_exists() and version is None:
build_config = fileoperations.get_build_configuration()
LOG.debug("Retrieved build configuration from buildspec: {0}".format(build_config.__str__()))
if source_bundle:
file_name, file_path = label, source_bundle
version = commonops.handle_upload_target(
app_name,
None,
None,
file_name,
file_path,
label,
message,
process_app_versions,
build_config,
relative_to_project_root=False
)
else:
if fileoperations.build_spec_exists() and version is None:
build_config = fileoperations.get_build_configuration()
LOG.debug("Retrieved build configuration from buildspec: {0}".format(build_config.__str__()))

io.log_info('Deploying code to ' + env_name + " in region " + region_name)

Expand Down Expand Up @@ -59,7 +74,7 @@ def deploy(app_name, env_name, version, label, message, group_name=None,
build_config=build_config
)

if build_config is not None:
if not source_bundle and build_config is not None:
buildspecops.stream_build_configuration_app_version_creation(
app_name, app_version_label, build_config)
elif process_app_versions is True:
Expand Down
14 changes: 12 additions & 2 deletions ebcli/resources/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,7 @@
'Tags may only contain letters, numbers, and the following '
'symbols: / _ . : + = - @',
'tags.max': 'Elastic Beanstalk supports a maximum of 50 tags.',
'deploy.invalidoptions': 'You cannot use the "--version" option with either the "--message" '
'or "--label" option.',

'init.getvarsfromoldeb': 'You previous used an earlier version of eb. Getting options from '
'.elasticbeanstalk/config.\n'
'Credentials will now be stored in ~/.aws/config',
Expand Down Expand Up @@ -324,12 +323,22 @@
'region.china.credentials':
'To use the China (Beijing) region, account credentials unique to the '
'China (Beijing) region must be used.',

'deploy.notadirectory': 'The directory {module} does not exist.',
'deploy.modulemissingenvyaml':
'All specified modules require an env.yaml file.\n'
'The following modules are missing this file: {modules}',
'deploy.noenvname':
'No environment name was specified in env.yaml for module {module}. Unable to deploy.',
'deploy.invalidoptions': 'You cannot use the "--version" option with either the "--message" '
'or "--label" option.',
'deploy.archivewithoutregion': 'You cannot use the "--archive" option without the "--region" option.',
'deploy.archivewithoutenvname': 'You cannot use the "--archive" option without the environment name.',
'deploy.archivewithversion': 'You cannot use the "--archive" option with the "--version" option for environment updates. '
'These are mutually exclusive methods for specifying application code.',
'deploy.archivewithsource': 'You cannot use the "--archive" option with the "--source" option for environment updates. '
'These are mutually exclusive methods for specifying application code.',
'deploy.archive_must_be_dir_or_zip': 'The "--archive" option requires a directory or ZIP file as an argument.',
'compose.noenvyaml':
'The module {module} does not contain an env.yaml file. This module will be skipped.',
'compose.novalidmodules': 'No valid modules were found. No environments will be created.',
Expand Down Expand Up @@ -814,6 +823,7 @@
'deploy.group_suffix': 'group suffix',
'deploy.source': 'source of code to deploy directly; example source_location/repo/branch',
'deploy.process': 'enable preprocessing of the application version',
'deploy.archive': 'directory or ZIP file containing application version source code',

'platformevents.version': 'version to retrieve events for',
'events.follow': 'wait and continue to print events as they come',
Expand Down
Loading
Loading