def get_hooks(hooks_list: List[str]) -> RepokidHooks: """ Output should be a dictionary with keys as the names of hooks and values as a list of functions (in order) to call Args: hooks_list: A list of paths to load hooks from Returns: dict: Keys are hooks by name (AFTER_SCHEDULE_REPO) and values are a list of functions to execute """ # hooks is a temporary dictionary of priority/RepokidHook tuples hooks: DefaultDict[str, List[Tuple[ int, RepokidHook]]] = collections.defaultdict(list) for hook in hooks_list: module = import_string(hook) # get members retrieves all the functions from a given module all_funcs = inspect.getmembers(module, inspect.isfunction) # first argument is the function name (which we don't need) for (_, func) in all_funcs: # we only look at functions that have been decorated with _implements_hook if hasattr(func, "_implements_hook"): h: Tuple[int, RepokidHook] = (func._implements_hook["priority"], func) # append to the dictionary in whatever order we see them, we'll sort later. Dictionary value should be # a list of tuples (priority, function) hooks[func._implements_hook["hook_name"]].append(h) # sort by priority for k in hooks.keys(): hooks[k] = sorted(hooks[k], key=lambda priority: int(priority[0])) # get rid of the priority - we don't need it anymore # save to a new dict that conforms to the RepokidHooks spec final_hooks: RepokidHooks = RepokidHooks() for k in hooks.keys(): final_hooks[k] = [func_tuple[1] for func_tuple in hooks[k]] return final_hooks
def test_repo_all_roles( self, mock_time, mock_repo_role, mock_iam_datasource_seed, mock_aa_datasource, mock_role_list_fetch_all, mock_role_list_from_ids, mock_call_hooks, ): hooks = RepokidHooks() mock_iam_datasource_seed.return_value = [ "AROAABCDEFGHIJKLMNOPA", "AROAABCDEFGHIJKLMNOPB", "AROAABCDEFGHIJKLMNOPC", ] roles = RoleList( [ Role( **{ "Arn": "arn:aws:iam::123456789012:role/ROLE_A", "RoleId": "AROAABCDEFGHIJKLMNOPA", "Active": True, "RoleName": "ROLE_A", "RepoScheduled": 100, "CreateDate": datetime.datetime.now() - datetime.timedelta(days=100), } ), Role( **{ "Arn": "arn:aws:iam::123456789012:role/ROLE_B", "RoleId": "AROAABCDEFGHIJKLMNOPB", "Active": True, "RoleName": "ROLE_B", "RepoScheduled": 0, "CreateDate": datetime.datetime.now() - datetime.timedelta(days=100), } ), Role( **{ "Arn": "arn:aws:iam::123456789012:role/ROLE_C", "RoleId": "AROAABCDEFGHIJKLMNOPC", "Active": True, "RoleName": "ROLE_C", "RepoScheduled": 5, "CreateDate": datetime.datetime.now() - datetime.timedelta(days=100), } ), ] ) # time is past ROLE_C but before ROLE_A mock_time.return_value = 10 mock_role_list_from_ids.return_value = RoleList( [ roles[0], roles[1], roles[2], ] ) mock_repo_role.return_value = None # repo all roles in the account, should call repo with all roles repokid.commands.repo._repo_all_roles("", {}, hooks, scheduled=False) # repo only scheduled, should only call repo role with role C repokid.commands.repo._repo_all_roles("", {}, hooks, scheduled=True) assert mock_repo_role.mock_calls == [ call(hooks, commit=False, scheduled=False), call(hooks, commit=False, scheduled=False), call(hooks, commit=False, scheduled=False), call(hooks, commit=False, scheduled=True), ] assert mock_call_hooks.mock_calls == [ call( hooks, "BEFORE_REPO_ROLES", {"account_number": "", "roles": roles}, ), call( hooks, "AFTER_REPO_ROLES", {"account_number": "", "roles": roles, "errors": []}, ), call( hooks, "BEFORE_REPO_ROLES", {"account_number": "", "roles": RoleList([roles[2]])}, ), call( hooks, "AFTER_REPO_ROLES", { "account_number": "", "roles": RoleList([roles[2]]), "errors": [], }, ), ]