diff --git a/README.md b/README.md
index c586353..94d3ac8 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
Remove all `x-amazon`-tags from your Open API 3 or Swagger 2 specification. Useful if you are using Cloudformation to specify your API Gateways, and want to provide your consumers with the same specification but not wanting to disclose your internal Amazon integrations.
# Installation
+
`pip install --user deforest`
## Features
@@ -10,19 +11,32 @@ Remove all `x-amazon`-tags from your Open API 3 or Swagger 2 specification. Usef
- Clean keys starting with the string `x-amazon`
- Handles JSON and YAML input
- Handles JSON and YAML output (defaults to YAML)
+- Support for AWS CloudFormation templates
# Usage
+
```
Usage: deforest [OPTIONS] INFILE
Options:
-o, --outfile TEXT specify output file, default is
- ./
-.
+ ./-., ignored if input is
+ a CloudFormation template and the template
+ contains more than one ApiGateway resource)
-f, --format [yaml|json] output format [default: yaml]
-i, --indent INTEGER if output format is json, specify indentation
--version Show the version and exit.
--help Show this message and exit.
```
+## CloudFormation templates
+
+Version 0.1.1 and later supports CloudFormation templates as input. If more than one API Gateway is part of the template, the `--outfile` flag will be ignored.
+
# Limitations
+
The output file looses its order of the keys in the file, which shouldn't affect you if you're using a converter to create a graphical documentation/specification - but can be confusing if you have a specific internal order you wish to keep.
+
+# Contribute
+
+If you wish to see a specific feature, please create an issue in the issue tracker - and if you want to help develop deforest, you're free to create a pull request as well. All submitted code will be subject to the licensing specified in the LICENSE file.
diff --git a/deforest/cfcleaner.py b/deforest/cfcleaner.py
new file mode 100644
index 0000000..f40d0b2
--- /dev/null
+++ b/deforest/cfcleaner.py
@@ -0,0 +1,97 @@
+from tags import GetAttTag, SubTag, RefTag
+import yaml
+import re
+
+class CFCleaner():
+ keys = ["x-amazon"]
+ filedata = None
+ raw = {}
+ processed = None
+ KEY_TYPE = "Type"
+ KEY_RESOURCES = "Resources"
+ KEY_PROPERTIES = "Properties"
+ KEY_BODY = "Body"
+ TYPE_APIGW = "AWS::ApiGateway::RestApi"
+
+ multi_raw = []
+ multi_processed = []
+
+ def __init__(self,data):
+ self.filedata = data
+
+ def convert(self):
+ self._enable_custom_tags()
+ self._load()
+ self._clean_all_keys()
+ self._dump_all()
+ return self.multi_processed
+
+ def number_results(self):
+ return len(self.multi_processed)
+
+ def _clean_all_keys(self):
+ self._identify_apigw(self.raw)
+
+ def _identify_apigw(self,v):
+ resources = v[CFCleaner.KEY_RESOURCES]
+ for k in resources:
+ if isinstance(resources[k], dict):
+ if self._is_apigw(resources[k]) and self._has_properties_and_body(resources[k]):
+ self.multi_raw.append(resources[k][CFCleaner.KEY_PROPERTIES][CFCleaner.KEY_BODY])
+
+ for raw in self.multi_raw:
+ self._cleanup_keys(raw)
+
+ def _cleanup_keys(self,v):
+ for k in v.keys():
+ if any(m in k for m in self.keys):
+ del v[k]
+ else:
+ if isinstance(v[k], dict):
+ self._cleanup_keys(v[k])
+
+ def _is_apigw(self,node):
+ if CFCleaner.KEY_TYPE in node:
+ if node[CFCleaner.KEY_TYPE] == CFCleaner.TYPE_APIGW:
+ return True
+ return False
+
+ def _has_properties_and_body(self,node):
+ return CFCleaner.KEY_PROPERTIES in node and CFCleaner.KEY_BODY in node[CFCleaner.KEY_PROPERTIES]
+
+ def _namify(self, title, version):
+ s = "{}-{}".format(title.lower(),version.lower())
+ s = re.sub(r"\s+", '-', s)
+ return s
+
+ def get_title_and_version_all(self,index):
+ title = self.multi_raw[index]["info"]["title"] or "no-title"
+ version = self.multi_raw[index]["info"]["version"] or "no-version"
+ return self._namify(title,version)
+
+ def get_title_and_version(self):
+ return self.get_title_and_version(0)
+
+ def _enable_custom_tags(self):
+ yaml.SafeLoader.add_constructor('!GetAtt', GetAttTag.from_yaml)
+ yaml.SafeLoader.add_constructor('!Sub', SubTag.from_yaml)
+ yaml.SafeLoader.add_constructor('!Ref', RefTag.from_yaml)
+ yaml.SafeDumper.add_multi_representer(GetAttTag, GetAttTag.to_yaml)
+ yaml.SafeDumper.add_multi_representer(SubTag, SubTag.to_yaml)
+ yaml.SafeDumper.add_multi_representer(RefTag, RefTag.to_yaml)
+
+ def _load(self):
+ self.raw = yaml.safe_load(self.filedata)
+
+ def _dump(self):
+ self.processed = yaml.safe_dump(self.multi_raw[0])
+
+ def _dump_all(self):
+ for r in self.multi_raw:
+ self.multi_processed.append(yaml.safe_dump(r))
+
+ def get_raw(self,index):
+ return self.multi_raw[0]
+
+ def get_raw_all(self,index):
+ return self.multi_raw[index]
\ No newline at end of file
diff --git a/deforest/cleaner.py b/deforest/cleaner.py
index 8d17094..92bb743 100644
--- a/deforest/cleaner.py
+++ b/deforest/cleaner.py
@@ -21,15 +21,21 @@ def get_title_and_version(self):
version = self.raw["info"]["version"] or "no-version"
return self._namify(title,version)
+ def get_title_and_version_all(self,index):
+ return self.get_title_and_version()
+
def get_raw(self):
return self.raw
+ def get_raw_all(self,index):
+ return self.raw
+
def convert(self):
self._enable_custom_tags()
self._load()
self._clean_all_keys()
self._dump()
- return self.processed
+ return [self.processed]
def _clean_all_keys(self):
self._cleanup_keys(self.raw)
@@ -55,3 +61,6 @@ def _load(self):
def _dump(self):
self.processed = yaml.safe_dump(self.raw)
+
+ def number_results(self):
+ return 1
\ No newline at end of file
diff --git a/deforest/constant.py b/deforest/constant.py
index 15a791c..57c2dd0 100644
--- a/deforest/constant.py
+++ b/deforest/constant.py
@@ -1,2 +1,2 @@
-VERSION = "0.1.0"
+VERSION = "0.1.1"
LOGGER = "deforest"
diff --git a/deforest/deforest.py b/deforest/deforest.py
index ad34390..bdb02fe 100644
--- a/deforest/deforest.py
+++ b/deforest/deforest.py
@@ -3,27 +3,34 @@
from constant import VERSION, LOGGER
from cleaner import DeforestCleaner
+from solution import Solution
@click.command()
@click.argument("infile")
-@click.option("--outfile","-o", help="specify output file, default is ./-.")
+@click.option("--outfile","-o", help="specify output file, default is ./-., ignored if input is a CloudFormation template and the template contains more than one ApiGateway resource)")
@click.option("--format","-f", default="yaml", show_default=True, type=click.Choice(["yaml","json"]), help="output format")
@click.option("--indent", "-i", default=4,type=int, help="if output format is json, specify indentation")
-@click.version_option(None)
+@click.version_option(VERSION)
def main(infile,outfile,format,indent):
with open(infile, "r") as fh:
d = fh.read()
- cleaner = DeforestCleaner(d)
+ cleaner = Solution(d).cleaner()
result = cleaner.convert()
- if outfile:
+ filename = None
+ if outfile and len(result) < 2:
filename = outfile
else:
- filename = "{}.{}".format(cleaner.get_title_and_version(),"json" if format == "json" else "yaml")
+ print("output will be in {} files, ignoring --outfile flag setting".format(len(result)))
- with open(filename,"w+") as fh:
- if format == "json":
- fh.write(json.dumps(cleaner.get_raw(), indent=indent))
- else:
- fh.write(result)
+ for i,r in enumerate(result):
+ tfile = filename
+ if filename is None:
+ tfile = "{}.{}".format(cleaner.get_title_and_version_all(i),"json" if format == "json" else "yaml")
+
+ with open(tfile,"w+") as fh:
+ if format == "json":
+ fh.write(json.dumps(cleaner.get_raw_all(i), indent=indent))
+ else:
+ fh.write(r)
diff --git a/deforest/solution.py b/deforest/solution.py
new file mode 100644
index 0000000..08e045b
--- /dev/null
+++ b/deforest/solution.py
@@ -0,0 +1,41 @@
+from tags import GetAttTag, SubTag, RefTag
+import yaml
+import re
+from cleaner import DeforestCleaner
+from cfcleaner import CFCleaner
+
+class Solution():
+ raw = {}
+ filedata = None
+ processed = None
+
+ def __init__(self, data):
+ self.filedata = data
+
+ def _enable_custom_tags(self):
+ yaml.SafeLoader.add_constructor('!GetAtt', GetAttTag.from_yaml)
+ yaml.SafeLoader.add_constructor('!Sub', SubTag.from_yaml)
+ yaml.SafeLoader.add_constructor('!Ref', RefTag.from_yaml)
+ yaml.SafeDumper.add_multi_representer(GetAttTag, GetAttTag.to_yaml)
+ yaml.SafeDumper.add_multi_representer(SubTag, SubTag.to_yaml)
+ yaml.SafeDumper.add_multi_representer(RefTag, RefTag.to_yaml)
+
+ def _load(self):
+ self.raw = yaml.safe_load(self.filedata)
+
+ def _dump(self):
+ self.processed = yaml.safe_dump(self.raw)
+
+ def cleaner(self):
+ self._enable_custom_tags()
+ self._load()
+ cleaner = self._identify_cleaner()
+ if cleaner is "default":
+ return DeforestCleaner(self.filedata)
+ if cleaner is "cloudformation":
+ return CFCleaner(self.filedata)
+
+ def _identify_cleaner(self):
+ if "AWSTemplateFormatVersion" in self.raw:
+ return "cloudformation"
+ return "default"
\ No newline at end of file