From 39bd9b23a00703bb11aecbf04eaff77e33688bd2 Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Mon, 6 Jul 2026 19:12:36 +0200 Subject: [PATCH 1/2] (feat) Helm post-render strategies --- api/v1beta1/spec.go | 28 ++++++++++++ ...fig.projectsveltos.io_clusterprofiles.yaml | 11 +++++ ...g.projectsveltos.io_clusterpromotions.yaml | 11 +++++ ...ig.projectsveltos.io_clustersummaries.yaml | 11 +++++ .../config.projectsveltos.io_profiles.yaml | 11 +++++ controllers/export_test.go | 6 ++- controllers/handlers_helm.go | 10 +++++ controllers/handlers_helm_test.go | 38 ++++++++++++++++ manifest/manifest.yaml | 44 +++++++++++++++++++ 9 files changed, 168 insertions(+), 2 deletions(-) diff --git a/api/v1beta1/spec.go b/api/v1beta1/spec.go index fa068437..f30e1c66 100644 --- a/api/v1beta1/spec.go +++ b/api/v1beta1/spec.go @@ -151,6 +151,27 @@ const ( HelmChartActionUninstall = HelmChartAction("Uninstall") ) +// PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom +// are applied as a post-renderer during this HelmChart's install/upgrade. +// +kubebuilder:validation:Enum:=combined;separate;nohooks +type PostRenderStrategy string + +const ( + // PostRenderStrategyCombined sends hooks and regular templates together as a single + // stream to the post-renderer. This is Helm's own default. + PostRenderStrategyCombined = PostRenderStrategy("combined") + + // PostRenderStrategySeparate sends hooks and regular templates to the post-renderer + // in independent invocations. Use this to avoid duplicate-resource errors from + // post-renderers that de-duplicate by resource identity, such as Patches, when the + // same resource appears in both a hook and a regular template. + PostRenderStrategySeparate = PostRenderStrategy("separate") + + // PostRenderStrategyNoHooks sends only regular templates to the post-renderer and + // leaves hooks untouched. This matches Helm 3's behavior. + PostRenderStrategyNoHooks = PostRenderStrategy("nohooks") +) + type HelmOptions struct { // SkipCRDs controls whether CRDs should be installed during install/upgrade operation. // By default, CRDs are installed if not already present. @@ -233,6 +254,13 @@ type HelmOptions struct { // +optional RunTests bool `json:"runTests,omitempty"` + // PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + // are applied as a post-renderer during this chart's install/upgrade. Only relevant + // when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + // Helm's own default (combined) when unset. + // +optional + PostRenderStrategy PostRenderStrategy `json:"postRenderStrategy,omitempty"` + // HelmInstallOptions are options specific to helm install // +optional InstallOptions HelmInstallOptions `json:"installOptions,omitempty"` diff --git a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml index 111f5ad2..14fc107e 100644 --- a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml @@ -339,6 +339,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- diff --git a/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml b/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml index 566fcf33..8ade4b54 100644 --- a/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml +++ b/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml @@ -239,6 +239,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- diff --git a/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml b/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml index 02cd63b5..85d9f2fa 100644 --- a/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml +++ b/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml @@ -376,6 +376,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- diff --git a/config/crd/bases/config.projectsveltos.io_profiles.yaml b/config/crd/bases/config.projectsveltos.io_profiles.yaml index 81a779fc..a3a18f3f 100644 --- a/config/crd/bases/config.projectsveltos.io_profiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_profiles.yaml @@ -339,6 +339,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- diff --git a/controllers/export_test.go b/controllers/export_test.go index d4d29414..af23095b 100644 --- a/controllers/export_test.go +++ b/controllers/export_test.go @@ -258,11 +258,13 @@ var ( ) var ( - IsCRDEstablished = isCRDEstablished + IsCRDEstablished = isCRDEstablished + GetHelmUpgradeClient = getHelmUpgradeClient ) type ( - ReferencedObject = referencedObject + ReferencedObject = referencedObject + RegistryClientOptions = registryClientOptions ) // NewDeploymentContext constructs a deploymentContext for use in tests. diff --git a/controllers/handlers_helm.go b/controllers/handlers_helm.go index dc3f466b..9b48ae66 100644 --- a/controllers/handlers_helm.go +++ b/controllers/handlers_helm.go @@ -4077,6 +4077,14 @@ func getWaitHelmValue(options *configv1beta1.HelmOptions) bool { return false } +func getPostRenderStrategyValue(options *configv1beta1.HelmOptions) configv1beta1.PostRenderStrategy { + if options != nil { + return options.PostRenderStrategy + } + + return "" +} + func getWaitForJobsHelmValue(options *configv1beta1.HelmOptions) bool { if options != nil { return options.WaitForJobs @@ -4412,6 +4420,7 @@ func getHelmInstallClient(ctx context.Context, requestedChart *configv1beta1.Hel if len(patches) > 0 { installClient.PostRenderer = &patcher.CustomPatchPostRenderer{Patches: patches} + installClient.PostRenderStrategy = action.PostRenderStrategy(getPostRenderStrategyValue(requestedChart.Options)) } if templateOnly { @@ -4474,6 +4483,7 @@ func getHelmUpgradeClient(requestedChart *configv1beta1.HelmChart, actionConfig if len(patches) > 0 { upgradeClient.PostRenderer = &patcher.CustomPatchPostRenderer{Patches: patches} + upgradeClient.PostRenderStrategy = action.PostRenderStrategy(getPostRenderStrategyValue(requestedChart.Options)) } return upgradeClient diff --git a/controllers/handlers_helm_test.go b/controllers/handlers_helm_test.go index 699f83bc..cd7c06ab 100644 --- a/controllers/handlers_helm_test.go +++ b/controllers/handlers_helm_test.go @@ -30,6 +30,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "helm.sh/helm/v4/pkg/action" releasecommon "helm.sh/helm/v4/pkg/release/common" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -2312,3 +2313,40 @@ var _ = Describe("IsCRDEstablished", func() { Expect(controllers.IsCRDEstablished(obj)).To(BeFalse()) }) }) + +var _ = Describe("getHelmUpgradeClient PostRenderStrategy", func() { + It("sets PostRenderer and PostRenderStrategy when patches are present", func() { + requestedChart := &configv1beta1.HelmChart{ + ReleaseName: randomString(), + ReleaseNamespace: randomString(), + Options: &configv1beta1.HelmOptions{ + PostRenderStrategy: configv1beta1.PostRenderStrategySeparate, + }, + } + patches := []libsveltosv1beta1.Patch{ + {Patch: randomString()}, + } + + upgradeClient := controllers.GetHelmUpgradeClient(requestedChart, &action.Configuration{}, + &controllers.RegistryClientOptions{}, patches) + + Expect(upgradeClient.PostRenderer).ToNot(BeNil()) + Expect(upgradeClient.PostRenderStrategy).To(Equal(action.PostRenderStrategySeparate)) + }) + + It("leaves PostRenderer and PostRenderStrategy unset when no patches are present", func() { + requestedChart := &configv1beta1.HelmChart{ + ReleaseName: randomString(), + ReleaseNamespace: randomString(), + Options: &configv1beta1.HelmOptions{ + PostRenderStrategy: configv1beta1.PostRenderStrategySeparate, + }, + } + + upgradeClient := controllers.GetHelmUpgradeClient(requestedChart, &action.Configuration{}, + &controllers.RegistryClientOptions{}, nil) + + Expect(upgradeClient.PostRenderer).To(BeNil()) + Expect(upgradeClient.PostRenderStrategy).To(Equal(action.PostRenderStrategyCombined)) + }) +}) diff --git a/manifest/manifest.yaml b/manifest/manifest.yaml index c50d694c..0d48ce0a 100644 --- a/manifest/manifest.yaml +++ b/manifest/manifest.yaml @@ -648,6 +648,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- @@ -2825,6 +2836,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- @@ -6016,6 +6038,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- @@ -8228,6 +8261,17 @@ spec: description: PassCredentialsAll is the flag to pass credentials to all domains type: boolean + postRenderStrategy: + description: |- + PostRenderStrategy controls whether Helm hooks are included when Patches/PatchesFrom + are applied as a post-renderer during this chart's install/upgrade. Only relevant + when Patches or PatchesFrom is set on the Spec; ignored otherwise. Defaults to + Helm's own default (combined) when unset. + enum: + - combined + - separate + - nohooks + type: string runTests: default: false description: |- From 6f0146dcb3fc0ab50293ad502eaf5e9bb3beee3f Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Mon, 6 Jul 2026 07:51:10 +0200 Subject: [PATCH 2/2] (chore) update badges --- .github/workflows/codeql.yaml | 49 ++++++++++++++++++++++++++++++++ .github/workflows/scorecard.yaml | 45 +++++++++++++++++++++++++++++ README.md | 3 +- 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/codeql.yaml create mode 100644 .github/workflows/scorecard.yaml diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 00000000..de45c7e9 --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,49 @@ +name: CodeQL +on: + push: + branches: + - 'main' + pull_request: + branches: + - 'main' + schedule: + - cron: '21 3 * * 3' + +permissions: read-all + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: ['go'] + + steps: + - name: checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up Go + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version: 1.26.5 + + - name: Initialize CodeQL + uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/scorecard.yaml b/.github/workflows/scorecard.yaml new file mode 100644 index 00000000..58dc5be5 --- /dev/null +++ b/.github/workflows/scorecard.yaml @@ -0,0 +1,45 @@ +name: Scorecard supply-chain security +on: + branch_protection_rule: + schedule: + - cron: '30 2 * * 1' + push: + branches: + - 'main' + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + security-events: write + id-token: write + contents: read + actions: read + + steps: + - name: checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Run analysis + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: Upload artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: Upload to code-scanning + uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + with: + sarif_file: results.sarif diff --git a/README.md b/README.md index 8411067b..f6c608a1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![CI](https://github.com/projectsveltos/addon-controller/actions/workflows/main.yaml/badge.svg)](https://github.com/projectsveltos/addon-controller/actions) -[![Go Report Card](https://goreportcard.com/badge/github.com/projectsveltos/addon-controller)](https://goreportcard.com/report/github.com/projectsveltos/addon-controller) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/projectsveltos/addon-controller/badge)](https://scorecard.dev/viewer/?uri=github.com/projectsveltos/addon-controller) +[![CodeQL](https://github.com/projectsveltos/addon-controller/actions/workflows/codeql.yaml/badge.svg)](https://github.com/projectsveltos/addon-controller/actions/workflows/codeql.yaml) [![Docker Pulls](https://img.shields.io/docker/pulls/projectsveltos/addon-controller.svg)](https://store.docker.com/community/images/projectsveltos/addon-controller) [![Release](https://img.shields.io/github/v/release/projectsveltos/addon-controller)](https://github.com/projectsveltos/addon-controller/releases) [![License](https://img.shields.io/badge/license-Apache-blue.svg)](LICENSE)