From 0ef1a78e1fb98ff64ed6b59631d44bff9f83b2d2 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Mon, 14 Feb 2022 20:16:03 -0800 Subject: [PATCH 01/44] Update to display full results when --version flag is specified --- ebcli/core/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebcli/core/base.py b/ebcli/core/base.py index c8927f2b1..29238128c 100644 --- a/ebcli/core/base.py +++ b/ebcli/core/base.py @@ -39,7 +39,7 @@ class Meta: def default(self): if self.app.pargs.version: io.echo(strings['app.version_message'], __version__, - '(Python', sys.version[0:5] + ')') + '(Python', sys.version + ')') else: self.app.args.print_help() From 53a28879c70688cbb800030d8a382576c3de1982 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Mon, 14 Feb 2022 20:16:34 -0800 Subject: [PATCH 02/44] Update eb create command to create application elb as default --- ebcli/controllers/create.py | 2 +- tests/unit/controllers/test_create.py | 42 ++++++++++++++++++++------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/ebcli/controllers/create.py b/ebcli/controllers/create.py index f772caf8e..a37ee90e4 100644 --- a/ebcli/controllers/create.py +++ b/ebcli/controllers/create.py @@ -546,7 +546,7 @@ def get_elb_type_from_customer(interactive, single, tier): if single or (tier and not tier.is_webserver()): return elif not interactive: - return + return elb_names.APPLICATION_VERSION io.echo() io.echo('Select a load balancer type') diff --git a/tests/unit/controllers/test_create.py b/tests/unit/controllers/test_create.py index 8b4566a91..fd5b05e5d 100644 --- a/tests/unit/controllers/test_create.py +++ b/tests/unit/controllers/test_create.py @@ -1073,10 +1073,12 @@ def test_create_non_interactively__with_process_flag( self.app.setup() self.app.run() + elb_type= 'application' expected_environment_request = CreateEnvironmentRequest( app_name=self.app_name, env_name=self.env_name, platform=self.solution, + elb_type=elb_type, ) call_args, kwargs = make_new_env_mock.call_args actual_environment_request = call_args[0] @@ -1105,6 +1107,7 @@ def test_create_non_interactive__min_max_instances__valid( _determine_platform_mock.return_value = self.solution is_cname_available_mock.return_value = True get_default_keyname_mock.return_value = None + elb_type = 'application' env_name = 'my-awesome-env' min_instances = '5' max_instances = '15' @@ -1118,7 +1121,8 @@ def test_create_non_interactive__min_max_instances__valid( env_name=env_name, platform=self.solution, max_instances=max_instances, - min_instances=min_instances + min_instances=min_instances, + elb_type=elb_type, ) call_args, kwargs = make_new_env_mock.call_args actual_environment_request = call_args[0] @@ -1147,6 +1151,7 @@ def test_create_non_interactive__min_max_instances_with_spot( _determine_platform_mock.return_value = self.solution is_cname_available_mock.return_value = True get_default_keyname_mock.return_value = None + elb_type = 'application' env_name = 'my-awesome-env' min_instances = '15' max_instances = '5' @@ -1168,7 +1173,8 @@ def test_create_non_interactive__min_max_instances_with_spot( enable_spot=True, on_demand_base_capacity=on_demand_base_capacity, max_instances=max_instances, - min_instances=min_instances + min_instances=min_instances, + elb_type=elb_type, ) call_args, kwargs = make_new_env_mock.call_args actual_environment_request = call_args[0] @@ -1197,6 +1203,7 @@ def test_create_non_interactive__spot_request( _determine_platform_mock.return_value = self.solution is_cname_available_mock.return_value = True get_default_keyname_mock.return_value = None + elb_type = 'application' env_name = 'my-awesome-env' instance_types='t2.micro, t3.micro' @@ -1209,7 +1216,8 @@ def test_create_non_interactive__spot_request( env_name=env_name, platform=self.solution, enable_spot=True, - instance_types=instance_types + instance_types=instance_types, + elb_type=elb_type, ) call_args, kwargs = make_new_env_mock.call_args actual_environment_request = call_args[0] @@ -1238,6 +1246,7 @@ def test_create_non_interactive__instance_types_only( _determine_platform_mock.return_value = self.solution is_cname_available_mock.return_value = True get_default_keyname_mock.return_value = None + elb_type= 'application' env_name = 'my-awesome-env' instance_types="t2.micro, t3.micro" @@ -1250,7 +1259,8 @@ def test_create_non_interactive__instance_types_only( env_name=env_name, platform=self.solution, enable_spot=None, - instance_types=instance_types + instance_types=instance_types, + elb_type=elb_type ) call_args, kwargs = make_new_env_mock.call_args actual_environment_request = call_args[0] @@ -1279,6 +1289,7 @@ def test_create_non_interactive__all_spot_options__shorthand( _determine_platform_mock.return_value = self.solution is_cname_available_mock.return_value = True get_default_keyname_mock.return_value = None + elb_type='application' env_name = 'my-awesome-env' instance_types="t2.micro, t3.micro" spot_max_price = ".05" @@ -1303,7 +1314,8 @@ def test_create_non_interactive__all_spot_options__shorthand( instance_types=instance_types, spot_max_price=spot_max_price, on_demand_base_capacity=spot_on_demand_base, - on_demand_above_base_capacity=spot_on_demand_above_base + on_demand_above_base_capacity=spot_on_demand_above_base, + elb_type=elb_type, ) call_args, kwargs = make_new_env_mock.call_args actual_environment_request = call_args[0] @@ -1585,16 +1597,18 @@ def test_create_non_interactively__with_forward_slash_in_branch( argv=[ 'create', self.env_name, - '--source', 'codecommit/my-repository/my-branch/feature' + '--source', 'codecommit/my-repository/my-branch/feature', ] ) self.app.setup() self.app.run() + elb_type = 'application' expected_environment_request = CreateEnvironmentRequest( app_name=self.app_name, env_name=self.env_name, platform=self.solution, + elb_type=elb_type, ) call_args, kwargs = make_new_env_mock.call_args actual_environment_request = call_args[0] @@ -1606,7 +1620,7 @@ def test_create_non_interactively__with_forward_slash_in_branch( nohang=False, interactive=False, timeout=None, - source='codecommit/my-repository/my-branch/feature' + source='codecommit/my-repository/my-branch/feature', ) @@ -2099,11 +2113,13 @@ def test_create_interactively_with_custom_vpc__vpc_argument_triggers_interactive is_cname_available_mock.return_value = True get_default_keyname_mock.return_value = None + elb_type = 'application' env_name = 'my-awesome-env' expected_environment_request = CreateEnvironmentRequest( app_name=self.app_name, env_name=env_name, + elb_type=elb_type, platform=self.solution, vpc={ 'id': 'my-vpc-id', @@ -2257,6 +2273,7 @@ def test_create_non_interactively_with_database_and_vpc_arguments( is_cname_available_mock.return_value = True get_default_keyname_mock.return_value = None + elb_type = 'application' env_name = 'my-awesome-env' self.app = EB( @@ -2285,6 +2302,7 @@ def test_create_non_interactively_with_database_and_vpc_arguments( expected_environment_request = CreateEnvironmentRequest( app_name=self.app_name, env_name=env_name, + elb_type=elb_type, platform=self.solution, database={ 'username': 'root', @@ -2365,12 +2383,13 @@ def test_get_elb_type_from_customer__single_instance_environment(self): ) def test_get_elb_type_from_customer__non_interactive_mode(self): - self.assertIsNone( + self.assertEqual( create.get_elb_type_from_customer( interactive=False, single=False, tier=None - ) + ), + 'application' ) def test_get_elb_type_from_customer__non_interactive_mode_single(self): @@ -2383,12 +2402,13 @@ def test_get_elb_type_from_customer__non_interactive_mode_single(self): ) def test_get_elb_type_from_customer__non_interactive_mode_webserver(self): - self.assertIsNone( + self.assertEqual( create.get_elb_type_from_customer( interactive=False, single=False, tier=Tier.from_raw_string('webserver') - ) + ), + 'application' ) def test_get_elb_type_from_customer__non_interactive_mode_worker(self): From 6931cc538670bdd4e7a6f1a38297436669f680fe Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Tue, 22 Feb 2022 10:48:57 -0800 Subject: [PATCH 03/44] Resolve Notfound Error for Docker AL2 platform --- ebcli/objects/solutionstack.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ebcli/objects/solutionstack.py b/ebcli/objects/solutionstack.py index 8b7c94459..205b18858 100644 --- a/ebcli/objects/solutionstack.py +++ b/ebcli/objects/solutionstack.py @@ -152,6 +152,9 @@ def language_name(self): if 'Multi-container Docker' in self.name: return 'Multi-container Docker' + if '64bit Amazon Linux 2 ' in self.name and 'running Docker' in self.name: + return 'Docker running on 64bit Amazon Linux 2' + shorthand = self.platform_shorthand.split(' ')[0] if '(BETA)' in self.name: From 76969d528edd0e26497aae8b4d1e2002b162443b Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Mon, 2 May 2022 15:23:12 -0700 Subject: [PATCH 04/44] Update supported codecommit regions cr:https://code.amazon.com/reviews/CR-68895340 --- ebcli/lib/codecommit.py | 7 ++++--- tests/unit/lib/test_codecommit.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ebcli/lib/codecommit.py b/ebcli/lib/codecommit.py index bf9ff1b5b..bd54abd74 100644 --- a/ebcli/lib/codecommit.py +++ b/ebcli/lib/codecommit.py @@ -20,9 +20,11 @@ LOG = minimal_logger(__name__) SUPPORTED_REGIONS = [ + "af-south-1", # Africa (Cape Town) "ap-east-1", # Asia Pacific (Hong Kong) "ap-northeast-1", # Asia Pacific (Tokyo) "ap-northeast-2", # Asia Pacific (Seoul) + "ap-northeast-3", # Asia Pacific (Osaka-Local) "ap-south-1", # Asia Pacific (Mumbai) "ap-southeast-1", # Asia Pacific (Singapore) "ap-southeast-2", # Asia Pacific (Sydney) @@ -31,6 +33,7 @@ "cn-northwest-1" # China (Ningxia) "eu-central-1", # Europe (Frankfurt) "eu-north-1", # Europe (Stockholm) + "eu-south-1", # Europe (Milan) "eu-west-1", # Europe (Ireland) "eu-west-2", # Europe (London) "eu-west-3", # Europe (Paris) @@ -45,9 +48,7 @@ ] UNSUPPORTED_REGIONS = [ - "af-south-1", # Africa (Cape Town) - "ap-northeast-3", # Asia Pacific (Osaka-Local) - "eu-south-1", # Europe (Milan) + "ap-southeast-3", # Asia Pacific (Jakarta) ] diff --git a/tests/unit/lib/test_codecommit.py b/tests/unit/lib/test_codecommit.py index 1ac4a9e22..88df812c9 100644 --- a/tests/unit/lib/test_codecommit.py +++ b/tests/unit/lib/test_codecommit.py @@ -39,7 +39,7 @@ def test_region_supported__unsupported_region( list_repositories_mock, get_region_name_mock, ): - get_region_name_mock.return_value = 'af-south-1' + get_region_name_mock.return_value = 'ap-southeast-3' region_supported = codecommit.region_supported() self.assertFalse(region_supported) From 98fea284eea7b652249a648e69a6ace108815a07 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Tue, 12 Jul 2022 16:18:37 -0700 Subject: [PATCH 05/44] Fix a unit test cr: https://code.amazon.com/reviews/CR-72500602 --- tests/unit/operations/test_commonops.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/operations/test_commonops.py b/tests/unit/operations/test_commonops.py index 3107c516e..a4a44f611 100644 --- a/tests/unit/operations/test_commonops.py +++ b/tests/unit/operations/test_commonops.py @@ -2687,8 +2687,10 @@ def test_set_up_credentials__eb_cli_is_used_as_default_profile( @mock.patch('ebcli.operations.commonops.credentials_are_valid') @mock.patch('ebcli.operations.commonops.setup_credentials') @mock.patch('ebcli.controllers.initialize.fileoperations.write_config_setting') + @mock.patch('os.environ.get') def test_set_up_credentials__eb_cli_is_used_as_default_profile( self, + get_mock, write_config_setting_mock, setup_credentials_mock, credentials_are_valid_mock, @@ -2697,6 +2699,7 @@ def test_set_up_credentials__eb_cli_is_used_as_default_profile( ): check_credentials_mock.return_value = ['eb-cli', 'us-east-1'] credentials_are_valid_mock.return_value = True + get_mock.return_value = None commonops.set_up_credentials( None, From 029ec383c78252fedb680085da5140122c8a3a10 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Wed, 6 Jul 2022 15:30:46 -0700 Subject: [PATCH 06/44] Fix zipping permission bug cr: https://code.amazon.com/reviews/CR-72989492 --- ebcli/core/fileoperations.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ebcli/core/fileoperations.py b/ebcli/core/fileoperations.py index 04f07155d..9e5a2b032 100644 --- a/ebcli/core/fileoperations.py +++ b/ebcli/core/fileoperations.py @@ -15,15 +15,17 @@ import glob import json import os +import pathlib import shutil import stat import sys -import zipfile -import yaml import warnings +import yaml +import zipfile from pathspec import PathSpec from cement.utils.misc import minimal_logger +from ebcli.core import fileoperations from ebcli.objects.buildconfiguration import BuildConfiguration from six import StringIO from yaml import safe_load, safe_dump @@ -389,14 +391,19 @@ def delete_app_versions(): def zip_append_archive(target_file, source_file): zip_source = zipfile.ZipFile(source_file, 'r', allowZip64=True) zip_target = zipfile.ZipFile(target_file, 'a', allowZip64=True) + # Because zipfile doesn't preserve file permission code, we have to extract and then zip up using shutil + tmp_folder_name = 'tmpAppendFolder' with warnings.catch_warnings(): # Ignore UserWarning raised by zip module for zipping modules. warnings.simplefilter('ignore', category=UserWarning) - for filename in zip_source.namelist(): - zf = zip_source.read(filename) - zip_target.writestr(filename, zf) + zip_source.extractall(tmp_folder_name) + zip_target.extractall(tmp_folder_name) zip_target.close() zip_source.close() + delete_file(target_file) + path_without_suffix = pathlib.Path(target_file).with_suffix('') + shutil.make_archive(path_without_suffix, 'zip', tmp_folder_name) + delete_directory(tmp_folder_name) def zip_up_folder(directory, location, ignore_list=None): From 292287d36fe97ca4da8db264976545f48e9a4cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CYohanth?= Date: Wed, 17 Aug 2022 11:50:49 -0700 Subject: [PATCH 07/44] Updated previous commit with CR comments cr: https://code.amazon.com/reviews/CR-74441779 --- ebcli/core/fileoperations.py | 15 ++++++++++++--- requirements.txt | 2 +- tests/unit/core/test_fileoperations.py | 20 +++++++++++++++----- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ebcli/core/fileoperations.py b/ebcli/core/fileoperations.py index 9e5a2b032..0c3c54e62 100644 --- a/ebcli/core/fileoperations.py +++ b/ebcli/core/fileoperations.py @@ -112,6 +112,11 @@ def _get_option(config, section, key, default): def is_git_directory_present(): return os.path.isdir('.git') +def is_parent_directory_in_ignore_list(filepath, ignore_list): + for directory in ignore_list: + if os.path.abspath(filepath).startswith(os.path.abspath(directory)): + return True + return False def clean_up(): cwd = os.getcwd() @@ -430,7 +435,6 @@ def zip_up_project(location, ignore_list=None): finally: os.chdir(cwd) - def _zipdir(path, zipf, ignore_list=None): if ignore_list is None: ignore_list = {'.gitignore'} @@ -454,7 +458,10 @@ def _zipdir(path, zipf, ignore_list=None): zipInfo.external_attr = 2716663808 else: zipInfo.external_attr = long(2716663808) - zipf.writestr(zipInfo, os.readlink(cur_dir)) + if not is_parent_directory_in_ignore_list(cur_dir, ignore_list): + zipf.writestr(zipInfo, os.readlink(cur_dir)) + else: + io.log_info(' -skipping: {}'.format(cur_dir)) for f in files: cur_file = os.path.join(root, f) @@ -462,6 +469,7 @@ def _zipdir(path, zipf, ignore_list=None): cur_file.endswith('~') or cur_file in ignore_list or not _validate_file_for_archive(cur_file) + or is_parent_directory_in_ignore_list(cur_file, ignore_list) ): # Ignore editor backup files (like file.txt~) # Ignore anything in the .ebignore file @@ -819,7 +827,8 @@ def get_ebignore_list(): with codecs.open(location, 'r', encoding='utf-8') as f: spec = PathSpec.from_lines('gitwildmatch', f) - ignore_list = {f for f in spec.match_tree(get_project_root())} + matches = [f for f in spec.match_tree_entries(get_project_root())] + ignore_list = {match.path for match in matches} ignore_list.add('.ebignore') return ignore_list diff --git a/requirements.txt b/requirements.txt index 0b08fd72b..cdcd47e9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ botocore>1.23.41,<1.27.29 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses future>=0.16.0,<0.17.0 -pathspec==0.9.0 +pathspec==0.10.1 python-dateutil>=2.1,<3.0.0 # use the same range that 'botocore' uses requests>=2.20.1,<=2.26 setuptools >= 20.0 diff --git a/tests/unit/core/test_fileoperations.py b/tests/unit/core/test_fileoperations.py index acf3edf29..b15bc9693 100644 --- a/tests/unit/core/test_fileoperations.py +++ b/tests/unit/core/test_fileoperations.py @@ -717,24 +717,33 @@ def test_delete_app_file(self): def test_zip_up_project(self, _validate_file_for_archive_mock): _validate_file_for_archive_mock.side_effect = lambda f: not f.endswith('.sock') shutil.rmtree('home', ignore_errors=True) + os.mkdir('ignore-me') + os.mkdir('linkme') os.mkdir('src') os.mkdir(os.path.join('src', 'lib')) open(os.path.join('src', 'lib', 'app.py'), 'w').write('import os') open(os.path.join('src', 'lib', 'app.py~'), 'w').write('import os') open(os.path.join('src', 'lib', 'ignore-this-file.py'), 'w').write('import os') open(os.path.join('src', 'lib', 'test.sock'), 'w').write('mock socket file') + open(os.path.join('ignore-me', 'text.txt'), 'w').write('import os') + open(os.path.join('linkme', 'foobar.txt'), 'w').write('import os linkme') os.symlink( - os.path.join('src', 'lib', 'app.py'), - os.path.join('src', 'lib', 'app.py-copy') + os.path.abspath(os.path.join('src', 'lib', 'app.py')), + os.path.abspath(os.path.join('src', 'lib', 'app.py-copy')) ) os.mkdir(os.path.join('src', 'lib', 'api')) if sys.version_info > (3, 0): os.symlink( - os.path.join('src', 'lib', 'api'), - os.path.join('src', 'lib', 'api-copy'), + os.path.abspath(os.path.join('src', 'lib', 'api')), + os.path.abspath(os.path.join('src', 'lib', 'api-copy')), + target_is_directory=True + ) + os.symlink( + os.path.abspath(os.path.join('linkme')), + os.path.abspath(os.path.join('ignore-me','linkme')), target_is_directory=True ) else: @@ -747,7 +756,7 @@ def test_zip_up_project(self, _validate_file_for_archive_mock): fileoperations.zip_up_project( 'app.zip', - ignore_list=[os.path.join('src', 'lib', 'ignore-this-file.py')] + ignore_list=[os.path.join('src', 'lib', 'ignore-this-file.py'), os.path.join('ignore-me/')] ) os.mkdir('tmp') @@ -760,6 +769,7 @@ def test_zip_up_project(self, _validate_file_for_archive_mock): self.assertFalse(os.path.exists(os.path.join('tmp', 'src', 'lib', 'app.py~'))) self.assertFalse(os.path.exists(os.path.join('tmp', 'src', 'lib', 'ignore-this-file.py'))) self.assertFalse(os.path.exists(os.path.join('tmp', 'src', 'lib', 'test.sock'))) + self.assertFalse(os.path.exists(os.path.join('tmp','ignore-me', 'link-me'))) def test_delete_app_versions(self): os.mkdir(os.path.join('.elasticbeanstalk', 'app_versions')) From 5d491c49151ab2a4b0d52bde3beedb76eb347971 Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Mon, 19 Sep 2022 19:08:43 -0700 Subject: [PATCH 08/44] Fixed a Unit Test Bug cr: https://code.amazon.com/reviews/CR-76267850 --- tests/unit/operations/test_ebignore.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit/operations/test_ebignore.py b/tests/unit/operations/test_ebignore.py index bfa400791..dd299a3a5 100644 --- a/tests/unit/operations/test_ebignore.py +++ b/tests/unit/operations/test_ebignore.py @@ -140,6 +140,7 @@ def test_get_ebignore_list__includes_directories_in_the_ebignore_list( self.assertEqual( { 'directory_1{}.gitkeep'.format(os.path.sep), + 'directory_1', 'directory_2{}.gitkeep'.format(os.path.sep), '.ebignore' }, @@ -206,7 +207,7 @@ def test_get_ebignore_list__includes_filenames_with_spaces_in_ignore_list( open('file 1', 'w').close() with open('.ebignore', 'w') as file: - ebignore_file_contents = """ + ebignore_file_contents = r""" file\ 1 """ @@ -234,7 +235,7 @@ def test_get_ebignore_list__includes_filenames_with_exclamations_in_ignore_list( open('!file_1', 'w').close() with open('.ebignore', 'w') as file: - ebignore_file_contents = """ + ebignore_file_contents = r""" \!file_1 """ @@ -265,7 +266,7 @@ def test_get_ebignore_list__includes_files_with_unicode_names( open('ändrar något i databasen', 'w').close() with open('.ebignore', 'wb') as file: - ebignore_file_contents = """ + ebignore_file_contents = r""" 哈哈 昨夜のコンサートは最高でした。 ändrar\ något\ i\ databasen From 1e3e3fbb4347b71b2257bb2f7412bed3de629b2b Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Mon, 26 Sep 2022 13:37:01 -0700 Subject: [PATCH 09/44] Add new region (CGK) cr: https://code.amazon.com/reviews/CR-76664427 --- ebcli/objects/region.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ebcli/objects/region.py b/ebcli/objects/region.py index b8b272e9a..6213c1675 100644 --- a/ebcli/objects/region.py +++ b/ebcli/objects/region.py @@ -36,6 +36,7 @@ def get_all_regions(): Region('ap-east-1', 'Asia Pacific (Hong Kong)'), Region('me-south-1', 'Middle East (Bahrain)'), Region('af-south-1', 'Africa (Cape Town)'), + Region('ap-southeast-3', 'Asia Pacific (Jakarta)'), ] From 75aca159ff72b0630d9a73e57f5ee1bc0d71489f Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Sun, 27 Nov 2022 15:21:54 -0800 Subject: [PATCH 10/44] Add new Region (KIX) SIM: https://i.amazon.com/V768093424 cr: https://code.amazon.com/reviews/CR-80479370 --- ebcli/objects/region.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ebcli/objects/region.py b/ebcli/objects/region.py index 6213c1675..b6473abdb 100644 --- a/ebcli/objects/region.py +++ b/ebcli/objects/region.py @@ -37,6 +37,7 @@ def get_all_regions(): Region('me-south-1', 'Middle East (Bahrain)'), Region('af-south-1', 'Africa (Cape Town)'), Region('ap-southeast-3', 'Asia Pacific (Jakarta)'), + Region('ap-northeast-3', 'Asia Pacific (Osaka)'), ] From e46768ed0df71c454f03e4b497ef470321c2d082 Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Mon, 9 Jan 2023 08:59:57 -0800 Subject: [PATCH 11/44] Modules option bugfix SIM: https://i.amazon.com/D67161036 cr: https://code.amazon.com/reviews/CR-83059775 --- ebcli/controllers/initialize.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ebcli/controllers/initialize.py b/ebcli/controllers/initialize.py index dd4068e32..38ed0afa8 100644 --- a/ebcli/controllers/initialize.py +++ b/ebcli/controllers/initialize.py @@ -83,6 +83,7 @@ def do_command(self): if modules and len(modules) > 0: self.initialize_multiple_directories( modules, + app_name, region_name, interactive, force_non_interactive, @@ -125,6 +126,7 @@ def do_command(self): def initialize_multiple_directories( self, modules, + app_name, region, interactive, force_non_interactive, @@ -134,7 +136,6 @@ def initialize_multiple_directories( platform ): application_created = False - app_name = None cwd = os.getcwd() for module in modules: if os.path.exists(module) and os.path.isdir(module): From 13b87c70a10d10db55deec7a0ce49a9642a68221 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Tue, 17 Jan 2023 17:39:37 -0800 Subject: [PATCH 12/44] Merge with squash from github bugfix branch --- requirements.txt | 2 +- tests/unit/core/test_abstractcontroller.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cdcd47e9c..5e29d1569 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore>1.23.41,<1.27.29 +botocore>1.23.41,<1.29.52 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses future>=0.16.0,<0.17.0 diff --git a/tests/unit/core/test_abstractcontroller.py b/tests/unit/core/test_abstractcontroller.py index d2e2f666b..b56c77dea 100644 --- a/tests/unit/core/test_abstractcontroller.py +++ b/tests/unit/core/test_abstractcontroller.py @@ -90,7 +90,7 @@ def test_check_for_cli_update__checks_pip_update_exists( self.test_target.check_for_cli_update(input_version) cli_update_exists_mock.assert_called_once_with(input_version) - log_alert_mock.not_called() + log_alert_mock.assert_not_called() @mock.patch('ebcli.core.abstractcontroller.io.log_alert') @mock.patch('ebcli.core.abstractcontroller.cli_update_exists') From 258758f96c64582e97dffcf286bb76a893a38ed4 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Thu, 26 Jan 2023 17:07:54 -0800 Subject: [PATCH 13/44] Remove future package --- ebcli/bundled/asciimatics/exceptions.py | 4 ---- ebcli/bundled/asciimatics/screen.py | 4 ---- ebcli/bundled/asciimatics/utilities.py | 4 ---- ebcli/lib/s3.py | 1 - ebcli/objects/sourcecontrol.py | 1 - requirements.txt | 3 +-- 6 files changed, 1 insertion(+), 16 deletions(-) diff --git a/ebcli/bundled/asciimatics/exceptions.py b/ebcli/bundled/asciimatics/exceptions.py index 955aa8760..9e54ece32 100644 --- a/ebcli/bundled/asciimatics/exceptions.py +++ b/ebcli/bundled/asciimatics/exceptions.py @@ -14,10 +14,6 @@ """ This module defines the exceptions used by asciimatics. """ -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals class ResizeScreenError(Exception): diff --git a/ebcli/bundled/asciimatics/screen.py b/ebcli/bundled/asciimatics/screen.py index aab338179..9b063fb7e 100644 --- a/ebcli/bundled/asciimatics/screen.py +++ b/ebcli/bundled/asciimatics/screen.py @@ -16,10 +16,6 @@ This module defines common screen output function. For more details, see http://asciimatics.readthedocs.io/en/latest/io.html """ -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals import os import signal diff --git a/ebcli/bundled/asciimatics/utilities.py b/ebcli/bundled/asciimatics/utilities.py index a756bc74a..ac6f5ccaf 100644 --- a/ebcli/bundled/asciimatics/utilities.py +++ b/ebcli/bundled/asciimatics/utilities.py @@ -14,10 +14,6 @@ """ This module is just a collection of simple helper functions. """ -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals from datetime import date, datetime diff --git a/ebcli/lib/s3.py b/ebcli/lib/s3.py index cff1ad843..931a2cf8f 100644 --- a/ebcli/lib/s3.py +++ b/ebcli/lib/s3.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from __future__ import division import os from io import BytesIO import math diff --git a/ebcli/objects/sourcecontrol.py b/ebcli/objects/sourcecontrol.py index bc242fff8..dbf7d1652 100644 --- a/ebcli/objects/sourcecontrol.py +++ b/ebcli/objects/sourcecontrol.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from __future__ import print_function import datetime import fileinput import os diff --git a/requirements.txt b/requirements.txt index 5e29d1569..04457bc1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ -botocore>1.23.41,<1.29.52 +botocore>1.23.41,<1.29.59 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses -future>=0.16.0,<0.17.0 pathspec==0.10.1 python-dateutil>=2.1,<3.0.0 # use the same range that 'botocore' uses requests>=2.20.1,<=2.26 From e0fa998ec1faa4b8fb15f36e6c88e048ad5a502c Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Mon, 27 Feb 2023 14:59:22 -0800 Subject: [PATCH 14/44] Update CHANGES.rst and increment version to 3.20.4 cr: https://code.amazon.com/reviews/CR-86294270 --- CHANGES.rst | 14 ++++++++++++++ ebcli/__init__.py | 2 +- requirements.txt | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fd153aa03..cd868aed1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,20 @@ Changelog ========= +------------------- +3.20.4 (2023-02-27) +------------------- +- Updated botocore requirement to `>1.23.41,<1.29.81` +- Removed usage of future package +- Opened new regions (ap-southeast-3 and ap-northeast-3) +- Fixed symlinked files related issues +- Fixed modules related issues +- Fixed several unit tests +- Fixed Docker AL2 platform Notfound bug +- Integrated codecommit in new regions (eu-south-1,ap-northeast-3, af-south-1) +- Updated eb create command to create application elb as default +- Updated to display full results when --version flag is specified + ------------------- 3.20.3 (2022-01-21) ------------------- diff --git a/ebcli/__init__.py b/ebcli/__init__.py index 607e4c9c3..b77da7ceb 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.20.3' +__version__ = '3.20.4' diff --git a/requirements.txt b/requirements.txt index 04457bc1b..876df97e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore>1.23.41,<1.29.59 +botocore>1.23.41,<1.29.81 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses pathspec==0.10.1 From 898d090d9eb66fc6b0afc58155a34fc1abe8e960 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Tue, 28 Feb 2023 15:50:28 -0800 Subject: [PATCH 15/44] Include requirements.txt into pypi distribution tarball --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 876df97e1..ce3d9ffce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore>1.23.41,<1.29.81 +botocore>1.23.41,<1.29.82 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses pathspec==0.10.1 diff --git a/setup.py b/setup.py index a7d4743ed..3588277ff 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ description='Command Line Interface for AWS EB.', long_description=open('README.rst').read() + open('CHANGES.rst').read(), scripts=['bin/eb'], - data_files=[], + data_files=[('', ['requirements.txt'])], author='AWS Elastic Beanstalk', author_email='aws-eb-cli@amazon.com', url='http://aws.amazon.com/elasticbeanstalk/', From 85a8da49187f860c05db0d089743fdbb98f16cb7 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Tue, 28 Feb 2023 16:08:37 -0800 Subject: [PATCH 16/44] Update CHANGES.rst and increment version to 3.20.5 cr: https://code.amazon.com/reviews/CR-86378386 --- CHANGES.rst | 6 ++++++ ebcli/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index cd868aed1..9d1df7b51 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ Changelog ========= +------------------- +3.20.5 (2022-02-28) +------------------- +- Updated botocore requirement to `>1.23.41,<1.29.82` +- Include requirements.txt into pypi distribution tarball + ------------------- 3.20.4 (2023-02-27) ------------------- diff --git a/ebcli/__init__.py b/ebcli/__init__.py index b77da7ceb..36282628d 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.20.4' +__version__ = '3.20.5' From 8f565027764235dda704240e7f183bf7e5cbfaac Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Mon, 6 Mar 2023 10:24:41 -0800 Subject: [PATCH 17/44] Add TLV region cr: https://code.amazon.com/reviews/CR-86750582 --- ebcli/objects/region.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ebcli/objects/region.py b/ebcli/objects/region.py index b6473abdb..ca12feef0 100644 --- a/ebcli/objects/region.py +++ b/ebcli/objects/region.py @@ -35,6 +35,7 @@ def get_all_regions(): Region('eu-south-1', 'EU (Milano)'), Region('ap-east-1', 'Asia Pacific (Hong Kong)'), Region('me-south-1', 'Middle East (Bahrain)'), + Region('il-central-1', 'Middle East (Israel)'), Region('af-south-1', 'Africa (Cape Town)'), Region('ap-southeast-3', 'Asia Pacific (Jakarta)'), Region('ap-northeast-3', 'Asia Pacific (Osaka)'), From 3b7187d9384e92c00bb159dc9ff3bc6be4cc1829 Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Tue, 14 Mar 2023 18:09:55 +0000 Subject: [PATCH 18/44] Codebuild Bugfix # This is a combination of 4 commits. commit: 613a6c19c32e0933a8cc3c7d6988660e5112ebf9 Author: Yohanth Shetty Date: 2023-03-11T00:14:37.000Z Updated with comments commit: bffa5342a5259fcca66c7930896fdecbfde7cf3f Author: Yohanth Shetty Date: 2023-03-08T09:21:57.000Z improved code commit: 870bdcd2164b633c28a0a8847ca4dffa23d3c4f2 Author: Yohanth Shetty Date: 2023-02-23T23:19:14.000Z minor updates to cr commit: b0260e63d56390ed5f603598082c4b50efbfee63 Author: Yohanth Shetty Date: 2023-02-22T17:47:41.000Z codebuild bugfix SIM: https://i.amazon.com/D71670811 cr: https://code.amazon.com/reviews/CR-85955442 --- ebcli/lib/iam.py | 18 ++++++++++++++++-- ebcli/operations/buildspecops.py | 1 + tests/unit/operations/test_buildspecops.py | 5 ++--- tests/unit/operations/test_commonops.py | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ebcli/lib/iam.py b/ebcli/lib/iam.py index 49ee0a70c..6f19934da 100644 --- a/ebcli/lib/iam.py +++ b/ebcli/lib/iam.py @@ -37,8 +37,22 @@ def get_instance_profiles(): def get_roles(): - result = _make_api_call('list_roles') - return result['Roles'] + isTruncated = True + marker = '' + roles = [] + while isTruncated: + if marker == '': + result = _make_api_call('list_roles') + else: + result = _make_api_call('list_roles', Marker=marker) + isTruncated = result['IsTruncated'] + existing_roles = result['Roles'] + for role in existing_roles: + roles.append(role) + if isTruncated: + marker = result['Marker'] + + return roles def get_role(role_name): diff --git a/ebcli/operations/buildspecops.py b/ebcli/operations/buildspecops.py index 5562fa943..5bef891bb 100644 --- a/ebcli/operations/buildspecops.py +++ b/ebcli/operations/buildspecops.py @@ -86,6 +86,7 @@ def validate_build_config(build_config): for existing_role in existing_roles: if role == existing_role['Arn'] or role == existing_role['RoleName']: validated_role = existing_role['Arn'] + break if validated_role is None: LOG.debug("Role '{0}' not found in retrieved list of roles".format(role)) diff --git a/tests/unit/operations/test_buildspecops.py b/tests/unit/operations/test_buildspecops.py index f8d8b3d70..d3ee5332c 100644 --- a/tests/unit/operations/test_buildspecops.py +++ b/tests/unit/operations/test_buildspecops.py @@ -178,14 +178,13 @@ def test_validate_build_config_without_service_role(self): @mock.patch('ebcli.lib.iam.get_roles') def test_validate_build_config_without_image(self, mock_get_roles): build_config = copy.deepcopy(self.build_config) - build_config.image = None + build_config.image = None mock_get_roles.return_value = self.list_roles_response - + self.assertRaises(ValidationError, buildspecops.validate_build_config, build_config) mock_get_roles.assert_called_with() - @mock.patch('ebcli.lib.iam.get_roles') def test_validate_build_config_with_invalid_role(self, mock_get_roles): build_config = copy.deepcopy(self.build_config) diff --git a/tests/unit/operations/test_commonops.py b/tests/unit/operations/test_commonops.py index a4a44f611..e5e4a3441 100644 --- a/tests/unit/operations/test_commonops.py +++ b/tests/unit/operations/test_commonops.py @@ -61,7 +61,7 @@ class TestCommonOperations(unittest.TestCase): image = 'aws/codebuild/eb-java-8-amazonlinux-64:2.1.3' compute_type = 'BUILD_GENERAL1_SMALL' service_role = 'eb-test' - service_role_arn = 'arn:testcli:eb-test' + service_role_arn = 'arn:aws:iam::123456789123:role/eb-test' timeout = 60 build_config = BuildConfiguration(image=image, compute_type=compute_type, service_role=service_role, timeout=timeout) From 32aae98a9e64370800ec6b248ae79403b86c7e33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 20:08:57 +0000 Subject: [PATCH 19/44] Symlink and Performance fix cr: https://code.amazon.com/reviews/CR-88187191 --- ebcli/core/fileoperations.py | 13 +------------ requirements.txt | 2 +- tests/unit/core/test_fileoperations.py | 4 ++-- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/ebcli/core/fileoperations.py b/ebcli/core/fileoperations.py index 0c3c54e62..efd415a47 100644 --- a/ebcli/core/fileoperations.py +++ b/ebcli/core/fileoperations.py @@ -22,11 +22,11 @@ import warnings import yaml import zipfile -from pathspec import PathSpec from cement.utils.misc import minimal_logger from ebcli.core import fileoperations from ebcli.objects.buildconfiguration import BuildConfiguration +from pathspec import PathSpec from six import StringIO from yaml import safe_load, safe_dump from yaml.parser import ParserError @@ -112,12 +112,6 @@ def _get_option(config, section, key, default): def is_git_directory_present(): return os.path.isdir('.git') -def is_parent_directory_in_ignore_list(filepath, ignore_list): - for directory in ignore_list: - if os.path.abspath(filepath).startswith(os.path.abspath(directory)): - return True - return False - def clean_up(): cwd = os.getcwd() try: @@ -458,10 +452,6 @@ def _zipdir(path, zipf, ignore_list=None): zipInfo.external_attr = 2716663808 else: zipInfo.external_attr = long(2716663808) - if not is_parent_directory_in_ignore_list(cur_dir, ignore_list): - zipf.writestr(zipInfo, os.readlink(cur_dir)) - else: - io.log_info(' -skipping: {}'.format(cur_dir)) for f in files: cur_file = os.path.join(root, f) @@ -469,7 +459,6 @@ def _zipdir(path, zipf, ignore_list=None): cur_file.endswith('~') or cur_file in ignore_list or not _validate_file_for_archive(cur_file) - or is_parent_directory_in_ignore_list(cur_file, ignore_list) ): # Ignore editor backup files (like file.txt~) # Ignore anything in the .ebignore file diff --git a/requirements.txt b/requirements.txt index ce3d9ffce..1e79478e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore>1.23.41,<1.29.82 +botocore>1.23.41,<1.29.100 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses pathspec==0.10.1 diff --git a/tests/unit/core/test_fileoperations.py b/tests/unit/core/test_fileoperations.py index b15bc9693..1204641cd 100644 --- a/tests/unit/core/test_fileoperations.py +++ b/tests/unit/core/test_fileoperations.py @@ -756,7 +756,7 @@ def test_zip_up_project(self, _validate_file_for_archive_mock): fileoperations.zip_up_project( 'app.zip', - ignore_list=[os.path.join('src', 'lib', 'ignore-this-file.py'), os.path.join('ignore-me/')] + ignore_list=[os.path.join('src', 'lib', 'ignore-this-file.py'), os.path.join('ignore-me/'), os.path.join('src', 'lib', 'api-copy')] ) os.mkdir('tmp') @@ -765,7 +765,7 @@ def test_zip_up_project(self, _validate_file_for_archive_mock): self.assertTrue(os.path.exists(os.path.join('tmp', 'src', 'lib', 'app.py'))) self.assertTrue(os.path.exists(os.path.join('tmp', 'src', 'lib', 'api'))) self.assertTrue(os.path.exists(os.path.join('tmp', 'src', 'lib', 'app.py-copy'))) - self.assertTrue(os.path.exists(os.path.join('tmp', 'src', 'lib', 'api-copy'))) + self.assertFalse(os.path.exists(os.path.join('tmp', 'src', 'lib', 'api-copy'))) self.assertFalse(os.path.exists(os.path.join('tmp', 'src', 'lib', 'app.py~'))) self.assertFalse(os.path.exists(os.path.join('tmp', 'src', 'lib', 'ignore-this-file.py'))) self.assertFalse(os.path.exists(os.path.join('tmp', 'src', 'lib', 'test.sock'))) From 69f19f6eb0267db5797e9a2f5a0cf0e5a5a1debb Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Thu, 30 Mar 2023 00:18:38 -0700 Subject: [PATCH 20/44] Symlink and Performance Bugfix --- ebcli/core/fileoperations.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ebcli/core/fileoperations.py b/ebcli/core/fileoperations.py index efd415a47..0e75a18ff 100644 --- a/ebcli/core/fileoperations.py +++ b/ebcli/core/fileoperations.py @@ -438,20 +438,6 @@ def _zipdir(path, zipf, ignore_list=None): if '.elasticbeanstalk' in root: io.log_info(' -skipping: {}'.format(root)) continue - for d in dirs: - cur_dir = os.path.join(root, d) - if os.path.islink(cur_dir): - # It is probably safe to remove this code since os.walk seems to categorize - # symlinks-to-directories as files. This doesn't matter as far as creation - # of the zip is concerned, but just having the code around is confusing. - zipInfo = zipfile.ZipInfo() - zipInfo.filename = os.path.join(root, d) - - # 2716663808L is the "magic code" for symlinks - if sys.version_info > (3,): - zipInfo.external_attr = 2716663808 - else: - zipInfo.external_attr = long(2716663808) for f in files: cur_file = os.path.join(root, f) From af754d73aa86ba52bab873f92e44454be096d539 Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Thu, 30 Mar 2023 23:41:10 +0000 Subject: [PATCH 21/44] Platform Bugfix SIM: https://i.amazon.com/P83611496 cr: https://code.amazon.com/reviews/CR-88397568 --- ebcli/objects/solutionstack.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ebcli/objects/solutionstack.py b/ebcli/objects/solutionstack.py index 205b18858..a3d7e15f2 100644 --- a/ebcli/objects/solutionstack.py +++ b/ebcli/objects/solutionstack.py @@ -405,6 +405,7 @@ def match_with_solution_string_language_name( :return: A SolutionStack object representing the input language name """ + solution_stack_list = sorted(solution_stack_list) for solution_stack in solution_stack_list: if solution_stack.language_name.lower() == language_name.lower(): return solution_stack From 6f457933cf04d95ae578632fc9adc417efc273e9 Mon Sep 17 00:00:00 2001 From: Yohanth Shetty Date: Tue, 11 Apr 2023 15:24:20 -0700 Subject: [PATCH 22/44] Update CHANGES.rst and increment version to 3.20.6 cr: https://code.amazon.com/reviews/CR-89238846 --- CHANGES.rst | 10 +++++++++- ebcli/__init__.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9d1df7b51..658e91aff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,15 @@ Changelog ========= ------------------- -3.20.5 (2022-02-28) +3.20.6 (2023-04-11) +------------------- +- Updated botocore requirement to `>1.23.41,<1.29.100` +- Fixed platforms bug +- Fixed Codebuild bug +- Fixed Symlink and Performance Issues + +------------------- +3.20.5 (2023-02-28) ------------------- - Updated botocore requirement to `>1.23.41,<1.29.82` - Include requirements.txt into pypi distribution tarball diff --git a/ebcli/__init__.py b/ebcli/__init__.py index 36282628d..58d731238 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.20.5' +__version__ = '3.20.6' From d5f4659bc2426217f60d9354d1aa3b8489cc62d9 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Thu, 25 May 2023 17:51:47 -0700 Subject: [PATCH 23/44] Merging in latest changes from github --- .gitignore | 1 + ebcli/display/data_poller.py | 2 +- requirements.txt | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e40e0f1d4..818314c74 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ awsebcli.egg-info/ *.xml *.iml .vscode/ +venv diff --git a/ebcli/display/data_poller.py b/ebcli/display/data_poller.py index a12a0dc62..277f9af88 100644 --- a/ebcli/display/data_poller.py +++ b/ebcli/display/data_poller.py @@ -209,7 +209,7 @@ def collapse_instance_health_data(instances_health): if 'LoadAverage' in instance else '-' instance['launched'] = utils.get_local_time_as_string(instance['LaunchedAt']) - instance['running'] = format_time_since(instance['LaunchedAt']) + instance['running'] = format_time_since(instance.get('LaunchedAt')) duration = instance.get('Duration', 10) instance['requests'] = request_count / (duration * 1.0) diff --git a/requirements.txt b/requirements.txt index 1e79478e3..948effa22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -botocore>1.23.41,<1.29.100 +botocore>1.23.41,<1.29.142 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses pathspec==0.10.1 python-dateutil>=2.1,<3.0.0 # use the same range that 'botocore' uses -requests>=2.20.1,<=2.26 +requests>=2.31 setuptools >= 20.0 semantic_version == 2.8.5 six>=1.11.0,<1.15.0 From 927eecfd023629dc56c909223039373453919883 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Wed, 21 Jun 2023 23:31:43 +0000 Subject: [PATCH 24/44] Update CHANGES.rst and increment version to 3.20.7 cr: https://code.amazon.com/reviews/CR-94767037 --- CHANGES.rst | 8 ++++++++ ebcli/__init__.py | 2 +- requirements.txt | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 658e91aff..08b8f13be 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,14 @@ Changelog ========= +------------------- +3.20.7 (2023-06-21) +------------------- +- Updated botocore requirement to `>1.23.41,<1.29.159` +- Updated requests requirement to `>2.31` +- Updated .gitignore +- Updated LauchedAt Attribute for DescribeInstancesHealth + ------------------- 3.20.6 (2023-04-11) ------------------- diff --git a/ebcli/__init__.py b/ebcli/__init__.py index 58d731238..0cf3ebbb7 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.20.6' +__version__ = '3.20.7' diff --git a/requirements.txt b/requirements.txt index 948effa22..421afcf5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore>1.23.41,<1.29.142 +botocore>1.23.41,<1.29.159 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses pathspec==0.10.1 From 4529991a3b487ccac29a2cce787d636c4c24fdc9 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Tue, 15 Aug 2023 14:42:10 -0700 Subject: [PATCH 25/44] Merge from github repos --- .github/dependabot.yml | 1 + ebcli/operations/logsops.py | 7 ++++++- requirements.txt | 6 +++--- setup.py | 2 +- tests/unit/operations/test_logsops.py | 14 ++++++++++++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1c5101d98..6b10280b1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,7 @@ version: 2 updates: + - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: diff --git a/ebcli/operations/logsops.py b/ebcli/operations/logsops.py index e78e85c3b..12564e79f 100644 --- a/ebcli/operations/logsops.py +++ b/ebcli/operations/logsops.py @@ -102,7 +102,12 @@ def deployment_logs_log_group_name(env_name): environment = elasticbeanstalk.get_environment(env_name=env_name) if 'windows' in environment.platform.name.lower(): log_group_suffix = 'EBDeploy-Log' - elif 'Amazon Linux 2/' in environment.platform.name: + # TODO: The logic here is we judge if platform is beyond or equal to Amazon Linux 2 - if it is we use "var/log/eb-engine.log" otherwise we use 'var/log/eb-activity.log' + # However the problem is Amazon Linux 1 platform naming does not have a fixed pattern, which is hard to write if statement + # Therefore we have to judge by 'Amazon Linux 2/' or 'Amazon Linux 2023/' which follows a fixed naming pattern + # We should resolve this tech debt by investigating the naming pattern of Amazon linux 1 platform. Luckily there + # will be no more new Amazon Linux 1 platforms so once we figured out a pattern, we would solve the issues forever. + elif 'Amazon Linux 2/' in environment.platform.name or 'Amazon Linux 2023/' in environment.platform.name: log_group_suffix = "var/log/eb-engine.log" else: log_group_suffix = 'var/log/eb-activity.log' diff --git a/requirements.txt b/requirements.txt index 421afcf5e..0ae21429b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore>1.23.41,<1.29.159 +botocore>1.23.41,<1.31.27 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses pathspec==0.10.1 @@ -9,5 +9,5 @@ semantic_version == 2.8.5 six>=1.11.0,<1.15.0 termcolor == 1.1.0 wcwidth>=0.1.7,<0.2.0 -PyYAML>=5.3.1,<5.5 # use the same range that 'aws-cli' uses. This is also compatible with 'docker-compose' -urllib3>=1.26.5 #1.26.5 fix CVE-2021-33503 +PyYAML>=5.3.1,<6.1 # use the same range that 'aws-cli' uses. This is also compatible with 'docker-compose' +urllib3>=1.26.5,<2 #1.26.5 fix CVE-2021-33503 diff --git a/setup.py b/setup.py index 3588277ff..695aac658 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ } if not sys.platform.startswith('win'): - requires.append('docker-compose >= 1.25.2, < 1.26.0') + requires.append('docker-compose >= 1.25.2, <= 1.29.2') requires.append('blessed>=1.9.5') diff --git a/tests/unit/operations/test_logsops.py b/tests/unit/operations/test_logsops.py index 24e00d833..0c9da0c4a 100644 --- a/tests/unit/operations/test_logsops.py +++ b/tests/unit/operations/test_logsops.py @@ -1341,6 +1341,20 @@ def test_deployment_logs_log_group_name__al2_platforms(self, get_environment_moc logsops.deployment_logs_log_group_name('my-env') ) + @mock.patch('ebcli.operations.logsops.elasticbeanstalk.get_environment') + def test_deployment_logs_log_group_name__al2023_platforms(self, get_environment_mock): + environment_mock = mock.MagicMock() + platform_mock = mock.MagicMock() + platform_mock.name = 'arn:aws:elasticbeanstalk:us-east-1::platform/PHP 8.2 running on 64bit Amazon Linux 2023/4.0.0' + environment_mock.platform = platform_mock + + get_environment_mock.return_value = environment_mock + + self.assertEqual( + '/aws/elasticbeanstalk/my-env/var/log/eb-engine.log', + logsops.deployment_logs_log_group_name('my-env') + ) + @mock.patch('ebcli.operations.logsops.elasticbeanstalk.get_environment') def test_deployment_logs_log_group_name__windows_platform(self, get_environment_mock): environment_mock = mock.MagicMock() From 682c35ecacda22445fcecd9b9a2d76e00b2e15ed Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Tue, 15 Aug 2023 14:25:41 -0700 Subject: [PATCH 26/44] Update CHANGES.rst and increment version to 3.20.8 --- CHANGES.rst | 8 ++++++++ ebcli/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 08b8f13be..01132ab4b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,14 @@ Changelog ========= +------------------- +3.20.8 (2023-08-15) +------------------- +- Updated botocore requirement to `>1.23.41,<1.31.27` +- Updated urllib3 requirement to `>=1.26.5,<2` +- Updated docker-compose requirement to `>= 1.25.2, <= 1.29.2` +- Updated PyYAML requirement to `>=5.3.1,<6.1` + ------------------- 3.20.7 (2023-06-21) ------------------- diff --git a/ebcli/__init__.py b/ebcli/__init__.py index 0cf3ebbb7..c8e97ee07 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.20.7' +__version__ = '3.20.8' From 392ff63d5aa0de16c2fdee3d1ea5957cf4fc6edb Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Thu, 24 Aug 2023 19:36:31 -0700 Subject: [PATCH 27/44] Update dependabot configuration to only update minor version; Expand botocore range cr: https://code.amazon.com/reviews/CR-99888335 sim: https://t.corp.amazon.com/P97134967 --- .github/dependabot.yml | 3 +++ requirements.txt | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6b10280b1..6fcf951fc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,3 +14,6 @@ updates: allow: - dependency-name: "botocore*" dependency-type: "all" + ignore: + - dependency-name: "botocore*" + update-types: [ "version-update:semver-patch" ] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0ae21429b..bfe935673 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore>1.23.41,<1.31.27 +botocore>1.23.41,<1.32.0 cement==2.8.2 colorama>=0.2.5,<0.4.4 # use the same range that 'docker-compose' uses pathspec==0.10.1 @@ -6,7 +6,7 @@ python-dateutil>=2.1,<3.0.0 # use the same range that 'botocore' uses requests>=2.31 setuptools >= 20.0 semantic_version == 2.8.5 -six>=1.11.0,<1.15.0 +six>=1.11.0,<1.17.0 termcolor == 1.1.0 wcwidth>=0.1.7,<0.2.0 PyYAML>=5.3.1,<6.1 # use the same range that 'aws-cli' uses. This is also compatible with 'docker-compose' From fc8b421167730966459e5c1a6ae93256a15915e4 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Tue, 29 Aug 2023 03:56:13 +0000 Subject: [PATCH 28/44] Fix to allow customers create environments via eb create if they have more than 100 CloudFormation stacks present in their environment cr: https://code.amazon.com/reviews/CR-100174680 --- ebcli/lib/cloudformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebcli/lib/cloudformation.py b/ebcli/lib/cloudformation.py index ab0166f48..dea962e7b 100644 --- a/ebcli/lib/cloudformation.py +++ b/ebcli/lib/cloudformation.py @@ -72,7 +72,7 @@ def describe_stacks(stack_name=None): all_stacks = response['Stacks'] while next_token: utils.sleep(sleep_time=0.5) - response = _make_api_call('describe_stacks') + response = _make_api_call('describe_stacks',NextToken=next_token) all_stacks.extend(response['Stacks']) next_token = response.get('NextToken') return all_stacks From 2e928d9e6db190ebbe4b62d21dcc443901e87bf9 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Thu, 31 Aug 2023 16:32:24 +0000 Subject: [PATCH 29/44] Updated CHANGES.rst and increment version to 3.20.9 cr: https://code.amazon.com/reviews/CR-100402941 --- CHANGES.rst | 8 ++++++++ ebcli/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 01132ab4b..374f49ee9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,14 @@ Changelog ========= +------------------- +3.20.9 (2023-08-31) +------------------- +- Updated botocore requirement to `>1.23.41,<1.32.0` +- Updated six requirement tp `>=1.11.0,<1.17.0` +- Update dependabot configuration to only update minor version +- Bug fix for Cloudformation pagination token + ------------------- 3.20.8 (2023-08-15) ------------------- diff --git a/ebcli/__init__.py b/ebcli/__init__.py index c8e97ee07..e5b4329b7 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.20.8' +__version__ = '3.20.9' From 45ff907ea41d791076810c017f2e63a8a87179ea Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Thu, 31 Aug 2023 16:46:48 +0000 Subject: [PATCH 30/44] Fix typo and updated version to 3.20.9 cr: https://code.amazon.com/reviews/CR-100404117 --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 374f49ee9..74b8c95bd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changelog 3.20.9 (2023-08-31) ------------------- - Updated botocore requirement to `>1.23.41,<1.32.0` -- Updated six requirement tp `>=1.11.0,<1.17.0` +- Updated six requirement to `>=1.11.0,<1.17.0` - Update dependabot configuration to only update minor version - Bug fix for Cloudformation pagination token From 85576b8615d1592f1bd0f6aae68ba75defe023b5 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Tue, 5 Sep 2023 15:58:17 +0000 Subject: [PATCH 31/44] Removing Docker-compose as a dependancy as per issue #456 cr: https://code.amazon.com/reviews/CR-100744613 --- ebcli/containers/commands.py | 5 ++++- ebcli/containers/utils.py | 8 ++++++++ setup.py | 1 - 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 ebcli/containers/utils.py diff --git a/ebcli/containers/commands.py b/ebcli/containers/commands.py index c93533cef..d3691c6cb 100644 --- a/ebcli/containers/commands.py +++ b/ebcli/containers/commands.py @@ -19,6 +19,7 @@ from ebcli.lib import utils from ebcli.objects.exceptions import ValidationError, CommandError from ebcli.resources.strings import strings +from .utils import is_docker_compose_installed EXPOSE_CMD = 'EXPOSE' @@ -113,7 +114,9 @@ def up(compose_path=None, allow_insecure_ssl=False): def _compose_run(args): - utils.exec_cmd_live_output(['docker-compose'] + args) + if not is_docker_compose_installed(): + raise RuntimeError("Docker Compose v2 is not installed. Please install it and try again.") + utils.exec_cmd_live_output(['docker','compose'] + args) def get_container_lowlvl_info(container_id): diff --git a/ebcli/containers/utils.py b/ebcli/containers/utils.py new file mode 100644 index 000000000..4dc2df7a3 --- /dev/null +++ b/ebcli/containers/utils.py @@ -0,0 +1,8 @@ +import subprocess + +def is_docker_compose_installed(): + try: + subprocess.run(['docker','compose','version'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except subprocess.CalledProcessError: + return False \ No newline at end of file diff --git a/setup.py b/setup.py index 695aac658..f6dfe0c34 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,6 @@ } if not sys.platform.startswith('win'): - requires.append('docker-compose >= 1.25.2, <= 1.29.2') requires.append('blessed>=1.9.5') From 0665bac387af0d0021dad63c58a29fa4821abc41 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Tue, 5 Sep 2023 18:54:14 +0000 Subject: [PATCH 32/44] Updating as per comments --- ebcli/containers/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebcli/containers/commands.py b/ebcli/containers/commands.py index d3691c6cb..1f29360ad 100644 --- a/ebcli/containers/commands.py +++ b/ebcli/containers/commands.py @@ -115,7 +115,7 @@ def up(compose_path=None, allow_insecure_ssl=False): def _compose_run(args): if not is_docker_compose_installed(): - raise RuntimeError("Docker Compose v2 is not installed. Please install it and try again.") + raise RuntimeError("Docker Compose is not installed. Please install it and try again.") utils.exec_cmd_live_output(['docker','compose'] + args) From c8ff43cb12cbcd497ebb13fb7bdd615ba68e1d21 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Wed, 13 Sep 2023 18:16:51 +0000 Subject: [PATCH 33/44] Refactor and fix unit tests for ebcli initialize module to fix distorted images bug cr: https://code.amazon.com/reviews/CR-101416246 --- ebcli/controllers/initialize.py | 40 ++++++++++++++++----- ebcli/operations/initializeops.py | 24 ++++++++++--- tests/unit/controllers/test_init.py | 9 ++--- tests/unit/operations/test_initializeops.py | 4 +-- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/ebcli/controllers/initialize.py b/ebcli/controllers/initialize.py index 38ed0afa8..0a69cd0b5 100644 --- a/ebcli/controllers/initialize.py +++ b/ebcli/controllers/initialize.py @@ -422,20 +422,42 @@ def get_keyname(keyname, keyname_of_existing_app, interactive, force_non_interac def handle_buildspec_image(solution, force_non_interactive): if not fileoperations.build_spec_exists(): - return + return None build_spec = fileoperations.get_build_configuration() + if not force_non_interactive and build_spec and build_spec.image is None: - LOG.debug("Buildspec file is present but image is does not exist. Attempting to fill best guess.") + LOG.debug("Buildspec file is present but image does not exist. Attempting to fill best guess.") platform_image = initializeops.get_codebuild_image_from_platform(solution) - if type(platform_image) is dict: - io.echo(strings['codebuild.latestplatform'].replace('{platform}', solution)) - else: - io.echo(prompts['codebuild.getplatform'].replace('{platform}', solution)) - selected = utils.prompt_for_index_in_list([image['description'] for image in platform_image][0]) - platform_image = [image for image in platform_image if selected == image['description']][0] - fileoperations.write_buildspec_config_header('Image', platform_image['name']) + if not platform_image: + io.echo("No images found for platform: {platform}".format(platform=solution)) + return None + + if isinstance(platform_image[0], dict): + if len(platform_image) == 1: + io.echo(strings['codebuild.latestplatform'].replace('{platform}', solution)) + selected_image = platform_image[0] + else: + io.echo(prompts['codebuild.getplatform'].replace('{platform}', solution)) + selected = int(utils.prompt_for_index_in_list([image['description'] for image in platform_image])) + + if selected is not None and 0 <= selected < len(platform_image): + selected_description = platform_image[selected]['description'] + else: + LOG.error("Invalid selection.") + return None + + matching_images = [image for image in platform_image if selected_description == image['description']] + + if not matching_images: + LOG.error(f"No matching images found for selected description: {selected}") + return None + + selected_image = matching_images[0] + fileoperations.write_buildspec_config_header('Image', selected_image['name']) + + return None def set_default_env(interactive, force_non_interactive): diff --git a/ebcli/operations/initializeops.py b/ebcli/operations/initializeops.py index f1f383989..ce8901223 100644 --- a/ebcli/operations/initializeops.py +++ b/ebcli/operations/initializeops.py @@ -95,12 +95,26 @@ def setup_ignore_file(): def get_codebuild_image_from_platform(platform): - platform_image = None + platform_images = [] # Store multiple matches in a list beanstalk_images = codebuild.list_curated_environment_images() + + # Log the entire list for debugging purposes + LOG.debug("Fetched beanstalk_images: {}".format(beanstalk_images)) + + # Validate that beanstalk_images is a list of dictionaries + if not isinstance(beanstalk_images, list) or not all(isinstance(i, dict) for i in beanstalk_images): + LOG.error("Unexpected format for beanstalk_images. Expected a list of dictionaries.") + return [] + for image in beanstalk_images: - if platform in image['description']: - platform_image = image + if 'description' in image and platform in image['description']: + platform_images.append(image) + + # If no platform-specific images found, return all images + if not platform_images: + LOG.debug("No matching platform images found. Returning all images.") + return beanstalk_images - LOG.debug("Searching for images for platform '{0}'. Found: {1}".format(platform, platform_image)) + LOG.debug("Searching for images for platform '{0}'. Found: {1}".format(platform, platform_images)) - return beanstalk_images if platform_image is None else platform_image + return platform_images diff --git a/tests/unit/controllers/test_init.py b/tests/unit/controllers/test_init.py index 22af2d2a9..e76ce65c7 100644 --- a/tests/unit/controllers/test_init.py +++ b/tests/unit/controllers/test_init.py @@ -1436,7 +1436,7 @@ def test_handle_buildspec_image__multiple_matching_images_for_platform( 'description': 'Java 8 Running on Amazon Linux 32bit ' } ] - prompt_for_index_in_list_mock.return_value = 'Java 8 Running on Amazon Linux 32bit ' + prompt_for_index_in_list_mock.return_value = 1 self.assertIsNone(initialize.handle_buildspec_image('Java 8', False)) @@ -1453,7 +1453,8 @@ def test_handle_buildspec_image__multiple_matching_images_for_platform( ) ] ) - prompt_for_index_in_list_mock.assert_called_once_with('Java 8 Running on Amazon Linux 64bit ') + prompt_for_index_in_list_mock.assert_called_once_with(['Java 8 Running on Amazon Linux 64bit ', 'Java 8 Running on Amazon Linux 32bit ']) + @mock.patch('ebcli.controllers.initialize.fileoperations.get_build_configuration') @mock.patch('ebcli.controllers.initialize.initializeops.get_codebuild_image_from_platform') @@ -1481,10 +1482,10 @@ def test_handle_buildspec_image__single_image_matches( ) build_spec_exists_mock.return_value = True get_build_configuration_mock.return_value = build_config - get_codebuild_image_from_platform_mock.return_value = { + get_codebuild_image_from_platform_mock.return_value = [{ 'name': 'aws/codebuild/eb-java-8-amazonlinux-64:2.1.6', 'description': 'Java 8 Running on Amazon Linux 64bit ' - } + }] prompt_for_index_in_list_mock.return_value = 'Java 8 Running on Amazon Linux 32bit ' self.assertIsNone(initialize.handle_buildspec_image('Java 8', False)) diff --git a/tests/unit/operations/test_initializeops.py b/tests/unit/operations/test_initializeops.py index 827d4ff91..ec25ec681 100644 --- a/tests/unit/operations/test_initializeops.py +++ b/tests/unit/operations/test_initializeops.py @@ -68,8 +68,8 @@ def test_get_codebuild_image_from_platform_that_exists(self, mock_codebuild): images_for_platform = initializeops.get_codebuild_image_from_platform("Java 8") - self.assertEqual(expected_image, images_for_platform, - "Expected '{0}' but got: {1}".format(expected_image, images_for_platform)) + self.assertEqual([expected_image], images_for_platform, + "Expected '{0}' but got: {1}".format([expected_image], images_for_platform)) @mock.patch('ebcli.operations.initializeops.codebuild') def test_get_codebuild_image_from_platform_that_does_not_match(self, mock_codebuild): From cf421555a5df8c5029f61be52df28c7ad4529c19 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Fri, 15 Sep 2023 15:26:18 +0000 Subject: [PATCH 34/44] Updated CHANGES.rst and __init__.py for Version Update cr: https://code.amazon.com/reviews/CR-101563109 --- CHANGES.rst | 6 ++++++ ebcli/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 74b8c95bd..9dd3f7b5a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ Changelog ========= +------------------- +3.20.10 (2023-09-15) +------------------- +- Removed Docker-compose as a dependancy +- Fixed Codebuild image bug + ------------------- 3.20.9 (2023-08-31) ------------------- diff --git a/ebcli/__init__.py b/ebcli/__init__.py index e5b4329b7..6688a8e72 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.20.9' +__version__ = '3.20.10' From b29a6f0c42365fe95b7fb6770007442f9a331a32 Mon Sep 17 00:00:00 2001 From: Mingran Peng Date: Fri, 15 Sep 2023 13:11:05 -0700 Subject: [PATCH 35/44] Updated CHANGES.rst and increment version to 3.20.10 cr: https://code.amazon.com/reviews/CR-101586169 --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9dd3f7b5a..067c67c36 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,9 @@ Changelog ========= -------------------- +-------------------- 3.20.10 (2023-09-15) -------------------- +-------------------- - Removed Docker-compose as a dependancy - Fixed Codebuild image bug From 2d7bd5b27cc9253e7db8e011983e6be51a78e748 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Mon, 9 Oct 2023 16:49:34 +0000 Subject: [PATCH 36/44] Updated code to fix warnings. cr: https://code.amazon.com/reviews/CR-103454297 --- ebcli/controllers/create.py | 2 +- ebcli/display/screen.py | 5 +---- ebcli/lib/codebuild.py | 2 +- tests/unit/operations/tagops/test_taglist.py | 2 +- tests/unit/operations/test_envvarops.py | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ebcli/controllers/create.py b/ebcli/controllers/create.py index a37ee90e4..3648c7d39 100644 --- a/ebcli/controllers/create.py +++ b/ebcli/controllers/create.py @@ -218,7 +218,7 @@ def do_command(self): if (spot_max_price or on_demand_base_capacity or on_demand_above_base_capacity) and not enable_spot: raise InvalidOptionsError(strings['create.missing_enable_spot']) - if instance_types is "": + if instance_types == "": raise InvalidOptionsError(strings['spot.instance_types_validation']) if itype and instance_types: diff --git a/ebcli/display/screen.py b/ebcli/display/screen.py index c31774417..69f6e2090 100644 --- a/ebcli/display/screen.py +++ b/ebcli/display/screen.py @@ -439,10 +439,7 @@ def scroll_down(self, reverse=False): scrolled = visible_tables[0].scroll_down(reverse=reverse) if scrolled: for i in range(1, len(visible_tables)): - assert( - len(visible_tables[0].data) >= len(visible_tables[i].data), - 'First table should be the largest' - ) + assert len(visible_tables[0].data) >= len(visible_tables[i].data),'First table should be the largest' visible_tables[i].scroll_to_id(scrolled, reverse=reverse) def scroll_over(self, reverse=False): diff --git a/ebcli/lib/codebuild.py b/ebcli/lib/codebuild.py index fbc240302..11359c96a 100644 --- a/ebcli/lib/codebuild.py +++ b/ebcli/lib/codebuild.py @@ -57,7 +57,7 @@ def list_curated_environment_images(): each available platform from CodeBuild. :return: array of image dictionaries """ - regex_search_version = "AWS ElasticBeanstalk - (.*)v([0-9]+\.[0-9]+\.[0-9]+)" + regex_search_version = r"AWS ElasticBeanstalk - (.*?)v([0-9]+\.[0-9]+\.[0-9]+)" result = _make_api_call('list_curated_environment_images') beanstalk_images = [] for platform in result['platforms']: diff --git a/tests/unit/operations/tagops/test_taglist.py b/tests/unit/operations/tagops/test_taglist.py index f99456357..374ddeda3 100644 --- a/tests/unit/operations/tagops/test_taglist.py +++ b/tests/unit/operations/tagops/test_taglist.py @@ -134,7 +134,7 @@ def test_validate_key_value_pair__unicode_characters(self): Python identifies certain Unicode characters as letters, but not others. For example, the greek delta symbol is not a Unicode letter, and hence - will not match `\w` during regex comparisons, whereas the Swedish `a` + will not match `\\w` during regex comparisons, whereas the Swedish `a` is a letter. It is also to be noted that even if Python recognizes a Unicode character diff --git a/tests/unit/operations/test_envvarops.py b/tests/unit/operations/test_envvarops.py index 005ae1f19..7270f371a 100644 --- a/tests/unit/operations/test_envvarops.py +++ b/tests/unit/operations/test_envvarops.py @@ -30,7 +30,7 @@ def assertListsOfDictsEquivalent(self, ls1, ls2): Counter(frozenset(iteritems(d)) for d in ls2)) def test_sanitize_environment_variables_from_customer_input(self): - environment_variables_input = ' """ DB_USER""" = "\" r=o\"o\'t\"\" " , DB_PAS\ = SWORD="\"pass=\'\"word\""' + environment_variables_input = ' """ DB_USER""" = "\" r=o\"o\'t\"\" " , DB_PAS\\ = SWORD="\"pass=\'\"word\""' self.assertEqual( [ From 69606534956efbfd30cdec073b30af0eb91585e9 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Fri, 20 Oct 2023 17:27:53 +0000 Subject: [PATCH 37/44] Validate if service role exists before proceeding with environment creation cr: https://code.amazon.com/reviews/CR-104359557 --- ebcli/controllers/create.py | 5 ++++- ebcli/lib/iam.py | 12 ++++++++++++ tests/unit/lib/test_iam.py | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/ebcli/controllers/create.py b/ebcli/controllers/create.py index 3648c7d39..041acfc92 100644 --- a/ebcli/controllers/create.py +++ b/ebcli/controllers/create.py @@ -16,7 +16,7 @@ from ebcli.core import io, fileoperations, hooks from ebcli.core.abstractcontroller import AbstractBaseController -from ebcli.lib import elasticbeanstalk, utils +from ebcli.lib import elasticbeanstalk, utils, iam from ebcli.objects.exceptions import ( AlreadyExistsError, InvalidOptionsError, @@ -223,6 +223,9 @@ def do_command(self): if itype and instance_types: raise InvalidOptionsError(strings['create.itype_and_instances']) + + if service_role and not iam.role_exists(service_role): + raise InvalidOptionsError(f"The specified service role '{service_role}' does not exist. Please use a role that exists or create a new role .") platform = _determine_platform(platform, iprofile) diff --git a/ebcli/lib/iam.py b/ebcli/lib/iam.py index 6f19934da..a05c84ead 100644 --- a/ebcli/lib/iam.py +++ b/ebcli/lib/iam.py @@ -186,3 +186,15 @@ def get_managed_policy_document(arn): PolicyArn=arn, VersionId=policy_version) return details['PolicyVersion']['Document'] + +def role_exists(role_name): + """ + Check if a given IAM role exists. + :param role_name: Name of the IAM role to check. + :return: True if the role exists, False otherwise. + """ + roles = get_roles() + for role in roles: + if role['RoleName'] == role_name: + return True + return False \ No newline at end of file diff --git a/tests/unit/lib/test_iam.py b/tests/unit/lib/test_iam.py index 60d8765c2..a6bdfc3ca 100644 --- a/tests/unit/lib/test_iam.py +++ b/tests/unit/lib/test_iam.py @@ -29,3 +29,20 @@ def test_account_id( self.assertEqual('123123123123', iam.account_id()) make_api_call_mock.assert_called_once_with('iam', 'get_user') + + @mock.patch('ebcli.lib.iam.get_roles') + def test_role_exists(self, get_roles_mock): + # Mock the get_roles function to return a sample list of roles + mock_roles = [ + {'RoleName': 'aws-elasticbeanstalk-ec2-role'}, + {'RoleName': 'aws-elasticbeanstalk-service-role'} + ] + get_roles_mock.return_value = mock_roles + + # Test for a role that exists + self.assertTrue(iam.role_exists('aws-elasticbeanstalk-ec2-role')) + + # Test for a role that doesn't exist + self.assertFalse(iam.role_exists('SomeRandomIAMRole')) + + self.assertEqual(get_roles_mock.call_count, 2) \ No newline at end of file From e2ff96cc9567d30373c2bf90e001194729d1f87e Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Tue, 24 Oct 2023 22:06:26 +0000 Subject: [PATCH 38/44] Fix for issue #144 cr: https://code.amazon.com/reviews/CR-104615065 --- ebcli/operations/sshops.py | 2 +- tests/unit/operations/test_sshops.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ebcli/operations/sshops.py b/ebcli/operations/sshops.py index 9a5e48f23..e9a0b4e09 100644 --- a/ebcli/operations/sshops.py +++ b/ebcli/operations/sshops.py @@ -132,7 +132,7 @@ def ssh_into_instance(instance_id, keep_open=False, force_open=False, custom_ssh custom_ssh = custom_ssh.split() else: ident_file = _get_ssh_file(keypair_name) - custom_ssh = ['ssh', '-i', ident_file] + custom_ssh = ['ssh', '-i', ident_file, '-o', 'IdentitiesOnly yes'] custom_ssh.extend([user + '@' + ip]) diff --git a/tests/unit/operations/test_sshops.py b/tests/unit/operations/test_sshops.py index 7047236d2..9c90f3e86 100644 --- a/tests/unit/operations/test_sshops.py +++ b/tests/unit/operations/test_sshops.py @@ -320,7 +320,7 @@ def test_ssh_into_instance__uses_private_address( call_mock.return_value = 0 sshops.ssh_into_instance('instance-id') - call_mock.assert_called_once_with(['ssh', '-i', 'aws-eb-us-west-2', 'ec2-user@172.31.35.210']) + call_mock.assert_called_once_with(['ssh', '-i', 'aws-eb-us-west-2', '-o', 'IdentitiesOnly yes', 'ec2-user@172.31.35.210']) @mock.patch('ebcli.operations.sshops.ec2.describe_instance') @mock.patch('ebcli.operations.sshops.ec2.describe_security_group') @@ -346,7 +346,7 @@ def test_ssh_into_instance__ssh_rule_exists( sshops.ssh_into_instance('instance-id') authorize_ssh_mock.assert_not_called() revoke_ssh_mock.assert_not_called() - call_mock.assert_called_once_with(['ssh', '-i', 'aws-eb-us-west-2', 'ec2-user@54.218.96.238']) + call_mock.assert_called_once_with(['ssh', '-i', 'aws-eb-us-west-2', '-o', 'IdentitiesOnly yes', 'ec2-user@54.218.96.238']) @mock.patch('ebcli.operations.sshops.ec2.describe_instance') @mock.patch('ebcli.operations.sshops.ec2.describe_security_group') @@ -372,7 +372,7 @@ def test_ssh_into_instance__no_ssh_rule_exists( sshops.ssh_into_instance('instance-id') authorize_ssh_mock.assert_called_once_with('sg-12312313') revoke_ssh_mock.assert_called_once_with('sg-12312313') - call_mock.assert_called_once_with(['ssh', '-i', 'aws-eb-us-west-2', 'ec2-user@54.218.96.238']) + call_mock.assert_called_once_with(['ssh', '-i', 'aws-eb-us-west-2', '-o', 'IdentitiesOnly yes', 'ec2-user@54.218.96.238']) @mock.patch('ebcli.operations.sshops.prompt_for_ec2_keyname') @mock.patch('ebcli.operations.sshops.commonops.update_environment') From c54af6033772c48b9f695b92a09fa0bcb75376b0 Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Fri, 27 Oct 2023 18:29:22 +0000 Subject: [PATCH 39/44] Fix for lb setting in saved_configs cr: https://code.amazon.com/reviews/CR-104851320 --- ebcli/controllers/create.py | 51 +++++++++++++++++++--- tests/unit/controllers/test_create.py | 63 ++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/ebcli/controllers/create.py b/ebcli/controllers/create.py index 041acfc92..570512487 100644 --- a/ebcli/controllers/create.py +++ b/ebcli/controllers/create.py @@ -13,7 +13,7 @@ import argparse import os import time - +import yaml from ebcli.core import io, fileoperations, hooks from ebcli.core.abstractcontroller import AbstractBaseController from ebcli.lib import elasticbeanstalk, utils, iam @@ -239,7 +239,7 @@ def do_command(self): cname = cname or get_environment_cname(env_name, provided_env_name, tier) key_name = key_name or commonops.get_default_keyname() vpc = self.form_vpc_object(tier, single) - elb_type = elb_type or get_elb_type_from_customer(interactive, single, tier) + elb_type = elb_type or get_elb_type_from_customer(interactive, single, tier,cfg_flag_used=cfg) shared_lb = get_shared_load_balancer(interactive, elb_type, platform, shared_lb, vpc) shared_lb_port = shared_lb_port or shared_lb_ops.get_shared_lb_port_from_customer(interactive, shared_lb) enable_spot = enable_spot or spotops.get_spot_request_from_customer(interactive) @@ -533,8 +533,48 @@ def get_cname_from_customer(env_name): break return cname +def get_elb_type_from_configs(use_saved_config=False): + """ + Retrieve the ELB type from either the .ebextensions or saved_configs directories. + :param use_saved_config: Boolean indicating if --cfg flag was used. + :return: ELB type if found, else None. + """ + + # If --cfg flag is used, prioritize checking saved_configs first + if use_saved_config: + saved_configs_dir = './.elasticbeanstalk/saved_configs' + if os.path.exists(saved_configs_dir): + for config_file in os.listdir(saved_configs_dir): + with open(os.path.join(saved_configs_dir, config_file), 'r') as f: + try: + config = yaml.safe_load(f) + option_settings = config.get('OptionSettings', {}) + for namespace, setting in option_settings.items(): + if namespace == 'aws:elasticbeanstalk:environment' and setting.get('LoadBalancerType'): + return True + except yaml.YAMLError: + raise ValueError(f"Malformed YAML in file {config_file} in saved_configs.") -def get_elb_type_from_customer(interactive, single, tier): + else: + # Then check in .ebextensions + ebextensions_dir = './.ebextensions' + if os.path.exists(ebextensions_dir): + for config_file in os.listdir(ebextensions_dir): + with open(os.path.join(ebextensions_dir, config_file), 'r') as f: + try: + config = yaml.safe_load(f) + option_settings = config.get('option_settings', []) + for setting in option_settings: + if setting.get('namespace') == 'aws:elasticbeanstalk:environment' and setting.get('option_name') == 'LoadBalancerType': + if setting.get('value'): + return True + except yaml.YAMLError: + raise ValueError(f"Malformed YAML in file {config_file} in .ebextensions.") + + + return None + +def get_elb_type_from_customer(interactive, single, tier, cfg_flag_used=False): """ Prompt customer to specify the ELB type if operating in the interactive mode and on a load-balanced environment. @@ -546,8 +586,9 @@ def get_elb_type_from_customer(interactive, single, tier): :param tier: the tier type of the environment :return: selected ELB type which is one among ['application', 'classic', 'network'] """ - if single or (tier and not tier.is_webserver()): - return + elb_type_is_configured = get_elb_type_from_configs(use_saved_config=cfg_flag_used) + if single or (tier and not tier.is_webserver()) or elb_type_is_configured: + return elif not interactive: return elb_names.APPLICATION_VERSION diff --git a/tests/unit/controllers/test_create.py b/tests/unit/controllers/test_create.py index fd5b05e5d..387a3f168 100644 --- a/tests/unit/controllers/test_create.py +++ b/tests/unit/controllers/test_create.py @@ -13,10 +13,14 @@ import os import shutil -import mock +#import mock import unittest +from unittest import mock +from unittest.mock import patch, mock_open from ebcli.controllers import create +from ebcli.controllers.create import get_elb_type_from_configs + from ebcli.core import fileoperations from ebcli.core.ebcore import EB from ebcli.objects.exceptions import ( @@ -2332,6 +2336,7 @@ def test_create_non_interactively_with_database_and_vpc_arguments( self.assertEnvironmentRequestsEqual(expected_environment_request, actual_environment_request) + class TestCreateModuleE2E(unittest.TestCase): def setUp(self): if not os.path.exists('testDir'): @@ -2373,6 +2378,61 @@ def test_get_template_name__config_file_name_passed_in( self.assertEqual('some_cfg', create.get_template_name('some_app', 'some_cfg')) + @mock.patch('yaml.safe_load') + @mock.patch('os.path.exists') + @mock.patch('os.listdir') + @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="valid_yaml_content") + def test_saved_configs_prioritized(self, mock_file, mock_listdir, mock_exists, mock_yaml): + mock_exists.return_value = True + mock_listdir.return_value = ['config1.yaml'] + mock_yaml.return_value = { + 'OptionSettings': { + 'aws:elasticbeanstalk:environment': { + 'LoadBalancerType': True + } + } + } + + self.assertTrue(get_elb_type_from_configs(use_saved_config=True)) + + @mock.patch('yaml.safe_load') + @mock.patch('os.path.exists') + @mock.patch('os.listdir') + @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="valid_yaml_content") + def test_ebextensions_checked(self, mock_file, mock_listdir, mock_exists, mock_yaml): + mock_exists.return_value = True + mock_listdir.return_value = ['config1.yaml'] + mock_yaml.return_value = { + 'option_settings': [ + { + 'namespace': 'aws:elasticbeanstalk:environment', + 'option_name': 'LoadBalancerType', + 'value': True + } + ] + } + + self.assertTrue(get_elb_type_from_configs(use_saved_config=False)) + + @mock.patch('os.path.exists') + @mock.patch('os.listdir') + @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="invalid: yaml: data") + def test_malformed_yaml_raises_value_error(self, mock_file, mock_listdir, mock_exists): + mock_exists.return_value = True + mock_listdir.return_value = ['config1.yaml'] + + with self.assertRaises(ValueError): + get_elb_type_from_configs(use_saved_config=False) + + @mock.patch('os.path.exists') + @mock.patch('os.listdir') + @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="option_settings: []") + def test_returns_none_when_elb_not_found(self, mock_file, mock_listdir, mock_exists): + mock_exists.return_value = True + mock_listdir.return_value = ['config1.yaml'] + + self.assertIsNone(get_elb_type_from_configs(use_saved_config=False)) + def test_get_elb_type_from_customer__single_instance_environment(self): self.assertIsNone( create.get_elb_type_from_customer( @@ -2388,6 +2448,7 @@ def test_get_elb_type_from_customer__non_interactive_mode(self): interactive=False, single=False, tier=None + ), 'application' ) From 9241ba3b84b805f6a0bf2bc6eb793ce4fba7492c Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Fri, 27 Oct 2023 20:49:12 +0000 Subject: [PATCH 40/44] Updating based on comments and added boundary test case --- ebcli/controllers/create.py | 10 ++++----- tests/unit/controllers/test_create.py | 29 ++++++++++++++++++++------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/ebcli/controllers/create.py b/ebcli/controllers/create.py index 570512487..539210f0b 100644 --- a/ebcli/controllers/create.py +++ b/ebcli/controllers/create.py @@ -533,11 +533,11 @@ def get_cname_from_customer(env_name): break return cname -def get_elb_type_from_configs(use_saved_config=False): +def check_elb_type_from_configs(use_saved_config=False): """ - Retrieve the ELB type from either the .ebextensions or saved_configs directories. + Checks if the ELB type is present from either the .ebextensions or saved_configs directories. :param use_saved_config: Boolean indicating if --cfg flag was used. - :return: ELB type if found, else None. + :return: True, else False. """ # If --cfg flag is used, prioritize checking saved_configs first @@ -572,7 +572,7 @@ def get_elb_type_from_configs(use_saved_config=False): raise ValueError(f"Malformed YAML in file {config_file} in .ebextensions.") - return None + return False def get_elb_type_from_customer(interactive, single, tier, cfg_flag_used=False): """ @@ -586,7 +586,7 @@ def get_elb_type_from_customer(interactive, single, tier, cfg_flag_used=False): :param tier: the tier type of the environment :return: selected ELB type which is one among ['application', 'classic', 'network'] """ - elb_type_is_configured = get_elb_type_from_configs(use_saved_config=cfg_flag_used) + elb_type_is_configured = check_elb_type_from_configs(use_saved_config=cfg_flag_used) if single or (tier and not tier.is_webserver()) or elb_type_is_configured: return elif not interactive: diff --git a/tests/unit/controllers/test_create.py b/tests/unit/controllers/test_create.py index 387a3f168..0100f38cd 100644 --- a/tests/unit/controllers/test_create.py +++ b/tests/unit/controllers/test_create.py @@ -19,7 +19,7 @@ from unittest.mock import patch, mock_open from ebcli.controllers import create -from ebcli.controllers.create import get_elb_type_from_configs +from ebcli.controllers.create import check_elb_type_from_configs from ebcli.core import fileoperations from ebcli.core.ebcore import EB @@ -2336,7 +2336,6 @@ def test_create_non_interactively_with_database_and_vpc_arguments( self.assertEnvironmentRequestsEqual(expected_environment_request, actual_environment_request) - class TestCreateModuleE2E(unittest.TestCase): def setUp(self): if not os.path.exists('testDir'): @@ -2393,7 +2392,7 @@ def test_saved_configs_prioritized(self, mock_file, mock_listdir, mock_exists, m } } - self.assertTrue(get_elb_type_from_configs(use_saved_config=True)) + self.assertTrue(check_elb_type_from_configs(use_saved_config=True)) @mock.patch('yaml.safe_load') @mock.patch('os.path.exists') @@ -2412,7 +2411,7 @@ def test_ebextensions_checked(self, mock_file, mock_listdir, mock_exists, mock_y ] } - self.assertTrue(get_elb_type_from_configs(use_saved_config=False)) + self.assertTrue(check_elb_type_from_configs(use_saved_config=False)) @mock.patch('os.path.exists') @mock.patch('os.listdir') @@ -2422,7 +2421,7 @@ def test_malformed_yaml_raises_value_error(self, mock_file, mock_listdir, mock_e mock_listdir.return_value = ['config1.yaml'] with self.assertRaises(ValueError): - get_elb_type_from_configs(use_saved_config=False) + check_elb_type_from_configs(use_saved_config=False) @mock.patch('os.path.exists') @mock.patch('os.listdir') @@ -2431,8 +2430,24 @@ def test_returns_none_when_elb_not_found(self, mock_file, mock_listdir, mock_exi mock_exists.return_value = True mock_listdir.return_value = ['config1.yaml'] - self.assertIsNone(get_elb_type_from_configs(use_saved_config=False)) + self.assertIsNone(check_elb_type_from_configs(use_saved_config=False)) + + @mock.patch('yaml.safe_load') + @mock.patch('os.path.exists') + @mock.patch('os.listdir') + @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="OptionSettings: {random_namespace: {random_option: random_value}}") + def test_setting_not_present_in_saved_configs(self, mock_file, mock_listdir, mock_exists, mock_yaml): + mock_exists.return_value = True + mock_listdir.return_value = ['config1.yaml'] + mock_yaml.return_value = { + 'random_namespace': { + 'random_option': 'random_value' + }} + + self.assertFalse(check_elb_type_from_configs(use_saved_config=True)) + + def test_get_elb_type_from_customer__single_instance_environment(self): self.assertIsNone( create.get_elb_type_from_customer( @@ -2448,7 +2463,7 @@ def test_get_elb_type_from_customer__non_interactive_mode(self): interactive=False, single=False, tier=None - + ), 'application' ) From 108f26ea96407ca5d676e0f03d0b79effefa3ffa Mon Sep 17 00:00:00 2001 From: Nihal Muktala Date: Fri, 27 Oct 2023 22:36:35 +0000 Subject: [PATCH 41/44] Revision 3 --- tests/unit/controllers/test_create.py | 39 ++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/tests/unit/controllers/test_create.py b/tests/unit/controllers/test_create.py index 0100f38cd..9db464ef1 100644 --- a/tests/unit/controllers/test_create.py +++ b/tests/unit/controllers/test_create.py @@ -2381,7 +2381,7 @@ def test_get_template_name__config_file_name_passed_in( @mock.patch('os.path.exists') @mock.patch('os.listdir') @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="valid_yaml_content") - def test_saved_configs_prioritized(self, mock_file, mock_listdir, mock_exists, mock_yaml): + def test_check_elb_type_from_configs_saved_configs_prioritized(self, mock_file, mock_listdir, mock_exists, mock_yaml): mock_exists.return_value = True mock_listdir.return_value = ['config1.yaml'] mock_yaml.return_value = { @@ -2398,7 +2398,7 @@ def test_saved_configs_prioritized(self, mock_file, mock_listdir, mock_exists, m @mock.patch('os.path.exists') @mock.patch('os.listdir') @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="valid_yaml_content") - def test_ebextensions_checked(self, mock_file, mock_listdir, mock_exists, mock_yaml): + def test_check_elb_type_from_configs_ebextensions_checked(self, mock_file, mock_listdir, mock_exists, mock_yaml): mock_exists.return_value = True mock_listdir.return_value = ['config1.yaml'] mock_yaml.return_value = { @@ -2416,7 +2416,7 @@ def test_ebextensions_checked(self, mock_file, mock_listdir, mock_exists, mock_y @mock.patch('os.path.exists') @mock.patch('os.listdir') @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="invalid: yaml: data") - def test_malformed_yaml_raises_value_error(self, mock_file, mock_listdir, mock_exists): + def test_check_elb_type_from_configs_malformed_yaml_raises_value_error(self, mock_file, mock_listdir, mock_exists): mock_exists.return_value = True mock_listdir.return_value = ['config1.yaml'] @@ -2426,7 +2426,7 @@ def test_malformed_yaml_raises_value_error(self, mock_file, mock_listdir, mock_e @mock.patch('os.path.exists') @mock.patch('os.listdir') @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="option_settings: []") - def test_returns_none_when_elb_not_found(self, mock_file, mock_listdir, mock_exists): + def test_check_elb_type_from_configs_returns_none_when_elb_not_found(self, mock_file, mock_listdir, mock_exists): mock_exists.return_value = True mock_listdir.return_value = ['config1.yaml'] @@ -2436,7 +2436,7 @@ def test_returns_none_when_elb_not_found(self, mock_file, mock_listdir, mock_exi @mock.patch('os.path.exists') @mock.patch('os.listdir') @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="OptionSettings: {random_namespace: {random_option: random_value}}") - def test_setting_not_present_in_saved_configs(self, mock_file, mock_listdir, mock_exists, mock_yaml): + def test_check_elb_type_from_configs_setting_not_present_in_saved_configs(self, mock_file, mock_listdir, mock_exists, mock_yaml): mock_exists.return_value = True mock_listdir.return_value = ['config1.yaml'] mock_yaml.return_value = { @@ -2447,7 +2447,7 @@ def test_setting_not_present_in_saved_configs(self, mock_file, mock_listdir, moc self.assertFalse(check_elb_type_from_configs(use_saved_config=True)) - + def test_get_elb_type_from_customer__single_instance_environment(self): self.assertIsNone( create.get_elb_type_from_customer( @@ -2524,6 +2524,33 @@ def test_get_elb_type_from_customer__interactive_mode__not_applicable_for_worker ) ) + @mock.patch('ebcli.controllers.create.check_elb_type_from_configs') + def test_get_elb_type_from_customer_elb_type_configured_interactive(self, mock_check_elb): + mock_check_elb.return_value = True # ELB type is configured + + result = create.get_elb_type_from_customer( + interactive=True, + single=False, + tier=None, + cfg_flag_used=True + ) + + self.assertIsNone(result) + + @mock.patch('ebcli.controllers.create.check_elb_type_from_configs') + def test__get_elb_type_from_customer_elb_type_configured_non_interactive(self, mock_check_elb): + mock_check_elb.return_value = True # ELB type is configured + + result = create.get_elb_type_from_customer( + interactive=False, + single=False, + tier=None, + cfg_flag_used=True + ) + + self.assertIsNone(result) + + @mock.patch('ebcli.controllers.create.shared_lb_ops.validate_shared_lb_for_non_interactive') @mock.patch('ebcli.controllers.create.shared_lb_ops.get_shared_lb_from_customer') def test_get_shared_load_balancer__for_interactive( From 069a2fb6eb9dcef9399e95cebb7a173900e0868e Mon Sep 17 00:00:00 2001 From: Rahul Rajaram Date: Thu, 17 Apr 2025 21:53:54 -0400 Subject: [PATCH 42/44] Revert changes in origin/master that are not in github/master --- ebcli/controllers/create.py | 51 ++------------ ebcli/core/fileoperations.py | 13 +--- ebcli/objects/region.py | 1 - tests/unit/controllers/test_create.py | 98 --------------------------- 4 files changed, 8 insertions(+), 155 deletions(-) diff --git a/ebcli/controllers/create.py b/ebcli/controllers/create.py index 1e4566c4d..5f08e0348 100644 --- a/ebcli/controllers/create.py +++ b/ebcli/controllers/create.py @@ -13,7 +13,7 @@ import argparse import os import time -import yaml + from ebcli.core import io, fileoperations, hooks from ebcli.core.abstractcontroller import AbstractBaseController from ebcli.lib import elasticbeanstalk, utils, iam @@ -239,7 +239,7 @@ def do_command(self): cname = cname or get_environment_cname(env_name, provided_env_name, tier) key_name = key_name or commonops.get_default_keyname() vpc = self.form_vpc_object(tier, single) - elb_type = elb_type or get_elb_type_from_customer(interactive, single, tier,cfg_flag_used=cfg) + elb_type = elb_type or get_elb_type_from_customer(interactive, single, tier) shared_lb = get_shared_load_balancer(interactive, elb_type, platform, shared_lb, vpc) shared_lb_port = shared_lb_port or shared_lb_ops.get_shared_lb_port_from_customer(interactive, shared_lb) enable_spot = enable_spot or spotops.get_spot_request_from_customer(interactive) @@ -533,48 +533,8 @@ def get_cname_from_customer(env_name): break return cname -def check_elb_type_from_configs(use_saved_config=False): - """ - Checks if the ELB type is present from either the .ebextensions or saved_configs directories. - :param use_saved_config: Boolean indicating if --cfg flag was used. - :return: True, else False. - """ - - # If --cfg flag is used, prioritize checking saved_configs first - if use_saved_config: - saved_configs_dir = './.elasticbeanstalk/saved_configs' - if os.path.exists(saved_configs_dir): - for config_file in os.listdir(saved_configs_dir): - with open(os.path.join(saved_configs_dir, config_file), 'r') as f: - try: - config = yaml.safe_load(f) - option_settings = config.get('OptionSettings', {}) - for namespace, setting in option_settings.items(): - if namespace == 'aws:elasticbeanstalk:environment' and setting.get('LoadBalancerType'): - return True - except yaml.YAMLError: - raise ValueError(f"Malformed YAML in file {config_file} in saved_configs.") - else: - # Then check in .ebextensions - ebextensions_dir = './.ebextensions' - if os.path.exists(ebextensions_dir): - for config_file in os.listdir(ebextensions_dir): - with open(os.path.join(ebextensions_dir, config_file), 'r') as f: - try: - config = yaml.safe_load(f) - option_settings = config.get('option_settings', []) - for setting in option_settings: - if setting.get('namespace') == 'aws:elasticbeanstalk:environment' and setting.get('option_name') == 'LoadBalancerType': - if setting.get('value'): - return True - except yaml.YAMLError: - raise ValueError(f"Malformed YAML in file {config_file} in .ebextensions.") - - - return False - -def get_elb_type_from_customer(interactive, single, tier, cfg_flag_used=False): +def get_elb_type_from_customer(interactive, single, tier): """ Prompt customer to specify the ELB type if operating in the interactive mode and on a load-balanced environment. @@ -586,9 +546,8 @@ def get_elb_type_from_customer(interactive, single, tier, cfg_flag_used=False): :param tier: the tier type of the environment :return: selected ELB type which is one among ['application', 'classic', 'network'] """ - elb_type_is_configured = check_elb_type_from_configs(use_saved_config=cfg_flag_used) - if single or (tier and not tier.is_webserver()) or elb_type_is_configured: - return + if single or (tier and not tier.is_webserver()): + return elif not interactive: return elb_names.APPLICATION_VERSION diff --git a/ebcli/core/fileoperations.py b/ebcli/core/fileoperations.py index 0e75a18ff..79e319cdf 100644 --- a/ebcli/core/fileoperations.py +++ b/ebcli/core/fileoperations.py @@ -15,7 +15,6 @@ import glob import json import os -import pathlib import shutil import stat import sys @@ -24,7 +23,6 @@ import zipfile from cement.utils.misc import minimal_logger -from ebcli.core import fileoperations from ebcli.objects.buildconfiguration import BuildConfiguration from pathspec import PathSpec from six import StringIO @@ -390,19 +388,14 @@ def delete_app_versions(): def zip_append_archive(target_file, source_file): zip_source = zipfile.ZipFile(source_file, 'r', allowZip64=True) zip_target = zipfile.ZipFile(target_file, 'a', allowZip64=True) - # Because zipfile doesn't preserve file permission code, we have to extract and then zip up using shutil - tmp_folder_name = 'tmpAppendFolder' with warnings.catch_warnings(): # Ignore UserWarning raised by zip module for zipping modules. warnings.simplefilter('ignore', category=UserWarning) - zip_source.extractall(tmp_folder_name) - zip_target.extractall(tmp_folder_name) + for filename in zip_source.namelist(): + zf = zip_source.read(filename) + zip_target.writestr(filename, zf) zip_target.close() zip_source.close() - delete_file(target_file) - path_without_suffix = pathlib.Path(target_file).with_suffix('') - shutil.make_archive(path_without_suffix, 'zip', tmp_folder_name) - delete_directory(tmp_folder_name) def zip_up_folder(directory, location, ignore_list=None): diff --git a/ebcli/objects/region.py b/ebcli/objects/region.py index ca12feef0..b6473abdb 100644 --- a/ebcli/objects/region.py +++ b/ebcli/objects/region.py @@ -35,7 +35,6 @@ def get_all_regions(): Region('eu-south-1', 'EU (Milano)'), Region('ap-east-1', 'Asia Pacific (Hong Kong)'), Region('me-south-1', 'Middle East (Bahrain)'), - Region('il-central-1', 'Middle East (Israel)'), Region('af-south-1', 'Africa (Cape Town)'), Region('ap-southeast-3', 'Asia Pacific (Jakarta)'), Region('ap-northeast-3', 'Asia Pacific (Osaka)'), diff --git a/tests/unit/controllers/test_create.py b/tests/unit/controllers/test_create.py index 0322e44ba..93057c471 100644 --- a/tests/unit/controllers/test_create.py +++ b/tests/unit/controllers/test_create.py @@ -2376,77 +2376,6 @@ def test_get_template_name__config_file_name_passed_in( self.assertEqual('some_cfg', create.get_template_name('some_app', 'some_cfg')) - @mock.patch('yaml.safe_load') - @mock.patch('os.path.exists') - @mock.patch('os.listdir') - @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="valid_yaml_content") - def test_check_elb_type_from_configs_saved_configs_prioritized(self, mock_file, mock_listdir, mock_exists, mock_yaml): - mock_exists.return_value = True - mock_listdir.return_value = ['config1.yaml'] - mock_yaml.return_value = { - 'OptionSettings': { - 'aws:elasticbeanstalk:environment': { - 'LoadBalancerType': True - } - } - } - - self.assertTrue(check_elb_type_from_configs(use_saved_config=True)) - - @mock.patch('yaml.safe_load') - @mock.patch('os.path.exists') - @mock.patch('os.listdir') - @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="valid_yaml_content") - def test_check_elb_type_from_configs_ebextensions_checked(self, mock_file, mock_listdir, mock_exists, mock_yaml): - mock_exists.return_value = True - mock_listdir.return_value = ['config1.yaml'] - mock_yaml.return_value = { - 'option_settings': [ - { - 'namespace': 'aws:elasticbeanstalk:environment', - 'option_name': 'LoadBalancerType', - 'value': True - } - ] - } - - self.assertTrue(check_elb_type_from_configs(use_saved_config=False)) - - @mock.patch('os.path.exists') - @mock.patch('os.listdir') - @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="invalid: yaml: data") - def test_check_elb_type_from_configs_malformed_yaml_raises_value_error(self, mock_file, mock_listdir, mock_exists): - mock_exists.return_value = True - mock_listdir.return_value = ['config1.yaml'] - - with self.assertRaises(ValueError): - check_elb_type_from_configs(use_saved_config=False) - - @mock.patch('os.path.exists') - @mock.patch('os.listdir') - @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="option_settings: []") - def test_check_elb_type_from_configs_returns_none_when_elb_not_found(self, mock_file, mock_listdir, mock_exists): - mock_exists.return_value = True - mock_listdir.return_value = ['config1.yaml'] - - self.assertIsNone(check_elb_type_from_configs(use_saved_config=False)) - - @mock.patch('yaml.safe_load') - @mock.patch('os.path.exists') - @mock.patch('os.listdir') - @mock.patch('builtins.open', new_callable=mock.mock_open, read_data="OptionSettings: {random_namespace: {random_option: random_value}}") - def test_check_elb_type_from_configs_setting_not_present_in_saved_configs(self, mock_file, mock_listdir, mock_exists, mock_yaml): - mock_exists.return_value = True - mock_listdir.return_value = ['config1.yaml'] - mock_yaml.return_value = { - 'random_namespace': { - 'random_option': 'random_value' - }} - - self.assertFalse(check_elb_type_from_configs(use_saved_config=True)) - - - def test_get_elb_type_from_customer__single_instance_environment(self): self.assertIsNone( create.get_elb_type_from_customer( @@ -2523,33 +2452,6 @@ def test_get_elb_type_from_customer__interactive_mode__not_applicable_for_worker ) ) - @mock.patch('ebcli.controllers.create.check_elb_type_from_configs') - def test_get_elb_type_from_customer_elb_type_configured_interactive(self, mock_check_elb): - mock_check_elb.return_value = True # ELB type is configured - - result = create.get_elb_type_from_customer( - interactive=True, - single=False, - tier=None, - cfg_flag_used=True - ) - - self.assertIsNone(result) - - @mock.patch('ebcli.controllers.create.check_elb_type_from_configs') - def test__get_elb_type_from_customer_elb_type_configured_non_interactive(self, mock_check_elb): - mock_check_elb.return_value = True # ELB type is configured - - result = create.get_elb_type_from_customer( - interactive=False, - single=False, - tier=None, - cfg_flag_used=True - ) - - self.assertIsNone(result) - - @mock.patch('ebcli.controllers.create.shared_lb_ops.validate_shared_lb_for_non_interactive') @mock.patch('ebcli.controllers.create.shared_lb_ops.get_shared_lb_from_customer') def test_get_shared_load_balancer__for_interactive( From 67e85dc7c8d43098f6fbb9c79615f21614bb4770 Mon Sep 17 00:00:00 2001 From: Rahul Rajaram Date: Thu, 1 May 2025 11:40:40 -0400 Subject: [PATCH 43/44] Fix bug that causes eb on non-IIS server Windows machines to crash --- ebcli/controllers/migrate.py | 56 ++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/ebcli/controllers/migrate.py b/ebcli/controllers/migrate.py index 188cff022..7f46d84fa 100644 --- a/ebcli/controllers/migrate.py +++ b/ebcli/controllers/migrate.py @@ -24,33 +24,39 @@ import json import argparse +IIS_MODULES_ARE_AVAILABLE = False if sys.platform.startswith("win"): import winreg import clr import win32com.client - clr.AddReference("System.Reflection") - clr.AddReference(r"C:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll") - clr.AddReference("System") - clr.AddReference("System.Core") - clr.AddReference("System.DirectoryServices.AccountManagement") - from System.DirectoryServices.AccountManagement import ( - PrincipalContext, - ContextType, - UserPrincipal, - PrincipalSearcher, - ) - from System.Collections.Generic import HashSet, Queue - from System.Reflection import Assembly - from Microsoft.Web.Administration import ( - ServerManager, - Binding, - Site, - Application, - ObjectState, - ) - from System.Diagnostics import Process, ProcessStartInfo - from System.Runtime.InteropServices import COMException + try: + clr.AddReference("System.Reflection") + clr.AddReference(r"C:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll") + clr.AddReference("System") + clr.AddReference("System.Core") + clr.AddReference("System.DirectoryServices.AccountManagement") + from System.DirectoryServices.AccountManagement import ( + PrincipalContext, + ContextType, + UserPrincipal, + PrincipalSearcher, + ) + from System.Collections.Generic import HashSet, Queue + from System.Reflection import Assembly + from Microsoft.Web.Administration import ( + ServerManager, + Binding, + Site, + Application, + ObjectState, + ) + from System.Diagnostics import Process, ProcessStartInfo + from System.Runtime.InteropServices import COMException + + IIS_MODULES_ARE_AVAILABLE = True + except Exception as e: + print(f"Failed to import DLL or MS Server Python module. Sure necessary modules are installed and enabled on your server: {e}") from cement.utils.misc import minimal_logger @@ -84,6 +90,8 @@ class Meta: def do_command(self): if not sys.platform.startswith("win"): raise NotSupportedError("'eb migrate explore' is only supported on Windows") + if not IIS_MODULES_ARE_AVAILABLE: + raise NotSupportedError("'eb migrate explore' couldn't find one or more IIS modules or features. Ensure access to Microsoft.Web.Administration.dll") verbose = self.app.pargs.verbose if verbose: @@ -107,6 +115,8 @@ class Meta: def do_command(self): if not sys.platform.startswith("win"): raise NotSupportedError("'eb migrate cleanup' is only supported on Windows") + if not IIS_MODULES_ARE_AVAILABLE: + raise NotSupportedError("'eb migrate explore' couldn't find one or more IIS modules or features. Ensure access to Microsoft.Web.Administration.dll") force = self.app.pargs.force cleanup_previous_migration_artifacts(force, self.app.pargs.verbose) @@ -281,6 +291,8 @@ def generate_ms_deploy_source_bundle( def do_command(self): if not sys.platform.startswith("win"): raise NotSupportedError("'eb migrate' is only supported on Windows") + if not IIS_MODULES_ARE_AVAILABLE: + raise NotSupportedError("'eb migrate explore' couldn't find one or more IIS modules or features. Ensure access to Microsoft.Web.Administration.dll") validate_iis_version_greater_than_7_0() verbose = self.app.pargs.verbose From fefaa532924e1b1e7d2027f4e571eb1f5723d401 Mon Sep 17 00:00:00 2001 From: Rahul Rajaram Date: Thu, 1 May 2025 13:55:09 -0400 Subject: [PATCH 44/44] Update string messages, CHANGES.rst and version number to 3.23.3 --- CHANGES.rst | 5 +++++ ebcli/__init__.py | 2 +- ebcli/controllers/migrate.py | 13 ++++++------- ebcli/resources/strings.py | 23 ++++++++++++++--------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 13b16c0b9..a66dda59a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ Changelog ========= +-------------------- +3.23.3 (2025-05-01) +-------------------- +- Fixed bug that causes eb on non-IIS server Windows machines to crash + -------------------- 3.23.2 (2025-04-22) -------------------- diff --git a/ebcli/__init__.py b/ebcli/__init__.py index 4144cbbb1..06bb3b37a 100644 --- a/ebcli/__init__.py +++ b/ebcli/__init__.py @@ -11,4 +11,4 @@ # 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. -__version__ = '3.23.2' +__version__ = '3.23.3' diff --git a/ebcli/controllers/migrate.py b/ebcli/controllers/migrate.py index 7f46d84fa..0e348f7a1 100644 --- a/ebcli/controllers/migrate.py +++ b/ebcli/controllers/migrate.py @@ -126,7 +126,7 @@ class MigrateController(AbstractBaseController): class Meta: argument_formatter = argparse.RawTextHelpFormatter label = "migrate" - description = "This is an experimental command that enables you to migrate an IIS site from a source machine to Elastic Beanstalk" + description = "This command migrates an IIS site or application from a source Windows machine to an environment hosted on AWS Elastic Beanstalk." usage = "eb migrate [options ...]" arguments = [ (["-s", "--sites"], dict(help=flag_text["migrate.sites"])), @@ -607,7 +607,7 @@ def generate_upload_target_archive(upload_target_dir, env_name, region): except NotFoundError: io.echo( f"\nGenerated destination archive directory at .\\{relative_normalized_upload_target_dir_path}.zip. " - "You can create en environment with the zip using:\n\n" + "You can create an environment with the zip using:\n\n" f" eb migrate --environment-name {env_name} --archive .\\migrations\\latest\\upload_target.zip --region {region}\n" ) @@ -1090,7 +1090,7 @@ def setup_migrations_dir(verbose: bool) -> str: ) io.echo(f" .\\{relative_normalized_path}\\error.log -> msbuild.exe error logs") io.echo( - f" .\\{relative_normalized_path}\\upload_target\\ -> destination archive dir" + f" .\\{relative_normalized_path}\\upload_target\\ -> destination archive directory" ) return latest_migration_run_path @@ -1625,7 +1625,7 @@ def do_ms_deploy_sync_application( else: io.log_error(f"MSDeploy process exited with code {process.ExitCode}.") raise RuntimeError( - f"MSDeploy process exited with code {process.ExitCode}. You can find execution logs at .\\migrations\\latest\\error.log')" + f"MSDeploy process exited with code {process.ExitCode}. You can find execution logs at .\\migrations\\latest\\error.log" ) @@ -1773,8 +1773,7 @@ def warn_about_password_protection(site, application): for vdir in application.VirtualDirectories: if vdir.Password: io.log_warning( - f"{_iis_application_name_value} is hosted at {vdir.PhysicalPath} " - "which is password-protected and won't be copied." + f"Cannot copy virtual directory associated with site because it is password protected: Site [{site.Name}/{_iis_application_name_value}], Path hosting:[{vdir.PhysicalPath}]" ) except AttributeError: pass @@ -1903,7 +1902,7 @@ def export_arr_config(upload_target_dir: str, verbose: bool) -> None: "system.webServer/caching", ] if not _arr_enabled() and verbose: - io.echo("No Automatic Request Routing configuration found.") + io.echo("No Automatic Request Routing (ARR) configuration found.") return else: io.echo("Automatic Request Routing (ARR) configuration found.") diff --git a/ebcli/resources/strings.py b/ebcli/resources/strings.py index c22f6b6e8..a1d99c549 100644 --- a/ebcli/resources/strings.py +++ b/ebcli/resources/strings.py @@ -705,7 +705,9 @@ 'sharedlb.shared_load_balancer_prompt': 'Select a shared load balancer', 'sharedlb.listener_prompt': 'Select a listener port for your shared load balancer', - 'migrate.should_cleanup': 'Are you sure you would like to cleanup older artifacts within `./migrations/`?', + 'migrate.should_cleanup': "Are you sure you want to clean up older artifacts within the\n" + "'./migrations/directory'? The most recent successful migration\n" + "in directory './migrations/latest' will be preserved.", } alerts = { @@ -867,26 +869,28 @@ 'migrate.sites': 'Specify a comma-separated list of IIS sites to migrate. If not specified,\n' 'migrates all available sites on the server.', - 'migrate.environment_name': 'Name for the new Elastic Beanstalk environment. Defaults to EBMigratedApp.', - 'migrate.application_name': 'Name for the new Elastic Beanstalk application. Defaults to EBMigratedEnv.', + 'migrate.environment_name': 'Name for the new Elastic Beanstalk environment. Defaults to EBMigratedEnv.', + 'migrate.application_name': 'Name for the new Elastic Beanstalk application. Defaults to EBMigratedApp.', 'migrate.platform': 'Elastic Beanstalk platform runtime for the environment. If not specified,\n' 'automatically detected from host VM or application.\n' - 'Example: "64bit Windows Server 2016 v2.16.2 running IIS 10.0"', + 'Example: "64bit Windows Server 2016 v2.16.2 running IIS 10.0".' + "For a list of available platform versions, use the command 'eb platform list'.", 'migrate.execution_role': 'IAM role for executing eb migrate. Uses credentials from:\n' '1. ~/.aws/config\n' '2. AWS CLI credential chain (if config not found)', 'migrate.instance_type': 'EC2 instance type for the Elastic Beanstalk environment. Defaults to c5.2xlarge.', 'migrate.cname': 'CNAME prefix for the Elastic Beanstalk environment.', - 'migrate.instance_profile': 'Instance Profile to associate with the environment\'s EC2 instances.', - 'migrate.service_role': 'IAM service role for Elastic Beanstalk to manage related AWS services.', + 'migrate.instance_profile': 'Instance Profile to associate with the EC2 instances running on the environment.', + 'migrate.service_role': 'The name of the IAM service role for Elastic Beanstalk to manage related AWS services.\n ' + 'If not specified, creates a default service role with necessary permissions.', 'migrate.ebs_snapshots': 'Comma-separated list of EBS snapshot IDs to associate with the environment.', 'migrate.stream_to_cloudwatch': 'Stream EB CLI debug logs and execution metrics to CloudWatch.', 'migrate.use_host_ebs_configuration': 'Generate EBS snapshots from volumes attached to the current instance.', 'migrate.keyname': 'EC2 key pair to enable SSH/RDP access to environment instances.\n' 'Useful for investigating instance-level issues not visible in logs.', - 'migrate.interactive': 'Force interactive mode for the migration process.', + 'migrate.interactive': 'Force interactive mode for the migration process. Prompts for configuration values even when defaults are available.', 'migrate.tags': 'Comma-separated list of key=value pairs to tag new resources:\n' '- Elastic Beanstalk application\n' '- Environment\n' @@ -907,10 +911,11 @@ 'migrate.encrypt_ebs_volumes': 'Enforce encryption for all new EBS volumes.\n' 'Note: This is an account-wide setting that affects all future\n' 'EBS volume creation.', - 'migrate.ssl_certificate_arns': 'Comma-Separated list of Amazon Certificate Manager (ACM) SSL certificate\n' + 'migrate.ssl_certificate_arns': 'Comma-separated list of ARNs for Amazon Certificate Manager (ACM) SSL certificate\n' 'ARN to associate with the Application Load Balancer.', 'migrate.archive': 'The directory or the ZIP file containing source code that\n' - '`eb migrate --archive-only` previously generated.', + '`eb migrate --archive-only` previously generated. Use this\n' + 'option to deploy a previously created migration package.', 'migrate.vpc_config': """VPC config for the environment either in the form of a JSON file or' a string. In both cases, config must have the format: {