Skip to content

Commit c54cbf3

Browse files
sbkokskycolangelom
andauthored
Retry logic for DescribeRegions while creating new account, handling non-empty default vpc v2 (#348)
* retry logic for DescribeRegions while creating new account, handling non-empty default vpc * fixed copy/paste typo * Fixes according to code review comments on PR #238 Co-authored-by: COLANGELO Monica <[email protected]>
1 parent f9ffc7c commit c54cbf3

File tree

3 files changed

+84
-51
lines changed

3 files changed

+84
-51
lines changed

src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/provisioner/main.py

+48-23
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
from concurrent.futures import ThreadPoolExecutor
1111
import boto3
12+
import tenacity
1213
from organizations import Organizations
1314
from logger import configure_logger
1415
from parameter_store import ParameterStore
@@ -17,25 +18,32 @@
1718

1819

1920
LOGGER = configure_logger(__name__)
20-
ACCOUNTS_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'adf-accounts'))
21+
ACCOUNTS_FOLDER = os.path.abspath(os.path.join(
22+
os.path.dirname(__file__), '..', '..', 'adf-accounts'))
23+
2124

2225
def main():
2326
accounts = read_config_files(ACCOUNTS_FOLDER)
2427
if not bool(accounts):
25-
LOGGER.info(f"Found {len(accounts)} account(s) in configuration file(s). Account provisioning will not continue.")
28+
LOGGER.info(
29+
f"Found {len(accounts)} account(s) in configuration file(s). Account provisioning will not continue.")
2630
return
2731
LOGGER.info(f"Found {len(accounts)} account(s) in configuration file(s).")
2832
organizations = Organizations(boto3)
2933
support = Support(boto3)
3034
all_accounts = organizations.get_accounts()
31-
parameter_store = ParameterStore(os.environ.get('AWS_REGION', 'us-east-1'), boto3)
32-
adf_role_name = parameter_store.fetch_parameter('cross_account_access_role')
35+
parameter_store = ParameterStore(
36+
os.environ.get('AWS_REGION', 'us-east-1'), boto3)
37+
adf_role_name = parameter_store.fetch_parameter(
38+
'cross_account_access_role')
3339
for account in accounts:
3440
try:
35-
account_id = next(acc["Id"] for acc in all_accounts if acc["Name"] == account.full_name)
36-
except StopIteration: # If the account does not exist yet..
41+
account_id = next(
42+
acc["Id"] for acc in all_accounts if acc["Name"] == account.full_name)
43+
except StopIteration: # If the account does not exist yet..
3744
account_id = None
38-
create_or_update_account(organizations, support, account, adf_role_name, account_id)
45+
create_or_update_account(
46+
organizations, support, account, adf_role_name, account_id)
3947

4048

4149
def create_or_update_account(org_session, support_session, account, adf_role_name, account_id=None):
@@ -57,24 +65,12 @@ def create_or_update_account(org_session, support_session, account, adf_role_nam
5765
), 'adf_account_provisioning'
5866
)
5967

