Repository: emissary-ingress/emissary

Issue:
E2E tests don't run on generated YAML
The end-to-end tests don't run on the Ambassador generated YAML. This means that bugs in the YAML generation don't get caught.


Discussion:

Resolving patch:
diff --git a/end-to-end/005-single-namespace/fix_env.py b/end-to-end/005-single-namespace/fix_env.py
deleted file mode 100644
--- a/end-to-end/005-single-namespace/fix_env.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!python
-
-import sys
-
-import yaml
-
-import dpath
-
-manifest = list(yaml.safe_load_all(open(sys.argv[1], 'r')))
-
-for x in range(len(manifest)):
-    if dpath.util.get(manifest, "/%d/kind" % x) == 'Deployment':
-        path = "/%d/spec/template/spec/containers/0/env" % x
-        dpath.util.new(manifest, path, [
-            { 
-                'name': 'AMBASSADOR_SINGLE_NAMESPACE',
-                'value': 'YES'
-            },
-            {
-                'name': 'AMBASSADOR_NAMESPACE',
-                'value': 'default'
-            }
-        ])
-
-yaml.safe_dump_all(manifest, open(sys.argv[2], "w"), default_flow_style=False)
diff --git a/end-to-end/fix_deployment.py b/end-to-end/fix_deployment.py
deleted file mode 100644
--- a/end-to-end/fix_deployment.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#!python
-
-import sys
-
-import yaml
-
-import dpath
-
-namespace = sys.argv[1]
-ambassador_id = sys.argv[2]
-input_yaml_path = sys.argv[3]
-output_yaml_path = sys.argv[4]
-
-manifest = list(yaml.safe_load_all(open(input_yaml_path, 'r')))
-
-kinds_to_delete = {
-    "ClusterRole": True,
-    "ServiceAccount": True,
-    "ServiceAccount": True,
-    "ClusterRoleBinding": True,
-}
-
-keep = []
-
-for x in range(len(manifest)):
-    kind = dpath.util.get(manifest, "/%d/kind" % x)
-    name = dpath.util.get(manifest, "/%d/metadata/name" % x)
-
-    if kind in kinds_to_delete:
-        # print("Skipping %s %s" % (kind, name))
-        continue
-
-    print("Adding namespace %s to %s %s" % (namespace, kind, name))
-    dpath.util.new(manifest, "/%d/metadata/namespace" % x, namespace)
-
-    if kind == 'Deployment':
-        print("Setting environment for %s %s" % (kind, name))
-        path = "/%d/spec/template/spec/containers/0/env" % x
-        dpath.util.new(manifest, path, [
-            {
-                'name': 'AMBASSADOR_NAMESPACE',
-                'value': namespace
-            },
-            {
-                'name': 'AMBASSADOR_ID',
-                'value': ambassador_id
-            }            
-        ])
-    
-    keep.append(manifest[x])
-
-yaml.safe_dump_all(keep, open(output_yaml_path, "w"), default_flow_style=False)
diff --git a/end-to-end/yfix.py b/end-to-end/yfix.py
new file mode 100644
--- /dev/null
+++ b/end-to-end/yfix.py
@@ -0,0 +1,188 @@
+#!python
+
+import sys
+
+import re
+import yaml
+
+import dpath
+
+class Edit (object):
+    def __init__(self):
+        self.elements = {}
+
+    def __nonzero__(self):
+        return bool(self.elements)
+
+    def __str__(self):
+        return yaml.safe_dump(self.elements, default_flow_style=False)
+
+    def add(self, element, args):
+        self.elements.setdefault(element.lower(), []).append(args)
+
+    def match(self, manifest, x):
+        all_matched = True
+
+        if 'match' in self.elements:
+            for path, value in self.elements['match']:
+                qualified = '/%d/%s' % (x, path)
+                got_value = dpath.util.get(manifest, qualified)
+
+                if got_value != value:
+                    print("NOMATCH %s: %s != %s" % (qualified, value, got_value))
+                    all_matched = False
+                else:
+                    print("MATCH %s == %s" % (qualified, value))
+
+        return all_matched
+
+    def set(self, manifest, x, path, value):
+        qualified = '/%d/%s' % (x, path)
+        print("SET %s = %s" % (qualified, value))
+        dpath.util.new(manifest, qualified, value)
+
+    def execute(self, manifest, x, args):
+        if 'discard' in self.elements:
+            print("DISCARD")
+            return False
+
+        for element in self.elements.get('mklist', []):
+            path = element[0]
+            self.set(manifest, x, path, [])
+
+        for element in self.elements.get('mkdict', []):
+            path = element[0]
+            self.set(manifest, x, path, {})
+
+        for element in self.elements.get('set', []):
+            path, value = element
+            self.set(manifest, x, path, args.interpolate(value))
+
+        for element in self.elements.get('setint', []):
+            path, value = element
+            self.set(manifest, x, path, int(value))
+
+        for element in self.elements.get('delete', []):
+            path = element[0]
+            qualified = '/%d/%s' % (x, path)
+            print("DEL %s" % qualified)
+            dpath.util.delete(manifest, qualified)
+
+        return True
+
+class Edits (object):
+    def __init__(self):
+        self.edits = []
+        self.current = Edit()
+
+    def __iter__(self):
+        return iter(self.edits)
+
+    def __str__(self):
+        output = ''
+
+        for edit in edits:
+            output += '---\n%s\n' % edit
+
+        return output
+
+    def add(self, element, args):
+        self.current.add(element, args)
+
+    def finish(self):
+        if self.current:
+            self.edits.append(self.current)
+            self.current = Edit()
+
+class Args (object):
+    reVar = re.compile(r'\$(\d+)')
+
+    def __init__(self):
+        self.needed = 0
+        self.args = None
+
+    def load(self, args):
+        if len(args) != self.needed:
+            raise Exception("need %d arg%s, have %d\n" % 
+                            (self.needed, "" if self.needed == 1 else "s", 
+                             len(args)))
+
+        self.args = args
+
+    def counter(self, matchobj):
+        var = int(matchobj.group(1))
+
+        if var > self.needed:
+            self.needed = var
+
+    def replacer(self, matchobj):
+        var = int(matchobj.group(1))
+
+        return self.args[var - 1]
+
+    def count(self, args):
+        for text in args:
+            Args.reVar.sub(self.counter, text)
+
+    def interpolate(self, text):
+        return Args.reVar.sub(self.replacer, text)
+
+cmd_path = sys.argv.pop(1)  # Not 0. Trust me.
+
+edits = Edits()
+args = Args()
+
+for line in open(cmd_path, 'r'):
+    line = line.strip()
+
+    if not line:
+        edits.finish()
+        continue
+
+    fields = line.split(' ')
+
+    element = fields[0]
+    element_args = fields[1:]
+
+    args.count(element_args)    
+    edits.add(element, element_args)
+
+edits.finish()
+
+print(edits)
+print("args needed: %s" % args.needed)
+
+input_yaml_path = "-"
+output_yaml_path = "-"
+
+if len(sys.argv) > 1:
+    input_yaml_path = sys.argv.pop(1)
+
+    if len(sys.argv) > 1:
+        output_yaml_path = sys.argv.pop(1)
+
+args.load(sys.argv[1:])
+
+input_yaml = sys.stdin if input_yaml_path == "-" else open(input_yaml_path, 'r')
+output_yaml = sys.stdin if output_yaml_path == "-" else open(output_yaml_path, 'w')
+    
+manifest = list(yaml.safe_load_all(input_yaml))
+
+keep = []
+
+for x in range(len(manifest)):
+    matched = False
+
+    for edit in edits:
+        if edit.match(manifest, x):
+            matched = True
+
+            if edit.execute(manifest, x, args):
+                keep.append(manifest[x])
+            
+            break
+    
+    if not matched:
+        keep.append(manifest[x])
+
+yaml.safe_dump_all(keep, output_yaml, default_flow_style=False)


