PKLMBʯtroposphere_crunch/__init__.py__author__ = 'Josh Klar ' __description__ = ( 'A tool to compile and (optionally) deploy Troposphere templates with AWS CLI ' 'and a very opinionated structure.' ) __doc__ = __description__ __version__ = '1.0.2' PKLM|1=troposphere_crunch/consts.pyCAPABILITY_IAM = 'CAPABILITY_IAM' CAPABILITY_NAMED_IAM = 'CAPABILITY_NAMED_IAM' ALL_CAPABILITIES = (CAPABILITY_IAM, CAPABILITY_NAMED_IAM) PKLM@troposphere_crunch/main.pyimport argparse import importlib import os import pathlib import shutil import subprocess import sys import toml from tqdm import tqdm from .types import Stack BAR_FMT = '{desc}: {percentage:3.0f}%| {bar} | {n_fmt}/{total_fmt} [{elapsed}]' def main() -> int: # Needed for the config file to be able to define "myaws.myfile" and have # this script actually be able to access it (since "myaws.myfile" is not # part of site-packages or troposphere-crunch itself sys.path.insert(0, os.getcwd()) parser = argparse.ArgumentParser(description=( 'A tool to compile and (optionally) deploy Troposphere templates with AWS CLI and a very ' 'opinionated structure' )) parser.add_argument( '-c', '--config', type=str, help='path to TOML config file', required=True, ) parser.add_argument( '-C', '--clean', action='store_true', help='remove output_dir before building templates', required=False, ) parser.add_argument( '-d', '--deploy', action='store_true', help='after building, actually deploy stacks with awscli', required=False, ) parser.add_argument( '-o', '--output-dir', type=str, help='default output directory (overridable per stack in config)', required=False, default='cloudformation', ) args = parser.parse_args() with open(args.config, 'r') as config_file: config = toml.load(config_file) stacks = [] for stack in config['stacks']: stack_kwargs = { 'module': importlib.import_module(stack['module']), 'output_dir': args.output_dir, } if 'capabilities' in stack: stack_kwargs['capabilities'] = tuple(stack['capabilities']) if 'region' in stack: stack_kwargs['region'] = stack['region'] if 'output_dir' in stack: stack_kwargs['output_dir'] = stack['output_dir'] if 'parameters' in stack: stack_kwargs['parameters'] = stack['parameters'] stacks.append(Stack(**stack_kwargs)) if args.clean: output_dirs = {stack.output_dir for stack in stacks} for output_dir in output_dirs: try: shutil.rmtree(output_dir) except FileNotFoundError: pass for stack in tqdm(stacks, desc='Building stacks', unit='stacks', bar_format=BAR_FMT): pathlib.Path('cloudformation').mkdir(parents=True, exist_ok=True) with open(stack.json_filename, 'w') as cf_file: cf_file.write(stack.module.template.to_json()) if args.deploy: deploy_results = [] for stack in tqdm(stacks, desc='Deploying stacks', unit='stacks', bar_format=BAR_FMT): command = [ 'aws', 'cloudformation', 'deploy', '--region', stack.region, '--stack-name', stack.name, '--template-file', stack.json_filename, ] for capability in stack.capabilities: command.append('--capabilities') command.append(capability) if stack.parameters: command.append('--parameter-overrides') command.append(' '.join( f'{k}={v}' for k, v in stack.parameters.items() )) deploy_results.append((stack.name, subprocess.run(command, capture_output=True))) errors = [] for r in deploy_results: _, result = r if result.returncode != 0: stderr = result.stderr.decode('utf-8') if 'No changes to deploy' in stderr: continue errors.append(r) if errors: print('Some stacks failed to deploy!', file=sys.stderr) for stack_name, result in errors: if result.returncode != 0: stdout = result.stdout.decode('utf-8') if stdout: print(f'---> {stack_name} stdout', file=sys.stderr) print(stdout) print() stderr = result.stderr.decode('utf-8') if stderr: print(f'---> {stack_name} stderr', file=sys.stderr) print(stderr) print() return 1 return 0 PKLMZ[pptroposphere_crunch/types.pyimport os from dataclasses import dataclass, field from typing import Collection, Container, List from troposphere.constants import US_WEST_2 @dataclass class Stack: module: Container output_dir: str capabilities: Collection = tuple() # noqa region: str = US_WEST_2 parameters: List[dict] = field(default_factory=list) @property def name(self) -> str: ''' Convert python module path name to a Cloudformation-friendly string which is regionalized ex. "aws_klardotsh.iam" with default region becomes "us-west-2-iam" ''' return '{}-{}'.format( self.region, self.module.__name__.split('.', 1)[1].replace('.', '-'), ) @property def json_filename(self) -> str: return os.path.join( self.output_dir, f'{self.name}.json', ) PK!H5C3troposphere_crunch-1.0.2.dist-info/entry_points.txtN+I/N.,()*)//.H-JM.*KΰE&fY..PKLMK$99*troposphere_crunch-1.0.2.dist-info/LICENSEMIT License Copyright (c) 2018 Josh Klar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!Hd BUc(troposphere_crunch-1.0.2.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,rzd&Y)r$[)T&UD"PK!H%5+troposphere_crunch-1.0.2.dist-info/METADATAeMO0 >¡-1'!6,wOƗv~,VGR'a\Dk2",EO~F][6A'\穆Sln*2-=N!%ekֵp.Iyٌw$֠@Έ/x!73Bz *zN5 W.gX{8X` ^6bmJ`V] (q("c-GNѕ E("|#]ftm8J$0