mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-17 21:56:08 +02:00
* fix: resolve issues #2295, #2296, #2297 and OCI registry login This PR fixes four related bugs affecting chart preparation, caching, and OCI registry authentication. Issue #2295: OCI chart cache conflicts with parallel helmfile processes - Added filesystem-level locking using flock for cross-process sync - Implements double-check locking pattern for efficiency - Retry logic with 5-minute timeout and 3 retries - Refactored into reusable acquireChartLock() helper function - Added refresh marker coordination for cross-process cache management Issue #2296: helmDefaults.skipDeps and helmDefaults.skipRefresh ignored - Check both CLI options AND helmDefaults when deciding to skip repo sync Issue #2297: Local chart + transformers causes panic - Normalize local chart paths to absolute before calling chartify OCI Registry Login URL Fix: - Added extractRegistryHost() to extract just the registry host from URLs - Fixed SyncRepos to use extracted host for OCI registry login - e.g., "account.dkr.ecr.region.amazonaws.com/charts" -> "account.dkr.ecr.region.amazonaws.com" Test Plan: - Unit tests for issues #2295, #2296, #2297 - Unit tests for OCI registry login (extractRegistryHost, SyncRepos_OCI) - Integration tests for issues #2295 and #2297 - All existing unit tests pass (including TestLint) Fixes #2295 Fixes #2296 Fixes #2297 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: replace 60s timeout with reader-writer locks for OCI chart caching Address PR review feedback from @champtar about the OCI chart caching mechanism. The previous implementation used a 60-second timeout which was arbitrary and caused race conditions when helm deployments took longer (e.g., deployments triggering scaling up/down). Changes: - Replace 60s refresh marker timeout with proper reader-writer locks - Use shared locks (RLock) when using cached charts (allows concurrent reads) - Use exclusive locks (Lock) when refreshing/downloading charts - Hold locks during entire helm operation lifecycle (not just during download) - Add getNamedRWMutex() for in-process RW coordination - Update PrepareCharts() to return locks map for lifecycle management - Add chartLockReleaser in run.go to release locks after helm callback - Remove unused mutexMap and getNamedMutex (replaced by RW versions) - Add comprehensive tests for shared/exclusive lock behavior This eliminates the race condition where one process could delete a cached chart while another process's helm command was still using it. Fixes review comment on PR #2298 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: prevent deadlock when multiple releases share the same chart When multiple releases use the same OCI chart (e.g., same chart different values), workers in PrepareCharts would deadlock: 1. Worker 1 acquires lock for chart/path, downloads, adds to cache 2. Worker 2 finds chart in cache, tries to acquire lock on same path 3. Worker 2 blocks waiting for Worker 1's lock 4. Collector waits for Worker 2's result 5. Worker 1's lock held until PrepareCharts finishes -> deadlock The fix: when using the in-memory chart cache (which means another worker in the same process already downloaded the chart), don't acquire another lock. This is safe because: - The in-memory cache is only used within a single helmfile process - The tempDir cleanup is deferred until after helm callback completes - Cross-process coordination is still handled by file locks during downloads This fixes the "signal: killed" test failures in CI for: - oci_chart_pull_direct - oci_chart_pull_once - oci_chart_pull_once2 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: resolve deadlock by releasing OCI chart locks immediately after download This commit simplifies the OCI chart locking mechanism to fix deadlock issues that occurred when multiple releases shared the same chart. Problem: When multiple releases used the same OCI chart, workers in PrepareCharts would deadlock because: 1. Worker 1 acquires lock for chart/path, downloads chart 2. Worker 2 tries to acquire lock on same path, blocks waiting 3. PrepareCharts waits for all workers to complete 4. Worker 1's lock held until PrepareCharts finishes -> deadlock Solution: Release locks immediately after chart download completes. This is safe because: - The tempDir cleanup is deferred until after helm operations complete in withPreparedCharts(), so charts won't be deleted mid-use - The in-memory chart cache prevents redundant downloads within a process - Cross-process coordination via file locks still works during download Changes: - Remove chartLock field from chartPrepareResult struct - Release locks immediately in getOCIChart() and forcedDownloadChart() - Simplify PrepareCharts() by removing lock collection and release logic - Update function signatures to return only (path, error) This also fixes the "signal: killed" test failures in CI for: - oci_chart_pull_direct - oci_chart_pull_once - oci_chart_pull_once2 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: add double-check locking for in-memory chart cache When multiple workers concurrently process releases using the same chart, they all check the in-memory cache before acquiring locks. If none have populated the cache yet, all workers miss and try to download. Previously, even after acquiring the exclusive lock, the code would re-download the chart when needsRefresh=true (the default). This caused multiple "Pulling" messages in tests like oci_chart_pull_once. The fix adds a second in-memory cache check AFTER acquiring the lock. This implements proper double-check locking: 1. Check cache (outside lock) → miss 2. Acquire lock 3. Check cache again (inside lock) → hit if another worker populated it 4. If still miss, download and add to cache This ensures only one worker downloads the chart, while others use the cached version populated by the first worker. Changes: - Add in-memory cache double-check in getOCIChart() after acquiring lock - Add in-memory cache double-check in forcedDownloadChart() after acquiring lock This fixes the oci_chart_pull_once and oci_chart_pull_direct test failures where charts were being pulled multiple times instead of once. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: use callback to prevent redundant chart downloads within a process When multiple workers concurrently process releases using the same chart, they need to coordinate to avoid redundant downloads. The previous fix set SkipRefresh=true for OCI charts, which prevented legitimate refresh scenarios (e.g., floating tags). This commit implements a better solution using a callback mechanism: 1. acquireChartLock() now accepts an optional skipRefreshCheck callback 2. Before deleting a cached chart for refresh, the callback is invoked 3. If the callback returns true (in-memory cache has the chart), skip refresh 4. This allows deduplication within a process while respecting cross-run refresh The flow is now: - Worker 1 downloads chart, adds to in-memory cache, releases lock - Worker 2 acquires lock, sees needsRefresh=true, but callback sees in-memory cache is populated → uses cached instead of deleting This correctly handles: - Within-process deduplication: only one download per chart - Cross-run refresh: respects --skip-refresh flag for floating tags - Immutable versions: cached and reused as expected Changes: - Add skipRefreshCheck callback parameter to acquireChartLock() - Update getOCIChart() to pass in-memory cache check callback - Update forcedDownloadChart() to pass in-memory cache check callback - Remove SkipRefresh=true workaround for OCI charts Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: address Copilot review comments on PR #2298 This commit addresses the automated review comments from GitHub Copilot: 1. pkg/state/state.go: Add nil check for logger in Release() method to prevent potential nil pointer dereference when logger is nil. 2. pkg/state/state.go: Fix misleading comment about "external callers" to accurately reflect that Logger() is used by the app package. 3. pkg/state/issue_2296_test.go: Add comment noting that boolPtr helper is already defined in skip_test.go (shared across test files). 4. test/integration/test-cases/oci-parallel-pull.sh: Replace hardcoded /tmp paths with a dedicated temp directory for test outputs. Add cleanup for the output directory in the cleanup function. 5. test/integration/test-cases/issue-2297-local-chart-transformers.sh: Add cleanup trap to remove temp directory on exit, preventing leftover files from accumulating. 6. Remove dead code: The chartLocks map in PrepareCharts was always empty since locks are released immediately after download. Removed the unused return value and corresponding handling in run.go to improve code clarity and maintainability. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: make oci-parallel-pull test resilient to registry issues The integration test was intermittently failing in CI due to Docker Hub rate limiting or network issues. These failures are not helmfile bugs. Changes: - Add is_registry_error() function to detect external registry issues (rate limits, network timeouts, connection refused, etc.) - Check for the race condition bug (issue #2295) first and fail fast - If other failures occur, check if they're registry-related - Skip test gracefully when registry issues are detected instead of failing CI on external infrastructure problems This ensures the test still catches the actual race condition bug while not causing false failures due to Docker Hub rate limits in CI. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: make oci-parallel-pull test resilient to registry issues The integration test was failing in CI for two reasons: 1. Docker Hub rate limiting or network issues causing helmfile to fail 2. The test script exits early due to `set -e` when `wait` returns non-zero Changes: - Use `wait $pid || exit=$?` pattern to capture exit codes without triggering set -e. When wait returns non-zero, the || branch captures the exit code into the variable, preventing script termination. - Add is_registry_error() function to detect external registry issues (rate limits, network timeouts, connection refused, etc.) - Check for the race condition bug (issue #2295) first and fail fast - Skip test gracefully when registry issues are detected instead of failing CI on external infrastructure problems This ensures the test still catches the actual race condition bug while not causing false failures due to Docker Hub rate limits in CI. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: address PR #2298 review - reinitialize fileLock after release Address Copilot review comments: 1. pkg/state/state.go: Reinitialize fileLock after releasing shared lock When upgrading from shared to exclusive lock, the fileLock needs to be reinitialized with flock.New() after calling Release(). This ensures a fresh flock object is used for the exclusive lock acquisition. 2. test/integration/test-cases/oci-parallel-pull.sh: Add lock file verification warning if no lock files are found, to ensure the locking mechanism is actually being tested. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: address PR #2298 Copilot review comments (round 4) Address 8 Copilot review comments: 1. pkg/state/state.go: Release in-process mutex during retry backoff to avoid blocking other goroutines for up to 90 seconds. 2. pkg/state/state.go: Include chartPath in shared lock error message for better debugging. 3. pkg/state/state.go: Document that extractRegistryHost does not handle URLs with query parameters or fragments (uncommon for OCI registries). 4. pkg/state/state.go: Document that skipRefreshCheck callback should be fast and non-blocking since it runs while holding exclusive lock. 5. oci-parallel-pull.sh: Use case-insensitive grep (-i flag) to catch error variations like "I/O timeout". 6. helmfile.yaml: Expand comment explaining why library charts can't be used for this test (they can't be templated by Helm). Skipped (with justification): - PrepareChartKey helper: Only 2 usages with different source structs - Context reuse in retry: Per-attempt contexts provide clearer semantics Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: address PR #2298 Copilot review comments (round 5) 1. Make race condition detection grep more robust (oci-parallel-pull.sh) - Use case-insensitive extended regex (-iqE) - Add multiple pattern variations to catch different tar/helm versions 2. Remove unused Logger() method from HelmState (state.go) - Method was never called; all lock releases use st.logger directly 3. Add clarifying comments for lock retry behavior (state.go) - Document why file system errors are retried but timeouts are not - Explain flock returns (false, nil) on context deadline exceeded Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: clarify lock file check is informational only Lock files are ephemeral and may be cleaned up immediately after helmfile processes complete. Update comments and warning message to make clear their absence doesn't indicate locking wasn't used. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * fix: add HELM_BIN env var to Dockerfiles The helm-git plugin requires HELM_BIN environment variable to be set. Without it, the plugin fails with "HELM_BIN: parameter not set". Add HELM_BIN=/usr/local/bin/helm to all Dockerfile variants. Fixes #2303 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> --------- Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
4916 lines
117 KiB
Go
4916 lines
117 KiB
Go
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
"github.com/helmfile/vals"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/helmfile/helmfile/pkg/environment"
|
|
"github.com/helmfile/helmfile/pkg/exectest"
|
|
"github.com/helmfile/helmfile/pkg/filesystem"
|
|
"github.com/helmfile/helmfile/pkg/helmexec"
|
|
"github.com/helmfile/helmfile/pkg/testhelper"
|
|
)
|
|
|
|
var logger = helmexec.NewLogger(io.Discard, "warn")
|
|
var valsRuntime, _ = vals.New(vals.Options{CacheSize: 32})
|
|
|
|
func injectFs(st *HelmState, fs *testhelper.TestFs) *HelmState {
|
|
st.fs = fs.ToFileSystem()
|
|
return st
|
|
}
|
|
|
|
func TestLabelParsing(t *testing.T) {
|
|
cases := []struct {
|
|
labelString string
|
|
expectedFilter LabelFilter
|
|
errorExected bool
|
|
}{
|
|
{"foo=bar", LabelFilter{positiveLabels: [][]string{{"foo", "bar"}}, negativeLabels: [][]string{}}, false},
|
|
{"foo!=bar", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{{"foo", "bar"}}}, false},
|
|
{"foo!=bar,baz=bat", LabelFilter{positiveLabels: [][]string{{"baz", "bat"}}, negativeLabels: [][]string{{"foo", "bar"}}}, false},
|
|
{"foo", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{}}, true},
|
|
{"foo!=bar=baz", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{}}, true},
|
|
{"=bar", LabelFilter{positiveLabels: [][]string{}, negativeLabels: [][]string{}}, true},
|
|
}
|
|
for idx, c := range cases {
|
|
filter, err := ParseLabels(c.labelString)
|
|
if err != nil && !c.errorExected {
|
|
t.Errorf("[%d] Didn't expect an error parsing labels: %s", idx, err)
|
|
} else if err == nil && c.errorExected {
|
|
t.Errorf("[%d] Expected %s to result in an error but got none", idx, c.labelString)
|
|
} else if !reflect.DeepEqual(filter, c.expectedFilter) {
|
|
t.Errorf("[%d] parsed label did not result in expected filter: %v, expected: %v", idx, filter, c.expectedFilter)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHelmState_applyDefaultsTo(t *testing.T) {
|
|
type fields struct {
|
|
BaseChartPath string
|
|
Context string
|
|
Namespace string
|
|
Repositories []RepositorySpec
|
|
Releases []ReleaseSpec
|
|
}
|
|
type args struct {
|
|
spec ReleaseSpec
|
|
}
|
|
verify := false
|
|
specWithNamespace := ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &verify,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
Values: nil,
|
|
SetValues: nil,
|
|
EnvValues: nil,
|
|
}
|
|
|
|
specWithoutNamespace := specWithNamespace
|
|
specWithoutNamespace.Namespace = ""
|
|
specWithNamespaceFromFields := specWithNamespace
|
|
specWithNamespaceFromFields.Namespace = "test-namespace-field"
|
|
|
|
fieldsWithNamespace := fields{
|
|
BaseChartPath: ".",
|
|
Context: "test_context",
|
|
Namespace: specWithNamespaceFromFields.Namespace,
|
|
Repositories: nil,
|
|
Releases: []ReleaseSpec{
|
|
specWithNamespace,
|
|
},
|
|
}
|
|
|
|
fieldsWithoutNamespace := fieldsWithNamespace
|
|
fieldsWithoutNamespace.Namespace = ""
|
|
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want ReleaseSpec
|
|
}{
|
|
{
|
|
name: "Has a namespace from spec",
|
|
fields: fieldsWithoutNamespace,
|
|
args: args{
|
|
spec: specWithNamespace,
|
|
},
|
|
want: specWithNamespace,
|
|
},
|
|
{
|
|
name: "Has a namespace from flags and from spec",
|
|
fields: fieldsWithNamespace,
|
|
args: args{
|
|
spec: specWithNamespace,
|
|
},
|
|
want: specWithNamespaceFromFields,
|
|
},
|
|
{
|
|
name: "Spec and flag Has no a namespace",
|
|
fields: fieldsWithoutNamespace,
|
|
args: args{
|
|
spec: specWithoutNamespace,
|
|
},
|
|
want: specWithoutNamespace,
|
|
},
|
|
{
|
|
name: "Spec has no a namespace but from flag",
|
|
fields: fieldsWithNamespace,
|
|
args: args{
|
|
spec: specWithoutNamespace,
|
|
},
|
|
want: specWithNamespaceFromFields,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
basePath: tt.fields.BaseChartPath,
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
OverrideNamespace: tt.fields.Namespace,
|
|
Repositories: tt.fields.Repositories,
|
|
Releases: tt.fields.Releases,
|
|
},
|
|
}
|
|
if state.ApplyOverrides(&tt.args.spec); !reflect.DeepEqual(tt.args.spec, tt.want) {
|
|
t.Errorf("HelmState.ApplyOverrides() = %v, want %v", tt.args.spec, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func boolValue(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
func TestHelmState_flagsForUpgrade(t *testing.T) {
|
|
enable := true
|
|
disable := false
|
|
postRendererDefault := "foo-default.sh"
|
|
postRendererRelease := "foo-release.sh"
|
|
some := func(v int) *int {
|
|
return &v
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
version *semver.Version
|
|
defaults HelmSpec
|
|
release *ReleaseSpec
|
|
syncOpts *SyncOpts
|
|
want []string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "no-options",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "verify",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--verify",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "verify-from-default",
|
|
defaults: HelmSpec{
|
|
Verify: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "enable-dns",
|
|
defaults: HelmSpec{
|
|
EnableDNS: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
EnableDNS: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--enable-dns",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "enable-dns-from-default",
|
|
defaults: HelmSpec{
|
|
EnableDNS: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
EnableDNS: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "force",
|
|
defaults: HelmSpec{
|
|
Force: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Force: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--force",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "force-from-default",
|
|
defaults: HelmSpec{
|
|
Force: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Force: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "recreate-pods",
|
|
defaults: HelmSpec{
|
|
RecreatePods: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
RecreatePods: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--recreate-pods",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "recreate-pods-from-default",
|
|
defaults: HelmSpec{
|
|
RecreatePods: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
RecreatePods: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "wait",
|
|
defaults: HelmSpec{
|
|
Wait: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Wait: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--wait",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "wait-for-jobs",
|
|
defaults: HelmSpec{
|
|
WaitForJobs: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
WaitForJobs: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--wait-for-jobs",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "devel",
|
|
defaults: HelmSpec{
|
|
Devel: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Wait: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--devel",
|
|
"--wait",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "devel-release",
|
|
defaults: HelmSpec{
|
|
Devel: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Devel: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "wait-from-default",
|
|
defaults: HelmSpec{
|
|
Wait: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Wait: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "timeout",
|
|
defaults: HelmSpec{
|
|
Timeout: 0,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Timeout: some(123),
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--timeout", "123s",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "timeout-from-default",
|
|
defaults: HelmSpec{
|
|
Timeout: 123,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Timeout: nil,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--timeout", "123s",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "timeout-from-cli-flag",
|
|
defaults: HelmSpec{
|
|
Timeout: 123,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Timeout: some(456),
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
syncOpts: &SyncOpts{
|
|
Timeout: 789,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--timeout", "789s",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "atomic",
|
|
defaults: HelmSpec{
|
|
Atomic: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Atomic: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--atomic",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "atomic-override-default",
|
|
defaults: HelmSpec{
|
|
Atomic: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Atomic: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "atomic-from-default",
|
|
defaults: HelmSpec{
|
|
Atomic: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--atomic",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "cleanup-on-fail",
|
|
defaults: HelmSpec{
|
|
CleanupOnFail: false,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
CleanupOnFail: &enable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--cleanup-on-fail",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "cleanup-on-fail-override-default",
|
|
defaults: HelmSpec{
|
|
CleanupOnFail: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
CleanupOnFail: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "cleanup-on-fail-from-default",
|
|
defaults: HelmSpec{
|
|
CleanupOnFail: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--cleanup-on-fail",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-default-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--create-namespace",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-disabled-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &disable,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-release-override-enabled-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &disable,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &enable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--create-namespace",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-release-override-disabled-helm3.2",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.2.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "create-namespace-unsupported",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("2.16.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
},
|
|
wantErr: "releases[].createNamespace requires Helm 3.2.0 or greater",
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererDefault,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release-prior-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
basePath: "./",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{*tt.release},
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Version: tt.version,
|
|
}
|
|
|
|
args, _, err := state.flagsForUpgrade(helm, tt.release, 0, tt.syncOpts)
|
|
if err != nil && tt.wantErr == "" {
|
|
t.Errorf("unexpected error flagsForUpgrade: %v", err)
|
|
}
|
|
if tt.wantErr != "" && (err == nil || err.Error() != tt.wantErr) {
|
|
t.Errorf("expected error '%v'; got '%v'", err, tt.wantErr)
|
|
}
|
|
if !reflect.DeepEqual(args, tt.want) {
|
|
t.Errorf("flagsForUpgrade returned = %v, want %v", args, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_flagsForTemplate(t *testing.T) {
|
|
enable := true
|
|
disable := false
|
|
postRendererDefault := "foo-default.sh"
|
|
postRendererRelease := "foo-release.sh"
|
|
|
|
tests := []struct {
|
|
name string
|
|
version *semver.Version
|
|
defaults HelmSpec
|
|
release *ReleaseSpec
|
|
templateOpts TemplateOpts
|
|
want []string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "post-renderer-flags-use-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererDefault,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "post-renderer-flags-use-release-prior-helmdefault",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
PostRenderer: &postRendererDefault,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
PostRenderer: &postRendererRelease,
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--post-renderer", postRendererRelease,
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "kube-version-flag-should-be-used",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
},
|
|
templateOpts: TemplateOpts{
|
|
KubeVersion: "1.100",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-version", "1.100",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
{
|
|
name: "kube-version-flag-should-be-respected",
|
|
defaults: HelmSpec{
|
|
Verify: false,
|
|
CreateNamespace: &enable,
|
|
},
|
|
version: semver.MustParse("3.10.0"),
|
|
release: &ReleaseSpec{
|
|
Chart: "test/chart",
|
|
Version: "0.1",
|
|
Verify: &disable,
|
|
Name: "test-charts",
|
|
Namespace: "test-namespace",
|
|
CreateNamespace: &disable,
|
|
KubeVersion: "1.25",
|
|
},
|
|
templateOpts: TemplateOpts{
|
|
KubeVersion: "1.100",
|
|
},
|
|
want: []string{
|
|
"--version", "0.1",
|
|
"--kube-version", "1.100",
|
|
"--namespace", "test-namespace",
|
|
},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
basePath: "./",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{*tt.release},
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Version: tt.version,
|
|
}
|
|
|
|
args, _, err := state.flagsForTemplate(helm, tt.release, 0, &(tt.templateOpts))
|
|
if err != nil && tt.wantErr == "" {
|
|
t.Errorf("unexpected error flagsForUpgrade: %v", err)
|
|
}
|
|
if tt.wantErr != "" && (err == nil || err.Error() != tt.wantErr) {
|
|
t.Errorf("expected error '%v'; got '%v'", err, tt.wantErr)
|
|
}
|
|
if !reflect.DeepEqual(args, tt.want) {
|
|
t.Errorf("flagsForUpgrade returned = %v, want %v", args, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_isLocalChart(t *testing.T) {
|
|
type args struct {
|
|
chart string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "local chart",
|
|
args: args{
|
|
chart: "./",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "repo chart",
|
|
args: args{
|
|
chart: "stable/genius",
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "empty",
|
|
args: args{
|
|
chart: "",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "parent local path",
|
|
args: args{
|
|
chart: "../examples",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "parent-parent local path",
|
|
args: args{
|
|
chart: "../../",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "absolute path",
|
|
args: args{
|
|
chart: "/foo/bar/baz",
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "remote chart in 3-level deep dir (e.g. ChartCenter)",
|
|
args: args{
|
|
chart: "center/bar/baz",
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := isLocalChart(tt.args.chart); got != tt.want {
|
|
t.Errorf("%s(\"%s\") isLocalChart(): got %v, want %v", tt.name, tt.args.chart, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_normalizeChart(t *testing.T) {
|
|
type args struct {
|
|
basePath string
|
|
chart string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{
|
|
name: "construct local chart path",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "./app",
|
|
},
|
|
want: "/src/app",
|
|
},
|
|
{
|
|
name: "construct local chart path, without leading dot",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "published",
|
|
},
|
|
want: "/src/published",
|
|
},
|
|
{
|
|
name: "repo path",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "remote/app",
|
|
},
|
|
want: "remote/app",
|
|
},
|
|
{
|
|
name: "chartcenter repo path",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "center/stable/myapp",
|
|
},
|
|
want: "center/stable/myapp",
|
|
},
|
|
{
|
|
name: "construct local chart path, sibling dir",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "../app",
|
|
},
|
|
want: "/app",
|
|
},
|
|
{
|
|
name: "construct local chart path, parent dir",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "./..",
|
|
},
|
|
want: "/",
|
|
},
|
|
{
|
|
name: "too much parent levels",
|
|
args: args{
|
|
basePath: "/src",
|
|
chart: "../../app",
|
|
},
|
|
want: "/app",
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := normalizeChart(tt.args.basePath, tt.args.chart); got != tt.want {
|
|
t.Errorf("normalizeChart() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// mocking helmexec.Interface
|
|
func TestHelmState_SyncRepos(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
repos []RepositorySpec
|
|
helm *exectest.Helm
|
|
envs map[string]string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "normal repository",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "", "", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "ACR hosted repository",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
Managed: "acr",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "", "", "", "", "", "", "acr", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with cert and key",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "certfile",
|
|
KeyFile: "keyfile",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "certfile", "keyfile", "", "", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with ca file",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CaFile: "cafile",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "cafile", "", "", "", "", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with username and password",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "example_user",
|
|
Password: "example_password",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with username and password and pass-credentials",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "example_user",
|
|
Password: "example_password",
|
|
PassCredentials: true,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", "", "true", "false"},
|
|
},
|
|
{
|
|
name: "repository without username and password and environment with username and password",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "",
|
|
Password: "",
|
|
},
|
|
},
|
|
envs: map[string]string{
|
|
"NAME_USERNAME": "example_user",
|
|
"NAME_PASSWORD": "example_password",
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with username and password and environment with username and password",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "example_user1",
|
|
Password: "example_password1",
|
|
},
|
|
},
|
|
envs: map[string]string{
|
|
"NAME_USERNAME": "example_user2",
|
|
"NAME_PASSWORD": "example_password2",
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "example_user1", "example_password1", "", "false", "false"},
|
|
},
|
|
{
|
|
name: "repository with skip-tls-verify",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "name",
|
|
URL: "http://example.com/",
|
|
CertFile: "",
|
|
KeyFile: "",
|
|
Username: "",
|
|
Password: "",
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []string{"name", "http://example.com/", "", "", "", "", "", "", "false", "true"},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
for k, v := range tt.envs {
|
|
t.Setenv(k, v)
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: tt.repos,
|
|
},
|
|
}
|
|
if _, _ = state.SyncRepos(tt.helm, map[string]bool{}); !reflect.DeepEqual(tt.helm.Repo, tt.want) {
|
|
t.Errorf("HelmState.SyncRepos() for [%s] = %v, want %v", tt.name, tt.helm.Repo, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleases(t *testing.T) {
|
|
postRenderer := "foo.sh"
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
wantReleases []exectest.Release
|
|
wantErrorMsgs []string
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--reset-values"}}},
|
|
},
|
|
{
|
|
name: "escaped values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "someList",
|
|
Value: "a,b,c",
|
|
},
|
|
{
|
|
Name: "json",
|
|
Value: "{\"name\": \"john\"}",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}", "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "set single value from file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo",
|
|
Value: "FOO",
|
|
},
|
|
{
|
|
Name: "bar",
|
|
File: "path/to/bar",
|
|
},
|
|
{
|
|
Name: "baz",
|
|
Value: "BAZ",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ", "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "set single array value in an array",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo.bar[0]",
|
|
Values: []string{
|
|
"A",
|
|
"B",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}", "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "post renderer helm 3",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
PostRenderer: &postRenderer,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{Helm3: true}, // Helm 3 keeps script paths unchanged
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--post-renderer", postRenderer, "--reset-values"}}},
|
|
},
|
|
{
|
|
name: "post renderer helm 4",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
PostRenderer: &postRenderer,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{Helm4: true}, // Helm 4 converts script paths to plugin names
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--post-renderer", "foo", "--reset-values"}}}, // "foo.sh" -> "foo"
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); len(errs) > 0 {
|
|
if len(errs) != len(tt.wantErrorMsgs) {
|
|
t.Fatalf("Unexpected errors: %v\nExpected: %v", errs, tt.wantErrorMsgs)
|
|
}
|
|
var mismatch int
|
|
for i := range tt.wantErrorMsgs {
|
|
expected := tt.wantErrorMsgs[i]
|
|
actual := errs[i].Error()
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Errorf("Unexpected error: expected=%v, got=%v", expected, actual)
|
|
}
|
|
}
|
|
if mismatch > 0 {
|
|
t.Fatalf("%d unexpected errors detected", mismatch)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Releases, tt.wantReleases) {
|
|
t.Errorf("HelmState.SyncReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.wantReleases)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing.T) {
|
|
no := false
|
|
tests := []struct {
|
|
name string
|
|
release ReleaseSpec
|
|
listResult string
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "should install",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: ``,
|
|
expectedError: ``,
|
|
},
|
|
{
|
|
name: "should upgrade",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: ``,
|
|
},
|
|
{
|
|
name: "should uninstall",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Installed: &no,
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: ``,
|
|
},
|
|
{
|
|
name: "should fail installing due to missing values file",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Values: []any{"noexistent.values.yaml"},
|
|
},
|
|
listResult: ``,
|
|
expectedError: `failed processing release foo: values file matching "noexistent.values.yaml" does not exist in "."`,
|
|
},
|
|
{
|
|
name: "should fail upgrading due to missing values file",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Values: []any{"noexistent.values.yaml"},
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: `failed processing release foo: values file matching "noexistent.values.yaml" does not exist in "."`,
|
|
},
|
|
{
|
|
name: "should uninstall even when there is a missing values file",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
Values: []any{"noexistent.values.yaml"},
|
|
Installed: &no,
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
expectedError: ``,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
basePath: ".",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{tt.release},
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
fs := testhelper.NewTestFs(map[string]string{})
|
|
state = injectFs(state, fs)
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
//simulate the helm.list call result
|
|
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$"}] = tt.listResult
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
errs := state.SyncReleases(&affectedReleases, helm, []string{}, 1)
|
|
|
|
if tt.expectedError != "" {
|
|
if len(errs) == 0 {
|
|
t.Fatalf("expected error not occurred: expected=%s, got none", tt.expectedError)
|
|
}
|
|
if len(errs) != 1 {
|
|
t.Fatalf("too many errors: expected %d, got %d: %v", 1, len(errs), errs)
|
|
}
|
|
err := errs[0]
|
|
if err.Error() != tt.expectedError {
|
|
t.Fatalf("unexpected error: expected=%s, got=%v", tt.expectedError, err)
|
|
}
|
|
} else {
|
|
if len(errs) > 0 {
|
|
t.Fatalf("unexpected error(s): expected=0, got=%d: %v", len(errs), errs)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
|
|
no := false
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
installed []bool
|
|
wantAffected exectest.Affected
|
|
}{
|
|
{
|
|
name: "2 release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo",
|
|
Chart: "foo",
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "bar",
|
|
},
|
|
},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo", Flags: []string{}},
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Deleted: nil,
|
|
Failed: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "2 removed",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo",
|
|
Chart: "foo",
|
|
Installed: &no,
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "foo",
|
|
Installed: &no,
|
|
},
|
|
},
|
|
installed: []bool{true, true},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: nil,
|
|
Deleted: []*exectest.Release{
|
|
{Name: "releaseNameFoo", Flags: []string{}},
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Failed: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "2 errors",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo-error",
|
|
Chart: "foo",
|
|
},
|
|
{
|
|
Name: "releaseNameBar-error",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: nil,
|
|
Deleted: nil,
|
|
Failed: []*exectest.Release{
|
|
{Name: "releaseNameFoo-error", Flags: []string{}},
|
|
{Name: "releaseNameBar-error", Flags: []string{}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "1 removed, 1 new, 1 error",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo",
|
|
Chart: "foo",
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "foo",
|
|
Installed: &no,
|
|
},
|
|
{
|
|
Name: "releaseNameFoo-error",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
installed: []bool{true, true, true},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo", Flags: []string{}},
|
|
},
|
|
Deleted: []*exectest.Release{
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Failed: []*exectest.Release{
|
|
{Name: "releaseNameFoo-error", Flags: []string{}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
//simulate the release is already installed
|
|
for i, release := range tt.releases {
|
|
if tt.installed != nil && tt.installed[i] {
|
|
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name
|
|
}
|
|
}
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil {
|
|
if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) {
|
|
t.Errorf("HelmState.SynchAffectedRelease() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed)
|
|
} //else expected error
|
|
}
|
|
if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) {
|
|
t.Errorf("HelmState.SynchAffectedRelease() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded)
|
|
}
|
|
if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) {
|
|
t.Errorf("HelmState.SynchAffectedRelease() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleasesAffectedReleasesWithReinstallIfForbidden(t *testing.T) {
|
|
no := false
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
installed []bool
|
|
wantAffected exectest.Affected
|
|
}{
|
|
{
|
|
name: "2 new",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
{
|
|
Name: "releaseNameBar-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
|
{Name: "releaseNameBar-forbidden", Flags: []string{}},
|
|
},
|
|
Reinstalled: nil,
|
|
Deleted: nil,
|
|
Failed: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "1 removed, 1 new, 1 reinstalled first new",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseNameFoo-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
{
|
|
Name: "releaseNameBar",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
Installed: &no,
|
|
},
|
|
{
|
|
Name: "releaseNameFoo-forbidden",
|
|
Chart: "foo",
|
|
UpdateStrategy: "reinstallIfForbidden",
|
|
},
|
|
},
|
|
installed: []bool{true, true, true},
|
|
wantAffected: exectest.Affected{
|
|
Upgraded: []*exectest.Release{
|
|
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
|
},
|
|
Reinstalled: []*exectest.Release{
|
|
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
|
},
|
|
Deleted: []*exectest.Release{
|
|
{Name: "releaseNameBar", Flags: []string{}},
|
|
},
|
|
Failed: nil,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
//simulate the release is already installed
|
|
for i, release := range tt.releases {
|
|
if tt.installed != nil && tt.installed[i] {
|
|
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name
|
|
}
|
|
}
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil {
|
|
if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) {
|
|
t.Errorf("HelmState.SyncReleases() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed)
|
|
} //else expected error
|
|
}
|
|
if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) {
|
|
t.Errorf("HelmState.SyncReleases() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded)
|
|
}
|
|
if !testEq(affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) {
|
|
t.Errorf("HelmState.SyncReleases() reinstalled failed for [%s] = %v, want %v", tt.name, affectedReleases.Reinstalled, tt.wantAffected.Reinstalled)
|
|
}
|
|
if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) {
|
|
t.Errorf("HelmState.SyncReleases() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testEq(a []*ReleaseSpec, b []*exectest.Release) bool {
|
|
// If one is nil, the other must also be nil.
|
|
if (a == nil) != (b == nil) {
|
|
return false
|
|
}
|
|
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
for i := range a {
|
|
if a[i].Name != b[i].Name {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func TestGetDeployedVersion(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
release ReleaseSpec
|
|
listResult string
|
|
installedVersion string
|
|
}{
|
|
{
|
|
name: "chart version",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-2.0.4 0.1.0 default`,
|
|
installedVersion: "2.0.4",
|
|
},
|
|
{
|
|
name: "chart version with a dash",
|
|
release: ReleaseSpec{
|
|
Name: "foo-bar",
|
|
Chart: "registry/foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-1.0.0-alpha.1 0.1.0 default`,
|
|
installedVersion: "1.0.0-alpha.1",
|
|
},
|
|
{
|
|
name: "chart version with dash and plus",
|
|
release: ReleaseSpec{
|
|
Name: "foo-bar",
|
|
Chart: "registry/foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-1.0.0-alpha+001 0.1.0 default`,
|
|
installedVersion: "1.0.0-alpha+001",
|
|
},
|
|
{
|
|
name: "chart version with dash and release with dash",
|
|
release: ReleaseSpec{
|
|
Name: "foo-bar",
|
|
Chart: "registry/foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo-bar-release 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar-1.0.0-alpha+001 0.1.0 default`,
|
|
installedVersion: "1.0.0-alpha+001",
|
|
},
|
|
{
|
|
name: "chart version from helm show chart",
|
|
release: ReleaseSpec{
|
|
Name: "foo",
|
|
Chart: "../../foo-bar",
|
|
},
|
|
listResult: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
|
foo 1 Wed Apr 17 17:39:04 2019 DEPLOYED foo-bar 0.1.0 default`,
|
|
installedVersion: "3.2.0",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{tt.release},
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
// simulate the helm.list call result
|
|
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = tt.listResult
|
|
|
|
affectedReleases := AffectedReleases{}
|
|
state.SyncReleases(&affectedReleases, helm, []string{}, 1)
|
|
|
|
if state.Releases[0].installedVersion != tt.installedVersion {
|
|
t.Errorf("HelmState.TestGetDeployedVersion() failed for [%s] = %v, want %v", tt.name, state.Releases[0].installedVersion, tt.installedVersion)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_DiffReleases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
wantReleases []exectest.Release
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--reset-values"}}},
|
|
},
|
|
{
|
|
name: "escaped values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "someList",
|
|
Value: "a,b,c",
|
|
},
|
|
{
|
|
Name: "json",
|
|
Value: "{\"name\": \"john\"}",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{
|
|
{Name: "releaseName", Flags: []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}", "--reset-values"}},
|
|
},
|
|
},
|
|
{
|
|
name: "set single value from file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo",
|
|
Value: "FOO",
|
|
},
|
|
{
|
|
Name: "bar",
|
|
File: "path/to/bar",
|
|
},
|
|
{
|
|
Name: "baz",
|
|
Value: "BAZ",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{
|
|
{Name: "releaseName", Flags: []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ", "--reset-values"}},
|
|
},
|
|
},
|
|
{
|
|
name: "set single array value in an array",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
SetValues: []SetValue{
|
|
{
|
|
Name: "foo.bar[0]",
|
|
Values: []string{
|
|
"A",
|
|
"B",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantReleases: []exectest.Release{
|
|
{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}", "--reset-values"}},
|
|
},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, []string{}, false, false, false, false, false)
|
|
if len(errs) > 0 {
|
|
t.Errorf("unexpected error: %v", errs)
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Diffed, tt.wantReleases) {
|
|
t.Errorf("HelmState.DiffReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.wantReleases)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_DiffFlags(t *testing.T) {
|
|
enable := true
|
|
disable := false
|
|
|
|
tests := []struct {
|
|
name string
|
|
defaults HelmSpec
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
wantDiffFlags []string
|
|
}{
|
|
{
|
|
name: "release with api version and kubeversion",
|
|
defaults: HelmSpec{},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
KubeVersion: "1.21",
|
|
ApiVersions: []string{"helmfile.test/v1", "helmfile.test/v2"},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--api-versions", "helmfile.test/v1", "--api-versions", "helmfile.test/v2", "--kube-version", "1.21"},
|
|
},
|
|
{
|
|
name: "release with kubeversion and plain http which is ignored",
|
|
defaults: HelmSpec{},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
KubeVersion: "1.21",
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--kube-version", "1.21"},
|
|
},
|
|
{
|
|
name: "release with enable-dns",
|
|
defaults: HelmSpec{EnableDNS: false},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
EnableDNS: &enable,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--enable-dns"},
|
|
},
|
|
{
|
|
name: "release with disable-dns override",
|
|
defaults: HelmSpec{EnableDNS: true},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
EnableDNS: &disable,
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: nil,
|
|
},
|
|
{
|
|
name: "release with enable-dns from default",
|
|
defaults: HelmSpec{EnableDNS: true},
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantDiffFlags: []string{"--enable-dns"},
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
for j := range tt.releases {
|
|
flags, _, errs := state.flagsForDiff(tt.helm, &tt.releases[j], false, 1, nil)
|
|
if errs != nil {
|
|
t.Errorf("unexpected error: %v", errs)
|
|
}
|
|
if !reflect.DeepEqual(flags, tt.wantDiffFlags) {
|
|
t.Errorf("HelmState.flagsForDiff() for [%s][%s] = %v, want %v", tt.name, tt.releases[j].Name, flags, tt.wantDiffFlags)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncReleasesCleanup(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
expectedNumRemovedFiles int
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 0,
|
|
},
|
|
{
|
|
name: "inline values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 1,
|
|
},
|
|
{
|
|
name: "inline values and values file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
"someFile",
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 2,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
numRemovedFiles := 0
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
testfs := testhelper.NewTestFs(map[string]string{
|
|
"/path/to/someFile": `foo: FOO`,
|
|
})
|
|
testfs.DeleteFile = func(f string) error {
|
|
numRemovedFiles += 1
|
|
return nil
|
|
}
|
|
state = injectFs(state, testfs)
|
|
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if errs := state.Clean(); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if numRemovedFiles != tt.expectedNumRemovedFiles {
|
|
t.Errorf("unexpected number of removed files: expected %d, got %d", tt.expectedNumRemovedFiles, numRemovedFiles)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_DiffReleasesCleanup(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
expectedNumRemovedFiles int
|
|
}{
|
|
{
|
|
name: "normal release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 0,
|
|
},
|
|
{
|
|
name: "inline values",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 1,
|
|
},
|
|
{
|
|
name: "inline values and values file",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseName",
|
|
Chart: "foo",
|
|
Values: []any{
|
|
map[any]any{
|
|
"someList": "a,b,c",
|
|
},
|
|
"someFile",
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
expectedNumRemovedFiles: 2,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
numRemovedFiles := 0
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
testfs := testhelper.NewTestFs(map[string]string{
|
|
"/path/to/someFile": `foo: bar
|
|
`,
|
|
})
|
|
testfs.DeleteFile = func(f string) error {
|
|
numRemovedFiles += 1
|
|
return nil
|
|
}
|
|
state = injectFs(state, testfs)
|
|
if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, []string{}, false, false, false, false, false); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if errs := state.Clean(); len(errs) > 0 {
|
|
t.Errorf("unexpected errors: %v", errs)
|
|
}
|
|
|
|
if numRemovedFiles != tt.expectedNumRemovedFiles {
|
|
t.Errorf("unexpected number of removed files: expected %d, got %d", tt.expectedNumRemovedFiles, numRemovedFiles)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_UpdateDeps(t *testing.T) {
|
|
helm := &exectest.Helm{
|
|
UpdateDepsCallbacks: map[string]func(string) error{},
|
|
}
|
|
|
|
var generatedDir string
|
|
tempDir := func(dir, prefix string) (string, error) {
|
|
var err error
|
|
generatedDir, err = os.MkdirTemp(dir, prefix)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// nolint: unparam
|
|
helm.UpdateDepsCallbacks[generatedDir] = func(chart string) error {
|
|
content := []byte(`dependencies:
|
|
- name: envoy
|
|
repository: https://kubernetes-charts.storage.googleapis.com
|
|
version: 1.5.0
|
|
- name: envoy
|
|
repository: https://kubernetes-charts.storage.googleapis.com
|
|
version: 1.4.0
|
|
digest: sha256:8194b597c85bb3d1fee8476d4a486e952681d5c65f185ad5809f2118bc4079b5
|
|
generated: 2019-05-16T15:42:45.50486+09:00
|
|
`)
|
|
filename := filepath.Join(generatedDir, "Chart.lock")
|
|
logger.Debugf("test: writing %s: %s", filename, content)
|
|
return os.WriteFile(filename, content, 0644)
|
|
}
|
|
return generatedDir, nil
|
|
}
|
|
|
|
logger := helmexec.NewLogger(io.Discard, "debug")
|
|
basePath := "/src"
|
|
state := &HelmState{
|
|
basePath: basePath,
|
|
FilePath: "/src/helmfile.yaml",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "/example",
|
|
},
|
|
{
|
|
Chart: "./example",
|
|
},
|
|
{
|
|
Chart: "published/deeper",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
Version: "1.5.0",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
Version: "1.4.0",
|
|
},
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "stable",
|
|
URL: "https://kubernetes-charts.storage.googleapis.com",
|
|
},
|
|
},
|
|
},
|
|
tempDir: tempDir,
|
|
logger: logger,
|
|
}
|
|
|
|
fs := testhelper.NewTestFs(map[string]string{
|
|
"/example/Chart.yaml": `foo: FOO`,
|
|
"/src/example/Chart.yaml": `foo: FOO`,
|
|
})
|
|
fs.Cwd = basePath
|
|
state = injectFs(state, fs)
|
|
errs := state.UpdateDeps(helm, false)
|
|
|
|
want := []string{"/example", "./example", generatedDir}
|
|
if !reflect.DeepEqual(helm.Charts, want) {
|
|
t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.Charts, want)
|
|
}
|
|
if len(errs) != 0 {
|
|
t.Errorf("HelmState.UpdateDeps() - unexpected %d errors: %v", len(errs), errs)
|
|
}
|
|
|
|
resolved, err := state.ResolveDeps()
|
|
if err != nil {
|
|
t.Errorf("HelmState.ResolveDeps() - unexpected error: %v", err)
|
|
}
|
|
|
|
if resolved.Releases[3].Version != "1.5.0" {
|
|
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version)
|
|
}
|
|
if resolved.Releases[4].Version != "1.4.0" {
|
|
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.4.0, got=%s", resolved.Releases[6].Version)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
|
|
logger := helmexec.NewLogger(io.Discard, "debug")
|
|
state := &HelmState{
|
|
basePath: "/src",
|
|
FilePath: "/src/helmfile.yaml",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "./..",
|
|
},
|
|
{
|
|
Chart: "../examples",
|
|
},
|
|
{
|
|
Chart: "../../helmfile",
|
|
},
|
|
{
|
|
Chart: "published",
|
|
},
|
|
{
|
|
Chart: "published/deeper",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
},
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "stable",
|
|
URL: "https://kubernetes-charts.storage.googleapis.com",
|
|
},
|
|
},
|
|
},
|
|
logger: logger,
|
|
fs: &filesystem.FileSystem{
|
|
ReadFile: func(f string) ([]byte, error) {
|
|
if f != "helmfile.lock" {
|
|
return nil, fmt.Errorf("stub: unexpected file: %s", f)
|
|
}
|
|
return nil, os.ErrNotExist
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := state.ResolveDeps()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_ResolveDeps_NoLockFile_WithCustomLockFile(t *testing.T) {
|
|
logger := helmexec.NewLogger(io.Discard, "debug")
|
|
state := &HelmState{
|
|
basePath: "/src",
|
|
FilePath: "/src/helmfile.yaml",
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
LockFile: "custom-lock-file",
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "./..",
|
|
},
|
|
{
|
|
Chart: "../examples",
|
|
},
|
|
{
|
|
Chart: "../../helmfile",
|
|
},
|
|
{
|
|
Chart: "published",
|
|
},
|
|
{
|
|
Chart: "published/deeper",
|
|
},
|
|
{
|
|
Chart: "stable/envoy",
|
|
},
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "stable",
|
|
URL: "https://kubernetes-charts.storage.googleapis.com",
|
|
},
|
|
},
|
|
},
|
|
logger: logger,
|
|
fs: &filesystem.FileSystem{
|
|
ReadFile: func(f string) ([]byte, error) {
|
|
if f != "custom-lock-file" {
|
|
return nil, fmt.Errorf("stub: unexpected file: %s", f)
|
|
}
|
|
return nil, os.ErrNotExist
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := state.ResolveDeps()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
func TestHelmState_ReleaseStatuses(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
want []exectest.Release
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseA",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []exectest.Release{
|
|
{Name: "releaseA", Flags: []string{}},
|
|
},
|
|
},
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "complain missing values file for desired release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
Values: []any{
|
|
"foo.yaml",
|
|
},
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "should not complain missing values file for undesired release",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
Values: []any{
|
|
"foo.yaml",
|
|
},
|
|
Installed: boolValue(false),
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
f := func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
fs: &filesystem.FileSystem{
|
|
FileExists: func(f string) (bool, error) {
|
|
if f != "foo.yaml" {
|
|
return false, fmt.Errorf("unexpected file: %s", f)
|
|
}
|
|
return true, nil
|
|
},
|
|
ReadFile: func(f string) ([]byte, error) {
|
|
if f != "foo.yaml" {
|
|
return nil, fmt.Errorf("unexpected file: %s", f)
|
|
}
|
|
return []byte{}, nil
|
|
},
|
|
},
|
|
}
|
|
errs := state.ReleaseStatuses(tt.helm, 1)
|
|
if (errs != nil) != tt.wantErr {
|
|
t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Releases, tt.want) {
|
|
t.Errorf("HelmState.ReleaseStatuses() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.want)
|
|
}
|
|
}
|
|
t.Run(tt.name, f)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_TestReleasesNoCleanUp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cleanup bool
|
|
releases []ReleaseSpec
|
|
helm *exectest.Helm
|
|
want []exectest.Release
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseA",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []exectest.Release{{Name: "releaseA", Flags: []string{"--timeout", "1s"}}},
|
|
},
|
|
{
|
|
name: "do cleanup",
|
|
cleanup: true,
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "releaseB",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
want: []exectest.Release{{Name: "releaseB", Flags: []string{"--timeout", "1s"}}},
|
|
},
|
|
{
|
|
name: "happy path",
|
|
releases: []ReleaseSpec{
|
|
{
|
|
Name: "error",
|
|
},
|
|
},
|
|
helm: &exectest.Helm{},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: tt.releases,
|
|
},
|
|
logger: logger,
|
|
}
|
|
errs := state.TestReleases(tt.helm, tt.cleanup, 1, 1)
|
|
if (errs != nil) != tt.wantErr {
|
|
t.Errorf("TestReleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(tt.helm.Releases, tt.want) {
|
|
t.Errorf("HelmState.TestReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConditionEnabled(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
condition string
|
|
values map[string]any
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "enabled",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "disabled",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"enabled": false,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "typo in condition",
|
|
condition: "fooo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing enabled",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": map[string]any{
|
|
"something else": false,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "foo nil",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{
|
|
"foo": nil,
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "foo missing",
|
|
condition: "foo.enabled",
|
|
values: map[string]any{},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "wrong suffix",
|
|
condition: "services.foo_enabled",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "too short condition",
|
|
condition: "rnd42",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nested values",
|
|
condition: "rnd42.really.enabled",
|
|
values: map[string]any{
|
|
"rnd42": map[string]any{
|
|
"really": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nested values enabled missing",
|
|
condition: "rnd42.really.ok",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nested values unknown key",
|
|
condition: "rnd42.unknown.enabled",
|
|
values: map[string]any{
|
|
"rnd42": map[string]any{
|
|
"really": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nested values invalid type",
|
|
condition: "rnd42.invalid.enabled",
|
|
values: map[string]any{
|
|
"rnd42": map[string]any{
|
|
"invalid": "hello",
|
|
"really": map[string]any{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty",
|
|
condition: "",
|
|
want: true,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
res, err := ConditionEnabled(ReleaseSpec{Condition: tt.condition}, tt.values)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("ConditionEnabled() for %s expected err response", tt.name)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Errorf("ConditionEnabled() for %s unexpected err %v", tt.name, err)
|
|
}
|
|
if res != tt.want {
|
|
t.Errorf("ConditionEnabled() for %s = %v, want %v", tt.name, res, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_NoReleaseMatched(t *testing.T) {
|
|
releases := []ReleaseSpec{
|
|
{
|
|
Name: "releaseA",
|
|
Labels: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
labels string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
|
|
labels: "foo=bar",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "name does not exist",
|
|
labels: "name=releaseB",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "label does not match anything",
|
|
labels: "foo=notbar",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
f := func(t *testing.T) {
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
},
|
|
logger: logger,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
state.Selectors = []string{tt.labels}
|
|
errs := state.FilterReleases(false)
|
|
if (errs != nil) != tt.wantErr {
|
|
t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
}
|
|
t.Run(tt.name, f)
|
|
}
|
|
}
|
|
|
|
func TestHelmState_Delete(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deleted []exectest.Release
|
|
wantErr bool
|
|
desired *bool
|
|
installed bool
|
|
purge bool
|
|
flags string
|
|
namespace string
|
|
kubeContext string
|
|
defKubeContext string
|
|
deleteWait bool
|
|
deleteTimeout int
|
|
}{
|
|
{
|
|
name: "delete wait enabled",
|
|
deleteWait: true,
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--wait"}}},
|
|
},
|
|
{
|
|
name: "delete wait with deleteTimeout",
|
|
deleteWait: true,
|
|
deleteTimeout: 800,
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--wait", "--timeout", "800s"}}},
|
|
},
|
|
{
|
|
name: "desired and installed (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired(default) and installed (purge=false)",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired(default) and installed (purge=false) but error",
|
|
wantErr: true,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired and installed (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: true,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired but not installed (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: false,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "desired but not installed (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(true),
|
|
installed: false,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "installed but filtered (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: true,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "installed but filtered (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: true,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "not installed, and filtered (purge=false)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: false,
|
|
purge: false,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "not installed, and filtered (purge=true)",
|
|
wantErr: false,
|
|
desired: boolValue(false),
|
|
installed: false,
|
|
purge: true,
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}},
|
|
},
|
|
{
|
|
name: "with kubecontext",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: true,
|
|
kubeContext: "ctx",
|
|
flags: "--kube-contextctx",
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--kube-context", "ctx"}}},
|
|
},
|
|
{
|
|
name: "with default kubecontext",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: true,
|
|
defKubeContext: "defctx",
|
|
flags: "--kube-contextdefctx",
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--kube-context", "defctx"}}},
|
|
},
|
|
{
|
|
name: "with non-default and default kubecontexts",
|
|
wantErr: false,
|
|
desired: nil,
|
|
installed: true,
|
|
purge: true,
|
|
kubeContext: "ctx",
|
|
defKubeContext: "defctx",
|
|
flags: "--kube-contextctx",
|
|
deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--kube-context", "ctx"}}},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
name := "releaseA"
|
|
if tt.wantErr {
|
|
name = "releaseA-error"
|
|
}
|
|
release := ReleaseSpec{
|
|
Name: name,
|
|
Installed: tt.desired,
|
|
Namespace: tt.namespace,
|
|
KubeContext: tt.kubeContext,
|
|
}
|
|
releases := []ReleaseSpec{
|
|
release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
KubeContext: tt.defKubeContext,
|
|
DeleteWait: tt.deleteWait,
|
|
DeleteTimeout: tt.deleteTimeout,
|
|
},
|
|
Releases: releases,
|
|
},
|
|
logger: logger,
|
|
RenderedValues: map[string]any{},
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
Deleted: []exectest.Release{},
|
|
}
|
|
if tt.installed {
|
|
helm.Lists[exectest.ListKey{Filter: "^" + name + "$", Flags: tt.flags}] = name
|
|
}
|
|
affectedReleases := AffectedReleases{}
|
|
errs := state.DeleteReleases(&affectedReleases, helm, 1, tt.purge, "")
|
|
if errs != nil {
|
|
if !tt.wantErr || len(affectedReleases.DeleteFailed) != 1 || affectedReleases.DeleteFailed[0].Name != release.Name {
|
|
t.Errorf("DeleteReleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr)
|
|
return
|
|
}
|
|
} else if !(reflect.DeepEqual(tt.deleted, helm.Deleted) && (len(affectedReleases.Deleted) == len(tt.deleted))) {
|
|
t.Errorf("unexpected deletions happened: expected %v, got %v", tt.deleted, helm.Deleted)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDiffpareSyncReleases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
flags []string
|
|
diffOptions *DiffOpts
|
|
helmDefaults *HelmSpec
|
|
}{
|
|
{
|
|
name: "reuse-values",
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "reset-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "default-reuse-values",
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "force-reset-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-default-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
release := ReleaseSpec{
|
|
Name: tt.name,
|
|
}
|
|
releases := []ReleaseSpec{
|
|
release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, tt.diffOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags)
|
|
}
|
|
}
|
|
|
|
func TestPrepareSyncReleases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
flags []string
|
|
syncOptions *SyncOpts
|
|
helmDefaults *HelmSpec
|
|
}{
|
|
{
|
|
name: "reuse-values",
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "reset-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "reuse-default-values",
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "force-reset-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
},
|
|
{
|
|
name: "both-reset-reuse-default-reuse-values",
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
release := ReleaseSpec{
|
|
Name: tt.name,
|
|
}
|
|
releases := []ReleaseSpec{
|
|
release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareSyncReleases(helm, []string{}, 1, tt.syncOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags)
|
|
}
|
|
}
|
|
|
|
func TestReverse(t *testing.T) {
|
|
num := 8
|
|
st := &HelmState{}
|
|
|
|
for i := 0; i < num; i++ {
|
|
name := fmt.Sprintf("%d", i)
|
|
st.Helmfiles = append(st.Helmfiles, SubHelmfileSpec{
|
|
Path: name,
|
|
})
|
|
st.Releases = append(st.Releases, ReleaseSpec{
|
|
Name: name,
|
|
})
|
|
}
|
|
|
|
st.Reverse()
|
|
|
|
for i := 0; i < num; i++ {
|
|
j := num - 1 - i
|
|
want := fmt.Sprintf("%d", j)
|
|
|
|
if got := st.Helmfiles[i].Path; got != want {
|
|
t.Errorf("sub-helmfile at %d has incorrect path: want %q, got %q", i, want, got)
|
|
}
|
|
|
|
if got := st.Releases[i].Name; got != want {
|
|
t.Errorf("release at %d has incorrect name: want %q, got %q", i, want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_gatherUsernamePassword(t *testing.T) {
|
|
type args struct {
|
|
repoName string
|
|
username string
|
|
password string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
envUsernameKey string
|
|
envUsernameValue string
|
|
envPasswordKey string
|
|
envPasswordValue string
|
|
wantUsername string
|
|
wantPassword string
|
|
}{
|
|
{
|
|
name: "pass username/password from args",
|
|
args: args{
|
|
repoName: "myRegistry",
|
|
username: "username1",
|
|
password: "password1",
|
|
},
|
|
wantUsername: "username1",
|
|
wantPassword: "password1",
|
|
},
|
|
{
|
|
name: "repoName does not contain hyphen, read username/password from environment variables",
|
|
args: args{
|
|
repoName: "myRegistry",
|
|
},
|
|
envUsernameKey: "MYREGISTRY_USERNAME",
|
|
envUsernameValue: "username2",
|
|
envPasswordKey: "MYREGISTRY_PASSWORD",
|
|
envPasswordValue: "password2",
|
|
wantUsername: "username2",
|
|
wantPassword: "password2",
|
|
},
|
|
{
|
|
name: "repoName contain hyphen, read username/password from environment variables",
|
|
args: args{
|
|
repoName: "my-registry",
|
|
},
|
|
envUsernameKey: "MY_REGISTRY_USERNAME",
|
|
envUsernameValue: "username3",
|
|
envPasswordKey: "MY_REGISTRY_PASSWORD",
|
|
envPasswordValue: "password3",
|
|
wantUsername: "username3",
|
|
wantPassword: "password3",
|
|
},
|
|
}
|
|
for i := range tests {
|
|
tt := tests[i]
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.envUsernameKey != "" && tt.envUsernameValue != "" {
|
|
t.Setenv(tt.envUsernameKey, tt.envUsernameValue)
|
|
}
|
|
if tt.envPasswordKey != "" && tt.envPasswordValue != "" {
|
|
t.Setenv(tt.envPasswordKey, tt.envPasswordValue)
|
|
}
|
|
|
|
gotUsername, gotPassword := gatherUsernamePassword(tt.args.repoName, tt.args.username, tt.args.password)
|
|
if gotUsername != tt.wantUsername || gotPassword != tt.wantPassword {
|
|
t.Errorf("gatherUsernamePassword() = got username/password %v/%v, want username/password %v/%v", gotUsername, gotPassword, tt.wantUsername, tt.wantPassword)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_extractRegistryHost(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
url string
|
|
want string
|
|
}{
|
|
{
|
|
name: "ECR with chart path",
|
|
url: "123456789012.dkr.ecr.us-east-1.amazonaws.com/helm-charts",
|
|
want: "123456789012.dkr.ecr.us-east-1.amazonaws.com",
|
|
},
|
|
{
|
|
name: "GHCR with nested path",
|
|
url: "ghcr.io/deliveryhero/helm-charts",
|
|
want: "ghcr.io",
|
|
},
|
|
{
|
|
name: "registry with port and path",
|
|
url: "registry.example.com:5000/helm-charts",
|
|
want: "registry.example.com:5000",
|
|
},
|
|
{
|
|
name: "registry without path",
|
|
url: "registry.example.com",
|
|
want: "registry.example.com",
|
|
},
|
|
{
|
|
name: "with oci:// prefix and path",
|
|
url: "oci://ghcr.io/charts/nginx",
|
|
want: "ghcr.io",
|
|
},
|
|
{
|
|
name: "with https:// prefix and path",
|
|
url: "https://registry.example.com/helm-charts",
|
|
want: "registry.example.com",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := extractRegistryHost(tt.url); got != tt.want {
|
|
t.Errorf("extractRegistryHost() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_SyncRepos_OCI(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
repos []RepositorySpec
|
|
wantRegistryLoginHost string
|
|
}{
|
|
{
|
|
name: "OCI registry with chart path should extract host only",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "ecr",
|
|
URL: "123456789012.dkr.ecr.us-east-1.amazonaws.com/helm-charts",
|
|
OCI: true,
|
|
Username: "AWS",
|
|
Password: "token",
|
|
},
|
|
},
|
|
wantRegistryLoginHost: "123456789012.dkr.ecr.us-east-1.amazonaws.com",
|
|
},
|
|
{
|
|
name: "OCI registry without path should pass URL as-is",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "ghcr",
|
|
URL: "ghcr.io",
|
|
OCI: true,
|
|
Username: "user",
|
|
Password: "pass",
|
|
},
|
|
},
|
|
wantRegistryLoginHost: "ghcr.io",
|
|
},
|
|
{
|
|
name: "OCI registry with nested path",
|
|
repos: []RepositorySpec{
|
|
{
|
|
Name: "docker",
|
|
URL: "registry-1.docker.io/bitnamicharts",
|
|
OCI: true,
|
|
Username: "user",
|
|
Password: "pass",
|
|
},
|
|
},
|
|
wantRegistryLoginHost: "registry-1.docker.io",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
helm := &exectest.Helm{}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: tt.repos,
|
|
},
|
|
}
|
|
_, err := state.SyncRepos(helm, map[string]bool{})
|
|
if err != nil {
|
|
t.Errorf("SyncRepos() error = %v", err)
|
|
return
|
|
}
|
|
if helm.RegistryLoginHost != tt.wantRegistryLoginHost {
|
|
t.Errorf("RegistryLogin was called with host = %q, want %q", helm.RegistryLoginHost, tt.wantRegistryLoginHost)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateOutputFilePath(t *testing.T) {
|
|
tests := []struct {
|
|
envName string
|
|
filePath string
|
|
releaseName string
|
|
outputFileTemplate string
|
|
wantErr bool
|
|
expected string
|
|
}{
|
|
{
|
|
envName: "dev",
|
|
releaseName: "release1",
|
|
filePath: "/path/to/helmfile.yaml",
|
|
outputFileTemplate: "helmfile-{{ .Environment.Name }}.yaml",
|
|
expected: "helmfile-dev.yaml",
|
|
},
|
|
{
|
|
envName: "error",
|
|
releaseName: "release2",
|
|
filePath: "helmfile.yaml",
|
|
outputFileTemplate: "helmfile-{{ .Environment.Name",
|
|
wantErr: true,
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.envName, func(t *testing.T) {
|
|
st := &HelmState{
|
|
FilePath: tt.envName,
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Env: environment.Environment{
|
|
Name: tt.envName,
|
|
},
|
|
},
|
|
}
|
|
ra := &ReleaseSpec{
|
|
Name: tt.releaseName,
|
|
}
|
|
got, err := st.GenerateOutputFilePath(ra, tt.outputFileTemplate)
|
|
|
|
if tt.wantErr {
|
|
require.Errorf(t, err, "GenerateOutputFilePath() error = %v, want error", err)
|
|
} else {
|
|
require.NoError(t, err, "GenerateOutputFilePath() error = %v, want nil", err)
|
|
}
|
|
require.Equalf(t, got, tt.expected, "GenerateOutputFilePath() got = %v, want %v", got, tt.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFullFilePath(t *testing.T) {
|
|
fs := testhelper.NewTestFs(map[string]string{})
|
|
tests := []struct {
|
|
basePath string
|
|
filePath string
|
|
fs *filesystem.FileSystem
|
|
expected string
|
|
}{
|
|
{
|
|
basePath: ".",
|
|
filePath: "helmfile.yaml",
|
|
expected: "helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "./test-1/",
|
|
filePath: "helmfile.yaml",
|
|
expected: "test-1/helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "/test-2/",
|
|
filePath: "helmfile.yaml",
|
|
expected: "/test-2/helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "./test-3/",
|
|
filePath: "helmfile.yaml",
|
|
fs: fs.ToFileSystem(),
|
|
expected: "/path/to/test-3/helmfile.yaml",
|
|
},
|
|
{
|
|
basePath: "/test-4/",
|
|
filePath: "helmfile.yaml",
|
|
fs: fs.ToFileSystem(),
|
|
expected: "/path/to/test-4/helmfile.yaml",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.expected, func(t *testing.T) {
|
|
st := &HelmState{
|
|
basePath: tt.basePath,
|
|
FilePath: tt.filePath,
|
|
fs: tt.fs,
|
|
}
|
|
actual, err := st.FullFilePath()
|
|
require.Equalf(t, actual, tt.expected, "FullFilePath() got = %v, want %v", actual, tt.expected)
|
|
require.Equalf(t, err, nil, "error %v", err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetOCIQualifiedChartName(t *testing.T) {
|
|
devel := true
|
|
|
|
tests := []struct {
|
|
state HelmState
|
|
expected []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}
|
|
helmVersion string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "0.1.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name:0.1.2", "chart-name", "0.1.2"},
|
|
},
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "latest",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Version: "latest",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.7.0",
|
|
wantErr: true, // Now rejects "latest" for all Helm versions
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/chart-path",
|
|
OCI: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chart-name",
|
|
Version: "0.1.2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name:0.1.2", "chart-name", "0.1.2"},
|
|
},
|
|
},
|
|
{
|
|
state: HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci://registry/chart-path/chart-name",
|
|
Devel: &devel,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
helmVersion: "3.13.3",
|
|
expected: []struct {
|
|
qualifiedChartName string
|
|
chartName string
|
|
chartVersion string
|
|
}{
|
|
{"registry/chart-path/chart-name", "chart-name", ""},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(fmt.Sprintf("%+v", tt.expected), func(t *testing.T) {
|
|
for i, r := range tt.state.Releases {
|
|
qualifiedChartName, chartName, chartVersion, err := tt.state.getOCIQualifiedChartName(&r)
|
|
if tt.wantErr {
|
|
require.Error(t, err, "getOCIQualifiedChartName() error = nil, want error")
|
|
return
|
|
}
|
|
require.NoError(t, err, "getOCIQualifiedChartName() error = %v, want nil", err)
|
|
if len(tt.expected) > 0 {
|
|
require.Equalf(t, qualifiedChartName, tt.expected[i].qualifiedChartName, "qualifiedChartName got = %v, want %v", qualifiedChartName, tt.expected[i].qualifiedChartName)
|
|
require.Equalf(t, chartName, tt.expected[i].chartName, "chartName got = %v, want %v", chartName, tt.expected[i].chartName)
|
|
require.Equalf(t, chartVersion, tt.expected[i].chartVersion, "chartVersion got = %v, want %v", chartVersion, tt.expected[i].chartVersion)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateChartPath(t *testing.T) {
|
|
tests := []struct {
|
|
testName string
|
|
chartName string
|
|
release *ReleaseSpec
|
|
outputDir string
|
|
outputDirTemplate string
|
|
wantErr bool
|
|
expected string
|
|
}{
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndDefaultReleaseVersion",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseVersion",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", Version: "0.0.0"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-name/chart-name/0.0.0",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseNamespace",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", Namespace: "release-namespace"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-namespace/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseKubeContext",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", KubeContext: "kube-context"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/kube-context/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenReleaseNamespaceAndGivenReleaseKubeContext",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name", Namespace: "release-namespace", KubeContext: "kube-context"},
|
|
outputDir: "/output-dir",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-namespace/kube-context/release-name/chart-name/latest",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenOutputDirTemplateWithFieldNameOutputDir",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}",
|
|
wantErr: false,
|
|
expected: "/output-dir",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirAndGivenOutputDirTemplateWithFieldNamesOutputDirAndReleaseName",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDir: "/output-dir",
|
|
outputDirTemplate: "{{ .OutputDir }}/{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "/output-dir/release-name",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithFieldNamesOutputDir",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "{{ .OutputDir }}",
|
|
wantErr: false,
|
|
expected: "",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithFieldNameReleaseName",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "release-name",
|
|
},
|
|
{
|
|
testName: "PathGeneratedWithGivenOutputDirTemplateWithStringAndFieldNameReleaseName",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "./charts/{{ .Release.Name }}",
|
|
wantErr: false,
|
|
expected: "./charts/release-name",
|
|
},
|
|
{
|
|
testName: "ErrorReturnedWithGivenInvalidOutputDirTemplate",
|
|
chartName: "chart-name",
|
|
release: &ReleaseSpec{Name: "release-name"},
|
|
outputDirTemplate: "{{ .OutputDir }",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.testName, func(t *testing.T) {
|
|
got, err := generateChartPath(tt.chartName, tt.outputDir, tt.release, tt.outputDirTemplate)
|
|
|
|
if tt.wantErr {
|
|
require.Errorf(t, err, "GenerateChartPath() error \"%v\", want error", err)
|
|
} else {
|
|
require.NoError(t, err, "GenerateChartPath() error \"%v\", want no error", err)
|
|
}
|
|
require.Equalf(t, tt.expected, got, "GenerateChartPath() got \"%v\", want \"%v\"", got, tt.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommonDiffFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
// stripTrailingCR is a flag to strip trailing carriage returns from the output
|
|
stripTrailingCR bool
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "stripTrailingCR enabled",
|
|
stripTrailingCR: true,
|
|
expected: []string{
|
|
"--strip-trailing-cr",
|
|
},
|
|
},
|
|
{
|
|
name: "stripTrailingCR disenabled",
|
|
expected: []string{},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
st := &HelmState{}
|
|
result := st.commonDiffFlags(false, tt.stripTrailingCR, false, []string{}, false, false, false, &DiffOpts{})
|
|
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
}
|
|
|
|
func TestAppendChartDownloadFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
spec *ReleaseSetSpec
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "SkipTLSVerify in repo",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "testrepo",
|
|
URL: "registry/repo-path",
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "testrepo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp in repo, SkipTLSVerify everywhere",
|
|
spec: &ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "testrepo",
|
|
URL: "registry/repo-path",
|
|
SkipTLSVerify: true,
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "testrepo/chartname",
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
{
|
|
name: "SkipTLSVerify in defaults",
|
|
spec: &ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "SkipTLSVerify in release",
|
|
spec: &ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp in defaults",
|
|
spec: &ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
PlainHttp: true,
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp in release",
|
|
spec: &ReleaseSetSpec{
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "chartname",
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: *tt.spec,
|
|
}
|
|
|
|
result := st.appendChartDownloadFlags([]string{}, &st.Releases[0])
|
|
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNeedsPlainHttp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
release *ReleaseSpec
|
|
repo *RepositorySpec
|
|
defaults HelmSpec
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "PlainHttp in Release",
|
|
release: &ReleaseSpec{
|
|
PlainHttp: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "PlainHttp in Repository",
|
|
repo: &RepositorySpec{
|
|
PlainHttp: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "PlainHttp in HelmDefaults",
|
|
defaults: HelmSpec{
|
|
PlainHttp: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "PlainHttp not set",
|
|
expected: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
}
|
|
require.Equal(t, tt.expected, st.needsPlainHttp(tt.release, tt.repo))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNeedsInsecureSkipTLSVerify(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
release *ReleaseSpec
|
|
repo *RepositorySpec
|
|
defaults HelmSpec
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "InsecureSkipTLSVerify in Release",
|
|
release: &ReleaseSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "SkipTLSVerify in Repository",
|
|
repo: &RepositorySpec{
|
|
SkipTLSVerify: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "InsecureSkipTLSVerify in HelmDefaults",
|
|
defaults: HelmSpec{
|
|
InsecureSkipTLSVerify: true,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "InsecureSkipTLSVerify not set",
|
|
expected: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: tt.defaults,
|
|
},
|
|
}
|
|
require.Equal(t, tt.expected, st.needsInsecureSkipTLSVerify(tt.release, tt.repo))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHideChartURL(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"http://username:password@example.com/", "http://---:---@example.com/"},
|
|
{"http://example.com@", "http://---:---@"},
|
|
{"https://username:password@example.com/", "https://---:---@example.com/"},
|
|
{"https://username:@password@example.com/", "https://---:---@example.com/"},
|
|
{"https://username::password@example.com/", "https://---:---@example.com/"},
|
|
{"https://username:httpd@example.com/", "https://---:---@example.com/"},
|
|
{"https://username:httpsd@example.com/", "https://---:---@example.com/"},
|
|
{"https://example.com/", "https://example.com/"},
|
|
}
|
|
for _, test := range tests {
|
|
result, _ := hideChartCredentials(test.input)
|
|
if result != test.expected {
|
|
t.Errorf("For input '%s', expected '%s', but got '%s'", test.input, test.expected, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Test_appendExtraDiffFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputFlags []string
|
|
inputOpts *DiffOpts
|
|
inputDefaults []string
|
|
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Skipping default flags, because diffOpts is provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputOpts: &DiffOpts{
|
|
DiffArgs: "-bbbb --cccc",
|
|
},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-bbbb", "--cccc"},
|
|
},
|
|
{
|
|
name: "Use default flags, because diffOpts is not provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-dddd", "--eeee"},
|
|
},
|
|
{
|
|
name: "Don't add non-flag arguments",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-d=ddd", "non-flag", "--eeee"},
|
|
expected: []string{"aaaaa", "-d=ddd", "--eeee"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := (&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
DiffArgs: test.inputDefaults,
|
|
},
|
|
},
|
|
}).appendExtraDiffFlags(test.inputFlags, test.inputOpts)
|
|
if !reflect.DeepEqual(result, test.expected) {
|
|
t.Errorf("For input %v %v, expected %v, but got %v", test.inputFlags, test.inputOpts, test.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_appendExtraSyncFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputFlags []string
|
|
inputOpts *SyncOpts
|
|
inputDefaults []string
|
|
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Skipping default flags, because diffOpts is provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputOpts: &SyncOpts{
|
|
SyncArgs: "-bbbb --cccc",
|
|
},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-bbbb", "--cccc"},
|
|
},
|
|
{
|
|
name: "Use default flags, because diffOpts is not provided",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-dddd", "--eeee"},
|
|
expected: []string{"aaaaa", "-dddd", "--eeee"},
|
|
},
|
|
{
|
|
name: "Don't add non-flag arguments",
|
|
inputFlags: []string{"aaaaa"},
|
|
inputDefaults: []string{"-d=ddd", "non-flag", "--eeee"},
|
|
expected: []string{"aaaaa", "-d=ddd", "--eeee"},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := (&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
HelmDefaults: HelmSpec{
|
|
SyncArgs: test.inputDefaults,
|
|
},
|
|
},
|
|
}).appendExtraSyncFlags(test.inputFlags, test.inputOpts)
|
|
if !reflect.DeepEqual(result, test.expected) {
|
|
t.Errorf("For input %v %v, expected %v, but got %v", test.inputFlags, test.inputOpts, test.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_appendApiVersionsFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
kubeVersion string
|
|
flags []string
|
|
expectedFlags []string
|
|
releaseKubeVersion string
|
|
releaseApiVersion []string
|
|
stateKubeVersion string
|
|
stateApiVersion []string
|
|
}{
|
|
{
|
|
name: "no kubeVersion is set",
|
|
expectedFlags: []string{},
|
|
},
|
|
{
|
|
name: "flags are kept",
|
|
flags: []string{"--flag1", "--flag2"},
|
|
expectedFlags: []string{"--flag1", "--flag2"},
|
|
},
|
|
{
|
|
name: "kubeVersion is set",
|
|
kubeVersion: "1.18.0",
|
|
expectedFlags: []string{"--kube-version", "1.18.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion is set from release",
|
|
releaseKubeVersion: "1.19.0",
|
|
expectedFlags: []string{"--kube-version", "1.19.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion from release hasn't priority",
|
|
kubeVersion: "1.18.0",
|
|
releaseKubeVersion: "1.19.0",
|
|
expectedFlags: []string{"--kube-version", "1.18.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion is set from state",
|
|
stateKubeVersion: "1.18.0",
|
|
expectedFlags: []string{"--kube-version", "1.18.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion from state hasn't priority",
|
|
stateKubeVersion: "1.18.0",
|
|
releaseKubeVersion: "1.19.0",
|
|
expectedFlags: []string{"--kube-version", "1.19.0"},
|
|
},
|
|
{
|
|
name: "kubeVersion priority",
|
|
stateKubeVersion: "1.18.0",
|
|
releaseKubeVersion: "1.19.0",
|
|
kubeVersion: "1.20.0",
|
|
expectedFlags: []string{"--kube-version", "1.20.0"},
|
|
},
|
|
{
|
|
name: "api-version are set from state",
|
|
stateApiVersion: []string{"v1,v2"},
|
|
expectedFlags: []string{"--api-versions", "v1,v2"},
|
|
},
|
|
{
|
|
name: "api-version are set from release",
|
|
releaseApiVersion: []string{"v1,v2"},
|
|
expectedFlags: []string{"--api-versions", "v1,v2"},
|
|
},
|
|
{
|
|
name: "api-version priority",
|
|
stateApiVersion: []string{"v1"},
|
|
releaseApiVersion: []string{"v2"},
|
|
expectedFlags: []string{"--api-versions", "v2"},
|
|
},
|
|
{
|
|
name: "api-version multiple values",
|
|
stateApiVersion: []string{"v1", "v2"},
|
|
expectedFlags: []string{"--api-versions", "v1", "--api-versions", "v2"},
|
|
},
|
|
{
|
|
name: "All kubeVersion and api-version are set",
|
|
kubeVersion: "1.18.0",
|
|
stateKubeVersion: "1.19.0",
|
|
releaseKubeVersion: "1.20.0",
|
|
stateApiVersion: []string{"v1"},
|
|
releaseApiVersion: []string{"v2"},
|
|
flags: []string{"--previous-flag-1", "--previous-flag-2"},
|
|
expectedFlags: []string{"--previous-flag-1", "--previous-flag-2", "--api-versions", "v2", "--kube-version", "1.18.0"},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if test.expectedFlags == nil {
|
|
test.expectedFlags = []string{}
|
|
}
|
|
if test.flags == nil {
|
|
test.flags = []string{}
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
KubeVersion: test.stateKubeVersion,
|
|
ApiVersions: test.stateApiVersion,
|
|
},
|
|
}
|
|
r := &ReleaseSpec{
|
|
KubeVersion: test.releaseKubeVersion,
|
|
ApiVersions: test.releaseApiVersion,
|
|
}
|
|
result := state.appendApiVersionsFlags(test.flags, r, test.kubeVersion)
|
|
assert.Equal(t, test.expectedFlags, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetOCIChartPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tempDir string
|
|
release *ReleaseSpec
|
|
chartName string
|
|
chartVersion string
|
|
outputDirTemplate string
|
|
expectedPath string
|
|
expectedErr bool
|
|
}{
|
|
{
|
|
name: "OCI chart with template",
|
|
tempDir: "charts",
|
|
release: &ReleaseSpec{
|
|
Name: "karpenter",
|
|
Chart: "karpenter/karpenter",
|
|
},
|
|
chartName: "karpenter",
|
|
chartVersion: "0.37.0",
|
|
outputDirTemplate: "{{ .OutputDir }}/",
|
|
expectedPath: "charts/",
|
|
expectedErr: false,
|
|
},
|
|
{
|
|
name: "OCI chart with template containing unknown values",
|
|
tempDir: "charts",
|
|
release: &ReleaseSpec{
|
|
Name: "karpenter",
|
|
Chart: "karpenter/karpenter",
|
|
},
|
|
chartName: "karpenter",
|
|
chartVersion: "0.37.0",
|
|
outputDirTemplate: "{{ .SomethingThatDoesNotExist }}/",
|
|
expectedPath: "",
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "OCI chart without template",
|
|
tempDir: "charts",
|
|
release: &ReleaseSpec{
|
|
Name: "karpenter",
|
|
Chart: "karpenter/karpenter",
|
|
},
|
|
chartName: "karpenter",
|
|
chartVersion: "0.37.0",
|
|
expectedPath: "charts/karpenter/karpenter/0.37.0",
|
|
expectedErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{}
|
|
path, err := st.getOCIChartPath(tt.tempDir, tt.release, tt.chartName, tt.chartVersion, tt.outputDirTemplate)
|
|
|
|
if tt.expectedErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedPath, path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHelmState_chartOCIFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
spec *ReleaseSetSpec
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "CaFile enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
CaFile: "cafile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--ca-file",
|
|
"cafile",
|
|
},
|
|
},
|
|
{
|
|
name: "CertFile enabled and KeyFile disabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
CertFile: "certfile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "CertFile disabled and KeyFile enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
KeyFile: "keyfile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "CertFile and KeyFile enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
CertFile: "certfile",
|
|
KeyFile: "keyfile",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--cert-file",
|
|
"certfile",
|
|
"--key-file",
|
|
"keyfile",
|
|
},
|
|
},
|
|
{
|
|
name: "SkipTLSVerify enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--insecure-skip-tls-verify",
|
|
},
|
|
},
|
|
{
|
|
name: "Verify enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
Verify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--verify",
|
|
},
|
|
},
|
|
{
|
|
name: "Keyring enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
Keyring: "keyring.pgp",
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--keyring",
|
|
"keyring.pgp",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
PlainHttp: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
{
|
|
name: "PlainHttp and TLS options enabled",
|
|
spec: &ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "oci-repo",
|
|
URL: "registry/repo-path",
|
|
OCI: true,
|
|
PlainHttp: true,
|
|
CaFile: "cafile",
|
|
CertFile: "certfile",
|
|
KeyFile: "keyfile",
|
|
SkipTLSVerify: true,
|
|
},
|
|
},
|
|
Releases: []ReleaseSpec{
|
|
{
|
|
Chart: "oci-repo/chartname",
|
|
},
|
|
},
|
|
},
|
|
expected: []string{
|
|
"--plain-http",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
ReleaseSetSpec: *tt.spec,
|
|
}
|
|
flags := st.chartOCIFlags(&tt.spec.Releases[0])
|
|
require.Equal(t, tt.expected, flags)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsOCIChart(t *testing.T) {
|
|
cases := []struct {
|
|
st *HelmState
|
|
chart string
|
|
expected bool
|
|
}{
|
|
{&HelmState{}, "oci://myrepo/mychart", true},
|
|
{&HelmState{}, "oci://myrepo/mychart:1.0.0", true},
|
|
{&HelmState{}, "myrepo/mychart", false},
|
|
{&HelmState{}, "myrepo/mychart:1.0.0", false},
|
|
{
|
|
&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "ocirepo",
|
|
URL: "ocirepo.com",
|
|
OCI: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"ocirepo/chart",
|
|
true,
|
|
},
|
|
{
|
|
&HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Repositories: []RepositorySpec{
|
|
{
|
|
Name: "nonocirepo",
|
|
URL: "nonocirepo.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"nonocirepo/chart",
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
actual := c.st.IsOCIChart(c.chart)
|
|
if actual != c.expected {
|
|
t.Errorf("IsOCIChart(%s) = %t; expected %t", c.chart, actual, c.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAppendVerifyFlags(t *testing.T) {
|
|
st := &HelmState{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
repo []RepositorySpec
|
|
helmDefaults HelmSpec
|
|
release *ReleaseSpec
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Release with true verify flag",
|
|
release: &ReleaseSpec{Verify: boolValue(true)},
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{},
|
|
expected: []string{"--verify"},
|
|
},
|
|
{
|
|
name: "Release with false verify flag",
|
|
release: &ReleaseSpec{Verify: boolValue(false)},
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{},
|
|
expected: []string(nil),
|
|
},
|
|
{
|
|
name: "Repository with verify flag",
|
|
helmDefaults: HelmSpec{},
|
|
repo: []RepositorySpec{
|
|
{
|
|
Name: "myrepo",
|
|
Verify: true,
|
|
},
|
|
},
|
|
release: &ReleaseSpec{
|
|
Chart: "myrepo/mychart",
|
|
},
|
|
expected: []string{"--verify"},
|
|
},
|
|
{
|
|
name: "Helm defaults with verify flag",
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{
|
|
Verify: true,
|
|
},
|
|
release: &ReleaseSpec{},
|
|
expected: []string{"--verify"},
|
|
},
|
|
{
|
|
name: "No verify flag",
|
|
repo: nil,
|
|
helmDefaults: HelmSpec{},
|
|
release: &ReleaseSpec{},
|
|
expected: []string(nil),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st.ReleaseSetSpec.Repositories = tt.repo
|
|
st.ReleaseSetSpec.HelmDefaults = tt.helmDefaults
|
|
flags := st.appendVerifyFlags(nil, tt.release)
|
|
assert.Equal(t, tt.expected, flags)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestHelmState_setStringFlags tests the setStringFlags method
|
|
func TestHelmState_setStringFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setStringValues []SetValue
|
|
want []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "single value",
|
|
setStringValues: []SetValue{
|
|
{
|
|
Name: "key",
|
|
Value: "value",
|
|
},
|
|
},
|
|
want: []string{"--set-string", "key=value"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "multiple values",
|
|
setStringValues: []SetValue{
|
|
{
|
|
Name: "key",
|
|
Values: []string{"value1", "value2"},
|
|
},
|
|
},
|
|
want: []string{"--set-string", "key={value1,value2}"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "rendered value error",
|
|
setStringValues: []SetValue{
|
|
{
|
|
Name: "key",
|
|
Value: "ref+echo://value",
|
|
},
|
|
},
|
|
want: []string{"--set-string", "key=value"},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := &HelmState{
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
got, err := st.setStringFlags(tt.setStringValues)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("setStringFlags() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("setStringFlags() got = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
func TestPrepareDiffReleases_ValueControlReleaseOverride(t *testing.T) {
|
|
tests := []struct {
|
|
flags []string
|
|
diffOptions *DiffOpts
|
|
helmDefaults *HelmSpec
|
|
release *ReleaseSpec
|
|
}{
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-cli",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
diffOptions: &DiffOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-all",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-helm-defaults",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-release",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
diffOptions: &DiffOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-cli-overrides-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
releases := []ReleaseSpec{
|
|
*tt.release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, tt.diffOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name)
|
|
}
|
|
}
|
|
|
|
func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) {
|
|
tests := []struct {
|
|
flags []string
|
|
syncOptions *SyncOpts
|
|
helmDefaults *HelmSpec
|
|
release *ReleaseSpec
|
|
}{
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-from-cli",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reuse-values"},
|
|
syncOptions: &SyncOpts{
|
|
ReuseValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reuse-values-all",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{
|
|
ReuseValues: true,
|
|
},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-helm-defaults",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-from-release",
|
|
ReuseValues: boolValue(false),
|
|
},
|
|
},
|
|
{
|
|
flags: []string{"--reset-values"},
|
|
syncOptions: &SyncOpts{
|
|
ResetValues: true,
|
|
},
|
|
helmDefaults: &HelmSpec{},
|
|
release: &ReleaseSpec{
|
|
Name: "reset-values-cli-overrides-release",
|
|
ReuseValues: boolValue(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
releases := []ReleaseSpec{
|
|
*tt.release,
|
|
}
|
|
state := &HelmState{
|
|
ReleaseSetSpec: ReleaseSetSpec{
|
|
Releases: releases,
|
|
HelmDefaults: *tt.helmDefaults,
|
|
},
|
|
logger: logger,
|
|
valsRuntime: valsRuntime,
|
|
}
|
|
helm := &exectest.Helm{
|
|
Lists: map[exectest.ListKey]string{},
|
|
}
|
|
results, es := state.prepareSyncReleases(helm, []string{}, 1, tt.syncOptions)
|
|
|
|
require.Len(t, es, 0)
|
|
require.Len(t, results, 1)
|
|
|
|
r := results[0]
|
|
|
|
require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name)
|
|
}
|
|
}
|
|
|
|
func TestChartCacheKey(t *testing.T) {
|
|
st := &HelmState{}
|
|
|
|
// Test case 1: release with version
|
|
release1 := &ReleaseSpec{
|
|
Chart: "stable/nginx",
|
|
Version: "1.2.3",
|
|
}
|
|
|
|
key1 := st.getChartCacheKey(release1)
|
|
expected1 := ChartCacheKey{Chart: "stable/nginx", Version: "1.2.3"}
|
|
|
|
if key1 != expected1 {
|
|
t.Errorf("Expected %+v, got %+v", expected1, key1)
|
|
}
|
|
|
|
// Test case 2: release without version
|
|
release2 := &ReleaseSpec{
|
|
Chart: "stable/nginx",
|
|
}
|
|
|
|
key2 := st.getChartCacheKey(release2)
|
|
expected2 := ChartCacheKey{Chart: "stable/nginx", Version: ""}
|
|
|
|
if key2 != expected2 {
|
|
t.Errorf("Expected %+v, got %+v", expected2, key2)
|
|
}
|
|
}
|
|
|
|
func TestChartCache(t *testing.T) {
|
|
st := &HelmState{}
|
|
|
|
// Create a test key
|
|
key := ChartCacheKey{Chart: "stable/test", Version: "1.0.0"}
|
|
path := "/tmp/test-chart"
|
|
|
|
// Initially, chart should not be in cache
|
|
_, exists := st.checkChartCache(key)
|
|
if exists {
|
|
t.Error("Chart should not be in cache initially")
|
|
}
|
|
|
|
// Add to cache
|
|
st.addToChartCache(key, path)
|
|
|
|
// Now chart should be in cache
|
|
cachedPath, exists := st.checkChartCache(key)
|
|
if !exists {
|
|
t.Error("Chart should be in cache after adding")
|
|
}
|
|
if cachedPath != path {
|
|
t.Errorf("Expected path %s, got %s", path, cachedPath)
|
|
}
|
|
}
|