mirror of
https://github.com/helmfile/helmfile.git
synced 2026-09-30 08:00:19 +02:00
* fix: support go-getter URLs in ad-hoc dependencies to fix #821 Ad-hoc release dependencies (release.dependencies[].chart) that used go-getter URLs like "git::https://host/repo.git@path?ref=tag" were passed to chartify as-is. chartify then tried to resolve them via `helm repo list`, which fails with "no helm list entry found for repository \"git::https:\". please `helm repo add` it!". The primary chart already fetched such URLs via downloadChartWithGoGetter, but the ad-hoc dependency path in PrepareChartify only handled local directories and OCI rewrites, so go-getter URLs fell through. This adds a branch that detects remote go-getter URLs (remote.IsRemote) and fetches them to a local cache directory via a new downloadAdhocDepChartWithGoGetter helper, mirroring the primary-chart fetch path. chartify then sees a local chart and takes its file:// branch. Fixes #821 Signed-off-by: yxxhero <aiopsclub@163.com> * test: add integration test for go-getter ad-hoc dependencies (#821) Adds an integration test that commits a chart to a throwaway local git repo and references it via a "git::file://..." go-getter URL as a release ad-hoc dependency, then asserts `helmfile template` renders both the main chart and the fetched dependency. Using file:// (rather than https://) keeps the test deterministic and network-free while exercising the exact fix path (remote.IsRemote + downloadAdhocDepChartWithGoGetter in PrepareChartify). Verified to fail on the unfixed code with the original "no helm list entry found for repository \"git::file:\"" error and pass on the fixed code. Signed-off-by: yxxhero <aiopsclub@163.com> * test: surface helmfile error in issue #821 integration test The integration runner (run.sh) enables `set -e`, so a non-zero helmfile exit aborted the script before the captured output could be printed, hiding the real failure in CI. Disable `set -e` around the helmfile invocation and report the exit code plus full output on failure. Signed-off-by: yxxhero <aiopsclub@163.com> * test: fix issue-821 integration test chart path and errexit handling Two issues caused the integration test to fail in CI (while passing in my local smoke test, which used an absolute path): 1. The main chart path was relative to the integration CWD, but the helmfile.yaml is generated in a temp directory and helmfile resolves `chart:` relative to that directory (its basePath). The relative path was interpreted as a named-repo chart and failed instantly with `Error: repo test not found`. Resolve the case dir to an absolute path with `$(cd ... && pwd)`. 2. run.sh runs under `set -e`, so the unguarded helmfile invocation exited the whole script on failure before the captured output could be printed, hiding the real error. Capture the exit code via `cmd || rc=$?` so failures are reported. Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: yxxhero <aiopsclub@163.com>
103 lines
3.6 KiB
Go
103 lines
3.6 KiB
Go
package state
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/helmfile/helmfile/pkg/filesystem"
|
|
"github.com/helmfile/helmfile/pkg/helmexec"
|
|
"github.com/helmfile/helmfile/pkg/remote"
|
|
)
|
|
|
|
// initGitRepo creates a throwaway git repository at dir containing a chart at
|
|
// chartRel (e.g. "charts/mychart") and returns the go-getter URL that targets
|
|
// it on the given ref. It is used to exercise the go-getter fetch path without
|
|
// hitting the network.
|
|
func initGitRepo(t *testing.T, dir, chartRel, ref string) string {
|
|
t.Helper()
|
|
if _, err := exec.LookPath("git"); err != nil {
|
|
t.Skipf("git not available: %v", err)
|
|
}
|
|
run := func(args ...string) {
|
|
c := exec.Command("git", args...)
|
|
c.Dir = dir
|
|
out, err := c.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("git %v: %v\n%s", args, err, out)
|
|
}
|
|
}
|
|
run("init", "-b", ref)
|
|
run("config", "user.email", "test@example.com")
|
|
run("config", "user.name", "test")
|
|
chartDir := filepath.Join(dir, filepath.FromSlash(chartRel))
|
|
require.NoError(t, os.MkdirAll(chartDir, 0o755))
|
|
require.NoError(t, os.WriteFile(filepath.Join(chartDir, "Chart.yaml"),
|
|
[]byte("apiVersion: v2\nname: mychart\nversion: 0.1.0\n"), 0o644))
|
|
run("add", ".")
|
|
run("commit", "-m", "init")
|
|
return "git::file://" + dir + "@" + chartRel + "?ref=" + ref
|
|
}
|
|
|
|
// TestAdhocDependencyGoGetterDetection verifies that the go-getter URL shapes
|
|
// used in ad-hoc dependencies (release.dependencies[].chart) are detected as
|
|
// remote sources by remote.IsRemote, which is the predicate gating the fetch
|
|
// branch added for issue #821.
|
|
func TestAdhocDependencyGoGetterDetection(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
name string
|
|
chart string
|
|
remote bool
|
|
}{
|
|
{name: "forced git getter", chart: "git::https://github.com/example/repo.git@charts/app?ref=v1.0.0", remote: true},
|
|
{name: "forced git getter file", chart: "git::file:///tmp/repo@charts/app?ref=main", remote: true},
|
|
{name: "https url", chart: "https://example.com/charts/app-0.1.0.tgz", remote: true},
|
|
{name: "named repo passthrough", chart: "prometheus-community/kube-prometheus-stack", remote: false},
|
|
{name: "oci url passthrough", chart: "oci://registry.example.com/charts/app", remote: false},
|
|
{name: "local relative path", chart: "../charts/app", remote: false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, tc.remote, remote.IsRemote(tc.chart))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAdhocDependencyGoGetterFetch is the regression test for issue #821: an
|
|
// ad-hoc dependency using a go-getter URL must be fetched to a local directory
|
|
// via downloadAdhocDepChartWithGoGetter instead of being passed through to
|
|
// chartify (which would then fail with "no helm list entry found for
|
|
// repository"). It uses a local file:// git repo so no network access is
|
|
// required.
|
|
func TestAdhocDependencyGoGetterFetch(t *testing.T) {
|
|
repo := t.TempDir()
|
|
url := initGitRepo(t, repo, "charts/mychart", "main")
|
|
|
|
logger := helmexec.NewLogger(io.Discard, "warn")
|
|
st := &HelmState{
|
|
logger: logger,
|
|
fs: filesystem.DefaultFileSystem(),
|
|
basePath: repo,
|
|
}
|
|
release := &ReleaseSpec{
|
|
Name: "test-release",
|
|
Namespace: "test-ns",
|
|
}
|
|
|
|
got, err := st.downloadAdhocDepChartWithGoGetter(release, url)
|
|
require.NoError(t, err)
|
|
|
|
info, err := os.Stat(got)
|
|
require.NoError(t, err, "fetched dependency path must exist on disk")
|
|
assert.True(t, info.IsDir(), "fetched dependency must be a directory")
|
|
_, err = os.Stat(filepath.Join(got, "Chart.yaml"))
|
|
assert.NoError(t, err, "fetched dependency must contain the chart contents")
|
|
}
|