Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helm: add support for vendoring to subpaths #944

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
52 changes: 39 additions & 13 deletions pkg/helm/charts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package helm

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"slices"
"strings"

"github.com/rs/zerolog/log"
"sigs.k8s.io/yaml"
)

var (
// https://regex101.com/r/7xFFtU/3
chartExp = regexp.MustCompile(`^(?P<chart>\w+\/.+)@(?P<version>[^:\n\s]+)(?:\:(?P<path>[\w-. ]+))?$`)
// https://regex101.com/r/7xFFtU/4
chartExp = regexp.MustCompile(`^(?P<chart>\w+\/.+)@(?P<version>[^:\n\s]+)(?:\:(?P<path>[\w-. /\\]+))?$`)
repoExp = regexp.MustCompile(`^\w+$`)
)

Expand Down Expand Up @@ -101,6 +103,7 @@ func (c Charts) Vendor(prune bool) error {
}

expectedDirs := make(map[string]bool)
expectedDirs[c.Manifest.Directory] = true

repositoriesUpdated := false
log.Info().Msg("Vendoring...")
Expand All @@ -111,6 +114,9 @@ func (c Charts) Vendor(prune bool) error {
}
chartPath := filepath.Join(dir, chartSubDir)
chartManifestPath := filepath.Join(chartPath, "Chart.yaml")
for _, subDir := range strings.Split(chartSubDir, string(os.PathSeparator)) {
expectedDirs[subDir] = true
}
expectedDirs[chartSubDir] = true

chartDirExists, chartManifestExists := false, false
Expand Down Expand Up @@ -176,21 +182,41 @@ func (c Charts) Vendor(prune bool) error {
}

if prune {
items, err := os.ReadDir(dir)
if err != nil {
return fmt.Errorf("error listing the content of the charts dir: %w", err)
}
for _, i := range items {
if !expectedDirs[i.Name()] {
itemType := "file"
if i.IsDir() {
// Walk the charts directory looking for any unexpected directories or files to remove
// Skips walking an expected directory that contains a Chart.yaml
isChartFile := func(element fs.DirEntry) bool { return element.Name() == "Chart.yaml" }
projectRootFs := os.DirFS(c.projectRoot)

err := fs.WalkDir(projectRootFs, c.Manifest.Directory, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("error during prune: at path %s: %w", path, err)
}
itemType := "file"
if !expectedDirs[d.Name()] {
if d.IsDir() {
itemType = "directory"
}
log.Info().Msgf("Pruning %s: %s", itemType, i.Name())
if err := os.RemoveAll(filepath.Join(dir, i.Name())); err != nil {
return err
log.Info().Msgf("Pruning %s: %s", itemType, path)
if localErr := os.RemoveAll(filepath.Join(c.projectRoot, path)); localErr != nil {
return localErr
}
// If we just pruned a directory, the walk needs to skip it.
if d.IsDir() {
return filepath.SkipDir
}
} else {
items, localErr := fs.ReadDir(projectRootFs, path)
if localErr != nil {
return fmt.Errorf("error listing content of dir %s: %w", path, localErr)
}
if slices.ContainsFunc(items, isChartFile) {
return filepath.SkipDir
}
}
return nil
})
if err != nil {
return err
}
}

Expand Down
41 changes: 38 additions & 3 deletions pkg/helm/charts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ func TestParseReq(t *testing.T) {
Directory: "my weird-path_test",
},
},
{
name: "with-path-with-sub-path",
input: "stable/[email protected]:" + filepath.Join("myparentdir1", "mysubdir1", "mypath"),
expected: &Requirement{
Chart: "stable/package",
Version: "3.45.6",
Directory: filepath.Join("myparentdir1", "mysubdir1", "mypath"),
},
},
{
name: "url-instead-of-repo",
input: "https://helm.releases.hashicorp.com/[email protected]",
Expand Down Expand Up @@ -104,12 +113,21 @@ func TestAdd(t *testing.T) {
err = c.Add([]string{"stable/[email protected]:prometheus-11.12.0"})
assert.NoError(t, err)

// Add a chart with a nested extract directory
err = c.Add([]string{"stable/[email protected]:" + filepath.Join("zparentdir", "prometheus-11.12.0")})
assert.NoError(t, err)

// Check file contents
listResult, err := os.ReadDir(filepath.Join(tempDir, "charts"))
assert.NoError(t, err)
assert.Equal(t, 2, len(listResult))
assert.Equal(t, 3, len(listResult))
assert.Equal(t, "prometheus", listResult[0].Name())
assert.Equal(t, "prometheus-11.12.0", listResult[1].Name())
assert.Equal(t, "zparentdir", listResult[2].Name())
listResult, err = os.ReadDir(filepath.Join(tempDir, "charts", "zparentdir"))
assert.NoError(t, err)
assert.Equal(t, 1, len(listResult))
assert.Equal(t, "prometheus-11.12.0", listResult[0].Name())

chartContent, err := os.ReadFile(filepath.Join(tempDir, "charts", "prometheus", "Chart.yaml"))
assert.NoError(t, err)
Expand All @@ -118,6 +136,10 @@ func TestAdd(t *testing.T) {
chartContent, err = os.ReadFile(filepath.Join(tempDir, "charts", "prometheus-11.12.0", "Chart.yaml"))
assert.NoError(t, err)
assert.Contains(t, string(chartContent), `version: 11.12.0`)

chartContent, err = os.ReadFile(filepath.Join(tempDir, "charts", "zparentdir", "prometheus-11.12.0", "Chart.yaml"))
assert.NoError(t, err)
assert.Contains(t, string(chartContent), `version: 11.12.0`)
}

func TestAddOCI(t *testing.T) {
Expand Down Expand Up @@ -183,25 +205,38 @@ func TestPrune(t *testing.T) {
// Add a chart with a directory
require.NoError(t, c.Add([]string{"stable/[email protected]:custom-dir"}))

// Add a chart with a sub-directory
require.NoError(t, c.Add([]string{"stable/[email protected]:" + filepath.Join("second-dir", "chartdir")}))

// Add unrelated files and folders
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "charts", "foo.txt"), []byte("foo"), 0644))
require.NoError(t, os.Mkdir(filepath.Join(tempDir, "charts", "foo"), 0755))
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "charts", "foo", "Chart.yaml"), []byte("foo"), 0644))
require.NoError(t, os.Mkdir(filepath.Join(tempDir, "charts", "second-dir", "bar"), 0755))
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "charts", "second-dir", "bar.txt"), []byte("bar"), 0644))

