def remove_stack_set_instances(args): client = AWS.current_session().client('cloudformation') client.delete_stack_instances(StackSetName=args.stack_set, Accounts=args.accounts, Regions=args.regions, RetainStacks=args.retain) logger.info('Removed StackSet Instances for StackSet {}'.format(args.stack_set))
def aws_regions(): from formica.aws import AWS current_session = AWS.current_session() ec2 = current_session.client('ec2') regions = ec2.describe_regions() regions = [r['RegionName'] for r in regions['Regions']] return {'AWSRegions': regions}
def __manage_stack_set(args, create): client = AWS.current_session().client('cloudformation') params = parameters(parameters=args.parameters, tags=args.tags, capabilities=args.capabilities, accounts=vars(args).get('accounts'), regions=vars(args).get('regions'), execution_role_name=args.execution_role_name, administration_role_arn=args.administration_role_arn) loader = Loader(variables=args.vars) loader.load() if create: result = client.create_stack_set( StackSetName=args.stack_set, TemplateBody=loader.template(), ** params ) logger.info('StackSet {} created'.format(args.stack_set)) else: result = client.update_stack_set( StackSetName=args.stack_set, TemplateBody=loader.template(), ** params ) logger.info('StackSet {} updated in Operation {}'.format(args.stack_set, result['OperationId']))
def deploy(args): client = AWS.current_session().client('cloudformation') last_event = client.describe_stack_events( StackName=args.stack)['StackEvents'][0]['EventId'] client.execute_change_set( ChangeSetName=(CHANGE_SET_FORMAT.format(stack=args.stack)), StackName=args.stack) StackWaiter(args.stack, client).wait(last_event)
def remove(args): client = AWS.current_session().client('cloudformation') stack_id = client.describe_stacks( StackName=args.stack)['Stacks'][0]['StackId'] logger.info('Removing Stack and waiting for it to be removed, ...') last_event = client.describe_stack_events( StackName=args.stack)['StackEvents'][0]['EventId'] client.delete_stack(StackName=args.stack) StackWaiter(stack_id, client).wait(last_event)
def compare_stack_set(stack, vars=None, parameters={}, tags={}, main_account_parameter=False): client = AWS.current_session().client('cloudformation') stack_set = client.describe_stack_set(StackSetName=stack, )['StackSet'] __compare(stack_set['TemplateBody'], stack_set, vars, parameters, tags, main_account_parameter)
def change(args): client = AWS.current_session().client('cloudformation') loader = Loader() loader.load() change_set = ChangeSet(stack=args.stack, client=client) change_set.create(template=loader.template(), change_set_type='UPDATE', parameters=args.parameters, tags=args.tags, capabilities=args.capabilities) change_set.describe()
def new(args): client = AWS.current_session().client('cloudformation') loader = Loader() loader.load() logger.info('Creating change set for new stack, ...') change_set = ChangeSet(stack=args.stack, client=client) change_set.create(template=loader.template(), change_set_type='CREATE', parameters=args.parameters, tags=args.tags, capabilities=args.capabilities) change_set.describe() logger.info('Change set created, please deploy')
def stacks(args): client = AWS.current_session().client('cloudformation') stacks = client.describe_stacks() table = Texttable(max_width=150) table.add_rows([STACK_HEADERS]) for stack in stacks['Stacks']: table.add_row([ stack['StackName'], stack['CreationTime'], stack.get('LastUpdatedTime', ''), stack['StackStatus'] ]) logger.info("Current Stacks:\n" + table.draw() + "\n")
def resources(args): client = AWS.current_session().client('cloudformation') paginator = client.get_paginator('list_stack_resources').paginate( StackName=args.stack) table = Texttable(max_width=150) table.add_rows([RESOURCE_HEADERS]) for page in paginator: for resource in page['StackResourceSummaries']: table.add_row([ resource['LogicalResourceId'], resource['PhysicalResourceId'], resource['ResourceType'], resource['ResourceStatus'] ]) logger.info(table.draw() + "\n")
def aws_accounts(): from formica.aws import AWS current_session = AWS.current_session() organizations = current_session.client('organizations') sts = current_session.client('sts') orgs = organizations.list_accounts() accounts = [{ 'Id': a['Id'], 'Name': a['Name'], 'Email': a['Email'] } for a in orgs['Accounts'] if a['Status'] == 'ACTIVE'] account_id = sts.get_caller_identity()['Account'] return { 'AWSAccounts': accounts, 'AWSSubAccounts': [a for a in accounts if a['Id'] != account_id] }
def test_session_needs_to_be_set(session): AWS._AWS__session = None with pytest.raises(AttributeError): AWS.current_session()
def test_init_with_profile_and_region(session, botocore_session): AWS.initialize(profile=PROFILE, region=REGION) botocore_session.assert_called_with(profile=PROFILE) session.assert_called_with(botocore_session=botocore_session(), region_name=REGION)
def test_init_with_profile(session, botocore_session): AWS.initialize(profile=PROFILE) botocore_session.assert_called_with(profile=PROFILE) session.assert_called_with(botocore_session=botocore_session())
def test_init_with_region(session, botocore_session): AWS.initialize(region=REGION) session.assert_called_with(botocore_session=botocore_session(), region_name=REGION)
def test_init_without_parameters(session, botocore_session): AWS.initialize() session.assert_called_with(botocore_session=botocore_session())
def describe(args): client = AWS.current_session().client('cloudformation') change_set = ChangeSet(stack=args.stack, client=client) change_set.describe()
def create(self, template, change_set_type, parameters=[], tags=[], capabilities=[], role_arn=None, s3=False): optional_arguments = {} if parameters: optional_arguments['Parameters'] = [{ 'ParameterKey': key, 'ParameterValue': value, 'UsePreviousValue': False } for (key, value) in parameters.items()] if tags: optional_arguments['Tags'] = [{ 'Key': key, 'Value': value, } for (key, value) in tags.items()] if role_arn: optional_arguments['RoleARN'] = role_arn if capabilities: optional_arguments['Capabilities'] = capabilities if change_set_type == 'UPDATE': self.remove_existing_changeset() try: if s3: session = AWS.current_session() s3_client = session.client('s3') bucket_name = 'formica-deploy-{}'.format( str(uuid.uuid4()).lower()) bucket_path = '{}-template.json'.format(self.stack) logger.info('Creating Bucket: {}'.format(bucket_name)) s3_client.create_bucket( Bucket=bucket_name, CreateBucketConfiguration=dict( LocationConstraint=session.region_name)) logger.info('Uploading to bucket: {}/{}'.format( bucket_name, bucket_path)) s3_client.put_object(Bucket=bucket_name, Key=bucket_path, Body=template) template_url = 'https://{}.s3.amazonaws.com/{}'.format( bucket_name, bucket_path) optional_arguments['TemplateURL'] = template_url else: optional_arguments['TemplateBody'] = template self.client.create_change_set(StackName=self.stack, ChangeSetName=self.name, ChangeSetType=change_set_type, **optional_arguments) logger.info( 'Change set submitted, waiting for CloudFormation to calculate changes ...' ) waiter = self.client.get_waiter('change_set_create_complete') waiter.wait(ChangeSetName=self.name, StackName=self.stack) logger.info('Change set created successfully') except WaiterError as e: status_reason = e.last_response.get('StatusReason', '') logger.info(status_reason) if "didn't contain changes" not in status_reason: sys.exit(1) finally: if s3: logger.info('Deleting Object and Bucket: {}/{}'.format( bucket_name, bucket_path)) s3_client.delete_object(Bucket=bucket_name, Key=bucket_path) s3_client.delete_bucket(Bucket=bucket_name)
def test_init_with_profile(session): AWS.initialize(profile=PROFILE) session.assert_called_with(profile_name=PROFILE)
def test_init_with_region(session): AWS.initialize(region=REGION) session.assert_called_with(region_name=REGION)
def diff(args): Diff(AWS.current_session()).run(args.stack)
def compare_stack(stack, vars=None, parameters={}, tags={}): client = AWS.current_session().client('cloudformation') template = client.get_template(StackName=stack, )['TemplateBody'] stack = client.describe_stacks(StackName=stack, )['Stacks'][0] __compare(template, stack, vars, parameters, tags)
def main_account_id(): from formica.aws import AWS sts = AWS.current_session().client('sts') identity = sts.get_caller_identity() return identity['Account']
def remove_stack_set(args): client = AWS.current_session().client('cloudformation') client.delete_stack_set(StackSetName=args.stack_set) logger.info('Removed StackSet with name {}'.format(args.stack_set))
def test_AWS_constructor_cant_be_called(session): with pytest.raises(Exception): AWS()
def add_stack_set_instances(args): client = AWS.current_session().client('cloudformation') client.create_stack_instances(StackSetName=args.stack_set, Accounts=args.accounts, Regions=args.regions) logger.info('Added StackSet Instances for StackSet {}'.format(args.stack_set))
def test_AWS_is_singleton(session): AWS.initialize() AWS.current_session() AWS.current_session() session.assert_called_once()
def test_init_without_parameters(session): AWS.initialize() session.assert_called_with()
def main(cli_args): parser = argparse.ArgumentParser() parser.add_argument('--version', action='version', version='{}'.format(__version__)) subparsers = parser.add_subparsers(title='commands', help='Command to use', dest='command') subparsers.required = True # Template Command Arguments template_parser = subparsers.add_parser( 'template', description='Print the current template') template_parser.add_argument('-y', '--yaml', help="print output as yaml", action="store_true") template_parser.set_defaults(func=template) # Stacks Command Arguments stacks_parser = subparsers.add_parser('stacks', description='List all stacks') add_aws_arguments(stacks_parser) add_config_file_argument(stacks_parser) stacks_parser.set_defaults(func=stacks) # New Command Arguments new_parser = subparsers.add_parser( 'new', description='Create a change set for a new stack') add_aws_arguments(new_parser) add_stack_argument(new_parser) add_stack_parameters_argument(new_parser) add_stack_tags_argument(new_parser) add_capabilities_argument(new_parser) add_config_file_argument(new_parser) new_parser.set_defaults(func=new) # Change Command Arguments change_parser = subparsers.add_parser( 'change', description='Create a change set for an existing stack') add_aws_arguments(change_parser) add_stack_argument(change_parser) add_stack_parameters_argument(change_parser) add_stack_tags_argument(change_parser) add_capabilities_argument(change_parser) add_config_file_argument(change_parser) change_parser.set_defaults(func=change) # Deploy Command Arguments deploy_parser = subparsers.add_parser( 'deploy', description='Deploy the latest change set for a stack') add_aws_arguments(deploy_parser) add_stack_argument(deploy_parser) add_config_file_argument(deploy_parser) deploy_parser.set_defaults(func=deploy) # Describe Command Arguments describe_parser = subparsers.add_parser( 'describe', description='Describe the latest change-set of the stack') add_aws_arguments(describe_parser) add_stack_argument(describe_parser) add_config_file_argument(describe_parser) describe_parser.set_defaults(func=describe) # Diff Command Arguments diff_parser = subparsers.add_parser( 'diff', description='Print a diff between local and deployed stack') add_aws_arguments(diff_parser) add_stack_argument(diff_parser) add_config_file_argument(diff_parser) diff_parser.set_defaults(func=diff) # Resources Command Arguments resources_parser = subparsers.add_parser( 'resources', description='List all resources of a stack') add_aws_arguments(resources_parser) add_stack_argument(resources_parser) add_config_file_argument(resources_parser) resources_parser.set_defaults(func=resources) # Remove Command Arguments remove_parser = subparsers.add_parser( 'remove', description='Remove the configured stack') add_aws_arguments(remove_parser) add_stack_argument(remove_parser) add_config_file_argument(remove_parser) remove_parser.set_defaults(func=remove) # Argument Parsing args = parser.parse_args(cli_args) args_dict = vars(args) if args_dict.get('config_file'): load_config_file(args, args.config_file) try: # Initialise the AWS Profile and Region AWS.initialize(args_dict.get('region'), args_dict.get('profile')) # Execute Function if args_dict.get('func'): args.func(args) else: parser.print_usage() except (ProfileNotFound, NoCredentialsError, NoRegionError, EndpointConnectionError) as e: logger.info( 'Please make sure your credentials, regions and profiles are properly set:' ) logger.info(e) sys.exit(1) except ClientError as e: if e.response['Error']['Code'] == 'ValidationError': logger.info(e.response['Error']['Message']) sys.exit(1) else: logger.info(e) sys.exit(2)