diff --git a/pkg/app/pipedv1/plugin/kubernetes_multicluster/config/application.go b/pkg/app/pipedv1/plugin/kubernetes_multicluster/config/application.go index ca51727db0..6b740a33d9 100644 --- a/pkg/app/pipedv1/plugin/kubernetes_multicluster/config/application.go +++ b/pkg/app/pipedv1/plugin/kubernetes_multicluster/config/application.go @@ -195,10 +195,12 @@ func FindDeployTarget(cfg *config.PipedPlugin, name string) (KubernetesDeployTar } type KubernetesMultiTarget struct { - Target KubernetesMultiTargetDeployTarget `json:"target"` - Manifests []string `json:"manifests,omitempty"` - KubectlVersion string `json:"kubectlVersion,omitempty"` - KustomizeDir string `json:"kustomizeDir,omitempty"` + Target KubernetesMultiTargetDeployTarget `json:"target"` + Manifests []string `json:"manifests,omitempty"` + KubectlVersion string `json:"kubectlVersion,omitempty"` + KustomizeDir string `json:"kustomizeDir,omitempty"` + KustomizeVersion string `json:"kustomizeVersion,omitempty"` + KustomizeOptions map[string]string `json:"kustomizeOptions,omitempty"` } type KubernetesMultiTargetDeployTarget struct { diff --git a/pkg/app/pipedv1/plugin/kubernetes_multicluster/deployment/load_manifests_test.go b/pkg/app/pipedv1/plugin/kubernetes_multicluster/deployment/load_manifests_test.go new file mode 100644 index 0000000000..356d1cc03c --- /dev/null +++ b/pkg/app/pipedv1/plugin/kubernetes_multicluster/deployment/load_manifests_test.go @@ -0,0 +1,128 @@ +// Copyright 2025 The PipeCD 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 +// +// http://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 deployment + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + + sdk "github.com/pipe-cd/piped-plugin-sdk-go" + + kubeconfig "github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/kubernetes_multicluster/config" + "github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/kubernetes_multicluster/provider" +) + +// capturingLoader records the LoaderInput it receives so tests can assert on it. +type capturingLoader struct { + capturedInput provider.LoaderInput +} + +func (c *capturingLoader) LoadManifests(_ context.Context, input provider.LoaderInput) ([]provider.Manifest, error) { + c.capturedInput = input + return nil, nil +} + +func TestLoadManifests_KustomizeOverrides(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + specInput kubeconfig.KubernetesDeploymentInput + multiTarget *kubeconfig.KubernetesMultiTarget + wantVersion string + wantOptions map[string]string + }{ + { + name: "top-level version and options used when multiTarget has none", + specInput: kubeconfig.KubernetesDeploymentInput{ + KustomizeVersion: "5.3.0", + KustomizeOptions: map[string]string{"flag": "val"}, + }, + multiTarget: &kubeconfig.KubernetesMultiTarget{}, + wantVersion: "5.3.0", + wantOptions: map[string]string{"flag": "val"}, + }, + { + name: "multiTarget version overrides top-level version", + specInput: kubeconfig.KubernetesDeploymentInput{ + KustomizeVersion: "5.3.0", + }, + multiTarget: &kubeconfig.KubernetesMultiTarget{ + KustomizeVersion: "5.4.3", + }, + wantVersion: "5.4.3", + wantOptions: nil, + }, + { + name: "multiTarget options override top-level options", + specInput: kubeconfig.KubernetesDeploymentInput{ + KustomizeOptions: map[string]string{"original": "val"}, + }, + multiTarget: &kubeconfig.KubernetesMultiTarget{ + KustomizeOptions: map[string]string{"load-restrictor": "LoadRestrictionsNone"}, + }, + wantVersion: "", + wantOptions: map[string]string{"load-restrictor": "LoadRestrictionsNone"}, + }, + { + name: "multiTarget version and options both override top-level", + specInput: kubeconfig.KubernetesDeploymentInput{ + KustomizeVersion: "5.3.0", + KustomizeOptions: map[string]string{"flag": "val"}, + }, + multiTarget: &kubeconfig.KubernetesMultiTarget{ + KustomizeVersion: "5.4.3", + KustomizeOptions: map[string]string{"enable-helm": ""}, + }, + wantVersion: "5.4.3", + wantOptions: map[string]string{"enable-helm": ""}, + }, + { + name: "nil multiTarget uses top-level values", + specInput: kubeconfig.KubernetesDeploymentInput{ + KustomizeVersion: "5.3.0", + KustomizeOptions: map[string]string{"key": "val"}, + }, + multiTarget: nil, + wantVersion: "5.3.0", + wantOptions: map[string]string{"key": "val"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + cl := &capturingLoader{} + p := &Plugin{} + + spec := &kubeconfig.KubernetesApplicationSpec{ + Input: tt.specInput, + } + deploy := &sdk.Deployment{PipedID: "piped-id", ApplicationID: "app-id"} + source := &sdk.DeploymentSource[kubeconfig.KubernetesApplicationSpec]{} + + _, err := p.loadManifests(context.Background(), deploy, spec, source, cl, zaptest.NewLogger(t), tt.multiTarget) + require.NoError(t, err) + + assert.Equal(t, tt.wantVersion, cl.capturedInput.KustomizeVersion) + assert.Equal(t, tt.wantOptions, cl.capturedInput.KustomizeOptions) + }) + } +} diff --git a/pkg/app/pipedv1/plugin/kubernetes_multicluster/deployment/plugin.go b/pkg/app/pipedv1/plugin/kubernetes_multicluster/deployment/plugin.go index f850dd08c9..e7c79abdce 100644 --- a/pkg/app/pipedv1/plugin/kubernetes_multicluster/deployment/plugin.go +++ b/pkg/app/pipedv1/plugin/kubernetes_multicluster/deployment/plugin.go @@ -89,14 +89,22 @@ func (p *Plugin) ExecuteStage(ctx context.Context, _ *sdk.ConfigNone, dts []*sdk } func (p *Plugin) loadManifests(ctx context.Context, deploy *sdk.Deployment, spec *kubeconfig.KubernetesApplicationSpec, deploymentSource *sdk.DeploymentSource[kubeconfig.KubernetesApplicationSpec], loader loader, logger *zap.Logger, multiTarget *kubeconfig.KubernetesMultiTarget) ([]provider.Manifest, error) { - // Override manifest paths and kustomize directory from the per-target config if set. + // Start with top-level input values, then override with per-target values when set. manifestPathes := spec.Input.Manifests kustomizeDir := "" + kustomizeVersion := spec.Input.KustomizeVersion + kustomizeOptions := spec.Input.KustomizeOptions if multiTarget != nil { if len(multiTarget.Manifests) > 0 { manifestPathes = multiTarget.Manifests } kustomizeDir = multiTarget.KustomizeDir + if multiTarget.KustomizeVersion != "" { + kustomizeVersion = multiTarget.KustomizeVersion + } + if len(multiTarget.KustomizeOptions) > 0 { + kustomizeOptions = multiTarget.KustomizeOptions + } } manifests, err := loader.LoadManifests(ctx, provider.LoaderInput{ @@ -108,9 +116,9 @@ func (p *Plugin) loadManifests(ctx context.Context, deploy *sdk.Deployment, spec ConfigFilename: deploymentSource.ApplicationConfigFilename, Manifests: manifestPathes, Namespace: spec.Input.Namespace, - KustomizeVersion: spec.Input.KustomizeVersion, + KustomizeVersion: kustomizeVersion, KustomizeDir: kustomizeDir, - KustomizeOptions: spec.Input.KustomizeOptions, + KustomizeOptions: kustomizeOptions, HelmVersion: spec.Input.HelmVersion, HelmChart: spec.Input.HelmChart, HelmOptions: spec.Input.HelmOptions,