Merge "Stop using notCompute in favor of controller"
[apex-tripleo-heat-templates.git] / tripleo_heat_merge / merge.py
index 7b5951a..4571d28 100644 (file)
@@ -4,6 +4,27 @@ import yaml
 import argparse
 
 
+def apply_maps(template):
+    """Apply Merge::Map within template.
+
+    Any dict {'Merge::Map': {'Foo': 'Bar', 'Baz': 'Quux'}}
+    will resolve to ['Bar', 'Quux'] - that is a dict with key
+    'Merge::Map' is replaced entirely by that dict['Merge::Map'].values().
+    """
+    if isinstance(template, dict):
+        if 'Merge::Map' in template:
+            return sorted(
+                apply_maps(value) for value in template['Merge::Map'].values()
+                )
+        else:
+            return dict((key, apply_maps(value))
+                for key, value in template.items())
+    elif isinstance(template, list):
+        return [apply_maps(item) for item in template]
+    else:
+        return template
+
+
 def apply_scaling(template, scaling, in_copies=None):
     """Apply a set of scaling operations to template.
 
@@ -12,9 +33,12 @@ def apply_scaling(template, scaling, in_copies=None):
 
     Values are handled via scale_value.
 
-    Keys in dicts are always copied per the scaling rule.
+    Keys in dicts are copied per the scaling rule.
     Values are either replaced or copied depending on whether the given
     scaling rule is in in_copies.
+
+    in_copies is reset to None when a dict {'Merge::Map': someobject} is
+    encountered.
     """
     in_copies = dict(in_copies or {})
     # Shouldn't be needed but to avoid unexpected side effects/bugs we short
@@ -22,6 +46,8 @@ def apply_scaling(template, scaling, in_copies=None):
     if not scaling:
         return template
     if isinstance(template, dict):
+        if 'Merge::Map' in template:
+            in_copies = None
         new_template = {}
         for key, value in template.items():
             for prefix, copy_num, new_key in scale_value(
@@ -188,11 +214,17 @@ def main(argv=None):
         "be copied to Prefix1Foo in the output, and value Prefix0Bar to be"
         "renamed to Prefix1Bar inside that copy, or copied to Prefix1Bar "
         "outside of any copy.")
+    parser.add_argument(
+        '--change-image-params', action='store_true', default=False,
+        help="Change parameters in templates to match resource names. This was "
+             " the default at one time but it causes issues when parameter "
+             " names need to remain stable.")
     args = parser.parse_args(argv)
     templates = args.templates
     scaling = parse_scaling(args.scale)
     merged_template = merge(templates, args.master_role, args.slave_roles,
-                            args.included_template_dir, scaling=scaling)
+                            args.included_template_dir, scaling=scaling,
+                            change_image_params=args.change_image_params)
     if args.output == '-':
         out_file = sys.stdout
     else:
@@ -202,7 +234,7 @@ def main(argv=None):
 
 def merge(templates, master_role=None, slave_roles=None,
           included_template_dir=INCLUDED_TEMPLATE_DIR,
-          scaling=None):
+          scaling=None, change_image_params=None):
     scaling = scaling or {}
     errors = []
     end_template={'HeatTemplateFormatVersion': '2012-12-12',
@@ -239,11 +271,12 @@ def merge(templates, master_role=None, slave_roles=None,
         new_resources = template.get('Resources', {})
         for r, rbody in sorted(new_resources.items()):
             if rbody['Type'] in MERGABLE_TYPES:
-                if 'image' in MERGABLE_TYPES[rbody['Type']]:
-                    image_key = MERGABLE_TYPES[rbody['Type']]['image']
-                    # XXX Assuming ImageId is always a Ref
-                    ikey_val = end_template['Parameters'][rbody['Properties'][image_key]['Ref']]
-                    del end_template['Parameters'][rbody['Properties'][image_key]['Ref']]
+                if change_image_params:
+                    if 'image' in MERGABLE_TYPES[rbody['Type']]:
+                        image_key = MERGABLE_TYPES[rbody['Type']]['image']
+                        # XXX Assuming ImageId is always a Ref
+                        ikey_val = end_template['Parameters'][rbody['Properties'][image_key]['Ref']]
+                        del end_template['Parameters'][rbody['Properties'][image_key]['Ref']]
                 role = rbody.get('Metadata', {}).get('OpenStack::Role', r)
                 role = translate_role(role, master_role, slave_roles)
                 if role != r:
@@ -264,10 +297,11 @@ def merge(templates, master_role=None, slave_roles=None,
                 if 'Resources' not in end_template:
                     end_template['Resources'] = {}
                 end_template['Resources'][role] = rbody
-                if 'image' in MERGABLE_TYPES[rbody['Type']]:
-                    ikey = '%sImage' % (role)
-                    end_template['Resources'][role]['Properties'][image_key] = {'Ref': ikey}
-                    end_template['Parameters'][ikey] = ikey_val
+                if change_image_params:
+                    if 'image' in MERGABLE_TYPES[rbody['Type']]:
+                        ikey = '%sImage' % (role)
+                        end_template['Resources'][role]['Properties'][image_key] = {'Ref': ikey}
+                        end_template['Parameters'][ikey] = ikey_val
             elif rbody['Type'] == 'FileInclude':
                 # we trust os.path.join to DTRT: if FileInclude path isn't
                 # absolute, join to included_template_dir (./)
@@ -293,6 +327,7 @@ def merge(templates, master_role=None, slave_roles=None,
                 end_template['Resources'][r] = rbody
 
     end_template = apply_scaling(end_template, scaling)
+    end_template = apply_maps(end_template)
 
     def fix_ref(item, old, new):
         if isinstance(item, dict):