diff --git a/AutomatedTesting/Gem/PythonTests/AWS/README.md b/AutomatedTesting/Gem/PythonTests/AWS/README.md new file mode 100644 index 0000000000..8b5e65d6d7 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/README.md @@ -0,0 +1,26 @@ +# AWS Gem Automation Tests + +## Prerequisites +1. Build the O3DE Editor and AutomatedTesting.GameLauncher in Profile. +2. AWS CLI is installed and configured following [Configuration and Credential File Settings](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). +3. [AWS Cloud Development Kit (CDK)](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_install) is installed. + +## Deploy CDK Applications +1. Go to the AWS IAM console and create an IAM role called o3de-automation-tests which adds your own account as as a trusted entity and uses the "AdministratorAccess" permissions policy. +2. Copy {engine_root}\scripts\build\Platform\Windows\deploy_cdk_applications.cmd to your engine root folder. +3. Open a Command Prompt window at the engine root and set the following environment variables: + Set O3DE_AWS_PROJECT_NAME=AWSAUTO + Set O3DE_AWS_DEPLOY_REGION=us-east-1 + Set ASSUME_ROLE_ARN="arn:aws:iam::{your_aws_account_id}:role/o3de-automation-tests" + Set COMMIT_ID=HEAD +4. Deploy the CDK applications for AWS gems by running deploy_cdk_applications.cmd in the same Command Prompt window. +5. Edit AWS\common\constants.py to replace the assume role ARN with your own: + arn:aws:iam::{your_aws_account_id}:role/o3de-automation-tests + +## Run Automation Tests +### CLI +Open a Command Prompt window at the engine root and run the following CLI command: +python\python.cmd -m pytest {path_to_the_test_file} --build-directory {directory_to_the_profile_build} + +### Pycharm +You can also run any specific automation test directly from Pycharm by providing the "--build-directory" argument in the Run Configuration. \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py index cd8858e7f2..061db991cf 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py @@ -9,18 +9,18 @@ import logging import os import pytest import typing - from datetime import datetime + import ly_test_tools.log.log_monitor +from AWS.common import constants +from .aws_metrics_custom_thread import AWSMetricsThread + # fixture imports from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor from .aws_metrics_utils import aws_metrics_utils -from .aws_metrics_custom_thread import AWSMetricsThread AWS_METRICS_FEATURE_NAME = 'AWSMetrics' -GAME_LOG_NAME = 'Game.log' -CONTEXT_VARIABLE = ['-c', 'batch_processing=true'] logger = logging.getLogger(__name__) @@ -36,7 +36,7 @@ def setup(launcher: pytest.fixture, asset_processor.start() asset_processor.wait_for_idle() - file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME) + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), constants.GAME_LOG_NAME) # Initialize the log monitor. log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) @@ -73,23 +73,26 @@ def monitor_metrics_submission(log_monitor: pytest.fixture) -> None: f'unexpected_lines values: {unexpected_lines}') -def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, stack_name: str) -> None: +def query_metrics_from_s3(aws_metrics_utils: pytest.fixture, resource_mappings: pytest.fixture, stack_name: str) -> None: """ Verify that the metrics events are delivered to the S3 bucket and can be queried. - aws_metrics_utils: aws_metrics_utils fixture. - stack_name: name of the CloudFormation stack. + :param aws_metrics_utils: aws_metrics_utils fixture. + :param resource_mappings: resource_mappings fixture. + :param stack_name: name of the CloudFormation stack. """ - analytics_bucket_name = aws_metrics_utils.get_analytics_bucket_name(stack_name) - aws_metrics_utils.verify_s3_delivery(analytics_bucket_name) + aws_metrics_utils.verify_s3_delivery( + resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsBucketName') + ) logger.info('Metrics are sent to S3.') - aws_metrics_utils.run_glue_crawler(f'{stack_name}-EventsCrawler') + aws_metrics_utils.run_glue_crawler( + resource_mappings.get_resource_name_id('AWSMetrics.EventsCrawlerName')) + + # Remove the events_json table if exists so that the sample query can create a table with the same name. + aws_metrics_utils.delete_table(f'{stack_name}-eventsdatabase', 'events_json') aws_metrics_utils.run_named_queries(f'{stack_name}-AthenaWorkGroup') logger.info('Query metrics from S3 successfully.') - # Empty the S3 bucket. S3 buckets can only be deleted successfully when it doesn't contain any object. - aws_metrics_utils.empty_batch_analytics_bucket(analytics_bucket_name) - def verify_operational_metrics(aws_metrics_utils: pytest.fixture, stack_name: str, start_time: datetime) -> None: """ @@ -102,7 +105,7 @@ def verify_operational_metrics(aws_metrics_utils: pytest.fixture, stack_name: st 'AWS/Lambda', 'Invocations', [{'Name': 'FunctionName', - 'Value': f'{stack_name}-AnalyticsProcessingLambdaName'}], + 'Value': f'{stack_name}-AnalyticsProcessingLambda'}], start_time) logger.info('AnalyticsProcessingLambda metrics are sent to CloudWatch.') @@ -115,50 +118,59 @@ def verify_operational_metrics(aws_metrics_utils: pytest.fixture, stack_name: st logger.info('EventsProcessingLambda metrics are sent to CloudWatch.') -def start_kinesis_analytics_application(aws_metrics_utils: pytest.fixture, stack_name: str) -> None: +def update_kinesis_analytics_application_status(aws_metrics_utils: pytest.fixture, + resource_mappings: pytest.fixture, start_application: bool) -> None: """ - Start the Kinesis analytics application for real-time analytics. - aws_metrics_utils: aws_metrics_utils fixture. - stack_name: name of the CloudFormation stack. + Update the Kinesis analytics application to start or stop it. + :param aws_metrics_utils: aws_metrics_utils fixture. + :param resource_mappings: resource_mappings fixture. + :param start_application: whether to start or stop the application. """ - analytics_application_name = f'{stack_name}-AnalyticsApplication' - aws_metrics_utils.start_kinesis_data_analytics_application(analytics_application_name) + if start_application: + aws_metrics_utils.start_kinesis_data_analytics_application( + resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsApplicationName')) + else: + aws_metrics_utils.stop_kinesis_data_analytics_application( + resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsApplicationName')) @pytest.mark.SUITE_periodic @pytest.mark.usefixtures('automatic_process_killer') -@pytest.mark.parametrize('project', ['AutomatedTesting']) -@pytest.mark.parametrize('level', ['AWS/Metrics']) -@pytest.mark.parametrize('feature_name', [AWS_METRICS_FEATURE_NAME]) -@pytest.mark.usefixtures('resource_mappings') -@pytest.mark.parametrize('resource_mappings_filename', ['default_aws_resource_mappings.json']) @pytest.mark.usefixtures('aws_credentials') +@pytest.mark.usefixtures('resource_mappings') +@pytest.mark.parametrize('assume_role_arn', [constants.ASSUME_ROLE_ARN]) +@pytest.mark.parametrize('feature_name', [AWS_METRICS_FEATURE_NAME]) +@pytest.mark.parametrize('level', ['AWS/Metrics']) @pytest.mark.parametrize('profile_name', ['AWSAutomationTest']) -@pytest.mark.parametrize('region_name', ['us-west-2']) -@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) -@pytest.mark.usefixtures('cdk') -@pytest.mark.parametrize('session_name', ['o3de-Automation-session']) -@pytest.mark.parametrize('deployment_params', [CONTEXT_VARIABLE]) +@pytest.mark.parametrize('project', ['AutomatedTesting']) +@pytest.mark.parametrize('region_name', [constants.AWS_REGION]) +@pytest.mark.parametrize('resource_mappings_filename', [constants.AWS_RESOURCE_MAPPING_FILE_NAME]) +@pytest.mark.parametrize('session_name', [constants.SESSION_NAME]) +@pytest.mark.parametrize('stacks', [[f'{constants.AWS_PROJECT_NAME}-{AWS_METRICS_FEATURE_NAME}-{constants.AWS_REGION}']]) class TestAWSMetricsWindows(object): """ Test class to verify the real-time and batch analytics for metrics. """ - - @pytest.mark.parametrize('destroy_stacks_on_teardown', [False]) def test_realtime_and_batch_analytics(self, level: str, launcher: pytest.fixture, asset_processor: pytest.fixture, workspace: pytest.fixture, aws_utils: pytest.fixture, - cdk: pytest.fixture, + resource_mappings: pytest.fixture, + stacks: typing.List, aws_metrics_utils: pytest.fixture): """ Verify that the metrics events are sent to CloudWatch and S3 for analytics. """ # Start Kinesis analytics application on a separate thread to avoid blocking the test. - kinesis_analytics_application_thread = AWSMetricsThread(target=start_kinesis_analytics_application, - args=(aws_metrics_utils, cdk.stacks[0])) + kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status, + args=(aws_metrics_utils, resource_mappings, True)) kinesis_analytics_application_thread.start() + + # Clear the analytics bucket objects before sending new metrics. + aws_metrics_utils.empty_bucket( + resource_mappings.get_resource_name_id('AWSMetrics.AnalyticsBucketName')) + log_monitor = setup(launcher, asset_processor) # Kinesis analytics application needs to be in the running state before we start the game launcher. @@ -177,18 +189,22 @@ class TestAWSMetricsWindows(object): start_time) logger.info('Real-time metrics are sent to CloudWatch.') - # Run time-consuming verifications on separate threads to avoid blocking the test. - verification_threads = list() - verification_threads.append( - AWSMetricsThread(target=query_metrics_from_s3, args=(aws_metrics_utils, cdk.stacks[0]))) - verification_threads.append( - AWSMetricsThread(target=verify_operational_metrics, args=(aws_metrics_utils, cdk.stacks[0], start_time))) - for thread in verification_threads: + # Run time-consuming operations on separate threads to avoid blocking the test. + operational_threads = list() + operational_threads.append( + AWSMetricsThread(target=query_metrics_from_s3, + args=(aws_metrics_utils, resource_mappings, stacks[0]))) + operational_threads.append( + AWSMetricsThread(target=verify_operational_metrics, + args=(aws_metrics_utils, stacks[0], start_time))) + operational_threads.append( + AWSMetricsThread(target=update_kinesis_analytics_application_status, + args=(aws_metrics_utils, resource_mappings, False))) + for thread in operational_threads: thread.start() - for thread in verification_threads: + for thread in operational_threads: thread.join() - @pytest.mark.parametrize('destroy_stacks_on_teardown', [True]) def test_unauthorized_user_request_rejected(self, level: str, launcher: pytest.fixture, diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_utils.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_utils.py index 97cd563651..e7eb486d02 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_utils.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_utils.py @@ -198,7 +198,7 @@ class AWSMetricsUtils: assert state == 'SUCCEEDED', f'Failed to run the named query {named_query.get("Name", {})}' - def empty_batch_analytics_bucket(self, bucket_name: str) -> None: + def empty_bucket(self, bucket_name: str) -> None: """ Empty the S3 bucket following: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/migrations3.html @@ -211,25 +211,18 @@ class AWSMetricsUtils: for key in bucket.objects.all(): key.delete() - def get_analytics_bucket_name(self, stack_name: str) -> str: + def delete_table(self, database_name: str, table_name: str) -> None: """ - Get the name of the deployed S3 bucket. - :param stack_name: Name of the CloudFormation stack. - :return: Name of the deployed S3 bucket. - """ - - client = self._aws_util.client('cloudformation') + Delete an existing Glue table. - response = client.describe_stack_resources( - StackName=stack_name + :param database_name: Name of the Glue database. + :param table_name: Name of the table to delete. + """ + client = self._aws_util.client('glue') + client.delete_table( + DatabaseName=database_name, + Name=table_name ) - resources = response.get('StackResources', []) - - for resource in resources: - if resource.get('ResourceType') == 'AWS::S3::Bucket': - return resource.get('PhysicalResourceId', '') - - return '' @pytest.fixture(scope='function') diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_waiters.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_waiters.py index abfeaec076..46070a3a64 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_waiters.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_waiters.py @@ -45,7 +45,8 @@ class KinesisAnalyticsApplicationUpdatedWaiter(CustomWaiter): class GlueCrawlerReadyWaiter(CustomWaiter): """ Subclass of the base custom waiter class. - Wait for the Glue crawler to finish its processing. + Wait for the Glue crawler to finish its processing. Return when the crawler is in the "Stopping" status + to avoid wasting too much time in the automation tests on its shutdown process. """ def __init__(self, client: botocore.client): """ @@ -57,7 +58,7 @@ class GlueCrawlerReadyWaiter(CustomWaiter): 'GlueCrawlerReady', 'GetCrawler', 'Crawler.State', - {'READY': WaitState.SUCCESS}, + {'STOPPING': WaitState.SUCCESS}, client) def wait(self, crawler_name): diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/__init__.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/__init__.py deleted file mode 100644 index bbcbcf1807..0000000000 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk_utils.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk_utils.py deleted file mode 100644 index 3643c3bb36..0000000000 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk_utils.py +++ /dev/null @@ -1,248 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -import os -import pytest -import boto3 -import uuid -import logging -import subprocess -import botocore - -import ly_test_tools.environment.process_utils as process_utils -from typing import List - -BOOTSTRAP_STACK_NAME = 'CDKToolkit' -BOOTSTRAP_STAGING_BUCKET_LOGIC_ID = 'StagingBucket' - -logger = logging.getLogger(__name__) - - -class Cdk: - """ - Cdk class that provides methods to run cdk application commands. - Expects system to have NodeJS, AWS CLI and CDK installed globally and have their paths setup as env variables. - """ - - def __init__(self): - self._cdk_env = '' - self._stacks = [] - self._cdk_path = os.path.dirname(os.path.realpath(__file__)) - self._session = '' - - cdk_npm_latest_version_cmd = ['npm', 'view', 'aws-cdk', 'version'] - - output = process_utils.check_output( - cdk_npm_latest_version_cmd, - cwd=self._cdk_path, - shell=True) - cdk_npm_latest_version = output.split()[0] - - cdk_version_cmd = ['cdk', 'version'] - output = process_utils.check_output( - cdk_version_cmd, - cwd=self._cdk_path, - shell=True) - cdk_version = output.split()[0] - logger.info(f'Current CDK version {cdk_version}') - - if cdk_version != cdk_npm_latest_version: - try: - logger.info(f'Updating CDK to latest') - # uninstall and reinstall cdk in case npm has been updated. - output = process_utils.check_output( - 'npm uninstall -g aws-cdk', - cwd=self._cdk_path, - shell=True) - - logger.info(f'Uninstall CDK output: {output}') - - output = process_utils.check_output( - 'npm install -g aws-cdk@latest', - cwd=self._cdk_path, - shell=True) - - logger.info(f'Install CDK output: {output}') - except subprocess.CalledProcessError as error: - logger.warning(f'Failed reinstalling latest CDK on npm' - f'\nError:{error.stderr}') - - def setup(self, cdk_path: str, project: str, account_id: str, - workspace: pytest.fixture, session: boto3.session.Session): - """ - :param cdk_path: Path where cdk app.py is stored. - :param project: Project name used for cdk project name env variable. - :param account_id: AWS account id to use with cdk application. - :param workspace: ly_test_tools workspace fixture. - :param session: Current boto3 session, provides credentials and region. - """ - self._cdk_env = os.environ.copy() - unique_id = uuid.uuid4().hex[-4:] - self._cdk_env['O3DE_AWS_PROJECT_NAME'] = project[:4] + unique_id if len(project) > 4 else project + unique_id - self._cdk_env['O3DE_AWS_DEPLOY_REGION'] = session.region_name - self._cdk_env['O3DE_AWS_DEPLOY_ACCOUNT'] = account_id - self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH'] - - credentials = session.get_credentials().get_frozen_credentials() - self._cdk_env['AWS_ACCESS_KEY_ID'] = credentials.access_key - self._cdk_env['AWS_SECRET_ACCESS_KEY'] = credentials.secret_key - self._cdk_env['AWS_SESSION_TOKEN'] = credentials.token - self._cdk_path = cdk_path - - self._session = session - - output = process_utils.check_output( - 'python -m pip install -r requirements.txt', - cwd=self._cdk_path, - env=self._cdk_env, - shell=True) - - logger.info(f'Installing cdk python dependencies: {output}') - - self.bootstrap() - - def bootstrap(self) -> None: - """ - Deploy the bootstrap stack. - """ - try: - bootstrap_cmd = ['cdk', 'bootstrap', - f'aws://{self._cdk_env["O3DE_AWS_DEPLOY_ACCOUNT"]}/{self._cdk_env["O3DE_AWS_DEPLOY_REGION"]}'] - - process_utils.check_call( - bootstrap_cmd, - cwd=self._cdk_path, - env=self._cdk_env, - shell=True) - except botocore.exceptions.ClientError as clientError: - logger.warning(f'Failed creating Bootstrap stack {BOOTSTRAP_STACK_NAME} not found. ' - f'\nError:{clientError["Error"]["Message"]}') - - def list(self, deployment_params: List[str] = None) -> List[str]: - """ - lists cdk stack names. - :param deployment_params: Deployment parameters like --all can be passed in this way. - :return List of cdk stack names. - """ - if not self._cdk_path: - return [] - - list_cdk_application_cmd = ['cdk', 'list'] - if deployment_params: - list_cdk_application_cmd.extend(deployment_params) - - output = process_utils.check_output( - list_cdk_application_cmd, - cwd=self._cdk_path, - env=self._cdk_env, - shell=True) - - return output.splitlines() - - def synthesize(self, deployment_params: List[str] = None) -> None: - """ - Synthesizes all cdk stacks. - :param deployment_params: Deployment parameters like --all can be passed in this way. - """ - if not self._cdk_path: - return - - synth_cdk_application_cmd = ['cdk', 'synth'] - if deployment_params: - synth_cdk_application_cmd.extend(deployment_params) - - process_utils.check_output( - synth_cdk_application_cmd, - cwd=self._cdk_path, - env=self._cdk_env, - shell=True) - - def deploy(self, deployment_params: List[str] = None) -> List[str]: - """ - Deploys all the CDK stacks. - :param deployment_params: Deployment parameters like --all can be passed in this way. - :return List of deployed stack arns. - """ - if not self._cdk_path: - return [] - - deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never'] - if deployment_params: - deploy_cdk_application_cmd.extend(deployment_params) - - output = process_utils.check_output( - deploy_cdk_application_cmd, - cwd=self._cdk_path, - env=self._cdk_env, - shell=True) - - for line in output.splitlines(): - line_sections = line.split('/') - assert len(line_sections), 3 - self._stacks.append(line.split('/')[-2]) - - return self._stacks - - def destroy(self, deployment_params: List[str] = None) -> None: - """ - Destroys the cdk application. - :param deployment_params: Deployment parameters like --all can be passed in this way. - """ - - logger.info(f'CDK Path {self._cdk_path}') - destroy_cdk_application_cmd = ['cdk', 'destroy', '-f'] - if deployment_params: - destroy_cdk_application_cmd.extend(deployment_params) - - try: - process_utils.check_output( - destroy_cdk_application_cmd, - cwd=self._cdk_path, - env=self._cdk_env, - shell=True) - - except subprocess.CalledProcessError as e: - logger.error(e.output) - raise e - - self._stacks = [] - - def remove_bootstrap_stack(self) -> None: - """ - Remove the CDK bootstrap stack. - :param aws_utils: aws_utils fixture. - """ - # Check if the bootstrap stack exists. - response = self._session.client('cloudformation').describe_stacks( - StackName=BOOTSTRAP_STACK_NAME - ) - stacks = response.get('Stacks', []) - if not stacks or len(stacks) is 0: - return - - # Clear the bootstrap staging bucket before deleting the bootstrap stack. - response = self._session.client('cloudformation').describe_stack_resource( - StackName=BOOTSTRAP_STACK_NAME, - LogicalResourceId=BOOTSTRAP_STAGING_BUCKET_LOGIC_ID - ) - - staging_bucket_name = response.get('StackResourceDetail', {}).get('PhysicalResourceId', '') - if staging_bucket_name: - s3 = self._session.resource('s3') - bucket = s3.Bucket(staging_bucket_name) - for key in bucket.objects.all(): - key.delete() - - # Delete the bootstrap stack. - # Should not need to delete the stack if S3 bucket can be cleaned. - # self._session.client('cloudformation').delete_stack( - # StackName=BOOTSTRAP_STACK_NAME - # ) - - @property - def stacks(self): - return self._stacks diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py index bdd1eea469..b56d3f88f5 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/aws_client_auth_automation_test.py @@ -4,45 +4,42 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -import pytest -import os + import logging +import os +import pytest + import ly_test_tools.log.log_monitor +from AWS.common import constants + # fixture imports from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor -AWS_PROJECT_NAME = 'AWS-AutomationTest' AWS_CLIENT_AUTH_FEATURE_NAME = 'AWSClientAuth' -AWS_CLIENT_AUTH_DEFAULT_PROFILE_NAME = 'default' - -GAME_LOG_NAME = 'Game.log' logger = logging.getLogger(__name__) @pytest.mark.SUITE_periodic -@pytest.mark.usefixtures('automatic_process_killer') @pytest.mark.usefixtures('asset_processor') +@pytest.mark.usefixtures('automatic_process_killer') +@pytest.mark.usefixtures('aws_utils') @pytest.mark.usefixtures('workspace') -@pytest.mark.parametrize('project', ['AutomatedTesting']) -@pytest.mark.usefixtures('cdk') +@pytest.mark.parametrize('assume_role_arn', [constants.ASSUME_ROLE_ARN]) @pytest.mark.parametrize('feature_name', [AWS_CLIENT_AUTH_FEATURE_NAME]) +@pytest.mark.parametrize('project', ['AutomatedTesting']) @pytest.mark.usefixtures('resource_mappings') -@pytest.mark.parametrize('resource_mappings_filename', ['default_aws_resource_mappings.json']) -@pytest.mark.usefixtures('aws_utils') -@pytest.mark.parametrize('region_name', ['us-west-2']) -@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) -@pytest.mark.parametrize('session_name', ['o3de-Automation-session']) -@pytest.mark.usefixtures('cdk') -@pytest.mark.parametrize('deployment_params', [[]]) +@pytest.mark.parametrize('resource_mappings_filename', [constants.AWS_RESOURCE_MAPPING_FILE_NAME]) +@pytest.mark.parametrize('region_name', [constants.AWS_REGION]) +@pytest.mark.parametrize('session_name', [constants.SESSION_NAME]) +@pytest.mark.parametrize('stacks', [[f'{constants.AWS_PROJECT_NAME}-{AWS_CLIENT_AUTH_FEATURE_NAME}-Stack-{constants.AWS_REGION}']]) class TestAWSClientAuthWindows(object): """ Test class to verify AWS Client Auth gem features on Windows. """ @pytest.mark.parametrize('level', ['AWS/ClientAuth']) - @pytest.mark.parametrize('destroy_stacks_on_teardown', [False]) def test_anonymous_credentials(self, level: str, launcher: pytest.fixture, @@ -53,14 +50,14 @@ class TestAWSClientAuthWindows(object): """ Test to verify AWS Cognito Identity pool anonymous authorization. - Setup: Deploys cdk and updates resource mapping file. + Setup: Updates resource mapping file using existing CloudFormation stacks. Tests: Getting credentials when no credentials are configured Verification: Log monitor looks for success credentials log. """ asset_processor.start() asset_processor.wait_for_idle() - file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME) + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), constants.GAME_LOG_NAME) log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) launcher.args = ['+LoadLevel', level] @@ -74,10 +71,8 @@ class TestAWSClientAuthWindows(object): ) assert result, 'Anonymous credentials fetched successfully.' - @pytest.mark.parametrize('destroy_stacks_on_teardown', [True]) def test_password_signin_credentials(self, launcher: pytest.fixture, - cdk: pytest.fixture, resource_mappings: pytest.fixture, workspace: pytest.fixture, asset_processor: pytest.fixture, @@ -86,16 +81,29 @@ class TestAWSClientAuthWindows(object): """ Test to verify AWS Cognito IDP Password sign in and Cognito Identity pool authenticated authorization. - Setup: Deploys cdk and updates resource mapping file. + Setup: Updates resource mapping file using existing CloudFormation stacks. Tests: Sign up new test user, admin confirm the user, sign in and get aws credentials. Verification: Log monitor looks for success credentials log. """ asset_processor.start() asset_processor.wait_for_idle() - file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME) + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), constants.GAME_LOG_NAME) log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) + cognito_idp = aws_utils.client('cognito-idp') + user_pool_id = resource_mappings.get_resource_name_id(f'{AWS_CLIENT_AUTH_FEATURE_NAME}.CognitoUserPoolId') + logger.info(f'UserPoolId:{user_pool_id}') + + # Remove the user if already exists + try: + cognito_idp.admin_delete_user( + UserPoolId=user_pool_id, + Username='test1' + ) + except cognito_idp.exceptions.UserNotFoundException: + pass + launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignUp'] launcher.args.extend(['-rhi=null']) @@ -109,9 +117,6 @@ class TestAWSClientAuthWindows(object): launcher.stop() - cognito_idp = aws_utils.client('cognito-idp') - user_pool_id = resource_mappings.get_resource_name_id(f'{AWS_CLIENT_AUTH_FEATURE_NAME}.CognitoUserPoolId') - print(f'UserPoolId:{user_pool_id}') cognito_idp.admin_confirm_sign_up( UserPoolId=user_pool_id, Username='test1' diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py index 55f06660e8..529151f3f6 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py @@ -5,10 +5,11 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -import os import logging -import typing +import os import shutil +import typing +from botocore.exceptions import ClientError import pytest import ly_test_tools @@ -16,16 +17,15 @@ import ly_test_tools.log.log_monitor import ly_test_tools.environment.process_utils as process_utils import ly_test_tools.o3de.asset_processor_utils as asset_processor_utils -from botocore.exceptions import ClientError +from AWS.common import constants + +# fixture imports from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor AWS_CORE_FEATURE_NAME = 'AWSCore' -AWS_RESOURCE_MAPPING_FILE_NAME = 'default_aws_resource_mappings.json' process_utils.kill_processes_named("o3de", ignore_extensions=True) # Kill ProjectManager windows -GAME_LOG_NAME = 'Game.log' - logger = logging.getLogger(__name__) @@ -46,7 +46,7 @@ def setup(launcher: pytest.fixture, asset_processor: pytest.fixture) -> typing.T asset_processor.start() asset_processor.wait_for_idle() - file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME) + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), constants.GAME_LOG_NAME) log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) return log_monitor, s3_download_dir @@ -58,7 +58,7 @@ def write_test_data_to_dynamodb_table(resource_mappings: pytest.fixture, aws_uti :param resource_mappings: resource_mappings fixture. :param aws_utils: aws_utils fixture. """ - table_name = resource_mappings.get_resource_name_id("AWSCore.ExampleDynamoTableOutput") + table_name = resource_mappings.get_resource_name_id(f'{AWS_CORE_FEATURE_NAME}.ExampleDynamoTableOutput') try: aws_utils.client('dynamodb').put_item( TableName=table_name, @@ -77,21 +77,19 @@ def write_test_data_to_dynamodb_table(resource_mappings: pytest.fixture, aws_uti @pytest.mark.SUITE_periodic @pytest.mark.usefixtures('automatic_process_killer') @pytest.mark.usefixtures('asset_processor') -@pytest.mark.usefixtures('cdk') @pytest.mark.parametrize('feature_name', [AWS_CORE_FEATURE_NAME]) -@pytest.mark.parametrize('region_name', ['us-west-2']) -@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) -@pytest.mark.parametrize('session_name', ['o3de-Automation-session']) +@pytest.mark.parametrize('region_name', [constants.AWS_REGION]) +@pytest.mark.parametrize('assume_role_arn', [constants.ASSUME_ROLE_ARN]) +@pytest.mark.parametrize('session_name', [constants.SESSION_NAME]) @pytest.mark.usefixtures('workspace') @pytest.mark.parametrize('project', ['AutomatedTesting']) @pytest.mark.parametrize('level', ['AWS/Core']) @pytest.mark.usefixtures('resource_mappings') -@pytest.mark.parametrize('resource_mappings_filename', [AWS_RESOURCE_MAPPING_FILE_NAME]) +@pytest.mark.parametrize('resource_mappings_filename', [constants.AWS_RESOURCE_MAPPING_FILE_NAME]) +@pytest.mark.parametrize('stacks', [[f'{constants.AWS_PROJECT_NAME}-{AWS_CORE_FEATURE_NAME}', + f'{constants.AWS_PROJECT_NAME}-{AWS_CORE_FEATURE_NAME}-Example-{constants.AWS_REGION}']]) @pytest.mark.usefixtures('aws_credentials') @pytest.mark.parametrize('profile_name', ['AWSAutomationTest']) -@pytest.mark.usefixtures('cdk') -@pytest.mark.parametrize('deployment_params', [['--all']]) -@pytest.mark.parametrize('destroy_stacks_on_teardown', [True]) class TestAWSCoreAWSResourceInteraction(object): """ Test class to verify the scripting behavior for the AWSCore gem. @@ -119,7 +117,7 @@ class TestAWSCoreAWSResourceInteraction(object): expected_lines: typing.List[str], unexpected_lines: typing.List[str]): """ - Setup: Deploys cdk and updates resource mapping file. + Setup: Updates resource mapping file using existing CloudFormation stacks. Tests: Interact with AWS S3, DynamoDB and Lambda services. Verification: Script canvas nodes can communicate with AWS services successfully. """ diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py deleted file mode 100644 index e01850f919..0000000000 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/AWS/common/constants.py b/AutomatedTesting/Gem/PythonTests/AWS/common/constants.py new file mode 100644 index 0000000000..be143547a7 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/constants.py @@ -0,0 +1,19 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +# ARN of the IAM role to assume for retrieving temporary AWS credentials +ASSUME_ROLE_ARN = 'arn:aws:iam::645075835648:role/o3de-automation-tests' +# Name of the AWS project deployed by the CDK applications +AWS_PROJECT_NAME = 'AWSAUTO' +# Region for the existing CloudFormation stacks used by the automation tests +AWS_REGION = 'us-east-1' +# Name of the default resource mapping config file used by the automation tests +AWS_RESOURCE_MAPPING_FILE_NAME = 'default_aws_resource_mappings.json' +# Name of the game launcher log +GAME_LOG_NAME = 'Game.log' +# Name of the IAM role session for retrieving temporary AWS credentials +SESSION_NAME = 'o3de-Automation-session' diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py b/AutomatedTesting/Gem/PythonTests/AWS/common/resource_mappings.py similarity index 90% rename from AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py rename to AutomatedTesting/Gem/PythonTests/AWS/common/resource_mappings.py index fd679de99d..5f01ecdbf8 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/resource_mappings.py @@ -6,9 +6,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ import os -import pytest import json import logging +from AWS.common import constants logger = logging.getLogger(__name__) @@ -22,18 +22,14 @@ class ResourceMappings: ResourceMappings class that handles writing Cloud formation outputs to resource mappings json file in a project. """ - def __init__(self, file_path: str, region: str, feature_name: str, account_id: str, workspace: pytest.fixture, - cloud_formation_client): + def __init__(self, file_path: str, region: str, feature_name: str, account_id: str, cloud_formation_client): """ :param file_path: Path for the resource mapping file. :param region: Region value for the resource mapping file. :param feature_name: Feature gem name to use to append name to mappings key. :param account_id: AWS account id value for the resource mapping file. - :param workspace: ly_test_tools workspace fixture. :param cloud_formation_client: AWS cloud formation client. """ - self._cdk_env = os.environ.copy() - self._cdk_env['PATH'] = f'{workspace.paths.engine_root()}\\python;' + self._cdk_env['PATH'] self._resource_mapping_file_path = file_path self._region = region self._feature_name = feature_name @@ -44,7 +40,7 @@ class ResourceMappings: f'Invalid resource mapping file path {self._resource_mapping_file_path}' self._client = cloud_formation_client - def populate_output_keys(self, stacks=[]) -> None: + def populate_output_keys(self, stacks=None) -> None: """ Calls describe stacks on cloud formation service and persists outputs to resource mappings file. :param stacks List of stack arns to describe and populate resource mappings with. @@ -58,7 +54,7 @@ class ResourceMappings: self._write_resource_mappings(stacks[0].get('Outputs', [])) - def _write_resource_mappings(self, outputs, append_feature_name = True) -> None: + def _write_resource_mappings(self, outputs, append_feature_name=True) -> None: with open(self._resource_mapping_file_path) as file_content: resource_mappings = json.load(file_content) @@ -91,7 +87,7 @@ class ResourceMappings: resource_mappings = json.load(file_content) resource_mappings[AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY] = '' - resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = 'us-west-2' + resource_mappings[AWS_RESOURCE_MAPPINGS_REGION_KEY] = constants.AWS_REGION # Append new mappings. resource_mappings[AWS_RESOURCE_MAPPINGS_KEY] = resource_mappings.get(AWS_RESOURCE_MAPPINGS_KEY, {}) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/conftest.py b/AutomatedTesting/Gem/PythonTests/AWS/conftest.py index 6ad495ab66..df65aa7a5c 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/conftest.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/conftest.py @@ -11,8 +11,7 @@ import typing from AWS.common.aws_utils import AwsUtils from AWS.common.aws_credentials import AwsCredentials -from AWS.Windows.cdk.cdk_utils import Cdk -from AWS.Windows.resource_mappings.resource_mappings import ResourceMappings +from AWS.common.resource_mappings import ResourceMappings logger = logging.getLogger(__name__) @@ -52,6 +51,7 @@ def resource_mappings( project: str, feature_name: str, resource_mappings_filename: str, + stacks: typing.List, workspace: pytest.fixture, aws_utils: pytest.fixture) -> ResourceMappings: """ @@ -61,6 +61,7 @@ def resource_mappings( :param project: Project to find resource mapping file. :param feature_name: AWS Gem name that is prepended to resource mapping keys. :param resource_mappings_filename: Name of resource mapping file. + :param stacks: List of stack names to describe and populate resource mappings with. :param workspace: ly_test_tools workspace fixture. :param aws_utils: AWS utils fixture. :return: ResourceMappings class object. @@ -70,8 +71,8 @@ def resource_mappings( logger.info(f'Resource mapping path : {path}') logger.info(f'Resource mapping resolved path : {abspath(path)}') resource_mappings_obj = ResourceMappings(abspath(path), aws_utils.assume_session().region_name, feature_name, - aws_utils.assume_account_id(), workspace, - aws_utils.client('cloudformation')) + aws_utils.assume_account_id(), aws_utils.client('cloudformation')) + resource_mappings_obj.populate_output_keys(stacks) def teardown(): resource_mappings_obj.clear_output_keys() @@ -81,56 +82,6 @@ def resource_mappings( return resource_mappings_obj -@pytest.fixture(scope='function') -def cdk( - request: pytest.fixture, - project: str, - feature_name: str, - workspace: pytest.fixture, - aws_utils: pytest.fixture, - resource_mappings: pytest.fixture, - deployment_params: typing.List[str], - destroy_stacks_on_teardown: bool) -> Cdk: - """ - Fixture for setting up a Cdk - :param request: _pytest.fixtures.SubRequest class that handles getting - a pytest fixture from a pytest function/fixture. - :param project: Project name used for cdk project name env variable. - :param feature_name: Feature gem name to expect cdk folder in. - :param workspace: ly_test_tools workspace fixture. - :param aws_utils: aws_utils fixture. - :param resource_mappings: resource_mappings fixture. - :param deployment_params: Parameters for the CDK application deployment. - :param destroy_stacks_on_teardown: option to control calling destroy ot the end of test. - :return Cdk class object. - """ - - cdk_path = f'{workspace.paths.engine_root()}/Gems/{feature_name}/cdk' - logger.info(f'CDK Path {cdk_path}') - - if pytest.cdk_obj is None: - pytest.cdk_obj = Cdk() - pytest.cdk_obj.setup(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session()) - - stacks = pytest.cdk_obj.deploy(deployment_params=deployment_params) - - logger.info(f'Cdk stack names:\n{stacks}') - resource_mappings.populate_output_keys(stacks) - - def teardown(): - if destroy_stacks_on_teardown: - pytest.cdk_obj.destroy(deployment_params=deployment_params) - # Enable after https://github.com/aws/aws-cdk/issues/986 is fixed. - # Until then clean the bootstrap bucket manually. - # pytest.cdk_obj.remove_bootstrap_stack() - - pytest.cdk_obj = None - - request.addfinalizer(teardown) - - return pytest.cdk_obj - - @pytest.fixture(scope='function') def aws_credentials(request: pytest.fixture, aws_utils: pytest.fixture, profile_name: str): """ diff --git a/Gems/AWSCore/cdk/app.py b/Gems/AWSCore/cdk/app.py index ea038f8a51..16773b5f69 100755 --- a/Gems/AWSCore/cdk/app.py +++ b/Gems/AWSCore/cdk/app.py @@ -25,7 +25,7 @@ ACCOUNT = os.environ.get('O3DE_AWS_DEPLOY_ACCOUNT', os.environ.get('CDK_DEFAULT_ PROJECT_NAME = os.environ.get('O3DE_AWS_PROJECT_NAME', f'O3DE-AWS-PROJECT').upper() # The name of this feature -FEATURE_NAME = 'Core' +FEATURE_NAME = 'AWSCore' # The name of this CDK application PROJECT_FEATURE_NAME = f'{PROJECT_NAME}-{FEATURE_NAME}' diff --git a/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py b/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py index 5be6562982..8398113bc4 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py @@ -96,9 +96,9 @@ class BatchAnalytics: ), athena.CfnNamedQuery( self._stack, - id='NamedQuery-NewUsersLastMonth', + id='NamedQuery-LoginLastMonth', name=resource_name_sanitizer.sanitize_resource_name( - f'{self._stack.stack_name}-NamedQuery-NewUsersLastMonth', 'athena_named_query'), + f'{self._stack.stack_name}-NamedQuery-LoginLastMonth', 'athena_named_query'), database=self._events_database_name, query_string="WITH detail AS (" "SELECT date_trunc('month', date(date_parse(CONCAT(year, '-', month, '-', day), '%Y-%m-%d'))) as event_month, * " @@ -107,9 +107,9 @@ class BatchAnalytics: "date_trunc('month', event_month) as month, " "count(*) as new_accounts " "FROM detail " - "WHERE event_name = 'user_registration' " + "WHERE event_name = 'login' " "GROUP BY date_trunc('month', event_month)", - description='New users over the last month', + description='Total number of login events over the last month', work_group=self._athena_work_group.name ) ] diff --git a/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py b/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py index aaaf03b1fa..e47b1a95c2 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py @@ -50,8 +50,7 @@ class DataLakeIntegration: # a specific name here, only one customer can deploy the bucket successfully. self._analytics_bucket = s3.Bucket( self._stack, - id=f'AnalyticsBucket'.lower(), - bucket_name=resource_name_sanitizer.sanitize_resource_name( + id=resource_name_sanitizer.sanitize_resource_name( f'{self._stack.stack_name}-AnalyticsBucket'.lower(), 's3_bucket'), encryption=s3.BucketEncryption.S3_MANAGED, block_public_access=s3.BlockPublicAccess( @@ -68,6 +67,13 @@ class DataLakeIntegration: cfn_bucket = self._analytics_bucket.node.find_child('Resource') cfn_bucket.apply_removal_policy(core.RemovalPolicy.DESTROY) + analytics_bucket_output = core.CfnOutput( + self._stack, + id='AnalyticsBucketName', + description='Name of the S3 bucket for storing metrics event data', + export_name=f"{self._application_name}:AnalyticsBucket", + value=self._analytics_bucket.bucket_name) + def _create_events_database(self) -> None: """ Create the Glue database for metrics events. diff --git a/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py b/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py index 52fdcdc122..4ef9caa022 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py @@ -182,7 +182,7 @@ class RealTimeDataProcessing: Generate the analytics processing lambda to send processed data to CloudWatch for visualization. """ analytics_processing_function_name = resource_name_sanitizer.sanitize_resource_name( - f'{self._stack.stack_name}-AnalyticsProcessingLambdaName', 'lambda_function') + f'{self._stack.stack_name}-AnalyticsProcessingLambda', 'lambda_function') self._analytics_processing_lambda_role = self._create_analytics_processing_lambda_role( analytics_processing_function_name ) diff --git a/scripts/build/Platform/Windows/pipeline.json b/scripts/build/Platform/Windows/pipeline.json index 4cfc6f696a..4c736d2292 100644 --- a/scripts/build/Platform/Windows/pipeline.json +++ b/scripts/build/Platform/Windows/pipeline.json @@ -24,28 +24,28 @@ "parameter_type": "string", "default_value": "", "use_last_run_value": true, - "description": "" + "description": "The name of the O3DE project that stacks should be deployed for." }, { "parameter_name": "O3DE_AWS_DEPLOY_REGION", "parameter_type": "string", "default_value": "", "use_last_run_value": true, - "description": "" + "description": "The region to deploy the stacks into." }, { "parameter_name": "ASSUME_ROLE_ARN", "parameter_type": "string", "default_value": "", "use_last_run_value": true, - "description": "" + "description": "The ARN of the IAM role to assume to retrieve temporary AWS credentials." }, { "parameter_name": "COMMIT_ID", "parameter_type": "string", "default_value": "", "use_last_run_value": true, - "description": "" + "description": "The commit ID for locking the version of CDK applications to deploy." } ] }