| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154 |
- /*
- Copyright © The ESO 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
- https://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 externalsecret
- import (
- "context"
- "testing"
- "github.com/go-logr/logr"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- v1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/types"
- "k8s.io/client-go/kubernetes/scheme"
- ctrl "sigs.k8s.io/controller-runtime"
- fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- "github.com/external-secrets/external-secrets/runtime/esutils"
- )
- func TestIsGenericTarget(t *testing.T) {
- tests := []struct {
- name string
- es *esv1.ExternalSecret
- expected bool
- }{
- {
- name: "nil manifest - Secret target",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: nil,
- },
- },
- },
- expected: false,
- },
- {
- name: "ConfigMap manifest target",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- },
- expected: true,
- },
- {
- name: "Custom Resource manifest target",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "argoproj.io/v1alpha1",
- Kind: "Application",
- },
- },
- },
- },
- expected: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := isGenericTarget(tt.es)
- assert.Equal(t, tt.expected, result)
- })
- }
- }
- func TestValidateGenericTarget(t *testing.T) {
- tests := []struct {
- name string
- es *esv1.ExternalSecret
- allowGenericTargets bool
- expectedError bool
- errorContains string
- }{
- {
- name: "ConfigMap target - flag enabled - valid",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- },
- allowGenericTargets: true,
- expectedError: false,
- },
- {
- name: "ConfigMap target - flag disabled",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- },
- allowGenericTargets: false,
- expectedError: true,
- errorContains: "generic targets are disabled",
- },
- {
- name: "Missing APIVersion",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "",
- Kind: "ConfigMap",
- },
- },
- },
- },
- allowGenericTargets: true,
- expectedError: true,
- errorContains: "apiVersion is required",
- },
- {
- name: "Missing Kind",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "",
- },
- },
- },
- },
- allowGenericTargets: true,
- expectedError: true,
- errorContains: "kind is required",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := &Reconciler{
- AllowGenericTargets: tt.allowGenericTargets,
- }
- log := ctrl.Log.WithName("test")
- err := r.validateGenericTarget(log, tt.es)
- if tt.expectedError {
- assert.Error(t, err)
- if tt.errorContains != "" {
- assert.Contains(t, err.Error(), tt.errorContains)
- }
- } else {
- assert.NoError(t, err)
- }
- })
- }
- }
- func TestGetTargetGVK(t *testing.T) {
- tests := []struct {
- name string
- es *esv1.ExternalSecret
- expected schema.GroupVersionKind
- }{
- {
- name: "ConfigMap target",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- },
- expected: schema.GroupVersionKind{
- Group: "",
- Version: "v1",
- Kind: "ConfigMap",
- },
- },
- {
- name: "ArgoCD Application target",
- es: &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Manifest: &esv1.ManifestReference{
- APIVersion: "argoproj.io/v1alpha1",
- Kind: "Application",
- },
- },
- },
- },
- expected: schema.GroupVersionKind{
- Group: "argoproj.io",
- Version: "v1alpha1",
- Kind: "Application",
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := getTargetGVK(tt.es)
- assert.Equal(t, tt.expected, result)
- })
- }
- }
- func TestGetTargetName(t *testing.T) {
- tests := []struct {
- name string
- es *esv1.ExternalSecret
- expected string
- }{
- {
- name: "Use target name when specified",
- es: &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "my-external-secret",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "custom-target-name",
- },
- },
- },
- expected: "custom-target-name",
- },
- {
- name: "Use ExternalSecret name when target name not specified",
- es: &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "my-external-secret",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "",
- },
- },
- },
- expected: "my-external-secret",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := getTargetName(tt.es)
- assert.Equal(t, tt.expected, result)
- })
- }
- }
- func TestCreateSimpleManifest(t *testing.T) {
- tests := []struct {
- name string
- kind string
- dataMap map[string][]byte
- validate func(t *testing.T, obj *unstructured.Unstructured)
- }{
- {
- name: "ConfigMap with data",
- kind: "ConfigMap",
- dataMap: map[string][]byte{
- "key1": []byte("value1"),
- "key2": []byte("value2"),
- },
- validate: func(t *testing.T, obj *unstructured.Unstructured) {
- // Directly access the data field
- data, ok := obj.Object["data"].(map[string]string)
- require.True(t, ok, "data should be map[string]string")
- assert.Equal(t, "value1", data["key1"])
- assert.Equal(t, "value2", data["key2"])
- },
- },
- {
- name: "Custom resource with spec.data",
- kind: "CustomResource",
- dataMap: map[string][]byte{
- "config": []byte("my-config"),
- },
- validate: func(t *testing.T, obj *unstructured.Unstructured) {
- spec, ok := obj.Object["spec"].(map[string]any)
- require.True(t, ok, "spec should be map[string]interface{}")
- data, ok := spec["data"].(map[string]string)
- require.True(t, ok, "spec.data should be map[string]string")
- assert.Equal(t, "my-config", data["config"])
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := &Reconciler{}
- obj := &unstructured.Unstructured{
- Object: make(map[string]any),
- }
- obj.SetKind(tt.kind)
- result := r.createSimpleManifest(obj, tt.dataMap)
- assert.NotNil(t, result)
- if tt.validate != nil {
- tt.validate(t, result)
- }
- })
- }
- }
- func TestApplyTemplateToManifest_SimpleConfigMap(t *testing.T) {
- // Setup
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{
- Client: fakeClient,
- Scheme: scheme.Scheme,
- }
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- dataMap := map[string][]byte{
- "key1": []byte("value1"),
- "key2": []byte("value2"),
- }
- // Execute
- result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, nil)
- // Verify
- require.NoError(t, err)
- assert.NotNil(t, result)
- assert.Equal(t, "ConfigMap", result.GetKind())
- assert.Equal(t, "test-configmap", result.GetName())
- assert.Equal(t, "default", result.GetNamespace())
- // Verify data
- data, ok := result.Object["data"].(map[string]string)
- require.True(t, ok, "data should be map[string]string")
- assert.Equal(t, "value1", data["key1"])
- assert.Equal(t, "value2", data["key2"])
- // Verify managed label
- labels := result.GetLabels()
- assert.Equal(t, esv1.LabelManagedValue, labels[esv1.LabelManaged])
- }
- func TestApplyTemplateToManifest_WithMetadata(t *testing.T) {
- // Setup
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{
- Client: fakeClient,
- Scheme: scheme.Scheme,
- }
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- Template: &esv1.ExternalSecretTemplate{
- EngineVersion: esv1.TemplateEngineV2, // Set engine version
- Metadata: esv1.ExternalSecretTemplateMetadata{
- Labels: map[string]string{
- "app": "myapp",
- "tier": "backend",
- },
- Annotations: map[string]string{
- "description": "This is a test",
- },
- },
- },
- },
- },
- }
- dataMap := map[string][]byte{
- "config": []byte("test-config"),
- }
- // Execute
- result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, nil)
- // Verify
- require.NoError(t, err)
- assert.NotNil(t, result)
- // Verify labels
- labels := result.GetLabels()
- assert.Equal(t, "myapp", labels["app"])
- assert.Equal(t, "backend", labels["tier"])
- assert.Equal(t, esv1.LabelManagedValue, labels[esv1.LabelManaged])
- // Verify annotations
- annotations := result.GetAnnotations()
- assert.Equal(t, "This is a test", annotations["description"])
- }
- func TestApplyTemplateToManifest_AppliesOwnershipWhenCreationPolicyOwner(t *testing.T) {
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{Client: fakeClient, Scheme: scheme.Scheme}
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- UID: "abc-123",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- CreationPolicy: esv1.CreatePolicyOwner,
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- result, err := r.applyTemplateToManifest(context.Background(), es, map[string][]byte{"key": []byte("val")}, nil)
- require.NoError(t, err)
- owners := result.GetOwnerReferences()
- require.Len(t, owners, 1)
- assert.Equal(t, "test-es", owners[0].Name)
- assert.True(t, *owners[0].Controller)
- labels := result.GetLabels()
- assert.Equal(t, esutils.ObjectHash("default/test-es"), labels[esv1.LabelOwner])
- }
- func TestApplyOwnership(t *testing.T) {
- _ = esv1.AddToScheme(scheme.Scheme)
- isController := true
- tests := []struct {
- name string
- creationPolicy esv1.ExternalSecretCreationPolicy
- existing *unstructured.Unstructured
- expectedErr error
- validate func(t *testing.T, result *unstructured.Unstructured)
- }{
- {
- name: "removes LabelOwner when policy is not Owner",
- creationPolicy: esv1.CreatePolicyOrphan,
- existing: func() *unstructured.Unstructured {
- u := &unstructured.Unstructured{}
- u.SetLabels(map[string]string{
- esv1.LabelOwner: esutils.ObjectHash("default/test-es"),
- })
- return u
- }(),
- validate: func(t *testing.T, result *unstructured.Unstructured) {
- assert.Empty(t, result.GetLabels()[esv1.LabelOwner])
- },
- },
- {
- name: "removes owner reference when policy changes from Owner to Orphan",
- creationPolicy: esv1.CreatePolicyOrphan,
- existing: func() *unstructured.Unstructured {
- u := &unstructured.Unstructured{}
- u.SetGroupVersionKind(schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"})
- u.SetOwnerReferences([]metav1.OwnerReference{
- {
- APIVersion: esv1.SchemeGroupVersion.String(),
- Kind: esv1.ExtSecretKind,
- Name: "test-es",
- UID: "abc-123",
- Controller: &isController,
- },
- })
- return u
- }(),
- validate: func(t *testing.T, result *unstructured.Unstructured) {
- assert.Empty(t, result.GetOwnerReferences())
- },
- },
- {
- name: "returns ErrSecretIsOwned when owned by a different ExternalSecret",
- creationPolicy: esv1.CreatePolicyOwner,
- existing: func() *unstructured.Unstructured {
- u := &unstructured.Unstructured{}
- u.SetOwnerReferences([]metav1.OwnerReference{
- {
- APIVersion: esv1.SchemeGroupVersion.String(),
- Kind: esv1.ExtSecretKind,
- Name: "other-es",
- UID: "xyz-999",
- Controller: &isController,
- },
- })
- return u
- }(),
- expectedErr: ErrSecretIsOwned,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{Client: fakeClient, Scheme: scheme.Scheme}
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- UID: "abc-123",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- CreationPolicy: tt.creationPolicy,
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- err := r.applyOwnership(es, tt.existing)
- if tt.expectedErr != nil {
- require.ErrorIs(t, err, tt.expectedErr)
- return
- }
- require.NoError(t, err)
- if tt.validate != nil {
- tt.validate(t, tt.existing)
- }
- })
- }
- }
- func TestApplyTemplateToManifest_NoOwnerRefWhenCreationPolicyOrphan(t *testing.T) {
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{Client: fakeClient, Scheme: scheme.Scheme}
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- UID: "abc-123",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- CreationPolicy: esv1.CreatePolicyOrphan,
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- result, err := r.applyTemplateToManifest(context.Background(), es, map[string][]byte{"key": []byte("val")}, nil)
- require.NoError(t, err)
- assert.Empty(t, result.GetOwnerReferences())
- }
- func TestApplyTemplateToManifest_PropagatesESLabelsAndAnnotations(t *testing.T) {
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{Client: fakeClient, Scheme: scheme.Scheme}
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- Labels: map[string]string{
- "app.kubernetes.io/instance": "my-argocd-app",
- "custom-label": "custom-value",
- },
- Annotations: map[string]string{
- "argocd.argoproj.io/tracking-id": "my-argocd-app:external-secrets.io/ExternalSecret:default/test-es",
- },
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- result, err := r.applyTemplateToManifest(context.Background(), es, map[string][]byte{"key": []byte("val")}, nil)
- require.NoError(t, err)
- labels := result.GetLabels()
- assert.Equal(t, "my-argocd-app", labels["app.kubernetes.io/instance"])
- assert.Equal(t, "custom-value", labels["custom-label"])
- assert.Equal(t, esv1.LabelManagedValue, labels[esv1.LabelManaged])
- annotations := result.GetAnnotations()
- assert.Equal(t, "my-argocd-app:external-secrets.io/ExternalSecret:default/test-es", annotations["argocd.argoproj.io/tracking-id"])
- }
- func TestApplyTemplateToManifest_TemplateMetadataWinsOverESLabels(t *testing.T) {
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{Client: fakeClient, Scheme: scheme.Scheme}
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- Labels: map[string]string{
- "app.kubernetes.io/instance": "my-argocd-app",
- },
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- Template: &esv1.ExternalSecretTemplate{
- EngineVersion: esv1.TemplateEngineV2,
- Metadata: esv1.ExternalSecretTemplateMetadata{
- Labels: map[string]string{
- "app": "explicit-template-label",
- },
- },
- },
- },
- },
- }
- result, err := r.applyTemplateToManifest(context.Background(), es, map[string][]byte{"key": []byte("val")}, nil)
- require.NoError(t, err)
- labels := result.GetLabels()
- assert.Equal(t, "explicit-template-label", labels["app"])
- assert.Empty(t, labels["app.kubernetes.io/instance"])
- assert.Equal(t, esv1.LabelManagedValue, labels[esv1.LabelManaged])
- }
- func TestApplyTemplateToManifest_NoESLabels(t *testing.T) {
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{Client: fakeClient, Scheme: scheme.Scheme}
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-configmap",
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- result, err := r.applyTemplateToManifest(context.Background(), es, map[string][]byte{"key": []byte("val")}, nil)
- require.NoError(t, err)
- labels := result.GetLabels()
- assert.Equal(t, esv1.LabelManagedValue, labels[esv1.LabelManaged])
- assert.Len(t, labels, 1)
- }
- func TestGetGenericResource(t *testing.T) {
- // Setup
- _ = esv1.AddToScheme(scheme.Scheme)
- // Create a ConfigMap to find
- existingConfigMap := &unstructured.Unstructured{
- Object: map[string]any{
- "apiVersion": "v1",
- "kind": "ConfigMap",
- "metadata": map[string]any{
- "name": "test-cm",
- "namespace": "default",
- },
- "data": map[string]any{
- "key": "value",
- },
- },
- }
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(existingConfigMap).Build()
- r := &Reconciler{
- Client: fakeClient,
- Scheme: scheme.Scheme,
- }
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-cm",
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- // Execute
- result, err := r.getGenericResource(context.Background(), logr.Discard(), es)
- // Verify
- require.NoError(t, err)
- assert.NotNil(t, result)
- assert.Equal(t, "ConfigMap", result.GetKind())
- assert.Equal(t, "test-cm", result.GetName())
- // Verify data
- data, found, err := unstructured.NestedStringMap(result.Object, "data")
- require.NoError(t, err)
- require.True(t, found)
- assert.Equal(t, "value", data["key"])
- }
- func TestGetGenericResource_NotFound(t *testing.T) {
- // Setup
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{
- Client: fakeClient,
- Scheme: scheme.Scheme,
- }
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "nonexistent-cm",
- Manifest: &esv1.ManifestReference{
- APIVersion: "v1",
- Kind: "ConfigMap",
- },
- },
- },
- }
- // Execute
- result, err := r.getGenericResource(context.Background(), logr.Discard(), es)
- // Verify - should return an error and nil result when resource doesn't exist
- assert.Error(t, err)
- assert.True(t, apierrors.IsNotFound(err))
- assert.Nil(t, result)
- }
- func init() {
- // Initialize scheme for tests
- _ = esv1.AddToScheme(scheme.Scheme)
- _ = v1.AddToScheme(scheme.Scheme)
- }
- func TestApplyTemplateToManifest_LiteralWithDeployment(t *testing.T) {
- // Test that literal templates work with complex objects like Deployments
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{
- Client: fakeClient,
- Scheme: scheme.Scheme,
- }
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-deployment",
- Manifest: &esv1.ManifestReference{
- APIVersion: "apps/v1",
- Kind: "Deployment",
- },
- Template: &esv1.ExternalSecretTemplate{
- EngineVersion: esv1.TemplateEngineV2,
- TemplateFrom: []esv1.TemplateFrom{
- {
- Target: "spec",
- Literal: new(`
- replicas: {{ .replicas }}
- selector:
- matchLabels:
- app: myapp
- template:
- metadata:
- labels:
- app: myapp
- spec:
- containers:
- - name: nginx
- image: nginx:{{ .version }}
- ports:
- - containerPort: 80
- `),
- },
- },
- },
- },
- },
- }
- dataMap := map[string][]byte{
- "replicas": []byte("3"),
- "version": []byte("1.21"),
- }
- result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, nil)
- require.NoError(t, err)
- assert.NotNil(t, result)
- assert.Equal(t, "Deployment", result.GetKind())
- assert.Equal(t, "test-deployment", result.GetName())
- spec, found, err := unstructured.NestedMap(result.Object, "spec")
- require.NoError(t, err)
- require.True(t, found, "spec should exist")
- replicas, found, err := unstructured.NestedInt64(result.Object, "spec", "replicas")
- require.NoError(t, err)
- require.True(t, found, "spec.replicas should exist")
- assert.Equal(t, int64(3), replicas)
- containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers")
- require.NoError(t, err)
- require.True(t, found, "containers should exist")
- require.Len(t, containers, 1, "should have 1 container")
- container, ok := containers[0].(map[string]any)
- require.True(t, ok, "container should be a map")
- assert.Equal(t, "nginx:1.21", container["image"])
- t.Logf("Result spec: %+v", spec)
- }
- func TestApplyTemplateToManifest_MergeBehavior(t *testing.T) {
- _ = esv1.AddToScheme(scheme.Scheme)
- fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme.Scheme).Build()
- r := &Reconciler{
- Client: fakeClient,
- Scheme: scheme.Scheme,
- }
- es := &esv1.ExternalSecret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-es",
- Namespace: "default",
- },
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- Name: "test-slack-config",
- Manifest: &esv1.ManifestReference{
- APIVersion: "notification.toolkit.fluxcd.io/v1beta1",
- Kind: "Provider",
- },
- Template: &esv1.ExternalSecretTemplate{
- EngineVersion: esv1.TemplateEngineV2,
- TemplateFrom: []esv1.TemplateFrom{
- {
- Target: "spec.slack",
- Literal: new(`api_url: {{ .url }}`),
- },
- },
- },
- },
- },
- }
- existingResource := &unstructured.Unstructured{
- Object: map[string]any{
- "apiVersion": "notification.toolkit.fluxcd.io/v1beta1",
- "kind": "Provider",
- "metadata": map[string]any{
- "name": "test-slack-config",
- "namespace": "default",
- "resourceVersion": "12345",
- "uid": "test-uid-123",
- },
- "spec": map[string]any{
- "type": "slack",
- "slack": map[string]any{
- "channel": "general",
- "username": "bot",
- },
- },
- },
- }
- dataMap := map[string][]byte{
- "url": []byte("https://hooks.slack.com/services/XXX"),
- }
- result, err := r.applyTemplateToManifest(context.Background(), es, dataMap, existingResource)
- require.NoError(t, err)
- assert.NotNil(t, result)
- assert.Equal(t, "Provider", result.GetKind())
- assert.Equal(t, "test-slack-config", result.GetName())
- specType, found, err := unstructured.NestedString(result.Object, "spec", "type")
- require.NoError(t, err)
- require.True(t, found, "spec.type should be preserved")
- assert.Equal(t, "slack", specType, "spec.type should be preserved from existing resource")
- slackChannel, found, err := unstructured.NestedString(result.Object, "spec", "slack", "channel")
- require.NoError(t, err)
- require.True(t, found, "spec.slack.channel should be preserved")
- assert.Equal(t, "general", slackChannel, "spec.slack.channel should be preserved from existing resource")
- slackUsername, found, err := unstructured.NestedString(result.Object, "spec", "slack", "username")
- require.NoError(t, err)
- require.True(t, found, "spec.slack.username should be preserved")
- assert.Equal(t, "bot", slackUsername, "spec.slack.username should be preserved from existing resource")
- apiURL, found, err := unstructured.NestedString(result.Object, "spec", "slack", "api_url")
- require.NoError(t, err)
- require.True(t, found, "spec.slack.api_url should be added from template")
- assert.Equal(t, "https://hooks.slack.com/services/XXX", apiURL, "spec.slack.api_url should come from template")
- assert.Equal(t, "12345", result.GetResourceVersion(), "resourceVersion should be preserved")
- assert.Equal(t, "test-uid-123", string(result.GetUID()), "uid should be preserved")
- t.Logf("Result spec: %+v", result.Object["spec"])
- }
- func TestGenericTargetContentHash(t *testing.T) {
- tests := []struct {
- name string
- obj *unstructured.Unstructured
- wantErr bool
- }{
- {
- name: "hashes spec field",
- obj: &unstructured.Unstructured{
- Object: map[string]any{
- "spec": map[string]any{"key": "val"},
- },
- },
- },
- {
- name: "hashes data field when no spec",
- obj: &unstructured.Unstructured{
- Object: map[string]any{
- "data": map[string]any{"key": "val"},
- },
- },
- },
- {
- name: "prefers spec over data",
- obj: &unstructured.Unstructured{
- Object: map[string]any{
- "spec": map[string]any{"a": "1"},
- "data": map[string]any{"b": "2"},
- },
- },
- },
- {
- name: "errors when neither spec nor data",
- obj: &unstructured.Unstructured{
- Object: map[string]any{
- "status": map[string]any{"ready": true},
- },
- },
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- hash, err := genericTargetContentHash(tt.obj)
- if tt.wantErr {
- assert.Error(t, err)
- assert.Empty(t, hash)
- return
- }
- require.NoError(t, err)
- assert.NotEmpty(t, hash)
- })
- }
- t.Run("spec preferred over data produces spec hash", func(t *testing.T) {
- specData := map[string]any{"a": "1"}
- obj := &unstructured.Unstructured{
- Object: map[string]any{
- "spec": specData,
- "data": map[string]any{"b": "2"},
- },
- }
- hash, err := genericTargetContentHash(obj)
- require.NoError(t, err)
- assert.Equal(t, esutils.ObjectHash(specData), hash)
- })
- }
- func TestIsGenericTargetValid(t *testing.T) {
- makeES := func(policy esv1.ExternalSecretCreationPolicy) *esv1.ExternalSecret {
- return &esv1.ExternalSecret{
- Spec: esv1.ExternalSecretSpec{
- Target: esv1.ExternalSecretTarget{
- CreationPolicy: policy,
- },
- },
- }
- }
- makeTarget := func(uid string, labels map[string]string, annotations map[string]string, obj map[string]any) *unstructured.Unstructured {
- u := &unstructured.Unstructured{Object: obj}
- if uid != "" {
- u.SetUID(types.UID(uid))
- }
- u.SetLabels(labels)
- u.SetAnnotations(annotations)
- return u
- }
- t.Run("orphan policy always valid", func(t *testing.T) {
- valid, err := isGenericTargetValid(nil, makeES(esv1.CreatePolicyOrphan))
- require.NoError(t, err)
- assert.True(t, valid)
- })
- t.Run("nil target is invalid", func(t *testing.T) {
- valid, err := isGenericTargetValid(nil, makeES(esv1.CreatePolicyOwner))
- require.NoError(t, err)
- assert.False(t, valid)
- })
- t.Run("empty UID is invalid", func(t *testing.T) {
- obj := &unstructured.Unstructured{Object: map[string]any{}}
- valid, err := isGenericTargetValid(obj, makeES(esv1.CreatePolicyOwner))
- require.NoError(t, err)
- assert.False(t, valid)
- })
- t.Run("not managed is invalid", func(t *testing.T) {
- obj := makeTarget("some-uid", map[string]string{}, nil, map[string]any{
- "spec": map[string]any{"key": "val"},
- })
- valid, err := isGenericTargetValid(obj, makeES(esv1.CreatePolicyOwner))
- require.NoError(t, err)
- assert.False(t, valid)
- })
- t.Run("hash mismatch is invalid", func(t *testing.T) {
- obj := makeTarget(
- "some-uid",
- map[string]string{esv1.LabelManaged: esv1.LabelManagedValue},
- map[string]string{esv1.AnnotationDataHash: "wrong-hash"},
- map[string]any{"spec": map[string]any{"key": "val"}},
- )
- valid, err := isGenericTargetValid(obj, makeES(esv1.CreatePolicyOwner))
- require.NoError(t, err)
- assert.False(t, valid)
- })
- t.Run("matching hash is valid", func(t *testing.T) {
- specData := map[string]any{"key": "val"}
- hash := esutils.ObjectHash(specData)
- obj := makeTarget(
- "some-uid",
- map[string]string{esv1.LabelManaged: esv1.LabelManagedValue},
- map[string]string{esv1.AnnotationDataHash: hash},
- map[string]any{"spec": specData},
- )
- valid, err := isGenericTargetValid(obj, makeES(esv1.CreatePolicyOwner))
- require.NoError(t, err)
- assert.True(t, valid)
- })
- t.Run("errors when target has no spec or data", func(t *testing.T) {
- obj := makeTarget(
- "some-uid",
- map[string]string{esv1.LabelManaged: esv1.LabelManagedValue},
- nil,
- map[string]any{"status": map[string]any{}},
- )
- _, err := isGenericTargetValid(obj, makeES(esv1.CreatePolicyOwner))
- assert.Error(t, err)
- })
- }
|