require.NoError(t, c.Vendor(prune))

// Check if files are pruned
listResult, err := os.ReadDir(filepath.Join(tempDir, "charts"))
assert.NoError(t, err)
if prune {
assert.Equal(t, 2, len(listResult))
assert.Equal(t, 3, len(listResult))
assert.Equal(t, "custom-dir", listResult[0].Name())
assert.Equal(t, "prometheus", listResult[1].Name())
assert.Equal(t, "second-dir", listResult[2].Name())
listResult, err = os.ReadDir(filepath.Join(tempDir, "charts", "second-dir"))
assert.NoError(t, err)
assert.Equal(t, 1, len(listResult))
assert.Equal(t, "chartdir", listResult[0].Name())
} else {
assert.Equal(t, 4, len(listResult))
assert.Equal(t, 5, len(listResult))
chartContent, err := os.ReadFile(filepath.Join(tempDir, "charts", "foo", "Chart.yaml"))
assert.NoError(t, err)
assert.Contains(t, string(chartContent), `foo`)
chartContent, err = os.ReadFile(filepath.Join(tempDir, "charts", "second-dir", "bar.txt"))
assert.NoError(t, err)
assert.Contains(t, string(chartContent), `bar`)
}
})
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,14 @@ func (e ExecHelm) Pull(chart, version string, opts PullOpts) error {

// It is not possible to tell `helm pull` to extract to a specific directory
// so we extract to a temp dir and then move the files to the destination
// (if the destination is in any subdirectories, they are created if necessary)
finalChartPath := filepath.Join(opts.Destination, opts.ExtractDirectory)
if err := os.MkdirAll(filepath.Dir(finalChartPath), os.ModePerm); err != nil {
return err
}
return os.Rename(
filepath.Join(tempDir, chartName),
filepath.Join(opts.Destination, opts.ExtractDirectory),
finalChartPath,
)
}

Expand Down