diff --git a/Makefile b/Makefile index 063f7cd..72dcc81 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ GIT_COMMIT=$(shell git rev-parse HEAD) GIT_TAG=$(shell if [ -z "`git status --porcelain`" ]; then git describe --exact-match --tags HEAD 2>/dev/null; fi) KUBECTL_VERSION=$(shell go list -m all | grep k8s.io/client-go| cut -d' ' -f2) ENVTEST ?= $(LOCALBIN)/setup-envtest -ENVTEST_K8S_VERSION = 1.23.5 +ENVTEST_K8S_VERSION = 1.31.0 override LDFLAGS += \ -X ${PACKAGE}.version=${VERSION} \ @@ -204,7 +204,7 @@ ENVTEST=$(LOCALBIN)/setup-envtest .PHONY: envtest envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) - $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,release-0.16) + $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,release-0.19) .PHONY: bundle bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. diff --git a/api/common/common.go b/api/common/common.go index 29164b4..3cd4a0c 100644 --- a/api/common/common.go +++ b/api/common/common.go @@ -1,5 +1,9 @@ package common +import ( + corev1 "k8s.io/api/core/v1" +) + // StatusCreated is success status for Sonar resources. const StatusCreated = "created" @@ -18,3 +22,29 @@ type SonarRef struct { type HasSonarRef interface { GetSonarRef() SonarRef } + +// SourceRef is a reference to a key in a ConfigMap or a Secret. +// +kubebuilder:object:generate=true +type SourceRef struct { + // Selects a key of a ConfigMap. + // +optional + ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` + + // Selects a key of a secret. + // +optional + SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty"` +} + +type ConfigMapKeySelector struct { + // The ConfigMap to select from. + corev1.LocalObjectReference `json:",inline"` + // The key to select. + Key string `json:"key"` +} + +type SecretKeySelector struct { + // The name of the secret. + corev1.LocalObjectReference `json:",inline"` + // The key of the secret to select from. + Key string `json:"key"` +} diff --git a/api/common/zz_generated.deepcopy.go b/api/common/zz_generated.deepcopy.go new file mode 100644 index 0000000..1cfbabb --- /dev/null +++ b/api/common/zz_generated.deepcopy.go @@ -0,0 +1,32 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package common + +import () + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SourceRef) DeepCopyInto(out *SourceRef) { + *out = *in + if in.ConfigMapKeyRef != nil { + in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef + *out = new(ConfigMapKeySelector) + **out = **in + } + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(SecretKeySelector) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef. +func (in *SourceRef) DeepCopy() *SourceRef { + if in == nil { + return nil + } + out := new(SourceRef) + in.DeepCopyInto(out) + return out +} diff --git a/api/v1alpha1/sonar_types.go b/api/v1alpha1/sonar_types.go index 92c5827..4805998 100644 --- a/api/v1alpha1/sonar_types.go +++ b/api/v1alpha1/sonar_types.go @@ -2,6 +2,8 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/epam/edp-sonar-operator/api/common" ) // SonarSpec defines the desired state of Sonar. @@ -45,6 +47,11 @@ type SonarSetting struct { // +optional // +kubebuilder:example={beginBlockRegexp: ".*", endBlockRegexp: ".*"} FieldValues map[string]string `json:"fieldValues,omitempty"` + + // ValueRef is a reference to a key in a ConfigMap or a Secret. + // +optional + // +kubebuilder:example={secretKeyRef: {name: my-secret, key: my-key}} + ValueRef *common.SourceRef `json:"valueRef,omitempty"` } // SonarStatus defines the observed state of Sonar. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 9f5fbe2..f155a85 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5,6 +5,7 @@ package v1alpha1 import ( + "github.com/epam/edp-sonar-operator/api/common" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -507,6 +508,11 @@ func (in *SonarSetting) DeepCopyInto(out *SonarSetting) { (*out)[key] = val } } + if in.ValueRef != nil { + in, out := &in.ValueRef, &out.ValueRef + *out = new(common.SourceRef) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SonarSetting. diff --git a/config/crd/bases/edp.epam.com_sonars.yaml b/config/crd/bases/edp.epam.com_sonars.yaml index 12091f4..cb23b56 100644 --- a/config/crd/bases/edp.epam.com_sonars.yaml +++ b/config/crd/bases/edp.epam.com_sonars.yaml @@ -78,6 +78,47 @@ spec: example: https://my-sonarqube-instance.com maxLength: 4000 type: string + valueRef: + description: ValueRef is a reference to a key in a ConfigMap + or a Secret. + example: + secretKeyRef: + key: my-key + name: my-secret + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + required: + - key + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret. + properties: + key: + description: The key of the secret to select from. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object values: description: Setting multi value. To set several values, the parameter must be called once for each value. diff --git a/controllers/permission_template/sonarpermissiontemplate_controller_integration_test.go b/controllers/permission_template/sonarpermissiontemplate_controller_integration_test.go index ad5b027..1d14c97 100644 --- a/controllers/permission_template/sonarpermissiontemplate_controller_integration_test.go +++ b/controllers/permission_template/sonarpermissiontemplate_controller_integration_test.go @@ -27,7 +27,7 @@ var _ = Describe("PermissionTemplate controller", func() { ProjectKeyPattern: ".*.finance", Default: true, GroupsPermissions: map[string][]string{ - "sonar-developers": {"scan", "codeviewer"}, + "sonar-users": {"scan", "codeviewer"}, }, SonarRef: common.SonarRef{ Name: sonarName, @@ -35,15 +35,14 @@ var _ = Describe("PermissionTemplate controller", func() { }, } Expect(k8sClient.Create(ctx, newPermissionTemplate)).Should(Succeed()) - Eventually(func() bool { + Eventually(func(g Gomega) { createdPermissionTemplate := &sonarApi.SonarPermissionTemplate{} err := k8sClient.Get(ctx, types.NamespacedName{Name: permissionTemplateCRName, Namespace: namespace}, createdPermissionTemplate) - if err != nil { - return false - } - return createdPermissionTemplate.Status.Value == common.StatusCreated && createdPermissionTemplate.Status.Error == "" - }, timeout, interval).Should(BeTrue()) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(createdPermissionTemplate.Status.Error).Should(Equal(""), "Error should be empty") + g.Expect(createdPermissionTemplate.Status.Value).Should(Equal(common.StatusCreated), "Status should be created") + }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) }) It("Should delete PermissionTemplate object", func() { By("By creating not default PermissionTemplate object") diff --git a/controllers/sonar/chain/factory.go b/controllers/sonar/chain/factory.go index 58c8ce6..d4c20d4 100644 --- a/controllers/sonar/chain/factory.go +++ b/controllers/sonar/chain/factory.go @@ -1,13 +1,15 @@ package chain import ( + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/epam/edp-sonar-operator/pkg/client/sonar" ) -func MakeChain(sonarApiClient sonar.ClientInterface) SonarHandler { +func MakeChain(sonarApiClient sonar.ClientInterface, k8sClient client.Client) SonarHandler { ch := &chain{} ch.Use(NewCheckConnection(sonarApiClient)) - ch.Use(NewUpdateSettings(sonarApiClient)) + ch.Use(NewUpdateSettings(sonarApiClient, k8sClient)) ch.Use(NewSetDefaultPermissionTemplate(sonarApiClient)) return ch diff --git a/controllers/sonar/chain/update_settings.go b/controllers/sonar/chain/update_settings.go index 2f9e1af..b225eea 100644 --- a/controllers/sonar/chain/update_settings.go +++ b/controllers/sonar/chain/update_settings.go @@ -9,17 +9,20 @@ import ( "strings" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" sonarApi "github.com/epam/edp-sonar-operator/api/v1alpha1" "github.com/epam/edp-sonar-operator/pkg/client/sonar" + "github.com/epam/edp-sonar-operator/pkg/sourceref" ) type UpdateSettings struct { sonarApiClient sonar.Settings + k8sClient client.Client } -func NewUpdateSettings(sonarApiClient sonar.Settings) *UpdateSettings { - return &UpdateSettings{sonarApiClient: sonarApiClient} +func NewUpdateSettings(sonarApiClient sonar.Settings, k8sClient client.Client) *UpdateSettings { + return &UpdateSettings{sonarApiClient: sonarApiClient, k8sClient: k8sClient} } func (h *UpdateSettings) ServeRequest(ctx context.Context, sonar *sonarApi.Sonar) error { @@ -32,7 +35,12 @@ func (h *UpdateSettings) ServeRequest(ctx context.Context, sonar *sonarApi.Sonar processedSettings := make([]string, 0, len(sonar.Spec.Settings)) for _, s := range sonar.Spec.Settings { - if err := h.sonarApiClient.SetSetting(ctx, makeSetting(s)); err != nil { + setting, err := h.makeSetting(ctx, s, sonar.Namespace) + if err != nil { + return err + } + + if err = h.sonarApiClient.SetSetting(ctx, setting); err != nil { return fmt.Errorf("failed to set setting %s: %w", s.Key, err) } @@ -77,7 +85,11 @@ func settingsKeysMapToSlice(m map[string]struct{}) []string { return s } -func makeSetting(setting sonarApi.SonarSetting) url.Values { +func (h *UpdateSettings) makeSetting( + ctx context.Context, + setting sonarApi.SonarSetting, + namespace string, +) (url.Values, error) { if setting.FieldValues != nil { // nolint:errchkjson //we can skip error for marshal map[string]string fv, _ := json.Marshal(setting.FieldValues) @@ -85,19 +97,32 @@ func makeSetting(setting sonarApi.SonarSetting) url.Values { return url.Values{ "key": []string{setting.Key}, "fieldValues": []string{string(fv)}, - } + }, nil } if setting.Values != nil { return url.Values{ "key": []string{setting.Key}, "values": setting.Values, + }, nil + } + + if setting.ValueRef != nil { + val, err := sourceref.GetValueFromSourceRef(ctx, setting.ValueRef, namespace, h.k8sClient) + if err != nil { + return url.Values{}, fmt.Errorf("failed to get sonar setting from source ref: %w", err) } + + return newSettingValue(setting.Key, val), nil } + return newSettingValue(setting.Key, setting.Value), nil +} + +func newSettingValue(key, value string) url.Values { return url.Values{ - "key": []string{setting.Key}, - "value": []string{setting.Value}, + "key": []string{key}, + "value": []string{value}, } } diff --git a/controllers/sonar/chain/update_settings_test.go b/controllers/sonar/chain/update_settings_test.go index 0059096..08232c4 100644 --- a/controllers/sonar/chain/update_settings_test.go +++ b/controllers/sonar/chain/update_settings_test.go @@ -9,9 +9,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/epam/edp-sonar-operator/api/common" sonarApi "github.com/epam/edp-sonar-operator/api/v1alpha1" "github.com/epam/edp-sonar-operator/pkg/client/sonar" "github.com/epam/edp-sonar-operator/pkg/client/sonar/mocks" @@ -20,10 +25,14 @@ import ( func TestUpdateSettings_ServeRequest(t *testing.T) { t.Parallel() + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + tests := []struct { name string sonarApiClient func(t *testing.T) sonar.Settings sonar *sonarApi.Sonar + k8sClient func(t *testing.T) client.Client wantErr require.ErrorAssertionFunc wantStatus sonarApi.SonarStatus }{ @@ -31,7 +40,8 @@ func TestUpdateSettings_ServeRequest(t *testing.T) { name: "settings is set", sonar: &sonarApi.Sonar{ ObjectMeta: metav1.ObjectMeta{ - Name: "sonar", + Name: "sonar", + Namespace: "default", }, Spec: sonarApi.SonarSpec{ Settings: []sonarApi.SonarSetting{ @@ -50,19 +60,42 @@ func TestUpdateSettings_ServeRequest(t *testing.T) { "field2": "value2", }, }, + { + Key: "sonar.secret", + ValueRef: &common.SourceRef{ + SecretKeyRef: &common.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "sonar-secret", + }, + Key: "secret-key", + }, + }, + }, }, }, }, sonarApiClient: func(t *testing.T) sonar.Settings { m := mocks.NewClientInterface(t) m.On("SetSetting", mock.Anything, mock.Anything). - Return(nil).Times(3) + Return(nil).Times(4) return m }, + k8sClient: func(t *testing.T) client.Client { + return fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sonar-secret", + Namespace: "default", + }, + Data: map[string][]byte{"secret-key": []byte("secret-value")}, + }). + Build() + }, wantErr: require.NoError, wantStatus: sonarApi.SonarStatus{ - ProcessedSettings: "sonar.core.a,sonar.core.b,sonar.core.c", + ProcessedSettings: "sonar.core.a,sonar.core.b,sonar.core.c,sonar.secret", }, }, { @@ -92,6 +125,9 @@ func TestUpdateSettings_ServeRequest(t *testing.T) { return m }, + k8sClient: func(t *testing.T) client.Client { + return fake.NewClientBuilder().WithScheme(scheme).Build() + }, wantErr: require.NoError, wantStatus: sonarApi.SonarStatus{ ProcessedSettings: "sonar.core.a", @@ -124,6 +160,9 @@ func TestUpdateSettings_ServeRequest(t *testing.T) { return m }, + k8sClient: func(t *testing.T) client.Client { + return fake.NewClientBuilder().WithScheme(scheme).Build() + }, wantErr: func(t require.TestingT, err error, i ...interface{}) { require.Error(t, err) require.Contains(t, err.Error(), "failed to unset settings") @@ -154,6 +193,9 @@ func TestUpdateSettings_ServeRequest(t *testing.T) { return m }, + k8sClient: func(t *testing.T) client.Client { + return fake.NewClientBuilder().WithScheme(scheme).Build() + }, wantErr: func(t require.TestingT, err error, i ...interface{}) { require.Error(t, err) require.Contains(t, err.Error(), "failed to set setting") @@ -167,8 +209,9 @@ func TestUpdateSettings_ServeRequest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - h := NewUpdateSettings(tt.sonarApiClient(t)) + h := NewUpdateSettings(tt.sonarApiClient(t), tt.k8sClient(t)) err := h.ServeRequest(ctrl.LoggerInto(context.Background(), logr.Discard()), tt.sonar) + tt.wantErr(t, err) assert.Equal(t, tt.wantStatus, tt.sonar.Status) }) diff --git a/controllers/sonar/sonar_controller.go b/controllers/sonar/sonar_controller.go index 39c62eb..e444ee7 100644 --- a/controllers/sonar/sonar_controller.go +++ b/controllers/sonar/sonar_controller.go @@ -80,7 +80,7 @@ func (r *ReconcileSonar) Reconcile(ctx context.Context, request reconcile.Reques return reconcile.Result{RequeueAfter: defaultRequeueTime}, err } - if err = chain.MakeChain(sonarApiClient).ServeRequest(ctx, sonar); err != nil { + if err = chain.MakeChain(sonarApiClient, r.client).ServeRequest(ctx, sonar); err != nil { sonar.Status.Error = err.Error() if statusErr := r.updateSonarStatus(ctx, sonar, oldStatus); statusErr != nil { diff --git a/controllers/sonar/sonar_controller_integration_test.go b/controllers/sonar/sonar_controller_integration_test.go index 061c55b..13f3af6 100644 --- a/controllers/sonar/sonar_controller_integration_test.go +++ b/controllers/sonar/sonar_controller_integration_test.go @@ -1,6 +1,7 @@ package sonar import ( + "github.com/epam/edp-sonar-operator/api/common" "time" . "github.com/onsi/ginkgo/v2" @@ -30,8 +31,9 @@ var _ = Describe("Sonar controller", func() { Namespace: namespace, }, Data: map[string][]byte{ - "user": []byte(sonarUser), - "password": []byte(sonarPassword), + "user": []byte(sonarUser), + "password": []byte(sonarPassword), + "smtp-pass": []byte("smtp-password"), }, } Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) @@ -60,24 +62,34 @@ var _ = Describe("Sonar controller", func() { Key: "sonar.global.exclusions", Values: []string{"**/*.js", "**/*.ts", "**/*.tsx", "**/*.jsx"}, }, + { + Key: "email.smtp_password.secured", + ValueRef: &common.SourceRef{ + SecretKeyRef: &common.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: secret.Name, + }, + Key: "smtp-pass", + }, + }, + }, }, }, } Expect(k8sClient.Create(ctx, newSonar)).Should(Succeed()) - Eventually(func() bool { + Eventually(func(g Gomega) { createdSonar := &sonarApi.Sonar{} err := k8sClient.Get(ctx, types.NamespacedName{Name: sonarName, Namespace: namespace}, createdSonar) - if err != nil { - return false - } - - processedSettings := "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay,sonar.global.exclusions,sonar.issue.ignore.block" - - return createdSonar.Status.Connected && - createdSonar.Status.Error == "" && - createdSonar.Status.Value != "" && - createdSonar.Status.ProcessedSettings == processedSettings + g.Expect(err).ShouldNot(HaveOccurred()) - }, timeout, interval).Should(BeTrue()) + g.Expect(createdSonar.Status.Connected).Should(BeTrue(), "Sonar should be connected") + g.Expect(createdSonar.Status.Error).Should(BeEmpty(), "Error should be empty") + g.Expect(createdSonar.Status.Value).ShouldNot(BeEmpty(), "Value should not be empty") + g.Expect(createdSonar.Status.ProcessedSettings). + Should( + Equal("email.smtp_password.secured,sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay,sonar.global.exclusions,sonar.issue.ignore.block"), + "Processed settings should be equal", + ) + }).WithTimeout(timeout).WithPolling(interval).Should(Succeed()) }) }) diff --git a/deploy-templates/_crd_examples/sonar.yaml b/deploy-templates/_crd_examples/sonar.yaml index c38595f..88a3c6d 100644 --- a/deploy-templates/_crd_examples/sonar.yaml +++ b/deploy-templates/_crd_examples/sonar.yaml @@ -17,3 +17,16 @@ spec: endBlockRegexp: ".*" - key: sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay value: "20" + - key: email.smtp_password.secured + valueRef: + secretKeyRef: + key: password + name: sonar-smtp + +--- +apiVersion: v1 +kind: Secret +metadata: + name: sonar-smtp +data: + password: c29uYXItcGFzc3dvcmQ= diff --git a/deploy-templates/crds/edp.epam.com_sonars.yaml b/deploy-templates/crds/edp.epam.com_sonars.yaml index 12091f4..cb23b56 100644 --- a/deploy-templates/crds/edp.epam.com_sonars.yaml +++ b/deploy-templates/crds/edp.epam.com_sonars.yaml @@ -78,6 +78,47 @@ spec: example: https://my-sonarqube-instance.com maxLength: 4000 type: string + valueRef: + description: ValueRef is a reference to a key in a ConfigMap + or a Secret. + example: + secretKeyRef: + key: my-key + name: my-secret + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + required: + - key + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret. + properties: + key: + description: The key of the secret to select from. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object values: description: Setting multi value. To set several values, the parameter must be called once for each value. diff --git a/docs/api.md b/docs/api.md index 23f11d9..15c7b62 100644 --- a/docs/api.md +++ b/docs/api.md @@ -962,6 +962,13 @@ SonarSetting defines the setting of sonar. Value is the value of the setting.
false + + valueRef + object + + ValueRef is a reference to a key in a ConfigMap or a Secret.
+ + false values []string @@ -973,6 +980,112 @@ SonarSetting defines the setting of sonar. +### Sonar.spec.settings[index].valueRef +[↩ Parent](#sonarspecsettingsindex) + + + +ValueRef is a reference to a key in a ConfigMap or a Secret. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
configMapKeyRefobject + Selects a key of a ConfigMap.
+
false
secretKeyRefobject + Selects a key of a secret.
+
false
+ + +### Sonar.spec.settings[index].valueRef.configMapKeyRef +[↩ Parent](#sonarspecsettingsindexvalueref) + + + +Selects a key of a ConfigMap. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + The key to select.
+
true
namestring + Name of the referent. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +TODO: Add other useful fields. apiVersion, kind, uid?
+
false
+ + +### Sonar.spec.settings[index].valueRef.secretKeyRef +[↩ Parent](#sonarspecsettingsindexvalueref) + + + +Selects a key of a secret. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + The key of the secret to select from.
+
true
namestring + Name of the referent. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +TODO: Add other useful fields. apiVersion, kind, uid?
+
false
+ + ### Sonar.status [↩ Parent](#sonar) diff --git a/pkg/sourceref/sourceref.go b/pkg/sourceref/sourceref.go new file mode 100644 index 0000000..b94cb4e --- /dev/null +++ b/pkg/sourceref/sourceref.go @@ -0,0 +1,45 @@ +package sourceref + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/epam/edp-sonar-operator/api/common" +) + +// GetValueFromSourceRef retries value from ConfigMap or Secret by SourceRef. +func GetValueFromSourceRef(ctx context.Context, sourceRef *common.SourceRef, namespace string, k8sClient client.Client) (string, error) { + if sourceRef == nil { + return "", nil + } + + if sourceRef.ConfigMapKeyRef != nil { + configMap := &corev1.ConfigMap{} + if err := k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: sourceRef.ConfigMapKeyRef.Name, + }, configMap); err != nil { + return "", fmt.Errorf("unable to get configmap: %w", err) + } + + return configMap.Data[sourceRef.ConfigMapKeyRef.Key], nil + } + + if sourceRef.SecretKeyRef != nil { + secret := &corev1.Secret{} + if err := k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: sourceRef.SecretKeyRef.Name, + }, secret); err != nil { + return "", fmt.Errorf("unable to get secret: %w", err) + } + + return string(secret.Data[sourceRef.SecretKeyRef.Key]), nil + } + + return "", nil +} diff --git a/pkg/sourceref/sourceref_test.go b/pkg/sourceref/sourceref_test.go new file mode 100644 index 0000000..a54fd1d --- /dev/null +++ b/pkg/sourceref/sourceref_test.go @@ -0,0 +1,100 @@ +package sourceref + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/epam/edp-sonar-operator/api/common" +) + +func TestGetValueFromSourceRef(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + sourceRef *common.SourceRef + k8sClient func(t *testing.T) client.Client + want string + wantErr require.ErrorAssertionFunc + }{ + { + name: "config map key ref", + sourceRef: &common.SourceRef{ + ConfigMapKeyRef: &common.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "configmap-with-secret", + }, + Key: "key", + }, + }, + k8sClient: func(t *testing.T) client.Client { + return fake.NewClientBuilder().WithObjects( + &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap-with-secret", + Namespace: "default", + }, + Data: map[string]string{ + "key": "value", + }, + }).Build() + }, + want: "value", + wantErr: require.NoError, + }, + { + name: "secret key ref", + sourceRef: &common.SourceRef{ + SecretKeyRef: &common.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "secret-with-value", + }, + Key: "key", + }, + }, + k8sClient: func(t *testing.T) client.Client { + return fake.NewClientBuilder().WithObjects( + &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-with-value", + Namespace: "default", + }, + Data: map[string][]byte{ + "key": []byte("value"), + }, + }).Build() + }, + want: "value", + wantErr: require.NoError, + }, + { + name: "empty source ref", + k8sClient: func(t *testing.T) client.Client { + return fake.NewClientBuilder().Build() + }, + want: "", + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetValueFromSourceRef( + context.Background(), + tt.sourceRef, + "default", + tt.k8sClient(t), + ) + + assert.Equal(t, tt.want, got) + tt.wantErr(t, err) + }) + } +}