Manually merge CI files 54/72954/1
authorSawyer Bergeron <sbergeron@iol.unh.edu>
Thu, 7 Oct 2021 21:14:01 +0000 (17:14 -0400)
committerSawyer Bergeron <sbergeron@iol.unh.edu>
Thu, 7 Oct 2021 21:14:01 +0000 (17:14 -0400)
Signed-off-by: Sawyer Bergeron <sbergeron@iol.unh.edu>
Change-Id: Ic63d5da699578007ef2f2cc373350ded06c66971

requirements.txt
src/api/views.py
src/booking/quick_deployer.py
src/resource_inventory/models.py

index b34dd1e..d93ac7c 100644 (file)
@@ -16,3 +16,4 @@ django-fernet-fields==0.6
 pyyaml==3.13
 pytz==2018.5
 mozilla-django-oidc==1.2.3
+deepmerge==0.3
index 79da84c..5af3d69 100644 (file)
@@ -37,6 +37,7 @@ from resource_inventory.models import (
 )
 
 import json
+from deepmerge import Merger
 
 """
 API views.
@@ -263,7 +264,26 @@ def resource_ci_userdata(request, lab_name="", job_id="", resource_id="", file_i
     except ObjectDoesNotExist:
         return HttpResponseNotFound("Could not find a matching resource by id " + str(resource_id))
 
-    return HttpResponse(cifile.text, status=200)
+    text = cifile.text
+
+    prepended_text = "#cloud-config\n"
+    #mstrat = CloudInitFile.merge_strategy()
+    #prepended_text = prepended_text + yaml.dump({"merge_strategy": mstrat}) + "\n"
+    #print("in cloudinitfile create")
+    text = prepended_text + text
+    cloud_dict = {
+            "datasource": {
+                "None": {
+                    "metadata": {
+                        "instance-id": str(uuid.uuid4())
+                    },
+                    "userdata_raw": text,
+                },
+            },
+            "datasource_list": ["None"],
+        }
+
+    return HttpResponse(yaml.dump(cloud_dict), status=200)
 
 @csrf_exempt
 def resource_ci_metadata(request, lab_name="", job_id="", resource_id="", file_id=0):
@@ -276,7 +296,36 @@ def resource_ci_userdata_directory(request, lab_name="", job_id="", resource_id=
     files = resource.config.cloud_init_files
     files = [{"id": file.id, "priority": file.priority} for file in files.order_by("priority").all()]
 
-    return HttpResponse(json.dumps(files), status=200)
+    d = {
+            'merge_failures': []
+        }
+
+    merger = Merger(
+            [
+                (list, ["append"]),
+                (dict, ["merge"]),
+            ],
+            ["override"], # fallback
+            ["override"], # if types conflict (shouldn't happen in CI, but handle case)
+        )
+
+    for file in files.order_by("priority").all():
+        try:
+            other_dict = yaml.load(file.text)
+            if not (type(d) is dict):
+                raise Exception("CI file was valid yaml but was not a dict")
+
+            merger.merge(d, other_dict)
+        except Exception as e:
+            # if fail to merge, then just skip
+            print("Failed to merge file in, as it had invalid content:", file.id)
+            print("File text was:")
+            print(file.text)
+            d['merge_failures'].append({file.id: str(e)})
+
+    file = CloudInitFile.create(text=yaml.dump(d), priority=0)
+
+    return HttpResponse(json.dumps([{"id": file.id, "priority": file.priority}]), status=200)
 
 
 def new_jobs(request, lab_name=""):
index 2ab18a6..261b095 100644 (file)
@@ -219,6 +219,12 @@ def create_from_form(form, request):
     global_cloud_config = None if not form.cleaned_data['global_cloud_config'] else form.cleaned_data['global_cloud_config']
 
     if global_cloud_config:
+        try:
+            d = yaml.load(global_cloud_config)
+            if not (type(d) is dict):
+                raise Exception("CI file was valid yaml but was not a dict")
+        except Exception as e:
+            raise ValidationError("The provided Cloud Config is not valid yaml, please refer to the Cloud Init documentation for expected structure")
         print("about to create global cloud config")
         global_cloud_config = CloudInitFile.create(text=global_cloud_config, priority=CloudInitFile.objects.count())
         print("made global cloud config")
index 71046a8..6117035 100644 (file)
@@ -169,23 +169,23 @@ class CloudInitFile(models.Model):
 
     @classmethod
     def create(cls, text="", priority=0):
-        prepended_text = "#cloud-config\n"
-        mstrat = CloudInitFile.merge_strategy()
-        prepended_text = prepended_text + yaml.dump({"merge_strategy": mstrat}) + "\n"
-        print("in cloudinitfile create")
-        text = prepended_text + text
-        cloud_dict = {
-                "datasource": {
-                    "None": {
-                        "metadata": {
-                            "instance-id": str(uuid.uuid4())
-                        },
-                        "userdata_raw": text,
-                    },
-                },
-                "datasource_list": ["None"],
-            }
-        return CloudInitFile.objects.create(priority=priority, text=yaml.dump(cloud_dict))
+        #prepended_text = "#cloud-config\n"
+        #mstrat = CloudInitFile.merge_strategy()
+        #prepended_text = prepended_text + yaml.dump({"merge_strategy": mstrat}) + "\n"
+        #print("in cloudinitfile create")
+        #text = prepended_text + text
+        #cloud_dict = {
+        #        "datasource": {
+        #            "None": {
+        #                "metadata": {
+        #                    "instance-id": str(uuid.uuid4())
+        #                },
+        #                "userdata_raw": text,
+        #            },
+        #        },
+        #        "datasource_list": ["None"],
+        #    }
+        return CloudInitFile.objects.create(priority=priority, text=text)
 
 class ResourceTemplate(models.Model):
     """