├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Dockerfile.release ├── LICENSE ├── Makefile ├── README.md ├── build-requirements.txt ├── docker-compose.yml ├── docs └── _index.md ├── former ├── __init__.py ├── cli.py ├── resource.py └── specification.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_resource.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # Pypi 92 | README.rst -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | 4 | python: 5 | - 2.7 6 | - 3.6 7 | 8 | install: 9 | - pip install -U -r build-requirements.txt 10 | - python setup.py develop 11 | 12 | script: 13 | - make test 14 | 15 | after_success: 16 | - coveralls 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update -y -qq 6 | RUN apt-get install -y -qq groff pandoc 7 | 8 | RUN pip install -U wheel pygments twine 9 | RUN pip install -U awslogs awscli 10 | COPY build-requirements.txt build-requirements.txt 11 | RUN pip install -U -r build-requirements.txt 12 | 13 | COPY setup.py setup.py 14 | COPY setup.cfg setup.cfg 15 | COPY former/__init__.py former/__init__.py 16 | COPY README.md README.md 17 | 18 | RUN pandoc --from=markdown --to=rst --output=README.rst README.md 19 | 20 | RUN python setup.py develop 21 | -------------------------------------------------------------------------------- /Dockerfile.release: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | 3 | RUN pip install -U former 4 | 5 | ENTRYPOINT ["former"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Florian Motlik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build-dev: 2 | docker-compose build former 3 | 4 | shell: build-dev 5 | docker-compose run former bash 6 | 7 | test: 8 | py.test --cov=former tests 9 | pycodestyle . 10 | pyflakes . 11 | grep -r 'print(' former; [ "$$?" -gt 0 ] 12 | 13 | clean: 14 | rm -fr dist build 15 | 16 | build: clean build-dev 17 | docker-compose run former python setup.py sdist bdist_wheel 18 | docker-compose run former pandoc --from=markdown --to=rst --output=build/README.rst README.md 19 | 20 | release-pip: clean build-dev build 21 | docker-compose run former twine upload dist/* 22 | 23 | CONTAINER=flomotlik/former 24 | build-docker: 25 | docker build -t $(CONTAINER) -f Dockerfile.release --no-cache . 26 | 27 | release-docker: build-docker 28 | docker push $(CONTAINER) 29 | 30 | release: release-pip release-docker 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Former 2 | CloudFormation supports lots of different AWS Resources with many parameters. Remembering all of them is a pain 3 | and slows down development dramatically. 4 | 5 | To make this faster `Former` lets you create a full CF resource example for any supported Resource. It parses 6 | the [CloudFormation Resource Specification](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html) 7 | to get the definition for all resources. 8 | 9 | ## Example 10 | 11 | You can create an example for any aws resource by calling former with the service and resource name. In the following 12 | example we're creating an `AWS::IAM::User` Resource: 13 | 14 | ```bash 15 | root@e41871e1eb3e:/app# former iam user 16 | Resources: 17 | AWSIAMUser: 18 | Parameters: 19 | Groups: 20 | - String 21 | LoginProfile: 22 | Password: String - Required 23 | PasswordResetRequired: Boolean 24 | ManagedPolicyArns: 25 | - String 26 | Path: String 27 | Policies: 28 | - PolicyDocument: Json - Required 29 | PolicyName: String - Required 30 | UserName: String 31 | Type: AWS::IAM::User 32 | ``` 33 | 34 | Some Resources also have a subtype (e.g. LoginProfile for the IAM::User). If you only want to show the subtype 35 | you can add it as a third parameter: 36 | 37 | ```bash 38 | root@e41871e1eb3e:/app# former iam user loginprofile 39 | Resources: 40 | AWSIAMUserLoginProfile: 41 | Parameters: 42 | Password: String - Required 43 | PasswordResetRequired: Boolean 44 | Type: AWS::IAM::User.LoginProfile 45 | ``` 46 | 47 | Of course this is not valid CloudFormation as the `LoginProfile` is not a valid CF Resource. But it helps when you 48 | want to get a quick overview for a subtype. 49 | 50 | ## Options 51 | 52 | * `--json` Print output in json instead of yaml 53 | * `--docs` Open the AWS docs to the resource specified -------------------------------------------------------------------------------- /build-requirements.txt: -------------------------------------------------------------------------------- 1 | pycodestyle 2 | pyflakes 3 | autopep8 4 | coverage 5 | python-coveralls 6 | pytest 7 | path.py 8 | pytest-cov 9 | mock 10 | pytest-mock 11 | setuptools 12 | pip -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | former: 4 | build: . 5 | volumes: 6 | - .:/app 7 | -------------------------------------------------------------------------------- /docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Former 3 | subtitle: CloudFormation resource examples the easy way 4 | weight: 300 5 | disable_pagination: true 6 | --- 7 | 8 | CloudFormation supports lots of different AWS Resources with many parameters. Remembering all of them is a pain 9 | and slows down development dramatically. 10 | 11 | To make this faster `Former` lets you create a full CF resource example for any supported Resource. It parses 12 | the [CloudFormation Resource Specification](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html) 13 | to get the definition for all resources. 14 | 15 | ## Installation 16 | 17 | former can be installed through pip: 18 | 19 | ```shell 20 | pip install former 21 | ``` 22 | 23 | or install with Whalebrew and Docker: 24 | 25 | ```shell 26 | whalebrew install flomotlik/former 27 | 28 | # For getting updates 29 | docker pull flomotlik/former 30 | ``` 31 | 32 | 33 | Alternatively you can clone this repository and run 34 | 35 | ```shell 36 | python setup.py install 37 | ``` 38 | 39 | 40 | 41 | ## Example 42 | 43 | You can create an example for any aws resource by calling former with the service and resource name. In the following 44 | example we're creating an `AWS::IAM::User` Resource: 45 | 46 | ```bash 47 | root@e41871e1eb3e:/app# former iam user 48 | Resources: 49 | AWSIAMUser: 50 | Parameters: 51 | Groups: 52 | - String 53 | LoginProfile: 54 | Password: String - Required 55 | PasswordResetRequired: Boolean 56 | ManagedPolicyArns: 57 | - String 58 | Path: String 59 | Policies: 60 | - PolicyDocument: Json - Required 61 | PolicyName: String - Required 62 | UserName: String 63 | Type: AWS::IAM::User 64 | ``` 65 | 66 | Some Resources also have a subtype (e.g. LoginProfile for the IAM::User). If you only want to show the subtype 67 | you can add it as a third parameter: 68 | 69 | ```bash 70 | root@e41871e1eb3e:/app# former iam user loginprofile 71 | Resources: 72 | AWSIAMUserLoginProfile: 73 | Parameters: 74 | Password: String - Required 75 | PasswordResetRequired: Boolean 76 | Type: AWS::IAM::User.LoginProfile 77 | ``` 78 | 79 | Of course this is not valid CloudFormation as the `LoginProfile` is not a valid CF Resource. But it helps when you 80 | want to get a quick overview for a subtype. 81 | 82 | It often helps to open the docs page right with the resource and the `--docs` or `-d` option will do that for you 83 | and print the full resource to the shell as well. 84 | 85 | ```bash 86 | root@e41871e1eb3e:/app# former iam user loginprofile -d 87 | Resources: 88 | AWSIAMUserLoginProfile: 89 | Parameters: 90 | Password: String - Required 91 | PasswordResetRequired: Boolean 92 | Type: AWS::IAM::User.LoginProfile 93 | ``` 94 | 95 | ## Options 96 | 97 | * `--json` Print output in json instead of yaml 98 | * `--required`(`-r`) Only print required parameters 99 | * `--docs`(`-d`) Open the documentation page of the resource in your browser 100 | * `--debug` Open the documentation page of the resource in your browser 101 | -------------------------------------------------------------------------------- /former/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | __version__ = '0.2.7' 5 | 6 | logger = logging.getLogger('former') 7 | handler = logging.StreamHandler(sys.stdout) 8 | formatter = logging.Formatter('%(message)s') 9 | handler.setFormatter(formatter) 10 | logger.addHandler(handler) 11 | logger.setLevel(logging.INFO) 12 | -------------------------------------------------------------------------------- /former/cli.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import argparse 3 | import json 4 | import sys 5 | import traceback 6 | import webbrowser 7 | import yaml 8 | import logging 9 | 10 | import former 11 | from former import __version__ 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | def arguments(): 17 | parser = argparse.ArgumentParser(description='Print CloudFormation Resources') 18 | parser.add_argument('--version', action='version', version='{}'.format(__version__)) 19 | parser.add_argument('service') 20 | parser.add_argument('type') 21 | parser.add_argument('subtype', default='', nargs='?') 22 | parser.add_argument('--json', action='store_true') 23 | parser.add_argument('--required', '-r', action='store_true') 24 | parser.add_argument('--docs', '-d', action='store_true') 25 | parser.add_argument('--debug', action='store_true') 26 | return parser.parse_args() 27 | 28 | 29 | def main(): 30 | args = arguments() 31 | 32 | try: 33 | from former.resource import Resource 34 | except Exception as e: 35 | logger.info("Couldn't get spec for CloudFormation resources - usually this is a network issue.") 36 | if args.debug: 37 | traceback.print_exc() 38 | else: 39 | logger.info("Exception: %s" % e) 40 | logger.info("Use `former --debug` to get the full traceback") 41 | sys.exit(1) 42 | 43 | type = former.resource.type_key(args.service, args.type, args.subtype) 44 | if type: 45 | resource = Resource(type) 46 | cf_resource = {'Type': type} 47 | 48 | cf_resource['Properties'] = resource.parameters(args.required) 49 | 50 | data = {'Resources': {''.join(e for e in type if e.isalnum()): cf_resource}} 51 | if args.docs: 52 | webbrowser.open_new_tab(resource.documentation()) 53 | if args.json: 54 | output = json.dumps(data, indent=2) 55 | else: 56 | output = yaml.safe_dump(data, allow_unicode=True, default_flow_style=False) 57 | logger.info(output) 58 | else: 59 | logger.error('Resource not found for: {} {} {}'.format(args.service, args.type, args.subtype)) 60 | sys.exit(1) 61 | -------------------------------------------------------------------------------- /former/resource.py: -------------------------------------------------------------------------------- 1 | from former import specification 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | PRIMITIVE_TYPE = 'PrimitiveType' 7 | PRIMITIVE_ITEM_TYPE = 'PrimitiveItemType' 8 | TYPE = 'Type' 9 | ITEM_TYPE = 'ItemType' 10 | REQUIRED = 'Required' 11 | 12 | SPEC = specification.specification() 13 | TYPES = {} 14 | TYPES.update(SPEC['ResourceTypes']) 15 | TYPES.update(SPEC['PropertyTypes']) 16 | 17 | TYPE_KEYS = {} 18 | for key, _ in TYPES.items(): 19 | TYPE_KEYS[key.lower()] = key 20 | 21 | 22 | def type_key(service, type, subtype): 23 | if subtype: 24 | subtype = '.' + subtype 25 | return TYPE_KEYS.get(('::'.join(['AWS', service, type]) + subtype).lower()) 26 | 27 | 28 | class Resource(object): 29 | def __init__(self, type): 30 | self.root_type = type 31 | 32 | def parameters(self, required_only): 33 | root_resource = TYPES[self.root_type] 34 | 35 | properties = {} 36 | for key, value in root_resource['Properties'].items(): 37 | prop = Property(self.root_type, key, value, required_only) 38 | prop_value = prop.value() 39 | if prop_value is None: 40 | logger.info("{} {}".format(key, prop_value)) 41 | if not required_only or prop.required(): 42 | properties[key] = prop_value 43 | return properties 44 | 45 | def documentation(self): 46 | root_resource = TYPES[self.root_type] 47 | return root_resource['Documentation'] 48 | 49 | 50 | class Property(object): 51 | def __init__(self, resource, property_type, definition, required_only): 52 | self.resource = resource 53 | self.definition = definition 54 | self.property_type = property_type 55 | self.required_only = required_only 56 | 57 | def required(self): 58 | return self.definition['Required'] 59 | 60 | def type(self): 61 | return self.definition.get(PRIMITIVE_TYPE) or self.definition[TYPE] 62 | 63 | def item_type(self): 64 | return self.definition[ITEM_TYPE] 65 | 66 | def collection_type(self): 67 | return self.definition.get(ITEM_TYPE) or self.definition[PRIMITIVE_ITEM_TYPE] 68 | 69 | def is_primitive(self): 70 | return PRIMITIVE_TYPE in self.definition or self.definition.get(TYPE) in ['Json'] 71 | 72 | def is_collection(self): 73 | return self.definition.get(TYPE) in ['List', 'Map'] 74 | 75 | def is_primitive_collection(self): 76 | return PRIMITIVE_ITEM_TYPE in self.definition or self.definition.get(ITEM_TYPE) in ['Json'] 77 | 78 | def __collection_description(self): 79 | return "{}{}".format(self.collection_type(), ' - Required' if self.required() else '') 80 | 81 | def map_property(self): 82 | if self.is_primitive_collection(): 83 | return {'SampleKey': self.__collection_description()} 84 | else: 85 | split_resource = self.resource.split('.') 86 | return Resource(split_resource[0] + '.' + self.item_type()).parameters(self.required_only) 87 | 88 | def list_property(self): 89 | if self.is_primitive_collection(): 90 | return [self.__collection_description()] 91 | elif self.definition.get(ITEM_TYPE) == 'Tag': 92 | return [Resource('Tag').parameters(self.required_only)] 93 | else: 94 | return [self.__new_resource(self.item_type())] 95 | 96 | def __get_value(self, type): 97 | return getattr(self, type.lower() + '_property')() 98 | 99 | def __new_resource(self, type): 100 | child_type = self.resource.split('.')[0] + '.' + type 101 | 102 | if self.resource != child_type and 'Properties' in TYPES.get(child_type, {}): 103 | return Resource(child_type).parameters(self.required_only) 104 | elif self.resource != child_type: 105 | child_type_resource = TYPES[child_type] 106 | return Property( 107 | child_type, 108 | child_type_resource.get(ITEM_TYPE, child_type_resource.get(PRIMITIVE_TYPE)), 109 | child_type_resource, 110 | self.required_only 111 | ).type() 112 | else: 113 | return {'Recursive': self.resource, REQUIRED: self.required()} 114 | 115 | def value(self): 116 | if self.is_primitive(): 117 | return "{}{}".format(self.type(), ' - Required' if self.required() else '') 118 | elif self.is_collection(): 119 | return self.__get_value(self.type()) 120 | else: 121 | return self.__new_resource(self.type()) 122 | -------------------------------------------------------------------------------- /former/specification.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import platform 4 | import tempfile 5 | 6 | import requests 7 | 8 | CACHE_PATH = tempfile.gettempdir() + '/former-spec.cached.json' 9 | 10 | def specification(): 11 | if os.path.exists(CACHE_PATH): 12 | with open(CACHE_PATH) as f: 13 | return json.load(f) 14 | response = requests.get( 15 | 'https://d1uauaxba7bl26.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json') 16 | response.raise_for_status() 17 | with open(CACHE_PATH, 'w') as f: 18 | f.write(response.text) 19 | return json.loads(response.text) 20 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # setup.cfg 2 | [bdist_wheel] 3 | universal = 1 4 | 5 | [pycodestyle] 6 | max-line-length = 120 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Packaging settings.""" 2 | 3 | from os.path import abspath, dirname, join, isfile 4 | 5 | from setuptools import setup 6 | 7 | from former import __version__ 8 | 9 | this_dir = abspath(dirname(__file__)) 10 | path = join(this_dir, 'README.rst') 11 | long_description = '' 12 | if isfile(path): 13 | with open(path) as file: 14 | long_description = file.read() 15 | 16 | setup( 17 | name='former', 18 | version=__version__, 19 | description='Create AWS CloudFormation yml for any resource', 20 | long_description=long_description, 21 | url='https://github.com/flomotlik/former', 22 | author='Florian Motlik', 23 | author_email='flo@flomotlik.me', 24 | license='MIT', 25 | classifiers=[ 26 | 'Intended Audience :: Developers', 27 | 'Topic :: Utilities', 28 | 'License :: Public Domain', 29 | 'Natural Language :: English', 30 | 'Operating System :: OS Independent', 31 | 'Programming Language :: Python :: 2', 32 | 'Programming Language :: Python :: 2.6', 33 | 'Programming Language :: Python :: 2.7', 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Python :: 3.2', 36 | 'Programming Language :: Python :: 3.3', 37 | 'Programming Language :: Python :: 3.4', 38 | ], 39 | keywords='cloudformation, aws, cloud', 40 | packages=['former'], 41 | install_requires=['requests', 'pyyaml'], 42 | entry_points={ 43 | 'console_scripts': [ 44 | 'former=former.cli:main', 45 | ], 46 | } 47 | ) 48 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theserverlessway/former/81883bae18e0830a2d7410c2219c7f50217a8cb7/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_resource.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import os 3 | 4 | import former.resource 5 | from former.specification import CACHE_PATH 6 | 7 | 8 | def test_former_for_every_resource(): 9 | if os.path.exists(CACHE_PATH): 10 | os.remove(CACHE_PATH) 11 | 12 | keys = list(former.resource.SPEC['ResourceTypes'].keys()) 13 | assert len(keys) > 0 14 | for idx, key in enumerate(keys): 15 | try: 16 | resource = former.resource.Resource(key) 17 | resource.parameters(False) 18 | except Exception: 19 | print('Failed at type %s after %s successes' % (key, idx)) 20 | raise 21 | 22 | 23 | def test_former_with_only_required_resources(): 24 | resource = former.resource.Resource('AWS::IAM::Role') 25 | assert resource.parameters(True) == {"AssumeRolePolicyDocument": "Json - Required"} 26 | assert resource.parameters(False) != {"AssumeRolePolicyDocument": "Json - Required"} 27 | --------------------------------------------------------------------------------