Skip to content

Commit

Permalink
Compute digest of configmap and secret from its data (#5115)
Browse files Browse the repository at this point in the history
  • Loading branch information
lfabriko authored Feb 12, 2024
1 parent e0ae4ad commit 25f5993
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 6 deletions.
39 changes: 39 additions & 0 deletions e2e/common/config/config_reload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ limitations under the License.
package config

import (
"strconv"
"testing"

. "github.com/onsi/gomega"
Expand Down Expand Up @@ -115,3 +116,41 @@ func TestSecretHotReload(t *testing.T) {

Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
}

func TestConfigmapWithOwnerRefHotReloadDefault(t *testing.T) {
CheckConfigmapWithOwnerRef(t, false)
}

func TestConfigmapWithOwnerRefHotReload(t *testing.T) {
CheckConfigmapWithOwnerRef(t, true)
}

func CheckConfigmapWithOwnerRef(t *testing.T, hotreload bool) {
RegisterTestingT(t)
name := RandomizedSuffixName("config-configmap-route")
cmName := RandomizedSuffixName("my-hot-cm-")
Expect(KamelRunWithID(operatorID, ns, "./files/config-configmap-route.groovy",
"--config",
"configmap:"+cmName,
"--name",
name,
"-t",
"mount.hot-reload="+strconv.FormatBool(hotreload),
).Execute()).To(Succeed())

Eventually(IntegrationPhase(ns, name), TestTimeoutLong).Should(Equal(v1.IntegrationPhaseError))
var cmData = make(map[string]string)
cmData["my-configmap-key"] = "my configmap content"
CreatePlainTextConfigmapWithOwnerRefWithLabels(ns, cmName, cmData, name, Integration(ns, name)().UID, map[string]string{"camel.apache.org/integration": "test"})
Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
Eventually(IntegrationLogs(ns, name), TestTimeoutLong).Should(ContainSubstring("my configmap content"))
cmData["my-configmap-key"] = "my configmap content updated"
UpdatePlainTextConfigmapWithLabels(ns, cmName, cmData, map[string]string{"camel.apache.org/integration": "test"})
if hotreload {
Eventually(IntegrationLogs(ns, name), TestTimeoutLong).Should(ContainSubstring("my configmap content updated"))
} else {
Eventually(IntegrationLogs(ns, name), TestTimeoutLong).Should(Not(ContainSubstring("my configmap content updated")))
}
Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
DeleteConfigmap(ns, cmName)
}
25 changes: 25 additions & 0 deletions e2e/support/test_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,31 @@ func CreatePlainTextConfigmapWithLabels(ns string, name string, data map[string]
return TestClient().Create(TestContext, &cm)
}

func CreatePlainTextConfigmapWithOwnerRefWithLabels(ns string, name string, data map[string]string, orname string, uid types.UID, labels map[string]string) error {
cm := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: corev1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
OwnerReferences: []metav1.OwnerReference{{
APIVersion: v1.SchemeGroupVersion.String(),
Kind: "Integration",
Name: orname,
UID: uid,
Controller: pointer.Bool(true),
BlockOwnerDeletion: pointer.Bool(true),
},
},
Labels: labels,
},
Data: data,
}
return TestClient().Create(TestContext, &cm)
}

func UpdatePlainTextConfigmap(ns string, name string, data map[string]string) error {
return UpdatePlainTextConfigmapWithLabels(ns, name, data, nil)
}
Expand Down
61 changes: 55 additions & 6 deletions pkg/util/digest/digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"hash"
"io"
"path/filepath"
Expand All @@ -37,6 +36,8 @@ import (
"github.com/apache/camel-k/v2/pkg/util/defaults"
"github.com/apache/camel-k/v2/pkg/util/dsl"
corev1 "k8s.io/api/core/v1"

"fmt"
)

const (
Expand Down Expand Up @@ -136,15 +137,63 @@ func ComputeForIntegration(integration *v1.Integration, configmaps []*corev1.Con
}
}

