/
githubmirror
/
trivy
Обзор
Документация
Войти
/
githubmirror
/
trivy
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
pkg/iac/scanners/helm/parser/parser_test.go
277 строк
8 KB
Nikita Pivkin
refactor(misconf): replace custom Helm archive parsing with Helm SDK loaders (#10718)
27 май 2026, 18:25
Не верифицирован
27 май 2026, 18:25
441251e
Код
Авторство
О чём код?
package parser_test import ( "bytes" "io/fs" "os" "path" "path/filepath" "strings" "testing" "testing/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" ) func assertManifestEqual(t *testing.T, fsys fs.FS, path, actual string) { t.Helper() expected, err := fs.ReadFile(fsys, path) require.NoError(t, err) assert.Equal(t, normalizeManifest(string(expected)), normalizeManifest(actual)) } func normalizeManifest(s string) string { s = strings.ReplaceAll(s, "\r\n", "\n") return strings.TrimRight(s, "\n") } func TestParseFS(t *testing.T) { tests := []struct { name string chartName string opts []parser.Option manifestCount int wantErr string wantParseErr string }{ { name: "simple chart", chartName: "testchart", manifestCount: 3, }, { name: "chart with integer name", chartName: "chart-with-integer-name", manifestCount: 1, }, { name: "chart without name returns error", chartName: "chart-without-name", wantParseErr: "chart.metadata.name is required", }, { name: "values file option", chartName: "testchart", opts: []parser.Option{parser.OptionWithValuesFile(filepath.Join("testdata", "values.yaml"))}, manifestCount: 3, }, { name: "non-existent values file", chartName: "testchart", opts: []parser.Option{parser.OptionWithValuesFile(filepath.Join("testdata", "nonexistent.yaml"))}, wantParseErr: "nonexistent.yaml", }, { name: "set value option", chartName: "testchart", opts: []parser.Option{parser.OptionWithValues("securityContext.runAsUser=0")}, manifestCount: 3, }, { name: "set string value option", chartName: "testchart", opts: []parser.Option{parser.OptionWithStringValues("securityContext.runAsUser=0")}, manifestCount: 3, }, { name: "set file value option", chartName: "testchart", opts: []parser.Option{parser.OptionWithFileValues("image.tag=" + path.Join("testdata", "values.yaml"))}, manifestCount: 3, }, { name: "api versions option", chartName: "with-api-version", opts: []parser.Option{parser.OptionWithAPIVersions("policy/v1/PodDisruptionBudget")}, manifestCount: 1, }, { name: "kube version option", chartName: "with-kube-version", opts: []parser.Option{parser.OptionWithKubeVersion("1.60")}, manifestCount: 1, }, { name: "invalid kube version", chartName: "with-kube-version", opts: []parser.Option{parser.OptionWithKubeVersion("a.b.c")}, wantErr: `could not parse "a.b.c" as version`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p, err := parser.New(tt.opts...) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) fsys := testutil.TxtarToFS(t, filepath.Join("testdata", tt.chartName+".txtar")) manifests, err := p.ParseFS(t.Context(), fsys, ".") if tt.wantParseErr != "" { assert.ErrorContains(t, err, tt.wantParseErr) return } require.NoError(t, err) assert.Len(t, manifests, tt.manifestCount) }) } } func TestParseFS_Rendered(t *testing.T) { tests := []struct { name string chartName string opts []parser.Option expectedFile string }{ { name: "simple chart", chartName: "testchart", expectedFile: "testchart.txtar", }, { name: "values file option", chartName: "testchart", opts: []parser.Option{parser.OptionWithValuesFile(filepath.Join("testdata", "values.yaml"))}, expectedFile: "testchart-with-options.txtar", }, { name: "set value option", chartName: "testchart", opts: []parser.Option{parser.OptionWithValues("securityContext.runAsUser=0")}, expectedFile: "testchart-with-options.txtar", }, { name: "api versions option", chartName: "with-api-version", opts: []parser.Option{parser.OptionWithAPIVersions("policy/v1/PodDisruptionBudget")}, expectedFile: "with-api-version.txtar", }, { name: "kube version option", chartName: "with-kube-version", opts: []parser.Option{parser.OptionWithKubeVersion("1.60")}, expectedFile: "with-kube-version.txtar", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p, err := parser.New(tt.opts...) require.NoError(t, err) fsys := testutil.TxtarToFS(t, filepath.Join("testdata", tt.chartName+".txtar")) manifests, err := p.ParseFS(t.Context(), fsys, ".") require.NoError(t, err) expectedFS := testutil.TxtarToFS(t, filepath.Join("testdata", "expected", tt.expectedFile)) for _, manifest := range manifests { assertManifestEqual(t, expectedFS, manifest.Path, manifest.Content) } }) } } func TestParseFS_WithArchivedDependency(t *testing.T) { expectedFS := testutil.TxtarToFS(t, filepath.Join("testdata", "expected", "chart-with-packaged-dep.txtar")) p, err := parser.New() require.NoError(t, err) // os.DirFS is used here because the chart contains a binary .tgz dependency (common-1.16.1.tgz) // which cannot be embedded in a txtar file. manifests, err := p.ParseFS(t.Context(), os.DirFS(filepath.Join("testdata", "chart-with-packaged-dep")), ".") require.NoError(t, err) assert.Len(t, manifests, 3) for _, manifest := range manifests { assertManifestEqual(t, expectedFS, manifest.Path, manifest.Content) } } func TestParseArchive(t *testing.T) { expectedFS := testutil.TxtarToFS(t, filepath.Join("testdata", "expected", "testchart.txtar")) fsys := testutil.TxtarToFS(t, filepath.Join("testdata", "testchart.txtar")) archiveData := testutil.FSToTarGz(t, fsys, "testchart") archiveFS := fstest.MapFS{"testchart.tar.gz": {Data: archiveData}} p, err := parser.New() require.NoError(t, err) manifests, err := p.ParseArchive(t.Context(), archiveFS, "testchart.tar.gz") require.NoError(t, err) assert.Len(t, manifests, 3) for _, manifest := range manifests { assertManifestEqual(t, expectedFS, manifest.Path, manifest.Content) } } func TestParseArchive_ChartWithoutName(t *testing.T) { fsys := testutil.TxtarToFS(t, filepath.Join("testdata", "chart-without-name.txtar")) archiveFS := fstest.MapFS{"chart-without-name.tgz": {Data: testutil.FSToTarGz(t, fsys, "chart-without-name")}} p, err := parser.New() require.NoError(t, err) _, err = p.ParseArchive(t.Context(), archiveFS, "chart-without-name.tgz") assert.ErrorContains(t, err, "chart.metadata.name is required") } func TestParseArchive_InvalidArchive(t *testing.T) { p, err := parser.New() require.NoError(t, err) fsys := fstest.MapFS{"chart.tgz": {Data: []byte("not a valid archive")}} _, err = p.ParseArchive(t.Context(), fsys, "chart.tgz") assert.ErrorContains(t, err, "load archive files") } // TODO: move to pkg/iac/detection func TestIsHelmChartArchive(t *testing.T) { chartYAML := "apiVersion: v2\nname: test\nversion: 1.0.0\n" helmFS := fstest.MapFS{"chart/Chart.yaml": {Data: []byte(chartYAML)}} nonHelmFS := fstest.MapFS{"chart/README.md": {Data: []byte("# readme\n")}} tests := []struct { name string filename string fsys fs.FS expected bool }{ { name: "gzip tarball with tar.gz extension", filename: "chart.tar.gz", fsys: helmFS, expected: true, }, { name: "gzip tarball with tgz extension", filename: "chart.tgz", fsys: helmFS, expected: true, }, { name: "non-helm tgz", filename: "chart.tgz", fsys: nonHelmFS, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { data := testutil.FSToTarGz(t, tt.fsys, "chart") isArchive := detection.IsHelmChartArchive(tt.filename, bytes.NewReader(data)) assert.Equal(t, tt.expected, isArchive) }) } }