diff --git a/CHANGELOG.md b/CHANGELOG.md index 09ca415..4b0f1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.6.0] - 2022-04-03 +### Added +- Botocove now supports AWS Organizational Unit IDs as a target and ignore ID. Child +accounts and OUs will be recursively discovered and targeted for the passed OU ID. ## [1.5.2] - 2022-23-02 ### Added - Botocove now supports a regions argument, allowing sessions to be run across diff --git a/README.md b/README.md index 32382eb..54d588e 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ # Botocove -Run a function against a selection of AWS accounts, or all AWS accounts in an -organization, concurrently with thread safety. Run in one or multiple regions. +Run a function against a selection of AWS accounts, Organizational Units (OUs) +or all AWS accounts in an organization, concurrently with thread safety. +Run in one or multiple regions. -By default, opinionated to work with the standard -AWS Organization master/member configuration from an organization master -account. +Opinionated by default to work with the standard AWS Organization master/member +configuration from an organization master account but customisable for any context. - Fast - Easy - Dolphin Themed 🐬 -A simple decorator for functions to remove time and complexity burden. Uses +Botocove is a simple decorator for functions to remove time and complexity burden. Uses `ThreadPoolExecutor` to run boto3 sessions against AWS accounts concurrently. -Decorating a function in `@cove` provides a boto3 session to the function and runs it -in every account required, gathering all results into a dictionary. +Decorating a function in `@cove` provides a boto3 session to the decorated Python +function and runs it in every account requested, gathering all results into a dictionary. **Warning**: this tool gives you the potential to make dangerous changes at scale. **Test carefully and make idempotent changes**! Please read available @@ -100,7 +100,7 @@ def main(): Here's an example of a more customised Cove decorator: ``` @cove( - target_ids=["123456789101", "234567891011"], + target_ids=["123456789101", "234567891011"], # also accepts OU ids! rolename="AWSControlTowerExecution", raise_exception=True, regions=["eu-west-1", "eu-west-2", "us-east-1"], @@ -125,13 +125,18 @@ Equivalent to: `target_ids`: List[str] -A list of AWS accounts and/or AWS Organization Units as strings to attempt to assume role in to. When unset, -default attempts to use every available account ID in an AWS organization. When specifing ou's it will recursivly fetch all child ou's as well. +A list of AWS account IDs and/or AWS Organization Units IDs to attempt to assume role +in to. When unset, attempts to use every available account ID in an AWS organization. +When specifing target OU's, all child OUs and accounts belonging to that OU will be +collected. `ignore_ids`: List[str] -A list of AWS account ID's that will not attempt assumption in to. Allows IDs to -be ignored. +A list of AWS account ID's and OU's to prevent functions being run by Cove. Ignored IDs +takes precedence over `target_ids`. Providing an OU ID will collect +all child OUs and accounts to ignore. + +The calling account that is running the Cove-wrapped function at runtime is always ignored. `rolename`: str diff --git a/botocove/cove_host_account.py b/botocove/cove_host_account.py index eb80aaa..f8dda5f 100644 --- a/botocove/cove_host_account.py +++ b/botocove/cove_host_account.py @@ -1,11 +1,24 @@ import logging import re -from typing import Any, Iterable, List, Literal, Optional, Sequence, Set, Tuple, Union +from functools import lru_cache +from typing import ( + Any, + Iterable, + List, + Literal, + Optional, + Sequence, + Set, + Tuple, + Union, + cast, +) import boto3 from boto3.session import Session from botocore.config import Config from mypy_boto3_organizations.client import OrganizationsClient +from mypy_boto3_organizations.type_defs import ListChildrenResponseTypeDef from mypy_boto3_sts.client import STSClient from botocove.cove_types import CoveSessionInformation @@ -193,12 +206,9 @@ def _get_all_accounts_by_organization_units( return account_list def _get_all_child_ous(self, parent_ou: str, ou_list: List[str]) -> None: - - child_ous = ( - self.org_client.get_paginator("list_children") - .paginate(ChildType="ORGANIZATIONAL_UNIT", ParentId=parent_ou) - .build_full_result() - ) + """Depth-first recursion mutates the current_ou_list present in the calling + function to establish all children of a parent OU""" + child_ous = self._get_child_ous(parent_ou) child_ous_list = [ou["Id"] for ou in child_ous["Children"]] ou_list.extend(child_ous_list) @@ -212,11 +222,7 @@ def _get_accounts_by_organization_units( account_list: List[str] = [] for ou in organization_units: - ou_children = ( - self.org_client.get_paginator("list_children") - .paginate(ChildType="ACCOUNT", ParentId=ou) - .build_full_result() - ) + ou_children = self._get_child_accounts(ou) account_list.extend(acc["Id"] for acc in ou_children["Children"]) return account_list @@ -228,3 +234,21 @@ def _get_active_org_accounts(self) -> Set[str]: .build_full_result()["Accounts"] ) return {acc["Id"] for acc in all_org_accounts if acc["Status"] == "ACTIVE"} + + @lru_cache() + def _get_child_ous(self, parent_ou: str) -> ListChildrenResponseTypeDef: + return cast( + ListChildrenResponseTypeDef, + self.org_client.get_paginator("list_children") + .paginate(ChildType="ORGANIZATIONAL_UNIT", ParentId=parent_ou) + .build_full_result(), + ) + + @lru_cache() + def _get_child_accounts(self, parent_ou: str) -> ListChildrenResponseTypeDef: + return cast( + ListChildrenResponseTypeDef, + self.org_client.get_paginator("list_children") + .paginate(ChildType="ACCOUNT", ParentId=parent_ou) + .build_full_result(), + ) diff --git a/pyproject.toml b/pyproject.toml index aafa835..1d8545e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "botocove" -version = "1.5.2" +version = "1.6.0" description = "A decorator to allow running a function against all AWS accounts in an organization" authors = ["Dave Connell "] license = "LGPL-3.0-or-later"