diff --git a/README.md b/README.md index 64fcba76f..8aca1ef9f 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,5 @@ within the AWS Console. - Refer to the [User Guide](docs/user-guide.md) for using ADF once it is setup. - Refer to the [Samples Guide](docs/samples-guide.md) for a detailed walk through of the provided samples. +- Refer to the [Integrations Guide](docs/integrations-guide.md) for information + on events produced by the ADF. diff --git a/docs/integrations-guide.md b/docs/integrations-guide.md new file mode 100644 index 000000000..9905c748c --- /dev/null +++ b/docs/integrations-guide.md @@ -0,0 +1,47 @@ +# Integrations Guide + +## Introduction + +The AWS Deployment Framework enables integrations with external workflows via an +Event Bus deployed into the organizations management account. + +## Account Management Events + +Currently - events are emitted for the following states: + +- `ACCOUNT_PROVISIONED` + Emitted when an AWS account is created. + Contains the account definition as well as the account_id. +- `ENTERPRISE_SUPPORT_REQUESTED` + Emitted when the support ticket to AWS Support is raised. + Contains the account definition as well as the account_id. +- `ACCOUNT_ALIAS_CONFIGURED` + Emitted when the accounts alias is configured by ADF. + The details section contains the account id and the alias value. + The resource field also contains the account id +- `ACCOUNT_TAGS_CONFIGURED` + Emitted when the accounts tags are updated by ADF. + The details section contains the account id and the tags. + The resource field also contains the account id +- `DEFAULT_VPC_DELETED` + Emitted when the default VPC in a region is deleted. + The details section contains the account id and the region of the VPC. + he resource field contains the deleted VPC id. +- `ACCOUNT_CREATION_COMPLETE` + Emitted when the state machine completes successfully. + Contains the account definition and the account id in the resource field. + +## Pipeline Management Events + +- `CROSS_ACCOUNT_RULE_CREATED_OR_UPDATED` + Emitted when a rule is created to trigger pipelines from a different account. + The details sections contains the source account id. + The resource sections contains the deployment account Id. +- `REPOSITORY_CREATED_OR_UPDATED` + Emitted when a codecommit repository is created. + The details sections contains: + the repository_account_id + the CloudFormation stack name + + The resource section contains an amalgamation of the account_id and the + pipeline name. diff --git a/src/lambda_codebase/account_processing/configure_account_alias.py b/src/lambda_codebase/account_processing/configure_account_alias.py index 243497166..39b20214a 100644 --- a/src/lambda_codebase/account_processing/configure_account_alias.py +++ b/src/lambda_codebase/account_processing/configure_account_alias.py @@ -6,22 +6,25 @@ """ import os +import json from sts import STS from aws_xray_sdk.core import patch_all from logger import configure_logger +from events import ADFEvents patch_all() LOGGER = configure_logger(__name__) ADF_ROLE_NAME = os.getenv("ADF_ROLE_NAME") AWS_PARTITION = os.getenv("AWS_PARTITION") +EVENTS = ADFEvents("AccountManagement") def delete_account_aliases(account, iam_client, current_aliases): for alias in current_aliases: LOGGER.info( "Account %s, removing alias %s", - account.get('account_full_name'), + account.get("account_full_name"), alias, ) iam_client.delete_account_alias(AccountAlias=alias) @@ -30,8 +33,8 @@ def delete_account_aliases(account, iam_client, current_aliases): def create_account_alias(account, iam_client): LOGGER.info( "Adding alias to: %s alias %s", - account.get('account_full_name'), - account.get('alias'), + account.get("account_full_name"), + account.get("alias"), ) try: iam_client.create_account_alias(AccountAlias=account.get("alias")) @@ -49,15 +52,15 @@ def create_account_alias(account, iam_client): def ensure_account_has_alias(account, iam_client): LOGGER.info( "Ensuring Account: %s has alias %s", - account.get('account_full_name'), - account.get('alias'), + account.get("account_full_name"), + account.get("alias"), ) - current_aliases = iam_client.list_account_aliases().get('AccountAliases') - if account.get('alias') in current_aliases: + current_aliases = iam_client.list_account_aliases().get("AccountAliases") + if account.get("alias") in current_aliases: LOGGER.info( "Account: %s already has alias %s", - account.get('account_full_name'), - account.get('alias'), + account.get("account_full_name"), + account.get("alias"), ) return @@ -76,9 +79,19 @@ def lambda_handler(event, _): "adf_account_alias_config", ) ensure_account_has_alias(event, role.client("iam")) + EVENTS.put_event( + detail=json.dumps( + { + "account_id": account_id, + "alias_value": event.get("alias"), + } + ), + detailType="ACCOUNT_ALIAS_CONFIGURED", + resources=[account_id], + ) else: LOGGER.info( "Account: %s does not need an alias", - event.get('account_full_name'), + event.get("account_full_name"), ) return event diff --git a/src/lambda_codebase/account_processing/configure_account_tags.py b/src/lambda_codebase/account_processing/configure_account_tags.py index b0c3bcec4..ab1fcb369 100644 --- a/src/lambda_codebase/account_processing/configure_account_tags.py +++ b/src/lambda_codebase/account_processing/configure_account_tags.py @@ -8,13 +8,17 @@ in the config file. """ -from organizations import Organizations +import json import boto3 + +from organizations import Organizations from aws_xray_sdk.core import patch_all from logger import configure_logger +from events import ADFEvents patch_all() +EVENTS = ADFEvents("AccountManagement") LOGGER = configure_logger(__name__) @@ -35,9 +39,16 @@ def lambda_handler(event, _): event.get("tags"), organizations, ) + EVENTS.put_event( + detail=json.dumps( + {"tags": event.get("tags"), "account_id": event.get("account_id")} + ), + detailType="ACCOUNT_TAGS_CONFIGURED", + resources=[event.get("account_id")], + ) else: LOGGER.info( "Account: %s does not need tags configured", - event.get('account_full_name'), + event.get("account_full_name"), ) return event diff --git a/src/lambda_codebase/account_processing/create_account.py b/src/lambda_codebase/account_processing/create_account.py index 2b02ebd1c..d6ef713fb 100644 --- a/src/lambda_codebase/account_processing/create_account.py +++ b/src/lambda_codebase/account_processing/create_account.py @@ -6,18 +6,23 @@ """ import os +import json from aws_xray_sdk.core import patch_all import boto3 + from logger import configure_logger +from events import ADFEvents + patch_all() LOGGER = configure_logger(__name__) ADF_ROLE_NAME = os.getenv("ADF_ROLE_NAME") +EVENTS = ADFEvents("AccountManagement") def create_account(account, adf_role_name, org_client): - LOGGER.info("Creating account %s", account.get('account_full_name')) + LOGGER.info("Creating account %s", account.get("account_full_name")) allow_billing = "ALLOW" if account.get("allow_billing", False) else "DENY" response = org_client.create_account( Email=account.get("email"), @@ -42,4 +47,9 @@ def create_account(account, adf_role_name, org_client): def lambda_handler(event, _): org_client = boto3.client("organizations") - return create_account(event, ADF_ROLE_NAME, org_client) + details = create_account(event, ADF_ROLE_NAME, org_client) + EVENTS.put_event( + detail=json.dumps(details), + detailType="ACCOUNT_PROVISIONED", + resources=[details.get("account_id")], + ) diff --git a/src/lambda_codebase/account_processing/delete_default_vpc.py b/src/lambda_codebase/account_processing/delete_default_vpc.py index 1e223c3ef..edcd1da40 100644 --- a/src/lambda_codebase/account_processing/delete_default_vpc.py +++ b/src/lambda_codebase/account_processing/delete_default_vpc.py @@ -5,15 +5,18 @@ Deletes the default VPC in a particular region """ import os +import json from sts import STS from aws_xray_sdk.core import patch_all from logger import configure_logger +from events import ADFEvents patch_all() LOGGER = configure_logger(__name__) ADF_ROLE_NAME = os.getenv("ADF_ROLE_NAME") AWS_PARTITION = os.getenv("AWS_PARTITION") +EVENTS = ADFEvents("AccountManagement") def assume_role(account_id): @@ -61,10 +64,9 @@ def delete_default_vpc(ec2_resource, ec2_client, default_vpc_id): ec2_client.delete_vpc(VpcId=default_vpc_id) - def lambda_handler(event, _): event = event.get("Payload") - LOGGER.info("Checking for default VPC: %s", event.get('account_full_name')) + LOGGER.info("Checking for default VPC: %s", event.get("account_full_name")) role = assume_role(account_id=event.get("account_id")) ec2_client = role.client("ec2", region_name=event.get("region")) @@ -74,9 +76,16 @@ def lambda_handler(event, _): LOGGER.info( "Default VPC found: %s in %s", default_vpc_id, - event.get('account_full_name'), + event.get("account_full_name"), ) ec2_resource = role.resource("ec2", region_name=event.get("region")) delete_default_vpc(ec2_resource, ec2_client, default_vpc_id) + EVENTS.put_event( + detail=json.dumps( + {"region": event.get("region"), "account_id": event.get("account_id")} + ), + detailType="DEFAULT_VPC_DELETED", + resources=[default_vpc_id], + ) return {"Payload": event} diff --git a/src/lambda_codebase/account_processing/register_account_for_support.py b/src/lambda_codebase/account_processing/register_account_for_support.py index 34f50c399..97dd9e2f6 100644 --- a/src/lambda_codebase/account_processing/register_account_for_support.py +++ b/src/lambda_codebase/account_processing/register_account_for_support.py @@ -8,14 +8,17 @@ """ from enum import Enum +import json import boto3 from botocore.exceptions import ClientError, BotoCoreError from botocore.config import Config from logger import configure_logger +from events import ADFEvents from aws_xray_sdk.core import patch_all LOGGER = configure_logger(__name__) +EVENTS = ADFEvents("AccountManagement") patch_all() @@ -191,6 +194,8 @@ def _enable_support_for_account( account_id, account.get("email"), ) + EVENTS.put_event(detail=json.dumps(account), detailType="ENTERPRISE_SUPPORT_REQUESTED", resources=[account.get("account_id")]) + except (ClientError, BotoCoreError): LOGGER.error( diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml index c6762d6d1..19c7b69e6 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/global.yml @@ -69,6 +69,13 @@ Globals: CodeUri: lambda_codebase Runtime: python3.9 +Mappings: + OrganizationPartitionRegionMapping: + aws: + region: "us-east-1" + aws-us-gov: + region: "us-gov-west-1" + Resources: LambdaLayerVersion: Type: "AWS::Serverless::LayerVersion" @@ -184,6 +191,7 @@ Resources: CrossAccountAccessRole: !Ref CrossAccountAccessRole PipelineBucket: !Ref PipelineBucket RootAccountId: !Ref MasterAccountId + RootAccountRegion: !FindInMap [OrganizationPartitionRegionMapping, !Ref "AWS::Partition", "region"] CodeBuildImage: !Ref Image CodeBuildComputeType: !Ref ComputeType SharedModulesBucket: !Ref SharedModulesBucket diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_or_update_rule.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_or_update_rule.py index 6ab2055b4..8eda9d78a 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_or_update_rule.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_or_update_rule.py @@ -5,18 +5,24 @@ """ import os +import json import boto3 from cache import Cache from rule import Rule from logger import configure_logger from cloudwatch import ADFMetrics +from events import ADFEvents +from aws_xray_sdk.core import patch_all LOGGER = configure_logger(__name__) DEPLOYMENT_ACCOUNT_ID = os.environ["ACCOUNT_ID"] CLOUDWATCH = boto3.client("cloudwatch") METRICS = ADFMetrics(CLOUDWATCH, "PIPELINE_MANAGEMENT/RULE") +EVENTS = ADFEvents("PipelineManagement") +patch_all() + _CACHE = None @@ -45,7 +51,7 @@ def lambda_handler(event, _): LOGGER.info(event) - pipeline = event['pipeline_definition'] + pipeline = event["pipeline_definition"] source_provider = ( pipeline.get("default_providers", {}) @@ -79,5 +85,10 @@ def lambda_handler(event, _): METRICS.put_metric_data( {"MetricName": "CreateOrUpdate", "Value": 1, "Unit": "Count"} ) + EVENTS.put_event( + detail=json.dumps({"source_account_id": source_account_id}), + detailType="CROSS_ACCOUNT_RULE_CREATED_OR_UPDATED", + resources=[DEPLOYMENT_ACCOUNT_ID], + ) return event diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_repository.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_repository.py index 69ecce656..e7b15ccea 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_repository.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/create_repository.py @@ -4,18 +4,24 @@ """ import os +import json import boto3 from repo import Repo from logger import configure_logger from cloudwatch import ADFMetrics from parameter_store import ParameterStore +from events import ADFEvents +from aws_xray_sdk.core import patch_all CLOUDWATCH = boto3.client("cloudwatch") METRICS = ADFMetrics(CLOUDWATCH, "PIPELINE_MANAGEMENT/REPO") LOGGER = configure_logger(__name__) DEPLOYMENT_ACCOUNT_REGION = os.environ["AWS_REGION"] +DEPLOYMENT_ACCOUNT_ID = os.environ["ACCOUNT_ID"] +EVENTS = ADFEvents("PipelineManagement") +patch_all() def lambda_handler(event, _): @@ -30,7 +36,7 @@ def lambda_handler(event, _): Returns: dict: The input event. """ - pipeline = event.get('pipeline_definition') + pipeline = event.get("pipeline_definition") source_provider = ( pipeline.get("default_providers", {}) .get("source", {}) @@ -38,8 +44,7 @@ def lambda_handler(event, _): ) if source_provider != "codecommit": LOGGER.debug( - "This pipeline is not a CodeCommit source provider. " - "No actions required." + "This pipeline is not a CodeCommit source provider. " "No actions required." ) return event @@ -47,13 +52,49 @@ def lambda_handler(event, _): auto_create_repositories = parameter_store.fetch_parameter( "auto_create_repositories" ) - LOGGER.debug("Auto create repositories is: %s", auto_create_repositories) - if auto_create_repositories != "enabled": - LOGGER.debug( - "ADF is not configured to automatically create CodeCommit " - "repositories if they don't exist yet." + LOGGER.info(auto_create_repositories) + if auto_create_repositories == "enabled": + code_account_id = ( + pipeline.get("default_providers", {}) + .get("source", {}) + .get("properties", {}) + .get("account_id", {}) ) - return event + has_custom_repo = ( + pipeline.get("default_providers", {}) + .get("source", {}) + .get("properties", {}) + .get("repository", {}) + ) + if ( + auto_create_repositories + and code_account_id + and str(code_account_id).isdigit() + and not has_custom_repo + ): + repo = Repo( + code_account_id, + pipeline.get("name"), + pipeline.get("description"), + ) + repo.create_update() + METRICS.put_metric_data( + { + "MetricName": "CreateOrUpdate", + "Value": 1, + "Unit": "Count", + } + ) + EVENTS.put_event( + detail=json.dumps( + { + "repository_account_id": code_account_id, + "stack_name": repo.stack_name, + } + ), + detailType="REPOSITORY_CREATED_OR_UPDATED", + resources=[f'{code_account_id}:{pipeline.get("name")}'], + ) code_account_id = ( pipeline.get("default_providers", {}) @@ -67,11 +108,7 @@ def lambda_handler(event, _): .get("properties", {}) .get("repository", {}) ) - if ( - code_account_id - and str(code_account_id).isdigit() - and not has_custom_repo - ): + if code_account_id and str(code_account_id).isdigit() and not has_custom_repo: repo = Repo( code_account_id, pipeline.get("name"), diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/generate_pipeline_inputs.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/generate_pipeline_inputs.py index b7e9cdf02..c93e0ad40 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/generate_pipeline_inputs.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/generate_pipeline_inputs.py @@ -13,8 +13,10 @@ from sts import STS from logger import configure_logger from partition import get_partition +from aws_xray_sdk.core import patch_all +patch_all() LOGGER = configure_logger(__name__) DEPLOYMENT_ACCOUNT_REGION = os.environ["AWS_REGION"] DEPLOYMENT_ACCOUNT_ID = os.environ["ACCOUNT_ID"] diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/identify_out_of_date_pipelines.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/identify_out_of_date_pipelines.py index e83a95817..9e984597a 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/identify_out_of_date_pipelines.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/identify_out_of_date_pipelines.py @@ -15,8 +15,10 @@ from logger import configure_logger from deployment_map import DeploymentMap from parameter_store import ParameterStore +from aws_xray_sdk.core import patch_all +patch_all() LOGGER = configure_logger(__name__) S3_BUCKET_NAME = os.environ["S3_BUCKET_NAME"] ADF_PIPELINE_PREFIX = os.environ["ADF_PIPELINE_PREFIX"] diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/requirements.txt b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/lambda_layer/requirements.txt similarity index 83% rename from src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/requirements.txt rename to src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/lambda_layer/requirements.txt index 272e34c72..dbe0bbe57 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/requirements.txt +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/lambda_layer/requirements.txt @@ -2,3 +2,5 @@ pyyaml==5.4.1 schema==0.7.5 tenacity==8.1.0 wrapt==1.14.1 # https://github.com/aws/aws-lambda-builders/issues/302 +aws-xray-sdk==2.11.0 + diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/process_deployment_map.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/process_deployment_map.py index 9fcb432e4..3a0ad3ed5 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/process_deployment_map.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/process_deployment_map.py @@ -16,8 +16,10 @@ import boto3 from botocore.exceptions import ClientError from logger import configure_logger +from aws_xray_sdk.core import patch_all +patch_all() LOGGER = configure_logger(__name__) PIPELINE_MANAGEMENT_STATEMACHINE = os.getenv( "PIPELINE_MANAGEMENT_STATE_MACHINE", diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/store_pipeline_definition.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/store_pipeline_definition.py index 232007868..f8712cc64 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/store_pipeline_definition.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/store_pipeline_definition.py @@ -9,8 +9,10 @@ import boto3 from logger import configure_logger +from aws_xray_sdk.core import patch_all +patch_all() LOGGER = configure_logger(__name__) S3_BUCKET_NAME = os.environ["S3_BUCKET_NAME"] diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/pipeline_management.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/pipeline_management.yml index 2a7a40232..4164de4f4 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/pipeline_management.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/pipeline_management.yml @@ -27,6 +27,10 @@ Parameters: Type: String MinLength: "1" + RootAccountRegion: + Type: String + MinLength: "1" + CodeBuildImage: Type: String MinLength: "1" @@ -61,8 +65,20 @@ Globals: Tracing: Active Layers: - !Ref LambdaLayer + - !Ref PipelineManagementLayerVersion Resources: + PipelineManagementLayerVersion: + Type: "AWS::Serverless::LayerVersion" + Properties: + ContentUri: "../../adf-build/shared/" + CompatibleRuntimes: + - python3.9 + Description: "Common dependencies for ADF Pipeline Management Functions" + LayerName: pipeline_management_layer + Metadata: + BuildMethod: python3.9 + ADFPipelineManagementLambdaBasePolicy: Type: "AWS::IAM::ManagedPolicy" Properties: @@ -79,6 +95,10 @@ Resources: - "xray:PutTraceSegments" - "cloudwatch:PutMetricData" Resource: "*" + - Effect: Allow + Action: + - "events:PutEvents" + Resource: !Sub "arn:${AWS::Partition}:events:${RootAccountRegion}:${RootAccountId}:event-bus/ADF-Event-Bus" Roles: - !Ref DeploymentMapProcessingLambdaRole - !Ref CreateOrUpdateRuleLambdaRole @@ -903,6 +923,8 @@ Resources: ADF_LOG_LEVEL: !Ref ADFLogLevel PIPELINE_MANAGEMENT_STATE_MACHINE: !Sub "arn:${AWS::Partition}:states:${AWS::Region}:${AWS::AccountId}:stateMachine:ADFPipelineManagementStateMachine" ADF_ROLE_NAME: !Ref CrossAccountAccessRole + ADF_EVENTBUS_ARN: !Sub "arn:${AWS::Partition}:events:${RootAccountRegion}:${RootAccountId}:event-bus/ADF-Event-Bus" + ADF_EVENTBUS_REGION: !Ref RootAccountRegion FunctionName: DeploymentMapProcessorFunction Role: !GetAtt DeploymentMapProcessingLambdaRole.Arn Events: @@ -941,6 +963,8 @@ Resources: ADF_LOG_LEVEL: !Ref ADFLogLevel ADF_ROLE_NAME: !Ref CrossAccountAccessRole S3_BUCKET_NAME: !Ref PipelineBucket + ADF_EVENTBUS_ARN: !Sub "arn:${AWS::Partition}:events:${RootAccountRegion}:${RootAccountId}:event-bus/ADF-Event-Bus" + ADF_EVENTBUS_REGION: !Ref RootAccountRegion FunctionName: ADFPipelineCreateOrUpdateRuleFunction Role: !GetAtt CreateOrUpdateRuleLambdaRole.Arn @@ -957,6 +981,8 @@ Resources: ADF_LOG_LEVEL: !Ref ADFLogLevel ADF_ROLE_NAME: !Ref CrossAccountAccessRole S3_BUCKET_NAME: !Ref PipelineBucket + ADF_EVENTBUS_ARN: !Sub "arn:${AWS::Partition}:events:${RootAccountRegion}:${RootAccountId}:event-bus/ADF-Event-Bus" + ADF_EVENTBUS_REGION: !Ref RootAccountRegion FunctionName: ADFPipelineCreateRepositoryFunction Role: !GetAtt CreateRepositoryLambdaRole.Arn diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/events.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/events.py new file mode 100644 index 000000000..089ec5fca --- /dev/null +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/events.py @@ -0,0 +1,48 @@ +""" +Standardized class for pushing events from within the ADF Namespace +""" + + +import os +import boto3 + + +class ADFEvents: + def __init__( + self, service, namespace="adf", eventbus_arn=None, client: boto3.client = None + ) -> None: + """ + Client: Any Boto3 EventBridge client + Service: The name of the Service e.g AccountManagement.EnableSupport + namespace: Defaults to ADF + eventbus_arn: Optionally specify a custom EventBridge ARN. If no ARN is specified, and no ENV variable set, will default to ADF-Event-Bus + + """ + self.events = ( + client + if client + else boto3.client( + "events", + region_name=os.getenv("ADF_EVENTBUS_REGION", os.getenv("AWS_REGION")), + ) + ) + self.source = f"{namespace}.{service}" + self.eventbus_arn = ( + os.environ.get("ADF_EVENTBUS_ARN", "ADF-Event-Bus") + if eventbus_arn is None + else eventbus_arn + ) + + # This dict isn't mutated. So it's safe to default to this + def put_event(self, detailType, detail, resources=[]): # pylint: disable=W0102 + payload = { + "Source": self.source, + "Resources": resources, + "DetailType": detailType, + "Detail": detail, + "EventBusName": self.eventbus_arn, + } + trace_id = os.getenv("_X_AMZN_TRACE_ID") + if trace_id: + payload["TraceHeader"] = trace_id.split(";")[0] + self.events.put_events(Entries=[payload]) diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/requirements.txt b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/requirements.txt index 0a0413f78..0717ec7c4 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/requirements.txt +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/requirements.txt @@ -60,3 +60,4 @@ schema~=0.7.5 tenacity==8.1.0 typing-extensions~=4.4.0 urllib3~=1.26.14 +aws-xray-sdk==2.11.0 diff --git a/src/template.yml b/src/template.yml index d8d4edd55..104086145 100644 --- a/src/template.yml +++ b/src/template.yml @@ -18,13 +18,13 @@ Metadata: Labels: ["adf", "aws-deployment-framework", "multi-account", "cicd", "devops"] HomePageUrl: https://github.com/awslabs/aws-deployment-framework - SemanticVersion: 3.2.0 + SemanticVersion: 3.2.2 SourceCodeUrl: https://github.com/awslabs/aws-deployment-framework Mappings: Metadata: ADF: - Version: 3.2.0 + Version: 3.2.2 Parameters: CrossAccountAccessRoleName: @@ -258,6 +258,9 @@ Resources: - "xray:PutTelemetryRecords" - "xray:PutTraceSegments" Resource: "*" + - Effect: "Allow" + Action: "events:PutEvents" + Resource: !GetAtt ADFEventBus.Arn Roles: - !Ref AccountProcessingLambdaRole - !Ref GetAccountRegionsFunctionRole @@ -285,6 +288,9 @@ Resources: PolicyDocument: Version: "2012-10-17" Statement: + - Effect: "Allow" + Action: "events:PutEvents" + Resource: !GetAtt ADFEventBus.Arn - Effect: Allow Action: - "xray:PutTelemetryRecords" @@ -388,6 +394,7 @@ Resources: ADF_VERSION: !FindInMap ['Metadata', 'ADF', 'Version'] ADF_LOG_LEVEL: !Ref LogLevel ADF_ROLE_NAME: !Ref CrossAccountAccessRoleName + ADF_EVENTBUS_ARN: !GetAtt ADFEventBus.Arn FunctionName: AccountAliasConfigurationFunction Role: !GetAtt AccountAliasConfigFunctionRole.Arn @@ -657,6 +664,11 @@ Resources: AccountManagementStateMachine: Type: "AWS::StepFunctions::StateMachine" + Metadata: + cfn-lint: + config: + ignore_checks: + - I3042 # Seems to be incorrectly thinking there's a hard-coded ARN in the definition Properties: RoleArn: !GetAtt StateMachineExecutionRole.Arn TracingConfiguration: @@ -812,7 +824,7 @@ Resources: "Next": "GetAccountDefaultRegionsFunction" } ], - "Default": "Success" + "Default": "PublishCompleteEvent" }, "GetAccountDefaultRegionsFunction": { "Type": "Task", @@ -840,7 +852,7 @@ Resources: }, "DeleteDefaultVPCMap": { "Type": "Map", - "Next": "Success", + "Next": "PublishCompleteEvent", "Iterator": { "StartAt": "DeleteDefaultVPC", "States": { @@ -877,6 +889,21 @@ Resources: }, "ResultPath": null }, + "PublishCompleteEvent": { + "Type": "Task", + "Resource": "arn:aws:states:::events:putEvents", + "Parameters": { + "Entries": [ + { + "Detail.$": "$", + "DetailType": "ACCOUNT_CREATION_COMPLETE", + "EventBusName": "ADF-Event-Bus", + "Source": "ADF.AccountManagement.AccountCreation" + } + ] + }, + "Next": "Success" + }, "Success": { "Type": "Succeed" } @@ -1957,6 +1984,27 @@ Resources: RoleArn: !GetAtt PipelineCloudWatchEventRole.Arn Id: adf-codepipeline-trigger-bootstrap + ADFEventBus: + Type: AWS::Events::EventBus + Properties: + Name: ADF-Event-Bus + + DeploymentAccountPutEventsPolicy: + Type: AWS::Events::EventBusPolicy + Properties: + StatementId: "DeploymentAccountPutEventStatement" + Statement: + Effect: "Allow" + Principal: "*" + Action: "events:PutEvents" + Resource: !GetAtt ADFEventBus.Arn + Condition: + ArnLike: + aws:PrincipalArn: + - !Sub "arn:${AWS::Partition}:iam::${DeploymentAccount.AccountId}:role/adf-automation/*" + EventBusName: + Ref: ADFEventBus + Outputs: ADFVersionNumber: Value: !FindInMap ["Metadata", "ADF", "Version"] diff --git a/tox.ini b/tox.ini index a7d5c8957..dcfd44f31 100644 --- a/tox.ini +++ b/tox.ini @@ -35,7 +35,7 @@ deps = -r{toxinidir}/src/lambda_codebase/cross_region_bucket/requirements.txt -r{toxinidir}/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/determine_default_branch/requirements.txt -r{toxinidir}/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/initial_commit/requirements.txt - -r{toxinidir}/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/requirements.txt + -r{toxinidir}/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/pipeline_management/lambda_layer/requirements.txt -r{toxinidir}/src/lambda_codebase/initial_commit/bootstrap_repository/adf-bootstrap/deployment/lambda_codebase/requirements.txt -r{toxinidir}/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/requirements.txt -r{toxinidir}/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/requirements.txt