diff --git a/docs/user-guide.md b/docs/user-guide.md index 68aa763a4..f073e86dd 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -1109,6 +1109,16 @@ pipeline for more details in the setup and integration. - `TARGET_ACCOUNTS`: comma separated list of target accounts. - `TARGET_OUS`: comma separated list of target leaf OUs (parent OUs are supported). +- `TARGET_TAGS`: specify filter tags with the following format: + ```Name=tagName1,Values=value1,value2,val*;Name=tagName2,Values=value3,value4``` + + Each clause is composed by `Name` and `Values` fields. You can input multiple values in a clause, separated + by comma, use `*` wildcard to match zero or more characters, use `?` wildcard to match exactly a character + + All filter clauses are applied with logical AND, all values in a single clause are applied with logical OR + + **Eg:** `TARGET_TAGS=Name=environment,Values=prd,dev;Name=cost-center,Values=ccoe` will match all + accounts tagged with `environment=prd` OR `environment=dev` AND `cost-center = ccoe` - `REGIONS`: comma separated list of target regions. If this parameter is empty, the main ADF region is used. - `MANAGEMENT_ACCOUNT_ID`: id of the AWS Organizations management account. diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml index ec598e675..2007b5951 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/global.yml @@ -48,6 +48,7 @@ Resources: - organizations:ListOrganizationalUnitsForParent - organizations:ListRoots - organizations:ListChildren + - organizations:ListTagsForResource - tag:GetResources Resource: "*" Roles: diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh index efcc64dcd..b489b313f 100755 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/adf_terraform.sh @@ -97,10 +97,12 @@ do AWS_REGION=$(echo -n "$REGION" | sed 's/^[ \t]*//;s/[ \t]*$//') # sed trims whitespaces export TF_VAR_TARGET_REGION=$AWS_REGION # if TARGET_ACCOUNTS and TARGET_OUS are not defined apply to all accounts - if [[ -z "$TARGET_ACCOUNTS" ]] && [[ -z "$TARGET_OUS" ]] + if [[ -z "$TARGET_ACCOUNTS" ]] && [[ -z "$TARGET_OUS" ]] && [[ -z "$TARGET_TAGS" ]] then echo "Apply to all accounts" - for ACCOUNT_ID in $(jq '.[].AccountId' "${CURRENT}/accounts.json" | sed 's/"//g' ) + ACCOUNTS=$(jq -r '.[].AccountId' "${CURRENT}/accounts.json") + echo $ACCOUNTS + for ACCOUNT_ID in $ACCOUNTS do tfrun done @@ -109,7 +111,8 @@ do if ! [[ -z "$TARGET_ACCOUNTS" ]] then # apply only on a subset of accounts (TARGET_ACCOUNTS) - echo "List of target account: $TARGET_ACCOUNTS" + echo "List of target account - Region $TF_VAR_TARGET_REGION" + echo $TARGET_ACCOUNTS for ACCOUNT_ID in $(echo "$TARGET_ACCOUNTS" | sed "s/,/ /g") do tfrun @@ -118,8 +121,23 @@ do if ! [[ -z "$TARGET_OUS" ]] then - echo "List target OUs: $TARGET_OUS" - for ACCOUNT_ID in $(jq '.[].AccountId' "${CURRENT}/accounts_from_ous.json" | sed 's/"//g' ) + ACCOUNTS=$(jq -r '.[].AccountId' "${CURRENT}/accounts_from_ous.json") + echo "List target OUs - Region $TF_VAR_TARGET_REGION" + echo $TARGET_OUS + echo Accounts matching OUs: $ACCOUNTS + for ACCOUNT_ID in $ACCOUNTS + do + tfrun + done + fi + + if ! [[ -z "$TARGET_TAGS" ]] + then + ACCOUNTS=$(jq -r '.[].AccountId' "${CURRENT}/accounts_from_tags.json") + echo "List target TAGS - Region $TF_VAR_TARGET_REGION" + echo $TARGET_TAGS + echo Accounts matching tags: $ACCOUNTS + for ACCOUNT_ID in $ACCOUNTS do tfrun done diff --git a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py index 91fab866f..dbe6c7235 100644 --- a/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py +++ b/src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/helpers/terraform/get_accounts.py @@ -11,6 +11,7 @@ import boto3 from paginator import paginator from partition import get_partition +from partition import get_organization_api_region # Configure logging logging.basicConfig(level=logging.INFO) @@ -20,10 +21,12 @@ MANAGEMENT_ACCOUNT_ID = os.environ["MANAGEMENT_ACCOUNT_ID"] TARGET_OUS = os.environ.get("TARGET_OUS") +TARGET_TAGS = os.environ.get("TARGET_TAGS") REGION_DEFAULT = os.environ["AWS_REGION"] PARTITION = get_partition(REGION_DEFAULT) sts = boto3.client('sts') ssm = boto3.client('ssm') +organizations = boto3.client('organizations') response = ssm.get_parameter(Name='cross_account_access_role') CROSS_ACCOUNT_ACCESS_ROLE = response['Parameter']['Value'] @@ -38,6 +41,11 @@ def main(): with open('accounts_from_ous.json', 'w', encoding='utf-8') as outfile: json.dump(accounts_from_ous, outfile) + if TARGET_TAGS: + accounts_from_tags = get_accounts_from_tags() + with open('accounts_from_tags.json', 'w', encoding='utf-8') as outfile: + json.dump(accounts_from_tags, outfile) + def list_organizational_units_for_parent(parent_ou): organizations = get_boto3_client( @@ -90,6 +98,41 @@ def get_accounts(): ) +def get_accounts_from_tags(): + tag_filters = [] + for tags in TARGET_TAGS.split(";"): + tag_name = tags.split(",", 1)[0].split("=")[1] + tag_values = tags.split(",", 1)[1].split("=")[1].split(",") + tag_filters.append({ + "Key": tag_name, + "Values": tag_values}) + LOGGER.info( + "Tag filters %s", + tag_filters + ) + organization_api_region = get_organization_api_region(REGION_DEFAULT) + print(organization_api_region) + tags_client = get_boto3_client( + 'resourcegroupstaggingapi', + ( + f'arn:{PARTITION}:sts::{MANAGEMENT_ACCOUNT_ID}:role/' + f'{CROSS_ACCOUNT_ACCESS_ROLE}-readonly' + ), + 'getaccountIDsFromTags', + region_name=organization_api_region, + ) + account_ids = [] + for resource in paginator( + tags_client.get_resources, + TagFilters=tag_filters, + ResourceTypeFilters=["organizations"], + ): + arn = resource["ResourceARN"] + account_id = arn.split("/")[::-1][0] + account_ids.append({"AccountId": account_id}) + return account_ids + + def get_accounts_from_ous(): parent_ou_id = None account_list = [] @@ -142,7 +185,7 @@ def get_accounts_from_ous(): return account_list -def get_boto3_client(service, role, session_name): +def get_boto3_client(service, role, session_name, region_name=''): role = sts.assume_role( RoleArn=role, RoleSessionName=session_name, @@ -153,7 +196,10 @@ def get_boto3_client(service, role, session_name): aws_secret_access_key=role['Credentials']['SecretAccessKey'], aws_session_token=role['Credentials']['SessionToken'] ) - return session.client(service) + if region_name != '': + return session.client(service, region_name=region_name) + else: + return session.client(service) def get_account_recursive(org_client: boto3.client, ou_id: str, path: str) -> list: