Repository: operator-framework/operator-sdk
Pull request #5039 (2021-07-02 15:38:09)

Add the missing test suite for the metrics
<!--

Welcome to the Operator SDK! Before contributing, make sure to:

- Read the contributing guidelines https://github.com/operator-framework/operator-sdk/blob/master/CONTRIBUTING.MD
- Rebase your branch on the latest upstream master
- Link any relevant issues, PR's, or documentation
- When fixing an issue, add "Closes #<ISSUE_NUMBER>"
- Follow the below checklist if making a user-facing change 

-->

**Description of the change:**
As described in #5040


**Motivation for the change:**
Fixes: #5040

**Checklist**

If the pull request includes user-facing changes, extra documentation is required:
- [ ] Add a new changelog fragment in `changelog/fragments` (see [`changelog/fragments/00-template.yaml`](https://github.com/operator-framework/operator-sdk/tree/master/changelog/fragments/00-template.yaml))
- [ ] Add or update relevant sections of the docs website in [`website/content/en/docs`](https://github.com/operator-framework/operator-sdk/tree/master/website/content/en/docs)



Patch:
diff --git a/changelog/fragments/helm-deepequals.yaml b/changelog/fragments/helm-deepequals.yaml
new file mode 100644
index 00000000..0a331945
--- /dev/null
+++ b/changelog/fragments/helm-deepequals.yaml
@@ -0,0 +1,4 @@
+entries:
+  - description: >
+      For Helm-based operators, fixed release equality comparison such that number values are compared and not their types to avoid unnecessary reconciliations.
+    kind: bugfix
\ No newline at end of file
diff --git a/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3.go b/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3.go
index fd19c6cf..2a5a41b8 100644
--- a/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3.go
+++ b/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3.go
@@ -66,7 +66,12 @@ func convertConfig3AlphaTo3(cfgBytes []byte) (_ []byte, err error) {
 	isMultigroup := hasMultigroup && multigroupObj.(bool)
 
 	if obj, hasRes := cfgObj["resources"]; hasRes {
-		domain := cfgObj["domain"].(string)
+		// Default to empty domain if no domain information is found.
+		domain := ""
+		if domainObj, ok := cfgObj["domain"]; ok {
+			domain = domainObj.(string)
+		}
+
 		resObjs := obj.([]interface{})
 		resources := make([]resource.Resource, len(resObjs))
 
diff --git a/internal/helm/release/manager.go b/internal/helm/release/manager.go
index 7ee19a0b..ffeb40b2 100644
--- a/internal/helm/release/manager.go
+++ b/internal/helm/release/manager.go
@@ -20,6 +20,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"reflect"
 	"strings"
 
 	jsonpatch "gomodules.xyz/jsonpatch/v3"
@@ -126,8 +127,10 @@ func (m *manager) Sync(ctx context.Context) error {
 	m.deployedRelease = deployedRelease
 	m.isInstalled = true
 
-	m.isUpgradeRequired = m.isUpgrade(deployedRelease)
-
+	m.isUpgradeRequired, err = m.isUpgrade(deployedRelease)
+	if err != nil {
+		return fmt.Errorf("failed to get upgrade status: %w", err)
+	}
 	return nil
 }
 
@@ -135,18 +138,41 @@ func notFoundErr(err error) bool {
 	return err != nil && strings.Contains(err.Error(), "not found")
 }
 
-func (m manager) isUpgrade(deployedRelease *rpb.Release) bool {
+// This is caused by the different logic of loading from local and loading from secret
+// For example, the Raw field, which has the tag `json:"-"`, causes the Unmarshal to be lost when it into Release
+// We need to make them follow the JSON tag
+// see: https://github.com/helm/helm/blob/cf0c6fed519d48101cd69ce01a355125215ee46f/pkg/storage/driver/util.go#L81
+func equalJSONStruct(a, b interface{}) (bool, error) {
+	if reflect.ValueOf(a).IsNil() || reflect.ValueOf(b).IsNil() {
+		return apiequality.Semantic.DeepEqual(a, b), nil
+	}
+
+	aBuf, bBuf := &bytes.Buffer{}, &bytes.Buffer{}
+	err := json.NewEncoder(aBuf).Encode(a)
+	if err != nil {
+		return false, err
+	}
+	err = json.NewEncoder(bBuf).Encode(b)
+	return aBuf.String() == bBuf.String(), err
+}
+
+func (m manager) isUpgrade(deployedRelease *rpb.Release) (bool, error) {
 	if deployedRelease == nil {
-		return false
+		return false, nil
 	}
 
 	// Judging whether to skip updates
 	skip := m.namespace == deployedRelease.Namespace
 	skip = skip && m.releaseName == deployedRelease.Name
-	skip = skip && apiequality.Semantic.DeepEqual(m.chart, deployedRelease.Chart)
-	skip = skip && apiequality.Semantic.DeepEqual(m.values, deployedRelease.Config)
 
-	return !skip
+	ok, err := equalJSONStruct(m.chart, deployedRelease.Chart)
+	if err != nil {
+		return false, err
+	}
+	skip = skip && ok
+
+	ok, err = equalJSONStruct(m.values, deployedRelease.Config)
+	return !(skip && ok), err
 }
 
 func (m manager) getDeployedRelease() (*rpb.Release, error) {


Test patch:
diff --git a/internal/annotations/metrics/metrics_suite_test.go b/internal/annotations/metrics/metrics_suite_test.go
new file mode 100644
index 00000000..37ce7c31
--- /dev/null
+++ b/internal/annotations/metrics/metrics_suite_test.go
@@ -0,0 +1,27 @@
+// Copyright 2021 The Operator-SDK Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package metrics
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+func TestMetrics(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Metrics Suite")
+}
diff --git a/internal/annotations/metrics/metrics_test.go b/internal/annotations/metrics/metrics_test.go
index 13c5537d..1ee9341d 100644
--- a/internal/annotations/metrics/metrics_test.go
+++ b/internal/annotations/metrics/metrics_test.go
@@ -36,6 +36,10 @@ var _ = Describe("SDK Label helper functions", func() {
 			output := parseVersion(version)
 			Expect(output).To(Equal("v0.18.0+git"))
 		})
-
+		It("should return unknown", func() {
+			version := "noneSemanticVersion"
+			output := parseVersion(version)
+			Expect(output).To(Equal("unknown"))
+		})
 	})
 })
diff --git a/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3_test.go b/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3_test.go
index b8794ba5..00dd5de0 100644
--- a/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3_test.go
+++ b/internal/cmd/operator-sdk/alpha/config3alphato3/convert_config_3-alpha_to_3_test.go
@@ -36,6 +36,7 @@ var _ = Describe("ConvertConfig3AlphaTo3", func() {
 		Entry("no resources", noResourcesConfig, noResourcesConfigExp),
 		Entry("basic", basicConfig, basicConfigExp),
 		Entry("complex", complexConfig, complexConfigExp),
+		Entry("no domain", noDomainConfig, noDomainConfigExp),
 	)
 })
 
@@ -68,6 +69,30 @@ version: "3"
 `
 )
 
+const (
+	noDomainConfig = `layout: ansible.sdk.operatorframework.io/v1
+projectName: memcached-operator
+resources:
+- crdVersion: v1
+  group: cache
+  kind: Memcached
+  version: v1alpha1
+version: 3-alpha`
+	noDomainConfigExp = `layout: ansible.sdk.operatorframework.io/v1
+projectName: memcached-operator
+resources:
+- api:
+    crdVersion: v1
+    # TODO(user): Uncomment the below line if this resource's CRD is namespace scoped, else delete it.
+    # namespaced: true
+  # TODO(user): Uncomment the below line if this resource implements a controller, else delete it.
+  # controller: true
+  group: cache
+  kind: Memcached
+  version: v1alpha1
+version: "3"`
+)
+
 const (
 	noResourcesConfig = `domain: example.com
 layout: ansible.sdk.operatorframework.io/v1
diff --git a/internal/helm/release/manager_test.go b/internal/helm/release/manager_test.go
index f930374e..70c5e5b5 100644
--- a/internal/helm/release/manager_test.go
+++ b/internal/helm/release/manager_test.go
@@ -15,6 +15,8 @@
 package release
 
 import (
+	"bytes"
+	"encoding/json"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -247,11 +249,20 @@ func TestManagerisUpgrade(t *testing.T) {
 			name:            "different values",
 			releaseName:     "deployed",
 			releaseNs:       "deployed-ns",
-			values:          map[string]interface{}{"key": "1"},
+			values:          map[string]interface{}{"key": "1", "int": int32(1)},
 			chart:           newTestChart(t, "./testdata/simple"),
-			deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": ""}, "deployed", "deployed-ns"),
+			deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{"key": "", "int": int64(1)}, "deployed", "deployed-ns"),
 			want:            true,
 		},
+		{
+			name:            "nil values",
+			releaseName:     "deployed",
+			releaseNs:       "deployed-ns",
+			values:          nil,
+			chart:           newTestChart(t, "./testdata/simple"),
+			deployedRelease: newTestRelease(newTestChart(t, "./testdata/simple"), map[string]interface{}{}, "deployed", "deployed-ns"),
+			want:            false,
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
@@ -261,8 +272,9 @@ func TestManagerisUpgrade(t *testing.T) {
 				values:      test.values,
 				chart:       test.chart,
 			}
-			isUpgrade := m.isUpgrade(test.deployedRelease)
+			isUpgrade, err := m.isUpgrade(test.deployedRelease)
 			assert.Equal(t, test.want, isUpgrade)
+			assert.Equal(t, nil, err)
 		})
 	}
 }