Test patch:
diff --git a/end-to-end/005-single-namespace/test.sh b/end-to-end/005-single-namespace/test.sh
--- a/end-to-end/005-single-namespace/test.sh
+++ b/end-to-end/005-single-namespace/test.sh
@@ -15,7 +15,9 @@ initialize_cluster
 
 kubectl cluster-info
 
-python fix_env.py ../ambassador-deployment.yaml k8s/ambassador-deployment.yaml
+python ${ROOT}/yfix.py ${ROOT}/fixes/single-namespace.yfix \
+    ${ROOT}/ambassador-deployment.yaml \
+    k8s/ambassador-deployment.yaml
 
 kubectl create namespace other
 kubectl apply -f k8s/ambassador.yaml
diff --git a/end-to-end/009-grpc/test.sh b/end-to-end/009-grpc/test.sh
--- a/end-to-end/009-grpc/test.sh
+++ b/end-to-end/009-grpc/test.sh
@@ -18,7 +18,11 @@ kubectl apply -f ${ROOT}/rbac.yaml
 
 kubectl cluster-info
 
-python ${ROOT}/fix_deployment.py grpc-test grpc-test ../ambassador-deployment.yaml k8s/ambassador-deployment.yaml
+python ${ROOT}/yfix.py ${ROOT}/fixes/test-dep.yfix \
+    ${ROOT}/ambassador-deployment.yaml \
+    k8s/ambassador-deployment.yaml \
+    grpc-test \
+    grpc-test
 
 kubectl apply -f k8s/rbac.yaml
 kubectl apply -f k8s/ambassador.yaml
diff --git a/end-to-end/010-multiple-ambassadors/test.sh b/end-to-end/010-multiple-ambassadors/test.sh
--- a/end-to-end/010-multiple-ambassadors/test.sh
+++ b/end-to-end/010-multiple-ambassadors/test.sh
@@ -15,8 +15,17 @@ initialize_cluster
 
 kubectl cluster-info
 
-python ${ROOT}/fix_deployment.py test-010-1 ambassador-1 ../ambassador-deployment.yaml k8s/ambassador-deployment-1.yaml
-python ${ROOT}/fix_deployment.py test-010-2 ambassador-2 ../ambassador-deployment.yaml k8s/ambassador-deployment-2.yaml
+python ${ROOT}/yfix.py ${ROOT}/fixes/test-dep.yfix \
+    ${ROOT}/ambassador-deployment.yaml \
+    k8s/ambassador-deployment-1.yaml \
+    test-010-1 \
+    ambassador-1
+
+python ${ROOT}/yfix.py ${ROOT}/fixes/test-dep.yfix \
+    ${ROOT}/ambassador-deployment.yaml \
+    k8s/ambassador-deployment-2.yaml \
+    test-010-2 \
+    ambassador-2
 
 kubectl create namespace test-010-1
 kubectl create namespace test-010-2