From 9fb4ce59c4d366e15cf5fddd2b8ae76e386bb711 Mon Sep 17 00:00:00 2001 From: Vincent Liu <5900509+onecent1101@users.noreply.github.com> Date: Fri, 28 May 2021 12:19:17 -0700 Subject: [PATCH] [LYN-2151] Add argument to override aws profile and config file path (#994) --- .../Code/Include/Private/AWSCoreInternalBus.h | 5 ++++ .../Configuration/AWSCoreConfiguration.h | 1 + .../Configuration/AWSCoreConfiguration.cpp | 15 +++++++++- .../UI/AWSCoreResourceMappingToolAction.cpp | 16 ++++++++--- .../AWSDefaultCredentialHandlerTest.cpp | 4 ++- .../AWSResourceMappingManagerTest.cpp | 1 + .../manager/configuration_manager.py | 9 ++++-- .../resource_mapping_tool.py | 8 +++++- .../manager/test_configuration_manager.py | 28 ++++++++++++++++++- .../tests/unit/manager/test_view_manager.py | 2 +- .../tests/unit/utils/test_aws_utils.py | 7 ++--- .../ResourceMappingTool/utils/aws_utils.py | 13 +++++++-- 12 files changed, 90 insertions(+), 19 deletions(-) diff --git a/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h b/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h index 738d41c796..27487ed481 100644 --- a/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h +++ b/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h @@ -38,6 +38,11 @@ namespace AWSCore //! @return The path of AWS resource mapping config file virtual AZStd::string GetResourceMappingConfigFilePath() const = 0; + //! GetResourceMappingConfigFolderPath + //! Get the path of AWS resource mapping config folder + //! @return The path of AWS resource mapping config folder + virtual AZStd::string GetResourceMappingConfigFolderPath() const = 0; + //! ReloadConfiguration //! Reload AWSCore configuration without restarting application virtual void ReloadConfiguration() = 0; diff --git a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h index bd30af3ce7..92082617b7 100644 --- a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h +++ b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h @@ -53,6 +53,7 @@ namespace AWSCore // AWSCoreInternalRequestBus interface implementation AZStd::string GetResourceMappingConfigFilePath() const override; + AZStd::string GetResourceMappingConfigFolderPath() const override; AZStd::string GetProfileName() const override; void ReloadConfiguration() override; diff --git a/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp b/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp index 3c0f48c058..b22749dbaa 100644 --- a/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp +++ b/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp @@ -58,6 +58,19 @@ namespace AWSCore return configFilePath; } + AZStd::string AWSCoreConfiguration::GetResourceMappingConfigFolderPath() const + { + if (m_sourceProjectFolder.empty()) + { + AZ_Warning(AWSCoreConfigurationName, false, ProjectSourceFolderNotFoundErrorMessage); + return ""; + } + AZStd::string configFolderPath = AZStd::string::format( + "%s/%s", m_sourceProjectFolder.c_str(), AWSCoreResourceMappingConfigFolderName); + AzFramework::StringFunc::Path::Normalize(configFolderPath); + return configFolderPath; + } + void AWSCoreConfiguration::InitConfig() { InitSourceProjectFolderPath(); @@ -123,7 +136,7 @@ namespace AWSCore auto profileNamePath = AZStd::string::format("%s%s", AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreProfileNameKey); m_settingsRegistry.Remove(profileNamePath); - m_profileName.clear(); + m_profileName = AWSCoreDefaultProfileName; auto resourceMappingConfigFileNamePath = AZStd::string::format("%s%s", AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreResourceMappingConfigFileNameKey); diff --git a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp index fb46a8a700..18d437a66c 100644 --- a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp +++ b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp @@ -14,6 +14,7 @@ #include #include +#include #include namespace AWSCore @@ -108,17 +109,24 @@ namespace AWSCore { return ""; } + + AZStd::string profileName = "default"; + AWSCoreInternalRequestBus::BroadcastResult(profileName, &AWSCoreInternalRequests::GetProfileName); + + AZStd::string configPath = ""; + AWSCoreInternalRequestBus::BroadcastResult(configPath, &AWSCoreInternalRequests::GetResourceMappingConfigFolderPath); + if (m_isDebug) { return AZStd::string::format( - "%s debug %s --binaries_path %s --debug", - m_enginePythonEntryPath.c_str(), m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str()); + "%s debug %s --binaries_path %s --debug --profile %s --config_path %s", m_enginePythonEntryPath.c_str(), + m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str(), profileName.c_str(), configPath.c_str()); } else { return AZStd::string::format( - "%s %s --binaries_path %s", - m_enginePythonEntryPath.c_str(), m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str()); + "%s %s --binaries_path %s --profile %s --config_path %s", m_enginePythonEntryPath.c_str(), + m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str(), profileName.c_str(), configPath.c_str()); } } diff --git a/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp b/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp index 297e56fc33..7f00adaae7 100644 --- a/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp +++ b/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp @@ -34,7 +34,8 @@ public: MOCK_METHOD0(GetAWSCredentials, Aws::Auth::AWSCredentials()); }; -class AWSDefaultCredentialHandlerMock : public AWSDefaultCredentialHandler +class AWSDefaultCredentialHandlerMock + : public AWSDefaultCredentialHandler { public: void SetupMocks( @@ -76,6 +77,7 @@ public: // AWSCoreInternalRequestBus interface implementation AZStd::string GetProfileName() const override { return m_profileName; } AZStd::string GetResourceMappingConfigFilePath() const override { return ""; } + AZStd::string GetResourceMappingConfigFolderPath() const override { return ""; } void ReloadConfiguration() override {} std::shared_ptr m_environmentCredentialsProviderMock; diff --git a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp index 3adebc9a24..b46a840d40 100644 --- a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp +++ b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp @@ -119,6 +119,7 @@ public: // AWSCoreInternalRequestBus interface implementation AZStd::string GetProfileName() const override { return ""; } AZStd::string GetResourceMappingConfigFilePath() const override { return m_normalizedConfigFilePath; } + AZStd::string GetResourceMappingConfigFolderPath() const override { return m_normalizedConfigFolderPath; } void ReloadConfiguration() override { m_reloadConfigurationCounter++; } AZStd::unique_ptr m_resourceMappingManager; diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py index ee305c750a..b679921196 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py @@ -48,11 +48,14 @@ class ConfigurationManager(object): def configuration(self, new_configuration: ConfigurationManager) -> None: self._configuration = new_configuration - def setup(self) -> None: + def setup(self, config_path: str) -> None: logger.info("Setting up default configuration ...") - # TODO: remove config directory and files default setup once integrating with user input try: - self._configuration.config_directory = file_utils.get_current_directory_path() + normalized_config_path: str = file_utils.normalize_file_path(config_path); + if normalized_config_path: + self._configuration.config_directory = normalized_config_path + else: + self._configuration.config_directory = file_utils.get_current_directory_path() self._configuration.config_files = \ file_utils.find_files_with_suffix_under_directory(self._configuration.config_directory, constants.RESOURCE_MAPPING_CONFIG_FILE_NAME_SUFFIX) diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py index 0ee7455e6c..e99bf5d441 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py @@ -13,13 +13,16 @@ from argparse import (ArgumentParser, Namespace) import logging import sys +from utils import aws_utils from utils import environment_utils from utils import file_utils # arguments setup argument_parser: ArgumentParser = ArgumentParser() argument_parser.add_argument('--binaries_path', help='Path to QT Binaries necessary for PySide.') +argument_parser.add_argument('--config_path', help='Path to resource mapping config directory.') argument_parser.add_argument('--debug', action='store_true', help='Execute on debug mode to enable DEBUG logging level') +argument_parser.add_argument('--profile', default='default', help='Named AWS profile to use for querying AWS resources') arguments: Namespace = argument_parser.parse_args() # logging setup @@ -70,9 +73,12 @@ if __name__ == "__main__": except FileNotFoundError: logger.warning("Failed to load style sheet for resource mapping tool") + logger.info("Initializing boto3 default session ...") + aws_utils.setup_default_session(arguments.profile) + logger.info("Initializing configuration manager ...") configuration_manager: ConfigurationManager = ConfigurationManager() - configuration_manager.setup() + configuration_manager.setup(arguments.config_path) logger.info("Initializing thread manager ...") thread_manager: ThreadManager = ThreadManager() diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py index a9dcf97af9..552f9fff01 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py @@ -43,7 +43,7 @@ class TestConfigurationManager(TestCase): mock_find_files_with_suffix_under_directory: MagicMock, mock_get_default_account_id: MagicMock, mock_get_default_region: MagicMock) -> None: - TestConfigurationManager._expected_configuration_manager.setup() + TestConfigurationManager._expected_configuration_manager.setup("") mock_get_current_directory_path.assert_called_once() mock_check_path_exists.assert_called_once_with(TestConfigurationManager._expected_directory_path) mock_find_files_with_suffix_under_directory.assert_called_once_with( @@ -58,3 +58,29 @@ class TestConfigurationManager(TestCase): TestConfigurationManager._expected_account_id assert TestConfigurationManager._expected_configuration_manager.configuration.region == \ TestConfigurationManager._expected_region + + @patch("utils.aws_utils.get_default_region", return_value=_expected_region) + @patch("utils.aws_utils.get_default_account_id", return_value=_expected_account_id) + @patch("utils.file_utils.find_files_with_suffix_under_directory", return_value=_expected_config_files) + @patch("utils.file_utils.check_path_exists", return_value=True) + @patch("utils.file_utils.normalize_file_path", return_value=_expected_directory_path) + def test_setup_get_configuration_setup_with_path_as_expected(self, mock_normalize_file_path: MagicMock, + mock_check_path_exists: MagicMock, + mock_find_files_with_suffix_under_directory: MagicMock, + mock_get_default_account_id: MagicMock, + mock_get_default_region: MagicMock) -> None: + TestConfigurationManager._expected_configuration_manager.setup(TestConfigurationManager._expected_directory_path) + mock_normalize_file_path.assert_called_once() + mock_check_path_exists.assert_called_once_with(TestConfigurationManager._expected_directory_path) + mock_find_files_with_suffix_under_directory.assert_called_once_with( + TestConfigurationManager._expected_directory_path, constants.RESOURCE_MAPPING_CONFIG_FILE_NAME_SUFFIX) + mock_get_default_account_id.assert_called_once() + mock_get_default_region.assert_called_once() + assert TestConfigurationManager._expected_configuration_manager.configuration.config_directory == \ + TestConfigurationManager._expected_directory_path + assert TestConfigurationManager._expected_configuration_manager.configuration.config_files == \ + TestConfigurationManager._expected_config_files + assert TestConfigurationManager._expected_configuration_manager.configuration.account_id == \ + TestConfigurationManager._expected_account_id + assert TestConfigurationManager._expected_configuration_manager.configuration.region == \ + TestConfigurationManager._expected_region diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py index eb8304182f..e5c84c6579 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py @@ -36,7 +36,7 @@ class TestViewManager(TestCase): main_window_patcher: patch = patch("manager.view_manager.QMainWindow") cls._mock_main_window = main_window_patcher.start() - window_icon_patcher: patch = patch("manager.view_manager.QPixmap") + window_icon_patcher: patch = patch("manager.view_manager.QIcon") window_icon_patcher.start() stacked_pages_patcher: patch = patch("manager.view_manager.QStackedWidget") diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py index 7244431e76..51bd6ade23 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py @@ -37,13 +37,12 @@ class TestAWSUtils(TestCase): .build() def setUp(self) -> None: - client_patcher: patch = patch("boto3.client") - self.addCleanup(client_patcher.stop) - self._mock_client: MagicMock = client_patcher.start() - session_patcher: patch = patch("boto3.session.Session") self.addCleanup(session_patcher.stop) self._mock_session: MagicMock = session_patcher.start() + self._mock_client: MagicMock = self._mock_session.return_value.client + + aws_utils.setup_default_session("default") def test_get_default_account_id_return_expected_account_id(self) -> None: mocked_sts_client: MagicMock = self._mock_client.return_value diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py index 329d3ff44e..b0c3c9c1b3 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py @@ -26,6 +26,8 @@ aws account, region, resources, etc. _PAGINATION_MAX_ITEMS: int = 10 _PAGINATION_PAGE_SIZE: int = 10 +default_session: boto3.session.Session = None + class AWSConstants(object): CLOUDFORMATION_SERVICE_NAME: str = "cloudformation" @@ -53,15 +55,20 @@ def _close_client_connection(client: BaseClient) -> None: def _initialize_boto3_aws_client(service: str, region: str = "") -> BaseClient: if region: - boto3_client: BaseClient = boto3.client(service, region_name=region) + boto3_client: BaseClient = default_session.client(service, region_name=region) else: - boto3_client: BaseClient = boto3.client(service) + boto3_client: BaseClient = default_session.client(service) boto3_client.meta.events.register( f"after-call.{service}.*", lambda **kwargs: _close_client_connection(boto3_client) ) return boto3_client +def setup_default_session(profile: str) -> None: + global default_session + default_session = boto3.session.Session(profile_name=profile) + + def get_default_account_id() -> str: sts_client: BaseClient = _initialize_boto3_aws_client(AWSConstants.STS_SERVICE_NAME) try: @@ -72,7 +79,7 @@ def get_default_account_id() -> str: def get_default_region() -> str: - region: str = boto3.session.Session().region_name + region: str = default_session.region_name if region: return region