Repository: replicatedhq/kots
Pull request #235 (2020-01-02 18:04:38)

Add support for optional resources


Patch:
diff --git a/pkg/base/base.go b/pkg/base/base.go
index f4fbe2fb3..05bdb46de 100644
--- a/pkg/base/base.go
+++ b/pkg/base/base.go
@@ -3,7 +3,9 @@ package base
 import (
 	"crypto/sha256"
 	"fmt"
+	"strconv"
 
+	"github.com/pkg/errors"
 	kotsscheme "github.com/replicatedhq/kots/kotskinds/client/kotsclientset/scheme"
 	troubleshootscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
 	"gopkg.in/yaml.v2"
@@ -20,18 +22,14 @@ type BaseFile struct {
 }
 
 type OverlySimpleGVK struct {
-	APIVersion string `yaml:"apiVersion"`
-	Kind       string `yaml:"kind"`
-}
-
-type OverlySimpleGVKWithName struct {
 	APIVersion string               `yaml:"apiVersion"`
 	Kind       string               `yaml:"kind"`
 	Metadata   OverlySimpleMetadata `yaml:"metadata"`
 }
 
 type OverlySimpleMetadata struct {
-	Name string `yaml:"name"`
+	Name        string                 `yaml:"name"`
+	Annotations map[string]interface{} `json:"annotations"`
 }
 
 func init() {
@@ -40,7 +38,7 @@ func init() {
 }
 
 func GetGVKWithNameHash(content []byte) []byte {
-	o := OverlySimpleGVKWithName{}
+	o := OverlySimpleGVK{}
 
 	if err := yaml.Unmarshal(content, &o); err != nil {
 		return nil
@@ -53,31 +51,66 @@ func GetGVKWithNameHash(content []byte) []byte {
 
 // ShouldBeIncludedInBaseKustomization attempts to determine if this is a valid Kubernetes manifest.
 // It accomplished this by trying to unmarshal the YAML and looking for a apiVersion and Kind
-func (f BaseFile) ShouldBeIncludedInBaseKustomization(excludeKotsKinds bool) bool {
+func (f BaseFile) ShouldBeIncludedInBaseKustomization(excludeKotsKinds bool) (bool, error) {
 	o := OverlySimpleGVK{}
 
 	if err := yaml.Unmarshal(f.Content, &o); err != nil {
-		return false
+		return false, nil
 	}
 
 	if o.APIVersion == "" || o.Kind == "" {
-		return false
+		return false, nil
 	}
 
+	if o.Metadata.Annotations != nil {
+		if val, ok := o.Metadata.Annotations["kots.io/exclude"]; ok {
+			if boolVal, ok := val.(bool); ok {
+				return !boolVal, nil
+			}
+
+			if strVal, ok := val.(string); ok {
+				boolVal, err := strconv.ParseBool(strVal)
+				if err != nil {
+					return true, errors.Errorf("unable to parse %s as bool in exclude annotation of object %s, kind %s/%s", strVal, o.Metadata.Name, o.APIVersion, o.Kind)
+				}
+
+				return !boolVal, nil
+			}
+
+			return true, errors.Errorf("unexpected type in exclude annotation of %s/%s: %T", o.APIVersion, o.Metadata.Name, val)
+		}
+
+		if val, ok := o.Metadata.Annotations["kots.io/when"]; ok {
+			if boolVal, ok := val.(bool); ok {
+				return boolVal, nil
+			}
+
+			if strVal, ok := val.(string); ok {
+				boolVal, err := strconv.ParseBool(strVal)
+				if err != nil {
+					return true, errors.Errorf("unable to parse %s as bool in wen annotation of object %s, kind %s/%s", strVal, o.Metadata.Name, o.APIVersion, o.Kind)
+				}
+
+				return boolVal, nil
+			}
+
+			return true, errors.Errorf("unexpected type in when annotation of %s/%s: %T", o.APIVersion, o.Metadata.Name, val)
+		}
+	}
 	if excludeKotsKinds {
 		if o.APIVersion == "kots.io/v1beta1" {
-			return false
+			return false, nil
 		}
 
 		if o.APIVersion == "troubleshoot.replicated.com/v1beta1" {
-			return false
+			return false, nil
 		}
 
 		// In addition to kotskinds, we exclude the application crd for now
 		if o.APIVersion == "app.k8s.io/v1beta1" {
-			return false
+			return false, nil
 		}
 	}
 
-	return true
+	return true, nil
 }
diff --git a/pkg/base/replicated.go b/pkg/base/replicated.go
index 93fbfdd3b..3f9b0ce0a 100644
--- a/pkg/base/replicated.go
+++ b/pkg/base/replicated.go
@@ -158,7 +158,12 @@ func renderReplicated(u *upstreamtypes.Upstream, renderOptions *RenderOptions) (
 
 		for _, helmBaseFile := range helmBase.Files {
 			// this is a little bit of an abuse of the next function
-			if !helmBaseFile.ShouldBeIncludedInBaseKustomization(false) {
+			include, err := helmBaseFile.ShouldBeIncludedInBaseKustomization(false)
+			if err != nil {
+				return nil, errors.Wrap(err, "failed to determine if file should be included in base")
+			}
+
+			if !include {
 				continue
 			}
 
diff --git a/pkg/base/write.go b/pkg/base/write.go
index 7482dc23c..0c44f16cd 100644
--- a/pkg/base/write.go
+++ b/pkg/base/write.go
@@ -38,7 +38,10 @@ func (b *Base) WriteBase(options WriteOptions) error {
 		}
 	}
 
-	resources, patches := deduplicateOnContent(b.Files, options.ExcludeKotsKinds)
+	resources, patches, err := deduplicateOnContent(b.Files, options.ExcludeKotsKinds)
+	if err != nil {
+		return errors.Wrap(err, "failed to deduplicate content")
+	}
 
 	kustomizeResources := []string{}
 	kustomizePatches := []kustomizetypes.PatchStrategicMerge{}
@@ -91,14 +94,17 @@ func (b *Base) WriteBase(options WriteOptions) error {
 	return nil
 }
 
-func deduplicateOnContent(files []BaseFile, excludeKotsKinds bool) ([]BaseFile, []BaseFile) {
+func deduplicateOnContent(files []BaseFile, excludeKotsKinds bool) ([]BaseFile, []BaseFile, error) {
 	resources := []BaseFile{}
 	patches := []BaseFile{}
 
 	foundGVKNames := [][]byte{}
 
 	for _, file := range files {
-		writeToKustomization := file.ShouldBeIncludedInBaseKustomization(excludeKotsKinds)
+		writeToKustomization, err := file.ShouldBeIncludedInBaseKustomization(excludeKotsKinds)
+		if err != nil {
+			return nil, nil, errors.Wrap(err, "failed to check if file should be included")
+		}
 
 		if !writeToKustomization {
 			continue
@@ -124,7 +130,7 @@ func deduplicateOnContent(files []BaseFile, excludeKotsKinds bool) ([]BaseFile,
 
 	}
 
-	return resources, patches
+	return resources, patches, nil
 }
 
 func (b *Base) GetOverlaysDir(options WriteOptions) string {


Test patch:
diff --git a/pkg/base/base_test.go b/pkg/base/base_test.go
index 83b6ae9a5..2e098d1e3 100644
--- a/pkg/base/base_test.go
+++ b/pkg/base/base_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestShouldBeIncludedInBaseKustomization(t *testing.T) {
@@ -122,16 +123,174 @@ connection:
             name: rds-postgres`),
 			expected: true,
 		},
+		{
+			name: "annotation of excluded true",
+			path: "config.yaml",
+			content: []byte(`apiVersion: kots.io/v1beta1
+kind: Config
+metadata:
+  name: sentry-enterprise
+  annotations:
+    kots.io/exclude: true
+spec:
+  groups:
+    - name: database
+      title: Database
+      description: Database Options
+      items:
+        - name: postgres_type
+          type: select_one
+          title: Postgres
+          default: embedded_postgres
+          items:
+            - name: embedded_postgres
+              title: Embedded Postgres
+            - name: external_postgres
+              title: External Postgres
+        - name: embedded_postgres_password
+          type: text
+          read_only: true
+          value: ""`),
+			excludeKotsKinds: true,
+			expected:         false,
+		},
+		{
+			name: "annotation of excluded false as string",
+			path: "config.yaml",
+			content: []byte(`apiVersion: kots.io/v1beta1
+kind: Config
+metadata:
+  name: sentry-enterprise
+  annotations:
+    kots.io/exclude: "false"
+spec:
+  groups:
+    - name: database
+      title: Database
+      description: Database Options
+      items:
+        - name: postgres_type
+          type: select_one
+          title: Postgres
+          default: embedded_postgres
+          items:
+            - name: embedded_postgres
+              title: Embedded Postgres
+            - name: external_postgres
+              title: External Postgres
+        - name: embedded_postgres_password
+          type: text
+          read_only: true
+          value: ""`),
+			excludeKotsKinds: true,
+			expected:         true,
+		},
+		{
+			name: "annotation of excluded false",
+			path: "config.yaml",
+			content: []byte(`apiVersion: kots.io/v1beta1
+kind: Config
+metadata:
+  name: sentry-enterprise
+  annotations:
+    kots.io/exclude: false
+spec:
+  groups:
+    - name: database
+      title: Database
+      description: Database Options
+      items:
+        - name: postgres_type
+          type: select_one
+          title: Postgres
+          default: embedded_postgres
+          items:
+            - name: embedded_postgres
+              title: Embedded Postgres
+            - name: external_postgres
+              title: External Postgres
+        - name: embedded_postgres_password
+          type: text
+          read_only: true
+          value: ""`),
+			excludeKotsKinds: true,
+			expected:         true,
+		},
+		{
+			name: "annotation of when false",
+			path: "config.yaml",
+			content: []byte(`apiVersion: kots.io/v1beta1
+kind: Config
+metadata:
+  name: sentry-enterprise
+  annotations:
+    kots.io/when: false
+spec:
+  groups:
+    - name: database
+      title: Database
+      description: Database Options
+      items:
+        - name: postgres_type
+          type: select_one
+          title: Postgres
+          default: embedded_postgres
+          items:
+            - name: embedded_postgres
+              title: Embedded Postgres
+            - name: external_postgres
+              title: External Postgres
+        - name: embedded_postgres_password
+          type: text
+          read_only: true
+          value: ""`),
+			excludeKotsKinds: true,
+			expected:         false,
+		},
+		{
+			name: "annotation of when true",
+			path: "config.yaml",
+			content: []byte(`apiVersion: kots.io/v1beta1
+kind: Config
+metadata:
+  name: sentry-enterprise
+  annotations:
+    kots.io/when: true
+spec:
+  groups:
+    - name: database
+      title: Database
+      description: Database Options
+      items:
+        - name: postgres_type
+          type: select_one
+          title: Postgres
+          default: embedded_postgres
+          items:
+            - name: embedded_postgres
+              title: Embedded Postgres
+            - name: external_postgres
+              title: External Postgres
+        - name: embedded_postgres_password
+          type: text
+          read_only: true
+          value: ""`),
+			excludeKotsKinds: true,
+			expected:         true,
+		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			req := require.New(t)
+
 			b := BaseFile{
 				Path:    test.path,
 				Content: test.content,
 			}
 
-			actual := b.ShouldBeIncludedInBaseKustomization(test.excludeKotsKinds)
+			actual, err := b.ShouldBeIncludedInBaseKustomization(test.excludeKotsKinds)
+			req.NoError(err)
 			assert.Equal(t, test.expected, actual)
 		})
 	}
diff --git a/pkg/base/write_test.go b/pkg/base/write_test.go
index e45405489..3e6131a63 100644
--- a/pkg/base/write_test.go
+++ b/pkg/base/write_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 var (
@@ -132,7 +133,11 @@ func Test_DeduplicateOnContent(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			actualResources, actualPatches := deduplicateOnContent(test.files, test.excludeKotsKinds)
+			req := require.New(t)
+
+			actualResources, actualPatches, err := deduplicateOnContent(test.files, test.excludeKotsKinds)
+			req.NoError(err)
+
 			assert.ElementsMatch(t, test.expectedResources, actualResources)
 			assert.ElementsMatch(t, test.expectedPatches, actualPatches)
 		})