@@ -273,13 +285,16 @@ func newTestChart(t *testing.T, path string) *cpb.Chart {
 	return chart
 }
 
-func newTestRelease(chart *cpb.Chart, values map[string]interface{}, name, namespace string) *rpb.Release {
+func newTestRelease(chart *cpb.Chart, values map[string]interface{}, name, namespace string) *rpb.Release { // nolint: unparam
 	release := rpb.Mock(&rpb.MockReleaseOptions{
 		Name:      name,
 		Namespace: namespace,
-		Chart:     chart,
 		Version:   1,
 	})
+
+	buffer := &bytes.Buffer{}
+	_ = json.NewEncoder(buffer).Encode(chart)
+	_ = json.NewDecoder(buffer).Decode(release.Chart)
 	release.Config = values
 	return release
 }
diff --git a/website/content/en/docs/olm-integration/testing-deployment.md b/website/content/en/docs/olm-integration/testing-deployment.md
index 3248dcb4..d69eab15 100644
--- a/website/content/en/docs/olm-integration/testing-deployment.md
+++ b/website/content/en/docs/olm-integration/testing-deployment.md
@@ -176,3 +176,4 @@ Let's look at the anatomy of the `cleanup` configuration model:
 [cli-olm-status]:/docs/cli/operator-sdk_olm_status
 [creating-bundles]:/docs/olm-integration/quickstart-bundle/#creating-a-bundle
 [add-sa-secret]:https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account
+[image-reg-config]:/docs/olm-integration/cli-overview#private-bundle-and-catalog-image-registries