Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions chartify.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,13 +382,35 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s
"This may result in outdated chart dependencies.", release)
} else {
// Flatten the chart by fetching dependent chart archives and merging their K8s manifests into the temporary local chart
// So that we can uniformly patch them with JSON patch, Strategic-Merge patch, or with injectors
depArgs := []string{"dependency", "up", tempDir}
// So that we can uniformly patch them with JSON patch, Strategic-Merge patch, or with injectors.
//
// Prefer `helm dependency build` when a Chart.lock exists and no adhoc dependencies were
// injected. `build` resolves dependencies from the lock file, matching the behavior of
// helmfile's non-chartify code path (pkg/helmexec/exec.go BuildDeps) and respecting the
// reproducibility users expect when they commit a Chart.lock. `up` re-resolves against
// Chart.yaml constraints and rewrites the lock, which silently picks up newer versions
// when constraints permit (e.g. `version: "*"`).
//
// Adhoc dependencies are appended to Chart.yaml after the lock was generated, so the lock
// is by definition out of sync — `build` would fail. Fall back to `up` in that case.
useBuild := len(u.AdhocChartDependencies) == 0 && hasChartLock(tempDir)
depCmd := "up"
if useBuild {
depCmd = "build"
}
depArgs := []string{"dependency", depCmd, tempDir}
// Helm 4 requires --plain-http for HTTP-only OCI registries
if u.OCIPlainHTTP && r.IsHelm4() {
depArgs = append(depArgs, "--plain-http")
}
_, err := r.run(nil, r.helmBin(), depArgs...)
if err != nil && useBuild {
// `helm dependency build` errors when Chart.lock is out of sync with Chart.yaml.
// Fall back to `up` so chartify keeps working on charts whose lock has drifted.
r.Logf("`helm dependency build` failed for release %s, falling back to `helm dependency up`: %v", release, err)
depArgs[1] = "up"
_, err = r.run(nil, r.helmBin(), depArgs...)
Comment on lines +407 to +412
Comment on lines +407 to +412
}
if err != nil {
return "", err
}
Expand Down Expand Up @@ -680,6 +702,18 @@ func (r *Runner) RewriteChartToPreventDoubleRendering(tempDir, filesDir string)
return nil
}

// hasChartLock reports whether a Chart.lock or requirements.lock file exists in the
// given chart directory. Helm uses Chart.lock for apiVersion v2 charts and the legacy
// requirements.lock for v1 charts; either is sufficient for `helm dependency build`.
func hasChartLock(chartDir string) bool {
for _, name := range []string{"Chart.lock", "requirements.lock"} {
if _, err := os.Stat(filepath.Join(chartDir, name)); err == nil {
return true
}
}
return false
}

func createDirForFile(f string) error {
dstFileDir := filepath.Dir(f)
if _, err := os.Lstat(dstFileDir); err == nil {
Expand Down
28 changes: 28 additions & 0 deletions util_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
package chartify

import (
"os"
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestHasChartLock(t *testing.T) {
tests := []struct {
name string
lockName string // empty means no lock file is created
want bool
}{
{name: "no lock file", lockName: "", want: false},
{name: "Chart.lock present", lockName: "Chart.lock", want: true},
{name: "requirements.lock present", lockName: "requirements.lock", want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
if tt.lockName != "" {
path := filepath.Join(dir, tt.lockName)
if err := os.WriteFile(path, []byte("dependencies: []\n"), 0644); err != nil {
t.Fatalf("writing fixture: %v", err)
}
}
if got := hasChartLock(dir); got != tt.want {
t.Errorf("hasChartLock() = %v, want %v", got, tt.want)
}
})
}
}

func TestCreateFlagChain(t *testing.T) {
testcases := []struct {
flag string
Expand Down