Skip to content

Commit d4c7291

Browse files
igordustsbkok
andauthored
Add caching to Organizations API calls (#789)
Add caching to Organizations API calls to avoid throttling --- Co-authored-by: Simon Kok <[email protected]>
1 parent 955808d commit d4c7291

File tree

4 files changed

+564
-70
lines changed

4 files changed

+564
-70
lines changed

src/lambda_codebase/determine_event.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,20 @@
2121
def lambda_handler(event, _):
2222
parameters = ParameterStore(region=REGION_DEFAULT, role=boto3)
2323
account_id = event.get('detail').get('requestParameters').get('accountId')
24-
organizations = Organizations(role=boto3, account_id=account_id)
24+
cache = Cache()
25+
organizations = Organizations(role=boto3, account_id=account_id, cache=cache)
2526
parsed_event = Event(
2627
event=event,
2728
parameter_store=parameters,
2829
organizations=organizations,
2930
account_id=account_id
3031
)
31-
cache = Cache()
3232

3333
account_path = (
3434
"ROOT" if parsed_event.moved_to_root
3535
else parsed_event.organizations.build_account_path(
3636
parsed_event.destination_ou_id,
37-
[], # Initial empty array to hold OU Path,
38-
cache,
37+
[], # Initial empty array to hold OU Path
3938
)
4039
)
4140

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

+7-8
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,14 @@ def worker_thread(
256256

257257
organizations = Organizations(
258258
role=boto3,
259-
account_id=account_id
259+
account_id=account_id,
260+
cache=cache,
260261
)
261262
ou_id = organizations.get_parent_info().get("ou_parent_id")
262263

263264
account_path = organizations.build_account_path(
264-
ou_id,
265-
[], # Initial empty array to hold OU Path,
266-
cache
265+
ou_id=ou_id,
266+
account_path=[], # Initial empty array to hold OU Path
267267
)
268268
try:
269269
role = ensure_generic_account_can_be_setup(
@@ -468,6 +468,7 @@ def main(): # pylint: disable=R0915
468468

469469
policies = OrganizationPolicy()
470470
config = Config()
471+
cache = Cache()
471472

472473
try:
473474
parameter_store = ParameterStore(REGION_DEFAULT, boto3)
@@ -476,7 +477,8 @@ def main(): # pylint: disable=R0915
476477
)
477478
organizations = Organizations(
478479
role=boto3,
479-
account_id=deployment_account_id
480+
account_id=deployment_account_id,
481+
cache=cache,
480482
)
481483
policies.apply(organizations, parameter_store, config.config)
482484
sts = STS()
@@ -485,13 +487,10 @@ def main(): # pylint: disable=R0915
485487
deployment_account_id=deployment_account_id,
486488
config=config
487489
)
488-
489-
cache = Cache()
490490
ou_id = organizations.get_parent_info().get("ou_parent_id")
491491
account_path = organizations.build_account_path(
492492
ou_id=ou_id,
493493
account_path=[],
494-
cache=cache
495494
)
496495
s3 = S3(
497496
region=REGION_DEFAULT,

src/lambda_codebase/initial_commit/bootstrap_repository/adf-build/shared/python/organizations.py

+48-32
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from logger import configure_logger
1818
from paginator import paginator
1919
from partition import get_organization_api_region
20+
from cache import Cache
2021

2122
LOGGER = configure_logger(__name__)
2223
AWS_REGION = os.getenv("AWS_REGION")
@@ -38,7 +39,7 @@ class Organizations: # pylint: disable=R0904
3839
)
3940

4041
def __init__(
41-
self, role=None, account_id=None, org_client=None, tagging_client=None
42+
self, role=None, account_id=None, org_client=None, tagging_client=None, cache=None
4243
):
4344
if role:
4445
LOGGER.warning(
@@ -69,8 +70,8 @@ def __init__(
6970
if not tagging_client
7071
else tagging_client
7172
)
73+
self.cache = cache or Cache()
7274
self.account_id = account_id
73-
self.root_id = None
7475

7576
def get_parent_info(self, account_id=None):
7677
"""
@@ -302,7 +303,9 @@ def get_accounts(self, protected_ou_ids=None, include_root=True):
302303
return accounts
303304

304305
def get_organization_info(self):
305-
response = self.client.describe_organization()
306+
if not self.cache.exists('organization'):
307+
self.cache.add('organization', self.client.describe_organization())
308+
response = self.cache.get('organization')
306309
return {
307310
"organization_management_account_id": (
308311
response
@@ -315,19 +318,26 @@ def get_organization_info(self):
315318

316319
def describe_ou_name(self, ou_id):
317320
try:
318-
response = self.client.describe_organizational_unit(
319-
OrganizationalUnitId=ou_id
320-
)
321-
return response["OrganizationalUnit"]["Name"]
321+
cache_key = f'ou_name_{ou_id}'
322+
if not self.cache.exists(cache_key):
323+
response = self.client.describe_organizational_unit(
324+
OrganizationalUnitId=ou_id
325+
)
326+
self.cache.add(cache_key, response["OrganizationalUnit"]["Name"])
327+
return self.cache.get(cache_key)
328+
322329
except ClientError as error:
323330
raise RootOUIDError(
324331
"OU is the Root of the Organization",
325332
) from error
326333

327334
def describe_account_name(self, account_id):
328335
try:
329-
response = self.client.describe_account(AccountId=account_id)
330-
return response["Account"]["Name"]
336+
cache_key = f'account_name_{account_id}'
337+
if not self.cache.exists(cache_key):
338+
response = self.client.describe_account(AccountId=account_id)
339+
self.cache.add(cache_key, response["Account"]["Name"])
340+
return self.cache.get(cache_key)
331341
except ClientError as error:
332342
LOGGER.error(
333343
"Failed to retrieve account name for account ID %s",
@@ -340,7 +350,10 @@ def determine_ou_path(ou_path, ou_child_name):
340350
return f"{ou_path}/{ou_child_name}" if ou_path else ou_child_name
341351

342352
def list_parents(self, ou_id):
343-
return self.client.list_parents(ChildId=ou_id).get("Parents")[0]
353+
cache_key = f'parents_{ou_id}'
354+
if not self.cache.exists(cache_key):
355+
self.cache.add(cache_key, self.client.list_parents(ChildId=ou_id).get("Parents")[0])
356+
return self.cache.get(cache_key)
344357

345358
def get_accounts_for_parent(self, parent_id):
346359
return paginator(self.client.list_accounts_for_parent, ParentId=parent_id)
@@ -351,7 +364,9 @@ def get_child_ous(self, parent_id):
351364
)
352365

353366
def get_ou_root_id(self):
354-
return self.client.list_roots().get("Roots")[0].get("Id")
367+
if not self.cache.exists('root_id'):
368+
self.cache.add('root_id', self.client.list_roots().get("Roots")[0].get("Id"))
369+
return self.cache.get('root_id')
355370

356371
def ou_path_to_id(self, path):
357372
nested_dir_paths = path.split('/')[1:]
@@ -399,23 +414,17 @@ def get_accounts_in_path(
399414
)
400415
return accounts
401416

402-
def build_account_path(self, ou_id, account_path, cache):
417+
def build_account_path(self, ou_id, account_path):
403418
"""
404419
Builds a path tree to the account from the root of the Organization
405420
"""
406421
current = self.list_parents(ou_id)
407422

408423
# While not at the root of the Organization
409424
while current.get("Type") != "ROOT":
410-
# check cache for ou name of id
411-
if not cache.exists(current.get("Id")):
412-
cache.add(
413-
current.get("Id"),
414-
self.describe_ou_name(current.get("Id")),
415-
)
416-
ou_name = cache.get(current.get("Id"))
425+
ou_name = self.describe_ou_name(current.get("Id"))
417426
account_path.append(ou_name)
418-
return self.build_account_path(current.get("Id"), account_path, cache)
427+
return self.build_account_path(current.get("Id"), account_path)
419428
return Organizations.determine_ou_path(
420429
"/".join(list(reversed(account_path))),
421430
self.describe_ou_name(self.get_parent_info().get("ou_parent_id")),
@@ -440,16 +449,23 @@ def get_account_ids_for_tags(self, tags):
440449
account_ids.append(account_id)
441450
return account_ids
442451

443-
def list_organizational_units_for_parent(self, parent_ou):
444-
organizational_units = [
452+
def _list_organizational_units_for_parent(self, parent_ou):
453+
return [
445454
ou
446455
for org_units in (
447456
self.client.get_paginator("list_organizational_units_for_parent")
448457
.paginate(ParentId=parent_ou)
449458
)
450459
for ou in org_units["OrganizationalUnits"]
451460
]
452-
return organizational_units
461+
462+
def list_organizational_units_for_parent(self, parent_ou):
463+
LOGGER.debug('Looking for children in %s', parent_ou)
464+
cache_key = f'children_{parent_ou}'
465+
if not self.cache.exists(cache_key):
466+
LOGGER.debug('Cache MISS for children of OU %s', parent_ou)
467+
self.cache.add(cache_key, self._list_organizational_units_for_parent(parent_ou))
468+
return self.cache.get(cache_key)
453469

454470
def get_account_id(self, account_name):
455471
for account in self.list_accounts():
@@ -462,21 +478,22 @@ def list_accounts(self):
462478
"""
463479
Retrieves all accounts in organization.
464480
"""
465-
existing_accounts = [
466-
account
467-
for accounts in self.client.get_paginator("list_accounts").paginate()
468-
for account in accounts["Accounts"]
469-
]
470-
return existing_accounts
481+
if not self.cache.exists('accounts'):
482+
self.cache.add('accounts', [
483+
account
484+
for accounts in self.client.get_paginator("list_accounts").paginate()
485+
for account in accounts["Accounts"]
486+
])
487+
return self.cache.get('accounts')
471488

472489
def get_ou_id(self, ou_path, parent_ou_id=None):
473490
# Return root OU if '/' is provided
474491
if ou_path.strip() == "/":
475-
return self.root_id
492+
return self.get_ou_root_id()
476493

477494
# Set initial OU to start looking for given ou_path
478495
if parent_ou_id is None:
479-
parent_ou_id = self.root_id
496+
parent_ou_id = self.get_ou_root_id()
480497

481498
# Parse ou_path and find the ID
482499
ou_hierarchy = ou_path.strip("/").split("/")
@@ -497,7 +514,6 @@ def get_ou_id(self, ou_path, parent_ou_id=None):
497514
return parent_ou_id
498515

499516
def move_account(self, account_id, ou_path):
500-
self.root_id = self.get_ou_root_id()
501517
ou_id = self.get_ou_id(ou_path)
502518
response = self.client.list_parents(ChildId=account_id)
503519
source_parent_id = response["Parents"][0]["Id"]

0 commit comments

Comments
 (0)