helmfile/pkg/state/chartify_kubecontext_test.go

290 lines
8.4 KiB
Go

package state
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem"
)
// TestProcessChartificationKubeContext tests that --kube-context flag is properly
// added to chartifyOpts.TemplateArgs when using jsonPatches with cluster-requiring commands.
// This is a regression test for the issue where helm template does not receive
// --kube-context arg when using kustomize (jsonPatches).
func TestProcessChartificationKubeContext(t *testing.T) {
tests := []struct {
name string
helmfileCommand string
kubeContext string
envKubeContext string
helmDefaults string
expectContext bool
expectDryRun bool
}{
{
name: "diff command with helmDefaults kubeContext",
helmfileCommand: "diff",
helmDefaults: "minikube",
expectContext: true,
expectDryRun: true,
},
{
name: "apply command with release kubeContext",
helmfileCommand: "apply",
kubeContext: "prod-cluster",
expectContext: true,
expectDryRun: true,
},
{
name: "sync command with env kubeContext",
helmfileCommand: "sync",
envKubeContext: "staging-cluster",
expectContext: true,
expectDryRun: true,
},
{
name: "template command should not add cluster flags",
helmfileCommand: "template",
helmDefaults: "minikube",
expectContext: false,
expectDryRun: false,
},
{
name: "build command should not add cluster flags",
helmfileCommand: "build",
helmDefaults: "minikube",
expectContext: false,
expectDryRun: false,
},
{
name: "diff command without kubeContext",
helmfileCommand: "diff",
expectContext: false,
expectDryRun: true,
},
{
name: "destroy command with kubeContext",
helmfileCommand: "destroy",
helmDefaults: "test-cluster",
expectContext: true,
expectDryRun: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a release with jsonPatches to trigger chartification
release := &ReleaseSpec{
Name: "test-release",
Namespace: "default",
Chart: "./test-chart",
JSONPatches: []interface{}{
map[string]interface{}{
"target": map[string]interface{}{
"group": "apps",
"version": "v1",
"kind": "Deployment",
"name": "test",
},
"patch": []interface{}{
map[string]interface{}{
"op": "add",
"path": "/spec/template/spec/containers/0/args/-",
"value": "test",
},
},
},
},
}
// Set kubeContext on release if provided
if tt.kubeContext != "" {
release.KubeContext = tt.kubeContext
}
// Create HelmState
state := &HelmState{
basePath: "/tmp/test",
fs: filesystem.FromFileSystem(filesystem.FileSystem{
DirectoryExistsAt: func(path string) bool {
return strings.Contains(path, "test-chart")
},
FileExistsAt: func(path string) bool {
return false
},
DeleteFile: func(path string) error {
return nil
},
Glob: func(pattern string) ([]string, error) {
return nil, nil
},
}),
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]any{},
ReleaseSetSpec: ReleaseSetSpec{
Env: environment.Environment{
Name: "default",
},
Environments: map[string]EnvironmentSpec{
"default": {
KubeContext: tt.envKubeContext,
},
},
},
}
// Set helmDefaults kubeContext if provided
if tt.helmDefaults != "" {
state.ReleaseSetSpec.HelmDefaults.KubeContext = tt.helmDefaults
}
// Prepare chartify (this generates the Chartify object with jsonPatches)
chartification, clean, err := state.PrepareChartify(nil, release, "./test-chart", 0)
require.NoError(t, err)
defer clean()
// Ensure chartification is needed (jsonPatches should trigger it)
require.NotNil(t, chartification, "Chartification should be needed when jsonPatches are present")
// Process chartification with the test command
opts := ChartPrepareOptions{}
_, _, err = state.processChartification(chartification, release, "./test-chart", opts, false, tt.helmfileCommand)
// We expect an error because we don't have an actual chart, but we can still
// check that TemplateArgs was set correctly
// The error will come from chartify.Chartify, not from our logic
// So let's check the chartification.Opts.TemplateArgs directly
if tt.expectContext {
// Determine which kubeContext should be used
expectedContext := tt.kubeContext
if expectedContext == "" {
expectedContext = tt.envKubeContext
}
if expectedContext == "" {
expectedContext = tt.helmDefaults
}
assert.Contains(t, chartification.Opts.TemplateArgs, "--kube-context",
"TemplateArgs should contain --kube-context flag")
assert.Contains(t, chartification.Opts.TemplateArgs, expectedContext,
"TemplateArgs should contain the expected kube context: %s", expectedContext)
} else if tt.helmDefaults != "" || tt.kubeContext != "" || tt.envKubeContext != "" {
// If a context is configured but not expected (offline commands)
assert.NotContains(t, chartification.Opts.TemplateArgs, "--kube-context",
"TemplateArgs should not contain --kube-context flag for offline commands")
}
if tt.expectDryRun {
assert.Contains(t, chartification.Opts.TemplateArgs, "--dry-run=server",
"TemplateArgs should contain --dry-run=server flag")
} else {
assert.NotContains(t, chartification.Opts.TemplateArgs, "--dry-run",
"TemplateArgs should not contain --dry-run flag for offline commands")
}
})
}
}
// TestProcessChartificationKubeContextPriority tests the priority order
// for kube context selection: release.KubeContext > env.KubeContext > helmDefaults.KubeContext
func TestProcessChartificationKubeContextPriority(t *testing.T) {
tests := []struct {
name string
releaseContext string
envContext string
helmDefaultsContext string
expectedContext string
}{
{
name: "release context takes priority",
releaseContext: "release-ctx",
envContext: "env-ctx",
helmDefaultsContext: "defaults-ctx",
expectedContext: "release-ctx",
},
{
name: "env context when release not set",
envContext: "env-ctx",
helmDefaultsContext: "defaults-ctx",
expectedContext: "env-ctx",
},
{
name: "helmDefaults context when others not set",
helmDefaultsContext: "defaults-ctx",
expectedContext: "defaults-ctx",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
release := &ReleaseSpec{
Name: "test-release",
Namespace: "default",
Chart: "./test-chart",
JSONPatches: []interface{}{
map[string]interface{}{
"target": map[string]interface{}{
"kind": "Deployment",
},
},
},
}
if tt.releaseContext != "" {
release.KubeContext = tt.releaseContext
}
state := &HelmState{
basePath: "/tmp/test",
fs: filesystem.FromFileSystem(filesystem.FileSystem{
DirectoryExistsAt: func(path string) bool {
return strings.Contains(path, "test-chart")
},
FileExistsAt: func(path string) bool {
return false
},
DeleteFile: func(path string) error {
return nil
},
Glob: func(pattern string) ([]string, error) {
return nil, nil
},
}),
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]any{},
ReleaseSetSpec: ReleaseSetSpec{
Env: environment.Environment{
Name: "default",
},
Environments: map[string]EnvironmentSpec{
"default": {
KubeContext: tt.envContext,
},
},
HelmDefaults: HelmSpec{
KubeContext: tt.helmDefaultsContext,
},
},
}
chartification, clean, err := state.PrepareChartify(nil, release, "./test-chart", 0)
require.NoError(t, err)
defer clean()
require.NotNil(t, chartification)
opts := ChartPrepareOptions{}
_, _, _ = state.processChartification(chartification, release, "./test-chart", opts, false, "diff")
assert.Contains(t, chartification.Opts.TemplateArgs, tt.expectedContext,
"TemplateArgs should contain the expected context: %s", tt.expectedContext)
})
}
}