Skip to content

Commit

Permalink
feat: Add possibility to securely set Sonar settings (#28)
Browse files Browse the repository at this point in the history
Add `valueRef` for `Sonar.spec.settings` configuration.
This allows loading settings from a Secret or ConfigMap.
  • Loading branch information
zmotso authored and SergK committed Jan 17, 2025
1 parent 0e5b642 commit 6ddaa45
Show file tree
Hide file tree
Showing 17 changed files with 546 additions and 37 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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} \
Expand Down Expand Up @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions api/common/common.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package common

import (
corev1 "k8s.io/api/core/v1"
)

// StatusCreated is success status for Sonar resources.
const StatusCreated = "created"

Expand All @@ -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"`
}
32 changes: 32 additions & 0 deletions api/common/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions api/v1alpha1/sonar_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions config/crd/bases/edp.epam.com_sonars.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,22 @@ 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,
},
},
}
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")
Expand Down
6 changes: 4 additions & 2 deletions controllers/sonar/chain/factory.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down
39 changes: 32 additions & 7 deletions controllers/sonar/chain/update_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}

Expand Down Expand Up @@ -77,27 +85,44 @@ 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)

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},
}
}

Expand Down
Loading

0 comments on commit 6ddaa45

Please sign in to comment.