Skip to content

Commit

Permalink
fix(helm): properly handle multiple archived dependencies (#7782)
Browse files Browse the repository at this point in the history
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
  • Loading branch information
nikpivkin authored Oct 29, 2024
1 parent c70b6fa commit 6fab88d
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 16 deletions.
47 changes: 31 additions & 16 deletions pkg/iac/scanners/helm/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io/fs"
"path"
"path/filepath"
"regexp"
"sort"
Expand Down Expand Up @@ -46,7 +47,7 @@ type ChartFile struct {
ManifestContent string
}

func New(path string, opts ...Option) (*Parser, error) {
func New(src string, opts ...Option) (*Parser, error) {

client := action.NewInstall(&action.Configuration{})
client.DryRun = true // don't do anything
Expand All @@ -55,7 +56,7 @@ func New(path string, opts ...Option) (*Parser, error) {

p := &Parser{
helmClient: client,
ChartSource: path,
ChartSource: src,
logger: log.WithPrefix("helm parser"),
}

Expand All @@ -79,10 +80,10 @@ func New(path string, opts ...Option) (*Parser, error) {
return p, nil
}

func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error {
p.workingFS = target
func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, target string) error {
p.workingFS = fsys

if err := fs.WalkDir(p.workingFS, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error {
if err := fs.WalkDir(p.workingFS, filepath.ToSlash(target), func(filePath string, entry fs.DirEntry, err error) error {
select {
case <-ctx.Done():
return ctx.Err()
Expand All @@ -95,16 +96,20 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error {
return nil
}

if detection.IsArchive(path) {
tarFS, err := p.addTarToFS(path)
if _, err := fs.Stat(p.workingFS, filePath); err != nil {
return nil
}

if detection.IsArchive(filePath) && !isDependencyChartArchive(p.workingFS, filePath) {
tarFS, err := p.addTarToFS(filePath)
if errors.Is(err, errSkipFS) {
// an unpacked Chart already exists
return nil
} else if err != nil {
return fmt.Errorf("failed to add tar %q to FS: %w", path, err)
return fmt.Errorf("failed to add tar %q to FS: %w", filePath, err)
}

targetPath := filepath.Dir(path)
targetPath := filepath.Dir(filePath)
if targetPath == "" {
targetPath = "."
}
Expand All @@ -114,7 +119,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error {
}
return nil
} else {
return p.addPaths(path)
return p.addPaths(filePath)
}
}); err != nil {
return fmt.Errorf("walk dir error: %w", err)
Expand All @@ -123,19 +128,29 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error {
return nil
}

func isDependencyChartArchive(fsys fs.FS, archivePath string) bool {
parent := path.Dir(archivePath)
if path.Base(parent) != "charts" {
return false
}

_, err := fs.Stat(fsys, path.Join(parent, "..", "Chart.yaml"))
return err == nil
}

func (p *Parser) addPaths(paths ...string) error {
for _, path := range paths {
if _, err := fs.Stat(p.workingFS, path); err != nil {
for _, filePath := range paths {
if _, err := fs.Stat(p.workingFS, filePath); err != nil {
return err
}

if strings.HasSuffix(path, "Chart.yaml") && p.rootPath == "" {
if err := p.extractChartName(path); err != nil {
if strings.HasSuffix(filePath, "Chart.yaml") && p.rootPath == "" {
if err := p.extractChartName(filePath); err != nil {
return err
}
p.rootPath = filepath.Dir(path)
p.rootPath = filepath.Dir(filePath)
}
p.filepaths = append(p.filepaths, path)
p.filepaths = append(p.filepaths, filePath)
}
return nil
}
Expand Down
30 changes: 30 additions & 0 deletions pkg/iac/scanners/helm/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,34 @@ func TestParseFS(t *testing.T) {
}
assert.Equal(t, expectedFiles, p.filepaths)
})

t.Run("chart with multiple archived deps", func(t *testing.T) {
p, err := New(".")
require.NoError(t, err)

fsys := os.DirFS(filepath.Join("testdata", "multiple-archived-deps"))
require.NoError(t, p.ParseFS(context.TODO(), fsys, "."))

expectedFiles := []string{
"Chart.yaml",
"charts/common-2.26.0.tgz",
"charts/opentelemetry-collector-0.108.0.tgz",
}
assert.Equal(t, expectedFiles, p.filepaths)
})

t.Run("archives are not dependencies", func(t *testing.T) {
p, err := New(".")
require.NoError(t, err)

fsys := os.DirFS(filepath.Join("testdata", "non-deps-archives"))
require.NoError(t, p.ParseFS(context.TODO(), fsys, "."))

expectedFiles := []string{
"Chart.yaml",
"backup_charts/wordpress-operator/Chart.yaml",
"backup_charts/mysql-operator/Chart.yaml",
}
assert.Subset(t, p.filepaths, expectedFiles)
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v2
appVersion: "1.1"
description: Test Chart
name: y-chart
version: 1.0.0
kubeVersion: ">=1.21"

dependencies:
- name: common
repository: https://charts.bitnami.com/bitnami
version: 2.26.0
- name: opentelemetry-collector
version: 0.108.0
repository: https://open-telemetry.github.io/opentelemetry-helm-charts
Binary file not shown.
Binary file not shown.
14 changes: 14 additions & 0 deletions pkg/iac/scanners/helm/parser/testdata/non-deps-archives/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v2
appVersion: "1.1"
description: Test Chart
name: y-chart
version: 1.0.0
kubeVersion: ">=1.21"

dependencies:
- name: mysql-operator
repository: https://mysql.github.io/mysql-operator/
version: 2.2.2
- name: wordpress-operator
version: 0.12.4
repository: https://helm-charts.bitpoke.io
Binary file not shown.
Binary file not shown.

0 comments on commit 6fab88d

Please sign in to comment.