mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
fix(externalsecret): infinite reconcile loop with Merge secret (#2525)
* fix(externalsecret): infinite reconcile loop with Merge secret Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * code review Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * lint Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * add unit tests Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * lint Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> * Use objectHash instead of value Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com> --------- Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
This commit is contained in:
parent
b50415edf0
commit
21928a45b9
5 changed files with 62 additions and 20 deletions
|
@ -7,6 +7,7 @@
|
||||||
- [DaangnPay](https://www.daangnpay.com/)
|
- [DaangnPay](https://www.daangnpay.com/)
|
||||||
- [Epidemic Sound](https://www.epidemicsound.com/)
|
- [Epidemic Sound](https://www.epidemicsound.com/)
|
||||||
- [Form3](https://www.form3.tech/)
|
- [Form3](https://www.form3.tech/)
|
||||||
|
- [GoTo](https://www.goto.com/)
|
||||||
- [Heureka Group](https://heureka.group)
|
- [Heureka Group](https://heureka.group)
|
||||||
- [K8S Website Infra](https://k8s.io/)
|
- [K8S Website Infra](https://k8s.io/)
|
||||||
- [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
|
- [Mercedes-Benz Tech Innovation](https://www.mercedes-benz-techinnovation.com/)
|
||||||
|
|
|
@ -77,7 +77,7 @@ export IMAGE=$(make docker.imagename)
|
||||||
make docker.build
|
make docker.build
|
||||||
|
|
||||||
# Load docker image into local kind cluster
|
# Load docker image into local kind cluster
|
||||||
kind load docker-image $IMAGE:$TAG -n external-secrets
|
kind load docker-image $IMAGE:$TAG --name external-secrets
|
||||||
|
|
||||||
# (Optional) Pull the image from GitHub Repo to copy into kind
|
# (Optional) Pull the image from GitHub Repo to copy into kind
|
||||||
# docker pull ghcr.io/external-secrets/external-secrets:v0.8.2
|
# docker pull ghcr.io/external-secrets/external-secrets:v0.8.2
|
||||||
|
|
|
@ -281,6 +281,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||||
secret.Labels[esv1beta1.LabelOwner] = lblValue
|
secret.Labels[esv1beta1.LabelOwner] = lblValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secret.Annotations[esv1beta1.AnnotationDataHash] = r.computeDataHashAnnotation(&existingSecret, secret)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,6 +601,18 @@ func isSecretValid(existingSecret v1.Secret) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// computeDataHashAnnotation generate a hash of the secret data combining the old key with the new keys to add or override.
|
||||||
|
func (r *Reconciler) computeDataHashAnnotation(existing, secret *v1.Secret) string {
|
||||||
|
data := make(map[string][]byte)
|
||||||
|
for k, v := range existing.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range secret.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return utils.ObjectHash(data)
|
||||||
|
}
|
||||||
|
|
||||||
// SetupWithManager returns a new controller builder that will be started by the provided Manager.
|
// SetupWithManager returns a new controller builder that will be started by the provided Manager.
|
||||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
|
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
|
||||||
r.recorder = mgr.GetEventRecorderFor("external-secrets")
|
r.recorder = mgr.GetEventRecorderFor("external-secrets")
|
||||||
|
|
|
@ -152,7 +152,6 @@ func (r *Reconciler) applyTemplate(ctx context.Context, es *esv1beta1.ExternalSe
|
||||||
// no template: copy data and return
|
// no template: copy data and return
|
||||||
if es.Spec.Target.Template == nil {
|
if es.Spec.Target.Template == nil {
|
||||||
secret.Data = dataMap
|
secret.Data = dataMap
|
||||||
secret.Annotations[esv1beta1.AnnotationDataHash] = utils.ObjectHash(secret.Data)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Merge Policy should merge secrets
|
// Merge Policy should merge secrets
|
||||||
|
@ -198,7 +197,6 @@ func (r *Reconciler) applyTemplate(ctx context.Context, es *esv1beta1.ExternalSe
|
||||||
if len(es.Spec.Target.Template.Data) == 0 && len(es.Spec.Target.Template.TemplateFrom) == 0 {
|
if len(es.Spec.Target.Template.Data) == 0 && len(es.Spec.Target.Template.TemplateFrom) == 0 {
|
||||||
secret.Data = dataMap
|
secret.Data = dataMap
|
||||||
}
|
}
|
||||||
secret.Annotations[esv1beta1.AnnotationDataHash] = utils.ObjectHash(secret.Data)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ import (
|
||||||
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
|
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
|
||||||
ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
|
ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
|
||||||
"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -77,6 +78,10 @@ type testCase struct {
|
||||||
type testTweaks func(*testCase)
|
type testTweaks func(*testCase)
|
||||||
|
|
||||||
var _ = Describe("Kind=secret existence logic", func() {
|
var _ = Describe("Kind=secret existence logic", func() {
|
||||||
|
validData := map[string][]byte{
|
||||||
|
"foo": []byte("value1"),
|
||||||
|
"bar": []byte("value2"),
|
||||||
|
}
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
Name string
|
Name string
|
||||||
Input v1.Secret
|
Input v1.Secret
|
||||||
|
@ -126,13 +131,10 @@ var _ = Describe("Kind=secret existence logic", func() {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
UID: "xxx",
|
UID: "xxx",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
esv1beta1.AnnotationDataHash: "caa0155759a6a9b3b6ada5a6883ee2bb",
|
esv1beta1.AnnotationDataHash: utils.ObjectHash(validData),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
Data: validData,
|
||||||
"foo": []byte("value1"),
|
|
||||||
"bar": []byte("value2"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ExpectedOutput: true,
|
ExpectedOutput: true,
|
||||||
},
|
},
|
||||||
|
@ -210,10 +212,14 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const secretVal = "some-value"
|
const (
|
||||||
const targetProp = "targetProperty"
|
secretVal = "some-value"
|
||||||
const remoteKey = "barz"
|
targetProp = "targetProperty"
|
||||||
const remoteProperty = "bang"
|
remoteKey = "barz"
|
||||||
|
remoteProperty = "bang"
|
||||||
|
existingKey = "pre-existing-key"
|
||||||
|
existingVal = "pre-existing-value"
|
||||||
|
)
|
||||||
|
|
||||||
makeDefaultTestcase := func() *testCase {
|
makeDefaultTestcase := func() *testCase {
|
||||||
return &testCase{
|
return &testCase{
|
||||||
|
@ -352,8 +358,6 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
// metadata.managedFields with the correct owner should be added to the secret
|
// metadata.managedFields with the correct owner should be added to the secret
|
||||||
mergeWithSecret := func(tc *testCase) {
|
mergeWithSecret := func(tc *testCase) {
|
||||||
const secretVal = "someValue"
|
const secretVal = "someValue"
|
||||||
const existingKey = "pre-existing-key"
|
|
||||||
existingVal := "pre-existing-value"
|
|
||||||
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
||||||
|
|
||||||
// create secret beforehand
|
// create secret beforehand
|
||||||
|
@ -393,8 +397,6 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
|
|
||||||
// should not update if no changes
|
// should not update if no changes
|
||||||
mergeWithSecretNoChange := func(tc *testCase) {
|
mergeWithSecretNoChange := func(tc *testCase) {
|
||||||
const existingKey = "pre-existing-key"
|
|
||||||
existingVal := "someValue"
|
|
||||||
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
||||||
|
|
||||||
// create secret beforehand
|
// create secret beforehand
|
||||||
|
@ -461,7 +463,6 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
const secretVal = "someValue"
|
const secretVal = "someValue"
|
||||||
// this should confict
|
// this should confict
|
||||||
const existingKey = targetProp
|
const existingKey = targetProp
|
||||||
existingVal := "pre-existing-value"
|
|
||||||
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
||||||
|
|
||||||
// create secret beforehand
|
// create secret beforehand
|
||||||
|
@ -1260,8 +1261,6 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
// if provider secret gets deleted only the managed field should get deleted
|
// if provider secret gets deleted only the managed field should get deleted
|
||||||
deleteSecretPolicyMerge := func(tc *testCase) {
|
deleteSecretPolicyMerge := func(tc *testCase) {
|
||||||
const secretVal = "someValue"
|
const secretVal = "someValue"
|
||||||
const existingKey = "some-existing-key"
|
|
||||||
existingVal := "some-existing-value"
|
|
||||||
tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second}
|
tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second}
|
||||||
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
||||||
tc.externalSecret.Spec.Target.DeletionPolicy = esv1beta1.DeletionPolicyMerge
|
tc.externalSecret.Spec.Target.DeletionPolicy = esv1beta1.DeletionPolicyMerge
|
||||||
|
@ -1736,7 +1735,36 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
const secretVal = "someValue"
|
const secretVal = "someValue"
|
||||||
fakeProvider.WithGetSecret([]byte(secretVal), nil)
|
fakeProvider.WithGetSecret([]byte(secretVal), nil)
|
||||||
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||||
Expect(secret.Annotations[esv1beta1.AnnotationDataHash]).To(Equal("9d30b95ca81e156f9454b5ef3bfcc6ee"))
|
expectedHash := utils.ObjectHash(map[string][]byte{
|
||||||
|
targetProp: []byte(secretVal),
|
||||||
|
})
|
||||||
|
Expect(secret.Annotations[esv1beta1.AnnotationDataHash]).To(Equal(expectedHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks that secret annotation has been written based on the all the data for merge keys
|
||||||
|
checkMergeSecretDataHashAnnotation := func(tc *testCase) {
|
||||||
|
const secretVal = "someValue"
|
||||||
|
tc.externalSecret.Spec.Target.CreationPolicy = esv1beta1.CreatePolicyMerge
|
||||||
|
|
||||||
|
// create secret beforehand
|
||||||
|
Expect(k8sClient.Create(context.Background(), &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: ExternalSecretTargetSecretName,
|
||||||
|
Namespace: ExternalSecretNamespace,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
existingKey: []byte(existingVal),
|
||||||
|
},
|
||||||
|
}, client.FieldOwner(FakeManager))).To(Succeed())
|
||||||
|
|
||||||
|
fakeProvider.WithGetSecret([]byte(secretVal), nil)
|
||||||
|
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
|
||||||
|
expectedHash := utils.ObjectHash(map[string][]byte{
|
||||||
|
existingKey: []byte(existingVal),
|
||||||
|
targetProp: []byte(secretVal),
|
||||||
|
})
|
||||||
|
Expect(secret.Annotations[esv1beta1.AnnotationDataHash]).To(Equal(expectedHash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2069,6 +2097,7 @@ var _ = Describe("ExternalSecret controller", Serial, func() {
|
||||||
},
|
},
|
||||||
Entry("should recreate deleted secret", checkDeletion),
|
Entry("should recreate deleted secret", checkDeletion),
|
||||||
Entry("should create proper hash annotation for the external secret", checkSecretDataHashAnnotation),
|
Entry("should create proper hash annotation for the external secret", checkSecretDataHashAnnotation),
|
||||||
|
Entry("should create proper hash annotation for the external secret with creationPolicy=Merge", checkMergeSecretDataHashAnnotation),
|
||||||
Entry("es deletes orphaned secrets", deleteOrphanedSecrets),
|
Entry("es deletes orphaned secrets", deleteOrphanedSecrets),
|
||||||
Entry("should refresh when the hash annotation doesn't correspond to secret data", checkSecretDataHashAnnotationChange),
|
Entry("should refresh when the hash annotation doesn't correspond to secret data", checkSecretDataHashAnnotationChange),
|
||||||
Entry("should use external secret name if target secret name isn't defined", syncWithoutTargetName),
|
Entry("should use external secret name if target secret name isn't defined", syncWithoutTargetName),
|
||||||
|
|
Loading…
Reference in a new issue