Recently, I’m tasked to transfer a set of AWS accounts to another cloud service provider. I realize there is not a simple way to capture the organizational tree structure. Knowing the tree structure is quite important in understanding the net effects of SCP. A python script is developed to solve this problem.
Initially, I used awscli and bash to traverse the tree and print out OU and account info. That is incredibly inefficient. The script was completely rewritten with python. It starts by checking the root id, then recursively traverse through the tree and print out all OU and member account info.
Source code is available on my git repo. Here is what it produces on a sandbox environment:
▶ ./aws-org-dump.py Root r-2le9 . xpk-mp 221414080000 . Workload ou-2le9-uibp6p5e .. Regional-Prod ou-2le9-3l86wfde .. Regional-Dev ou-2le9-bidgckf7 ... workload1 872439010000 . Core ou-2le9-6yr1a2uf .. Core-prod ou-2le9-patloil1 .. Core-Dev ou-2le9-scv4v7d1 ... logging 958052660000
The biggest challenge for me is to extract data from the API response. AWS returns a dictionary for every boto3 call. For example, list_organizational_units_for_parent() returns the following response:
{'Roots': [{'Id': 'r-2le9', 'Arn': 'arn:aws:organizations::221414080000:root/o-fn3felbr1b/r-2le9', 'Name': 'Root', 'PolicyTypes': [{'Type': 'BACKUP_POLICY', 'Status': 'ENABLED'}, {'Type': 'TAG_POLICY', 'Status': 'ENABLED'}, {'Type': 'SERVICE_CONTROL_POLICY', 'Status': 'ENABLED'}]}], 'ResponseMetadata': {'RequestId': 'f378ed19-50ae-4114-a3f4-5a40c2c1a35b', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': 'f378ed19-50ae-4114-a3f4-5a40c2c1a35b', 'content-type': 'application/x-amz-json-1.1', 'content-length': '264', 'date': 'Sat, 14 May 2022 03:22:35 GMT'}, 'RetryAttempts': 0}}
I’m only interested in the root id. So first I need to figure out the first key in the map, which is Roots. Then I only need the first element in the list, which is another map where the value of Id can be found. For other queries, the response can be a much more complex map. To even visualize the map, try do a json.dumps() and feed the result to jq. From what I see, the response dictionary is already json-like. json.dumps() simple replaces the single quotes with double quotes. Apparently, python map uses single quote for string, while json uses double quote.
Give it a try if you also need to compile something like this.