diff --git a/internal/chart/v3/loader/directory.go b/internal/chart/v3/loader/directory.go index 535468dd1..bb8c44595 100644 --- a/internal/chart/v3/loader/directory.go +++ b/internal/chart/v3/loader/directory.go @@ -82,6 +82,11 @@ func loadDir(dir string, budget int64) (*chart.Chart, error) { n = filepath.ToSlash(n) if err != nil { + // A symlink that cannot be resolved is skipped only if a + // .helmignore rule matches it. + if fi != nil && sympath.IsSymlink(fi) && rules.Ignore(n, fi) { + return nil + } return err } if fi.IsDir() { diff --git a/internal/sympath/walk.go b/internal/sympath/walk.go index b54b97ad4..7f5dfe721 100644 --- a/internal/sympath/walk.go +++ b/internal/sympath/walk.go @@ -69,7 +69,7 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { if IsSymlink(info) { resolved, err := filepath.EvalSymlinks(path) if err != nil { - return fmt.Errorf("error evaluating symlink %s: %w", path, err) + return walkFn(path, info, fmt.Errorf("error evaluating symlink %s: %w", path, err)) } // This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons. slog.Info("found symbolic link in path. Contents of linked file included and used", "path", path, "resolved", resolved) diff --git a/pkg/chart/v2/loader/directory.go b/pkg/chart/v2/loader/directory.go index bfc649f2a..92250d700 100644 --- a/pkg/chart/v2/loader/directory.go +++ b/pkg/chart/v2/loader/directory.go @@ -82,6 +82,11 @@ func loadDir(dir string, budget int64) (*chart.Chart, error) { n = filepath.ToSlash(n) if err != nil { + // A symlink that cannot be resolved is skipped only if a + // .helmignore rule matches it. + if fi != nil && sympath.IsSymlink(fi) && rules.Ignore(n, fi) { + return nil + } return err } if fi.IsDir() { diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index 5af88b341..eaad86f5c 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -90,6 +90,36 @@ func TestLoadDirWithSymlink(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirWithBrokenSymlink(t *testing.T) { + tests := []struct { + name string + helmignore string + wantErr bool + }{ + {"not ignored", "", true}, + {"ignored by .helmignore", "bar\n", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "Chart.yaml"), []byte("apiVersion: v2\nname: test\nversion: 0.1.0\n"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".helmignore"), []byte(tt.helmignore), 0o644)) + require.NoError(t, os.Mkdir(filepath.Join(dir, "templates"), 0o755)) + require.NoError(t, os.Symlink("foo", filepath.Join(dir, "templates", "bar"))) + + c, err := LoadDir(dir) + if tt.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), "error evaluating symlink") + return + } + require.NoError(t, err) + assert.Equal(t, "test", c.Name()) + assert.Empty(t, c.Templates) + }) + } +} + func TestBomTestData(t *testing.T) { testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"} for _, file := range testFiles {