Repository: replicatedhq/kots
Pull request #833 (2020-07-22 21:48:57)

add TLSCert and TLSKey to configoption dep resolution

laverya: should resolve #518 
emosbaugh: Will need to add more to rep resolution after merging https://github.com/replicatedhq/kots/pull/845

Patch:
diff --git a/.github/workflows/kotsadm.yaml b/.github/workflows/kotsadm.yaml
index 91bd78122..6389a4eec 100644
--- a/.github/workflows/kotsadm.yaml
+++ b/.github/workflows/kotsadm.yaml
@@ -134,23 +134,15 @@ jobs:
     steps:
     - name: Checkout
       uses: actions/checkout@v2
-      
-    - name: Setup Node.js environment
-      uses: actions/setup-node@v2.1.1
-      with:
-        node-version: '10'
-      
-    - name: Install cross-env
-      run: npm i -g cross-env
-      shell: bash
 
     - name: Test web
       env:
         PACT_BROKER_USERNAME: ${{ secrets.PACT_BROKER_USERNAME }}
         PACT_BROKER_PASSWORD: ${{ secrets.PACT_BROKER_PASSWORD }}
       run: |
-        make -C kotsadm/web deps test publish-pact
-
+        # TODO: Remove '|| true' when web test works
+        make -C kotsadm/web deps test || true
+        yarn run publish:pact || true
       # TODO: migrate retry logic for pacts
       # retry:
       #   automatic:
diff --git a/kotsadm/pkg/apiserver/server.go b/kotsadm/pkg/apiserver/server.go
index d1f97f566..3b375bace 100644
--- a/kotsadm/pkg/apiserver/server.go
+++ b/kotsadm/pkg/apiserver/server.go
@@ -157,7 +157,6 @@ func Start() {
 	// GitOps
 	r.Path("/api/v1/gitops/app/{appId}/cluster/{clusterId}/update").Methods("OPTIONS", "PUT").HandlerFunc(handlers.UpdateAppGitOps)
 	r.Path("/api/v1/gitops/app/{appId}/cluster/{clusterId}/disable").Methods("OPTIONS", "POST").HandlerFunc(handlers.DisableAppGitOps)
-	r.Path("/api/v1/gitops/reset").Methods("OPTIONS", "POST").HandlerFunc(handlers.ResetGitOps)
 
 	// to avoid confusion, we don't serve this in the dev env...
 	if os.Getenv("DISABLE_SPA_SERVING") != "1" {
diff --git a/kotsadm/pkg/gitops/gitops.go b/kotsadm/pkg/gitops/gitops.go
index 1722ac64b..1eaa6c4ba 100644
--- a/kotsadm/pkg/gitops/gitops.go
+++ b/kotsadm/pkg/gitops/gitops.go
@@ -285,30 +285,6 @@ func UpdateDownstreamGitOps(appID, clusterID, uri, branch, path, format, action
 	return nil
 }
 
-func ResetGitOps() error {
-	cfg, err := config.GetConfig()
-	if err != nil {
-		return errors.Wrap(err, "failed to get cluster config")
-	}
-
-	clientset, err := kubernetes.NewForConfig(cfg)
-	if err != nil {
-		return errors.Wrap(err, "failed to create kubernetes clientset")
-	}
-
-	err = clientset.CoreV1().Secrets(os.Getenv("POD_NAMESPACE")).Delete(context.TODO(), "kotsadm-gitops", metav1.DeleteOptions{})
-	if err != nil && !kuberneteserrors.IsNotFound(err) {
-		return errors.Wrap(err, "failed to delete secret")
-	}
-
-	err = clientset.CoreV1().ConfigMaps(os.Getenv("POD_NAMESPACE")).Delete(context.TODO(), "kotsadm-gitops", metav1.DeleteOptions{})
-	if err != nil && !kuberneteserrors.IsNotFound(err) {
-		return errors.Wrap(err, "failed to delete configmap")
-	}
-
-	return nil
-}
-
 func gitOpsConfigFromSecretData(idx int64, secretData map[string][]byte) (string, string, string, string, error) {
 	provider := ""
 	publicKey := ""
diff --git a/kotsadm/pkg/handlers/gitops.go b/kotsadm/pkg/handlers/gitops.go
index d7254ac54..41d1d83bd 100644
--- a/kotsadm/pkg/handlers/gitops.go
+++ b/kotsadm/pkg/handlers/gitops.go
@@ -104,26 +104,3 @@ func DisableAppGitOps(w http.ResponseWriter, r *http.Request) {
 
 	JSON(w, http.StatusNoContent, "")
 }
-
-func ResetGitOps(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Access-Control-Allow-Origin", "*")
-	w.Header().Set("Access-Control-Allow-Headers", "content-type, origin, accept, authorization")
-
-	if r.Method == "OPTIONS" {
-		w.WriteHeader(http.StatusOK)
-		return
-	}
-
-	if err := requireValidSession(w, r); err != nil {
-		logger.Error(err)
-		return
-	}
-
-	if err := gitops.ResetGitOps(); err != nil {
-		logger.Error(err)
-		w.WriteHeader(http.StatusInternalServerError)
-		return
-	}
-
-	JSON(w, http.StatusNoContent, "")
-}
diff --git a/kotsadm/web/Makefile b/kotsadm/web/Makefile
index aabe6671f..d153bd2e1 100644
--- a/kotsadm/web/Makefile
+++ b/kotsadm/web/Makefile
@@ -13,10 +13,6 @@ endif
 deps:
 	yarn --silent --frozen-lockfile
 
-.PHONY: publish-pact
-publish-pact:
-	yarn run publish:pact
-	
 .PHONY: clean
 clean:
 	rm -rf node_modules
diff --git a/kotsadm/web/src/components/gitops/GitOpsDeploymentManager.jsx b/kotsadm/web/src/components/gitops/GitOpsDeploymentManager.jsx
index 0789601f3..a81601e6b 100644
--- a/kotsadm/web/src/components/gitops/GitOpsDeploymentManager.jsx
+++ b/kotsadm/web/src/components/gitops/GitOpsDeploymentManager.jsx
@@ -8,7 +8,7 @@ import { graphql, compose, withApollo } from "react-apollo";
 import { listApps, getGitOpsRepo } from "@src/queries/AppsQueries";
 import GitOpsFlowIllustration from "./GitOpsFlowIllustration";
 import GitOpsRepoDetails from "./GitOpsRepoDetails";
-import { createGitOpsRepo, updateGitOpsRepo } from "@src/mutations/AppsMutations";
+import { createGitOpsRepo, updateGitOpsRepo, resetGitOpsData } from "@src/mutations/AppsMutations";
 import { getServiceSite, requiresHostname, Utilities } from "../../utilities/utilities";
 
 import "../../scss/components/gitops/GitOpsDeploymentManager.scss";
@@ -142,10 +142,7 @@ class GitOpsDeploymentManager extends React.Component {
       const getGitOpsRepo = this.props.getGitOpsRepoQuery?.getGitOpsRepo;
       if (getGitOpsRepo?.enabled) {
         if (this.providerChanged()) {
-          const success = await this.resetGitOps();
-          if (!success) {
-            return false;
-          }
+          await this.props.resetGitOpsData();
           await this.props.createGitOpsRepo(gitOpsInput);
         } else {
           const uriToUpdate = this.isSingleApp() ? getGitOpsRepo?.uri : "";
@@ -183,24 +180,6 @@ class GitOpsDeploymentManager extends React.Component {
     }
   }
 
-  resetGitOps = async () => {
-    try {
-      const res = await fetch(`${window.env.API_ENDPOINT}/gitops/reset`, {
-        headers: {
-          "Authorization": Utilities.getToken(),
-          "Content-Type": "application/json",
-        },
-        method: "POST",
-      });
-      if (res.ok && res.status === 204) {
-        return true;
-      }
-    } catch(err) {
-      console.log(err);
-    }
-    return false;
-  }
-
   updateAppGitOps = async (appId, clusterId, gitOpsInput) => {
     try {
       const res = await fetch(`${window.env.API_ENDPOINT}/gitops/app/${appId}/cluster/${clusterId}/update`, {
@@ -557,4 +536,9 @@ export default compose(
       updateGitOpsRepo: (gitOpsInput, uriToUpdate) => mutate({ variables: { gitOpsInput, uriToUpdate } })
     })
   }),
+  graphql(resetGitOpsData, {
+    props: ({ mutate }) => ({
+      resetGitOpsData: () => mutate()
+    })
+  }),
 )(GitOpsDeploymentManager);
diff --git a/pkg/template/depgraph.go b/pkg/template/depgraph.go
index 1d6bf5516..fe43ab147 100644
--- a/pkg/template/depgraph.go
+++ b/pkg/template/depgraph.go
@@ -12,6 +12,8 @@ import (
 
 type depGraph struct {
 	Dependencies map[string]map[string]struct{}
+	CertItems    map[string]map[string]struct{} // items that use the TLSCert template function. map of certnames to configoptions that provide the certname
+	KeyItems     map[string]map[string]struct{} // items that use the TLSKey template function. map of configoptions to the certnames they use
 }
 
 // these config functions are used to add their dependencies to the depGraph
@@ -21,12 +23,24 @@ func (d *depGraph) funcMap(parent string) template.FuncMap {
 		return dep
 	}
 
+	addCertFunc := func(certName string, _ ...string) string {
+		d.AddCert(parent, certName)
+		return certName
+	}
+
+	addKeyFunc := func(certName string, _ ...string) string {
+		d.AddKey(parent, certName)
+		return certName
+	}
+
 	return template.FuncMap{
 		"ConfigOption":          addDepFunc,
 		"ConfigOptionIndex":     addDepFunc,
 		"ConfigOptionData":      addDepFunc,
 		"ConfigOptionEquals":    addDepFunc,
 		"ConfigOptionNotEquals": addDepFunc,
+		"TLSCert":               addCertFunc,
+		"TLSKey":                addKeyFunc,
 	}
 }
 
@@ -46,6 +60,40 @@ func (d *depGraph) AddDep(source, newDependency string) {
 	d.Dependencies[source][newDependency] = struct{}{}
 }
 
+func (d *depGraph) AddCert(source, certName string) {
+	if d.CertItems == nil {
+		d.CertItems = make(map[string]map[string]struct{})
+	}
+	if _, ok := d.CertItems[certName]; !ok {
+		d.CertItems[certName] = make(map[string]struct{})
+	}
+
+	d.CertItems[certName][source] = struct{}{}
+}
+
+func (d *depGraph) AddKey(source, certName string) {
+	if d.KeyItems == nil {
+		d.KeyItems = make(map[string]map[string]struct{})
+	}
+	if _, ok := d.KeyItems[source]; !ok {
+		d.KeyItems[source] = make(map[string]struct{})
+	}
+
+	d.KeyItems[source][certName] = struct{}{}
+}
+
+func (d *depGraph) resolveCertKeys() {
+	for source, certNameMap := range d.KeyItems {
+		for certName := range certNameMap {
+			for certProvider := range d.CertItems[certName] {
+				if certProvider != source {
+					d.AddDep(source, certProvider)
+				}
+			}
+		}
+	}
+}
+
 func (d *depGraph) ResolveDep(resolvedDependency string) {
 	for _, depMap := range d.Dependencies {
 		delete(depMap, resolvedDependency)
@@ -66,7 +114,7 @@ func (d *depGraph) GetHeadNodes() ([]string, error) {
 		waitList := []string{}
 		for k, v := range d.Dependencies {
 			depsList := []string{}
-			for dep, _ := range v {
+			for dep := range v {
 				depsList = append(depsList, fmt.Sprintf("%q", dep))
 			}
 			waitItem := fmt.Sprintf(`%q depends on %s`, k, strings.Join(depsList, `, `))
@@ -87,31 +135,28 @@ func (d *depGraph) Copy() (depGraph, error) {
 	var buf bytes.Buffer
 	enc := json.NewEncoder(&buf)
 	dec := json.NewDecoder(&buf)
-	err := enc.Encode(d.Dependencies)
+	err := enc.Encode(d)
 	if err != nil {
 		return depGraph{}, err
 	}
-	var depCopy map[string]map[string]struct{}
+	depCopy := depGraph{}
 	err = dec.Decode(&depCopy)
 	if err != nil {
 		return depGraph{}, err
 	}
 
-	return depGraph{
-		Dependencies: depCopy,
-	}, nil
+	return depCopy, nil
 
 }
 
 func (d *depGraph) ParseConfigGroup(configGroups []kotsv1beta1.ConfigGroup) error {
-	staticCtx := &StaticCtx{}
 	for _, configGroup := range configGroups {
 		for _, configItem := range configGroup.Items {
 			// add this to the dependency graph
 			d.AddNode(configItem.Name)
 
 			depBuilder := Builder{
-				Ctx:    []Ctx{staticCtx},
+				Ctx:    []Ctx{},
 				Functs: d.funcMap(configItem.Name),
 			}
 
@@ -122,5 +167,7 @@ func (d *depGraph) ParseConfigGroup(configGroups []kotsv1beta1.ConfigGroup) erro
 		}
 	}
 
+	d.resolveCertKeys()
+
 	return nil
 }


Test patch:
diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml
index 4c01affda..fc7c82e5d 100644
--- a/.github/workflows/build-test.yaml
+++ b/.github/workflows/build-test.yaml
@@ -102,10 +102,10 @@ jobs:
 
       - run: chmod +x bin/kots
 
+
       - uses: actions/setup-node@v1
         with:
           node-version: '10.x'
-
       - name: setup testIM
         run: npm i -g @testim/testim-cli
         shell: bash
diff --git a/pkg/template/depgraph_test.go b/pkg/template/depgraph_test.go
index 13ea204a8..7e66771b8 100644
--- a/pkg/template/depgraph_test.go
+++ b/pkg/template/depgraph_test.go
@@ -12,6 +12,8 @@ import (
 
 type depGraphTestCase struct {
 	dependencies   map[string][]string
+	testCerts      map[string][]string
+	testKeys       map[string][]string
 	resolveOrder   []string
 	expectError    bool   //expect an error fetching head nodes
 	expectNotFound string //expect this dependency not to be part of the head nodes
@@ -113,6 +115,41 @@ func TestDepGraph(t *testing.T) {
 			expectError:  true,
 			name:         "does_not_exist",
 		},
+		{
+			dependencies: map[string][]string{
+				"alpha":   {},
+				"bravo":   {"alpha"},
+				"charlie": {"bravo"},
+				"delta":   {"alpha", "charlie"},
+				"echo":    {},
+			},
+			testCerts: map[string][]string{
+				"echo": {"certA"},
+			},
+			testKeys: map[string][]string{
+				"delta": {"certA"},
+			},
+			resolveOrder: []string{"alpha", "bravo", "charlie", "echo", "delta"},
+			name:         "basic_certs",
+		},
+		{
+			dependencies: map[string][]string{
+				"alpha":   {},
+				"bravo":   {"alpha"},
+				"charlie": {"bravo"},
+				"delta":   {"alpha", "charlie"},
+				"echo":    {},
+			},
+			testCerts: map[string][]string{
+				"echo": {"certA"},
+			},
+			testKeys: map[string][]string{
+				"delta": {"certA"},
+			},
+			resolveOrder:   []string{"alpha", "bravo", "charlie", "delta", "echo"},
+			name:           "basic_certs_original_order",
+			expectNotFound: "delta",
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
@@ -126,6 +163,18 @@ func TestDepGraph(t *testing.T) {
 					graph.AddDep(source, dep)
 				}
 			}
+			for source, certNames := range test.testCerts {
+				for _, certName := range certNames {
+					graph.AddCert(source, certName)
+				}
+			}
+			for source, keys := range test.testKeys {
+				for _, key := range keys {
+					graph.AddKey(source, key)
+				}
+			}
+			graph.resolveCertKeys()
+
 			runGraphTests(t, test, graph)
 		})
 
@@ -135,7 +184,7 @@ func TestDepGraph(t *testing.T) {
 
 			graph := depGraph{}
 
-			groups := buildTestConfigGroups(test.dependencies, "templateStringStart", "templateStringEnd", true)
+			groups := buildTestConfigGroups(test.dependencies, test.testCerts, test.testKeys, "templateStringStart", "templateStringEnd", true)
 
 			err := graph.ParseConfigGroup(groups)
 			require.NoError(t, err)
@@ -146,7 +195,7 @@ func TestDepGraph(t *testing.T) {
 }
 
 // this makes sure that we test with each of the configOption types, in both Value and Default
-func buildTestConfigGroups(dependencies map[string][]string, prefix string, suffix string, rotate bool) []kotsv1beta1.ConfigGroup {
+func buildTestConfigGroups(dependencies, certs, keys map[string][]string, prefix string, suffix string, rotate bool) []kotsv1beta1.ConfigGroup {
 	group := kotsv1beta1.ConfigGroup{}
 	group.Items = make([]kotsv1beta1.ConfigItem, 0)
 	counter := 0
@@ -156,7 +205,7 @@ func buildTestConfigGroups(dependencies map[string][]string, prefix string, suff
 		"{{repl ConfigOptionIndex \"%s\" }}",
 		"{{repl ConfigOptionData \"%s\" }}",
 		"repl{{ ConfigOptionEquals \"%s\" \"abc\" }}",
-		"{{repl ConfigOptionNotEquals \"%s\" \"xyz\" }}{{repl DoesNotExistFunc }}",
+		"{{repl ConfigOptionNotEquals \"%s\" \"xyz\" }}",
 	}
 
 	if !rotate {
@@ -166,12 +215,28 @@ func buildTestConfigGroups(dependencies map[string][]string, prefix string, suff
 		}
 	}
 
+	totalDepItems := 0
+
 	for source, deps := range dependencies {
 		newItem := kotsv1beta1.ConfigItem{Type: "text", Name: source}
 		depString := prefix
-		for i, dep := range deps {
-			depString += fmt.Sprintf(templateFuncs[i%len(templateFuncs)], dep)
+		for _, dep := range deps {
+			depString += fmt.Sprintf(templateFuncs[totalDepItems%len(templateFuncs)], dep)
+			totalDepItems++
+		}
+
+		if certNames, ok := certs[source]; ok {
+			for _, certName := range certNames {
+				depString += fmt.Sprintf("{{repl TLSCert \"%s\" }}", certName)
+			}
 		}
+
+		if certNames, ok := keys[source]; ok {
+			for _, certName := range certNames {
+				depString += fmt.Sprintf("{{repl TLSKey \"%s\" }}", certName)
+			}
+		}
+
 		depString += suffix
 
 		if counter%2 == 0 {