60-
LOGGER.info(f'Ensuring account {account_id} (alias {account.alias}) is in OU {account.ou_path}')
68+
LOGGER.info(
69+
f'Ensuring account {account_id} (alias {account.alias}) is in OU {account.ou_path}')
6170
org_session.move_account(account_id, account.ou_path)
6271
if account.delete_default_vpc:
6372
ec2_client = role.client('ec2')
64-
all_regions = [
65-
region['RegionName']
66-
for region in ec2_client.describe_regions(
67-
AllRegions=False,
68-
Filters=[
69-
{
70-
'Name': 'opt-in-status',
71-
'Values': [
72-
'opt-in-not-required',
73-
]
74-
}
75-
]
76-
)['Regions']
77-
]
73+
all_regions = get_all_regions(ec2_client)
7874
args = (
7975
(account_id, region, role)
8076
for region in all_regions
@@ -88,10 +84,38 @@ def create_or_update_account(org_session, support_session, account, adf_role_nam
8884
org_session.create_account_alias(account.alias, role)
8985

9086
if account.tags:
91-
LOGGER.info(f'Ensuring tags exist for account {account_id}: {account.tags}')
87+
LOGGER.info(
88+
f'Ensuring tags exist for account {account_id}: {account.tags}')
9289
org_session.create_account_tags(account_id, account.tags)
9390

9491

92+
@tenacity.retry(
93+
stop=tenacity.stop_after_attempt(9),
94+
wait=tenacity.wait_random_exponential(),
95+
)
96+
def get_all_regions(ec2_client):
97+
try:
98+
all_regions = [
99+
region['RegionName']
100+
for region in ec2_client.describe_regions(
101+
AllRegions=False,
102+
Filters=[
103+
{
104+
'Name': 'opt-in-status',
105+
'Values': [
106+
'opt-in-not-required',
107+
]
108+
}
109+
]
110+
)['Regions']
111+
]
112+
LOGGER.info(f'Regions are: {all_regions}')
113+
return all_regions
114+
except Exception as ce:
115+
LOGGER.info('Failed to describe regions: %s, retrying...', ce)
116+
raise
117+
118+
95119
def schedule_delete_default_vpc(account_id, region, role):
96120
"""Schedule a delete_default_vpc on a thread
97121
:param account_id: The account ID to remove the VPC from
@@ -101,5 +125,6 @@ def schedule_delete_default_vpc(account_id, region, role):
101125
ec2_client = role.client('ec2', region_name=region)
102126
delete_default_vpc(ec2_client, account_id, region, role)
103127

128+
104129
if __name__ == '__main__':
105130
main()

src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/provisioner/src/vpc.py

+33-26
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,40 @@
1010
LOGGER = configure_logger(__name__)
1111

1212

13-
def vpc_cleanup(vpcid, role, region):
13+
def vpc_cleanup(account_id, vpcid, role, region):
1414
if not vpcid:
1515
return
16-
ec2 = role.resource('ec2', region_name=region)
17-
ec2client = ec2.meta.client
18-
vpc = ec2.Vpc(vpcid)
19-
# detach and delete all gateways associated with the vpc
20-
for gw in vpc.internet_gateways.all():
21-
vpc.detach_internet_gateway(InternetGatewayId=gw.id)
22-
gw.delete()
23-
# Route table associations
24-
for rt in vpc.route_tables.all():
25-
for rta in rt.associations:
26-
if not rta.main:
27-
rta.delete()
28-
# Security Group
29-
for sg in vpc.security_groups.all():
30-
if sg.group_name != 'default':
31-
sg.delete()
32-
# Network interfaces
33-
for subnet in vpc.subnets.all():
34-
for interface in subnet.network_interfaces.all():
35-
interface.delete()
36-
subnet.delete()
37-
# Delete vpc
38-
ec2client.delete_vpc(VpcId=vpcid)
39-
LOGGER.info(f"VPC {vpcid} and associated resources has been deleted.")
16+
try:
17+
ec2 = role.resource('ec2', region_name=region)
18+
ec2client = ec2.meta.client
19+
vpc = ec2.Vpc(vpcid)
20+
# detach and delete all gateways associated with the vpc
21+
for gw in vpc.internet_gateways.all():
22+
vpc.detach_internet_gateway(InternetGatewayId=gw.id)
23+
gw.delete()
24+
# Route table associations
25+
for rt in vpc.route_tables.all():
26+
for rta in rt.associations:
27+
if not rta.main:
28+
rta.delete()
29+
# Security Group
30+
for sg in vpc.security_groups.all():
31+
if sg.group_name != 'default':
32+
sg.delete()
33+
# Network interfaces
34+
for subnet in vpc.subnets.all():
35+
for interface in subnet.network_interfaces.all():
36+
interface.delete()
37+
subnet.delete()
38+
# Delete vpc
39+
ec2client.delete_vpc(VpcId=vpcid)
40+
LOGGER.info(f"VPC {vpcid} and associated resources has been deleted.")
41+
except exceptions.ClientError:
42+
LOGGER.warning(
43+
f"WARNING: cannot delete VPC {vpcid} in account {account_id}",
44+
exc_info=True,
45+
)
46+
raise
4047

4148

4249
def delete_default_vpc(client, account_id, region, role):
@@ -73,4 +80,4 @@ def delete_default_vpc(client, account_id, region, role):
7380

7481
LOGGER.info(
7582
f"Found default VPC Id {default_vpc_id} in the {region} region")
76-
vpc_cleanup(default_vpc_id, role, region)
83+
vpc_cleanup(account_id, default_vpc_id, role, region)
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Install libs here that you might want in AWS CodeBuild (On Master Account)
22
astroid~=2.4.2
3-
awscli==1.18.140
43
aws-sam-cli==1.15.0
5-
botocore==1.17.63
4+
awscli==1.18.140
65
boto3==1.14.63
6+
botocore==1.17.63
77
jsii<1.20.0,>=1.16.0
88
mock~=4.0.3
99
pip~=20.2.3
1010
pylint~=2.6.0
1111
pytest~=6.2.1
1212
pyyaml>=5.3
1313
six~=1.15.0
14+
tenacity==6.3.0
1415
urllib3~=1.25.11

0 commit comments

Comments
 (0)