Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: terraform target by tags #685

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
10 changes: 10 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
accounts tagged with `environment=prd` OR `environment=dev` AND `cost-center = ccoe`
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Resources:
- organizations:ListOrganizationalUnitsForParent
- organizations:ListRoots
- organizations:ListChildren
- organizations:ListTagsForResource
- tag:GetResources
Resource: "*"
Roles:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you change this to include a higher retry count?
Otherwise it might run into the rate limitation of the AWS Organizations API and fail.
This would be likely if multiple Terraform pipelines would run concurrently.

An example how to change it: https://github.com/awslabs/aws-deployment-framework/pull/774/files#diff-05a19c0ecc528b81f98611295fccbb53ec8ce9f20937f67308f3bc886e4a5018R83-R89

response = ssm.get_parameter(Name='cross_account_access_role')
CROSS_ACCOUNT_ACCESS_ROLE = response['Parameter']['Value']

Expand All @@ -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(
Expand Down Expand Up @@ -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})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Values": tag_values})
"Values": tag_values,
})

LOGGER.info(
"Tag filters %s",
tag_filters
)
organization_api_region = get_organization_api_region(REGION_DEFAULT)
print(organization_api_region)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(organization_api_region)

tags_client = get_boto3_client(
'resourcegroupstaggingapi',
(
f'arn:{PARTITION}:sts::{MANAGEMENT_ACCOUNT_ID}:role/'
f'{CROSS_ACCOUNT_ACCESS_ROLE}-readonly'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be updated to the new role added in v4.

),
'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 = []
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down