// Configmap and secret content
// Configmap content
for _, cm := range configmaps {
if _, err := hash.Write([]byte(cm.String())); err != nil {
return "", err
if cm != nil {
// name, ns
if _, err := hash.Write([]byte(fmt.Sprintf("%s/%s", cm.Name, cm.Namespace))); err != nil {
return "", err
}
// Data with sorted keys
if cm.Data != nil {
// sort keys
keys := make([]string, 0, len(cm.Data))
for k := range cm.Data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if _, err := hash.Write([]byte(fmt.Sprintf("%s=%v,", k, cm.Data[k]))); err != nil {
return "", err
}
}
}
// BinaryData with sorted keys
if cm.BinaryData != nil {
keys := make([]string, 0, len(cm.BinaryData))
for k := range cm.BinaryData {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if _, err := hash.Write([]byte(fmt.Sprintf("%s=%v,", k, cm.BinaryData[k]))); err != nil {
return "", err
}
}
}
}
}

// Secret content
for _, s := range secrets {
if _, err := hash.Write([]byte(s.String())); err != nil {
return "", err
if s != nil {
// name, ns
if _, err := hash.Write([]byte(fmt.Sprintf("%s/%s", s.Name, s.Namespace))); err != nil {
return "", err
}
// Data with sorted keys
if s.Data != nil {
keys := make([]string, 0, len(s.Data))
for k := range s.Data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if _, err := hash.Write([]byte(fmt.Sprintf("%s=%v,", k, s.Data[k]))); err != nil {
return "", err
}
}
}
}
}

Expand Down
78 changes: 78 additions & 0 deletions pkg/util/digest/digest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import (
"os"
"testing"

"github.com/stretchr/testify/require"

"github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/pointer"

v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -60,3 +66,75 @@ func TestDigestSHA1FromTempFile(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "OXPdxTeLf5rqnsqvTi0CgmWoN/0=", sha1)
}

func TestDigestUsesConfigmap(t *testing.T) {
it := v1.Integration{
Spec: v1.IntegrationSpec{
Traits: v1.Traits{
Mount: &trait.MountTrait{
Configs: []string{"configmap:cm"},
HotReload: pointer.Bool(true),
},
},
},
}

digest1, err := ComputeForIntegration(&it, nil, nil)
require.NoError(t, err)

cm := corev1.ConfigMap{
Data: map[string]string{
"foo": "bar",
},
}
cms := []*corev1.ConfigMap{&cm}

digest2, err := ComputeForIntegration(&it, cms, nil)
require.NoError(t, err)
assert.NotEqual(t, digest1, digest2)

cm.Data["foo"] = "bar updated"
digest3, err := ComputeForIntegration(&it, cms, nil)
require.NoError(t, err)
assert.NotEqual(t, digest2, digest3)

digest4, err := ComputeForIntegration(&it, cms, nil)
require.NoError(t, err)
assert.Equal(t, digest4, digest3)
}

func TestDigestUsesSecret(t *testing.T) {
it := v1.Integration{
Spec: v1.IntegrationSpec{
Traits: v1.Traits{
Mount: &trait.MountTrait{
Configs: []string{"secret:mysec"},
HotReload: pointer.Bool(true),
},
},
},
}

digest1, err := ComputeForIntegration(&it, nil, nil)
require.NoError(t, err)

sec := corev1.Secret{
Data: map[string][]byte{
"foo": []byte("bar"),
},
StringData: map[string]string{
"foo2": "bar2",
},
}

secrets := []*corev1.Secret{&sec}

digest2, err := ComputeForIntegration(&it, nil, secrets)
require.NoError(t, err)
assert.NotEqual(t, digest1, digest2)

sec.Data["foo"] = []byte("bar updated")
digest3, err := ComputeForIntegration(&it, nil, secrets)
require.NoError(t, err)
assert.NotEqual(t, digest2, digest3)
}

0 comments on commit 25f5993

Please sign in to comment.