Improved auto dependency resolution for StackRef and FortyTwo Legacy Refs
This commit is contained in:
parent
309356b129
commit
d3efc7c336
@ -2,7 +2,7 @@ import logging
|
|||||||
|
|
||||||
__author__ = 'Stefan Reimer'
|
__author__ = 'Stefan Reimer'
|
||||||
__email__ = 'stefan@zero-downtimet.net'
|
__email__ = 'stefan@zero-downtimet.net'
|
||||||
__version__ = '0.5.0'
|
__version__ = '0.5.1'
|
||||||
|
|
||||||
|
|
||||||
# Set up logging to ``/dev/null`` like a library is supposed to.
|
# Set up logging to ``/dev/null`` like a library is supposed to.
|
||||||
|
@ -76,13 +76,12 @@ class Stack(object):
|
|||||||
# backwards comp
|
# backwards comp
|
||||||
if 'vars' in _config:
|
if 'vars' in _config:
|
||||||
self.options = dict_merge(self.options, _config['vars'])
|
self.options = dict_merge(self.options, _config['vars'])
|
||||||
if 'Mode' in self.options:
|
|
||||||
self.mode = self.options['Mode']
|
|
||||||
|
|
||||||
if 'options' in _config:
|
if 'options' in _config:
|
||||||
self.options = dict_merge(self.options, _config['options'])
|
self.options = dict_merge(self.options, _config['options'])
|
||||||
if 'Mode' in self.options:
|
|
||||||
self.mode = self.options['Mode']
|
if 'Mode' in self.options:
|
||||||
|
self.mode = self.options['Mode']
|
||||||
|
|
||||||
if 'dependencies' in _config:
|
if 'dependencies' in _config:
|
||||||
for dep in _config['dependencies']:
|
for dep in _config['dependencies']:
|
||||||
@ -98,9 +97,7 @@ class Stack(object):
|
|||||||
'Template.Hash': "__HASH__",
|
'Template.Hash': "__HASH__",
|
||||||
'CloudBender.Version': __version__
|
'CloudBender.Version': __version__
|
||||||
}
|
}
|
||||||
|
_config = {'mode': self.mode, 'options': self.options, 'metadata': template_metadata}
|
||||||
# cfn is provided for old configs
|
|
||||||
_config = {'mode': self.mode, 'options': self.options, 'metadata': template_metadata, 'cfn': self.options}
|
|
||||||
|
|
||||||
jenv = JinjaEnv(self.ctx['artifact_paths'])
|
jenv = JinjaEnv(self.ctx['artifact_paths'])
|
||||||
jenv.globals['_config'] = _config
|
jenv.globals['_config'] = _config
|
||||||
@ -118,21 +115,42 @@ class Stack(object):
|
|||||||
logger.error(self.cfn_template)
|
logger.error(self.cfn_template)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# Some sanity checks and final cosmetics
|
if not re.search('CloudBender::', self.cfn_template):
|
||||||
|
logger.info("CloudBender not required -> removing Transform and Conglomerate parameter")
|
||||||
|
self.cfn_template = self.cfn_template.replace('Transform: [CloudBender]', '')
|
||||||
|
|
||||||
|
_res = """
|
||||||
|
Conglomerate:
|
||||||
|
Type: String
|
||||||
|
Description: Project / Namespace this stack is part of
|
||||||
|
"""
|
||||||
|
self.cfn_template = re.sub(_res, '', self.cfn_template)
|
||||||
|
|
||||||
|
# Add Legacy FortyTwo resource to prevent AWS from replacing existing resources for NO reason ;-(
|
||||||
|
include = []
|
||||||
|
search_refs(self.cfn_data, include)
|
||||||
|
if len(include) and 'Legacy' in self.options:
|
||||||
|
_res = """
|
||||||
|
FortyTwo:
|
||||||
|
Type: Custom::FortyTwo
|
||||||
|
Properties:
|
||||||
|
ServiceToken:
|
||||||
|
Fn::Sub: "arn:aws:lambda:${{AWS::Region}}:${{AWS::AccountId}}:function:FortyTwo"
|
||||||
|
UpdateToken: __HASH__
|
||||||
|
Include: {}""".format(sorted(set(include)))
|
||||||
|
|
||||||
|
self.cfn_template = re.sub(r'Resources:', r'Resources:' + _res + '\n', self.cfn_template)
|
||||||
|
logger.info("Legacy Mode -> added Custom::FortyTwo")
|
||||||
|
|
||||||
|
# Re-read updated template
|
||||||
|
self.cfn_data = yaml.safe_load(self.cfn_template)
|
||||||
|
|
||||||
# Check for empty top level Parameters, Outputs and Conditions and remove
|
# Check for empty top level Parameters, Outputs and Conditions and remove
|
||||||
for key in ['Parameters', 'Outputs', 'Conditions']:
|
for key in ['Parameters', 'Outputs', 'Conditions']:
|
||||||
if key in self.cfn_data and self.cfn_data[key] is None:
|
if key in self.cfn_data and not self.cfn_data[key]:
|
||||||
# Delete from data structure which also takes care of json
|
|
||||||
del self.cfn_data[key]
|
del self.cfn_data[key]
|
||||||
|
|
||||||
# but also remove from rendered for the yaml file
|
|
||||||
self.cfn_template = self.cfn_template.replace('\n' + key + ":", '')
|
self.cfn_template = self.cfn_template.replace('\n' + key + ":", '')
|
||||||
|
|
||||||
if not re.search('CloudBender::', self.cfn_template):
|
|
||||||
logger.info("CloudBender not required -> removing Transform")
|
|
||||||
del self.cfn_data['Transform']
|
|
||||||
self.cfn_template = self.cfn_template.replace('Transform: [CloudBender]', '')
|
|
||||||
|
|
||||||
# Remove and condense multiple empty lines
|
# Remove and condense multiple empty lines
|
||||||
self.cfn_template = re.sub(r'\n\s*\n', '\n\n', self.cfn_template)
|
self.cfn_template = re.sub(r'\n\s*\n', '\n\n', self.cfn_template)
|
||||||
self.cfn_template = re.sub(r'^\s*', '', self.cfn_template)
|
self.cfn_template = re.sub(r'^\s*', '', self.cfn_template)
|
||||||
@ -140,38 +158,26 @@ class Stack(object):
|
|||||||
|
|
||||||
# set md5 last
|
# set md5 last
|
||||||
self.md5 = hashlib.md5(self.cfn_template.encode('utf-8')).hexdigest()
|
self.md5 = hashlib.md5(self.cfn_template.encode('utf-8')).hexdigest()
|
||||||
self.cfn_data['Metadata']['Hash'] = self.md5
|
|
||||||
|
|
||||||
# Add Legacy FortyTwo if needed to prevent AWS from replacing existing resources for NO reason ;-(
|
|
||||||
include = []
|
|
||||||
search_attributes(self.cfn_data, include)
|
|
||||||
if len(include):
|
|
||||||
_res = """
|
|
||||||
FortyTwo:
|
|
||||||
Type: Custom::FortyTwo
|
|
||||||
Properties:
|
|
||||||
ServiceToken:
|
|
||||||
Fn::Sub: "arn:aws:lambda:${{AWS::Region}}:${{AWS::AccountId}}:function:FortyTwo"
|
|
||||||
UpdateToken: {}
|
|
||||||
Include: {}""".format(self.md5, sorted(set(include)))
|
|
||||||
self.cfn_data['Resources'].update(yaml.safe_load(_res))
|
|
||||||
|
|
||||||
self.cfn_template = re.sub(r'Resources:', r'Resources:' + _res + '\n', self.cfn_template)
|
|
||||||
logger.info("Legacy Mode -> added Custom::FortyTwo")
|
|
||||||
|
|
||||||
self.cfn_template = self.cfn_template.replace('__HASH__', self.md5)
|
self.cfn_template = self.cfn_template.replace('__HASH__', self.md5)
|
||||||
|
|
||||||
# Update internal data structures
|
# Update internal data structures
|
||||||
self._parse_metadata()
|
self._parse_metadata()
|
||||||
|
print(self.dependencies)
|
||||||
|
|
||||||
def _parse_metadata(self):
|
def _parse_metadata(self):
|
||||||
# Extract dependencies if present
|
# Extract dependencies
|
||||||
try:
|
try:
|
||||||
for dep in self.cfn_data['Metadata']['CloudBender']['Dependencies']:
|
for dep in self.cfn_data['Metadata']['CloudBender']['Dependencies']:
|
||||||
self.dependencies.add(dep)
|
self.dependencies.add(dep)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Add CloudBender or FortyTwo dependencies
|
||||||
|
include = []
|
||||||
|
search_refs(self.cfn_data, include)
|
||||||
|
for ref in include:
|
||||||
|
self.dependencies.add(ref.split('.')[0])
|
||||||
|
|
||||||
def write_template_file(self):
|
def write_template_file(self):
|
||||||
if self.cfn_template:
|
if self.cfn_template:
|
||||||
yaml_file = os.path.join(self.ctx['template_path'], self.rel_path, self.stackname + ".yaml")
|
yaml_file = os.path.join(self.ctx['template_path'], self.rel_path, self.stackname + ".yaml")
|
||||||
@ -497,21 +503,28 @@ class Stack(object):
|
|||||||
os.makedirs(os.path.join(self.ctx[path], self.rel_path))
|
os.makedirs(os.path.join(self.ctx[path], self.rel_path))
|
||||||
|
|
||||||
|
|
||||||
def search_attributes(template, attributes):
|
def search_refs(template, attributes):
|
||||||
""" Traverses a template and searches for all Fn::GetAtt calls to FortyTwo
|
""" Traverses a template and searches for all Fn::GetAtt calls to FortyTwo
|
||||||
adding them to the passed in attributes set
|
adding them to the passed in attributes set
|
||||||
"""
|
"""
|
||||||
if isinstance(template, dict):
|
if isinstance(template, dict):
|
||||||
for k, v in template.items():
|
for k, v in template.items():
|
||||||
# Look for Fn::GetAtt
|
# FortyTwo Fn::GetAtt
|
||||||
if k == "Fn::GetAtt" and isinstance(v, list):
|
if k == "Fn::GetAtt" and isinstance(v, list):
|
||||||
if v[0] == "FortyTwo":
|
if v[0] == "FortyTwo":
|
||||||
attributes.append(v[1])
|
attributes.append(v[1])
|
||||||
|
|
||||||
|
# CloudBender::StackRef
|
||||||
|
if k == "CloudBender::StackRef":
|
||||||
|
try:
|
||||||
|
attributes.append(v['StackTags']['Artifact'])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
if isinstance(v, dict) or isinstance(v, list):
|
if isinstance(v, dict) or isinstance(v, list):
|
||||||
search_attributes(v, attributes)
|
search_refs(v, attributes)
|
||||||
|
|
||||||
elif isinstance(template, list):
|
elif isinstance(template, list):
|
||||||
for k in template:
|
for k in template:
|
||||||
if isinstance(k, dict) or isinstance(k, list):
|
if isinstance(k, dict) or isinstance(k, list):
|
||||||
search_attributes(k, attributes)
|
search_refs(k, attributes)
|
||||||
|
Loading…
Reference in New Issue
Block a user