Skip to content

Commit c2746ce

Browse files
committed
use .helmignore when identifying changed charts
Signed-off-by: Cyril Jouve <[email protected]>
1 parent 1ba3fd2 commit c2746ce

16 files changed

+424
-6
lines changed

ct/cmd/root.go

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func addCommonFlags(flags *pflag.FlagSet) {
8080
flags.Bool("github-groups", false, heredoc.Doc(`
8181
Change the delimiters for github to create collapsible groups
8282
for command output`))
83+
flags.Bool("use-helmignore", false, "Use .helmignore when identifying changed charts")
8384
}
8485

8586
func addCommonLintAndInstallFlags(flags *pflag.FlagSet) {

doc/ct_install.md

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ ct install [flags]
7575
--target-branch string The name of the target branch used to identify changed charts (default "master")
7676
--upgrade Whether to test an in-place upgrade of each chart from its previous revision if the
7777
current version should not introduce a breaking change according to the SemVer spec
78+
--use-helmignore Use .helmignore when identifying changed charts
7879
```
7980

8081
### SEE ALSO

doc/ct_lint-and-install.md

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ ct lint-and-install [flags]
7070
--target-branch string The name of the target branch used to identify changed charts (default "master")
7171
--upgrade Whether to test an in-place upgrade of each chart from its previous revision if the
7272
current version should not introduce a breaking change according to the SemVer spec
73+
--use-helmignore Use .helmignore when identifying changed charts
7374
--validate-chart-schema Enable schema validation of 'Chart.yaml' using Yamale (default true)
7475
--validate-maintainers Enable validation of maintainer account names in chart.yml.
7576
Works for GitHub, GitLab, and Bitbucket (default true)

doc/ct_lint.md

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ ct lint [flags]
6868
--remote string The name of the Git remote used to identify changed charts (default "origin")
6969
--since string The Git reference used to identify changed charts (default "HEAD")
7070
--target-branch string The name of the target branch used to identify changed charts (default "master")
71+
--use-helmignore Use .helmignore when identifying changed charts
7172
--validate-chart-schema Enable schema validation of 'Chart.yaml' using Yamale (default true)
7273
--validate-maintainers Enable validation of maintainer account names in chart.yml.
7374
Works for GitHub, GitLab, and Bitbucket (default true)

doc/ct_list-changed.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ ct list-changed [flags]
2828
--remote string The name of the Git remote used to identify changed charts (default "origin")
2929
--since string The Git reference used to identify changed charts (default "HEAD")
3030
--target-branch string The name of the target branch used to identify changed charts (default "master")
31+
--use-helmignore Use .helmignore when identifying changed charts
3132
```
3233

3334
### SEE ALSO

pkg/chart/chart.go

+26-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/helm/chart-testing/v3/pkg/config"
2727
"github.com/helm/chart-testing/v3/pkg/exec"
28+
"github.com/helm/chart-testing/v3/pkg/ignore"
2829
"github.com/helm/chart-testing/v3/pkg/tool"
2930
"github.com/helm/chart-testing/v3/pkg/util"
3031
)
@@ -242,6 +243,7 @@ type Testing struct {
242243
directoryLister DirectoryLister
243244
utils Utils
244245
previousRevisionWorktree string
246+
loadRules func(string) (*ignore.Rules, error)
245247
}
246248

247249
// TestResults holds results and overall status
@@ -271,6 +273,7 @@ func NewTesting(config config.Configuration, extraSetArgs string) (Testing, erro
271273
accountValidator: tool.AccountValidator{},
272274
directoryLister: util.DirectoryLister{},
273275
utils: util.Utils{},
276+
loadRules: ignore.LoadRules,
274277
}
275278

276279
versionString, err := testing.helm.Version()
@@ -744,7 +747,7 @@ func (t *Testing) ComputeChangedChartDirectories() ([]string, error) {
744747
return nil, fmt.Errorf("failed creating diff: %w", err)
745748
}
746749

747-
var changedChartDirs []string
750+
changedChartFiles := map[string][]string{}
748751
for _, file := range allChangedChartFiles {
749752
pathElements := strings.SplitN(filepath.ToSlash(file), "/", 3)
750753
if len(pathElements) < 2 || util.StringSliceContains(cfg.ExcludedCharts, pathElements[1]) {
@@ -761,15 +764,33 @@ func (t *Testing) ComputeChangedChartDirectories() ([]string, error) {
761764
continue
762765
}
763766
}
764-
// Only add it if not already in the list
765-
if !util.StringSliceContains(changedChartDirs, chartDir) {
766-
changedChartDirs = append(changedChartDirs, chartDir)
767-
}
767+
changedChartFiles[chartDir] = append(changedChartFiles[chartDir], strings.TrimPrefix(file, chartDir+"/"))
768768
} else {
769769
fmt.Fprintf(os.Stderr, "Directory %q is not a valid chart directory. Skipping...\n", dir)
770770
}
771771
}
772772

773+
changedChartDirs := []string{}
774+
if t.config.UseHelmignore {
775+
for chartDir, changedChartFiles := range changedChartFiles {
776+
rules, err := t.loadRules(chartDir)
777+
if err != nil {
778+
return nil, err
779+
}
780+
filteredChartFiles, err := ignore.FilterFiles(changedChartFiles, rules)
781+
if err != nil {
782+
return nil, err
783+
}
784+
if len(filteredChartFiles) > 0 {
785+
changedChartDirs = append(changedChartDirs, chartDir)
786+
}
787+
}
788+
} else {
789+
for chartDir := range changedChartFiles {
790+
changedChartDirs = append(changedChartDirs, chartDir)
791+
}
792+
}
793+
773794
return changedChartDirs, nil
774795
}
775796

pkg/chart/chart_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121

2222
"github.com/helm/chart-testing/v3/pkg/config"
23+
"github.com/helm/chart-testing/v3/pkg/ignore"
2324
"github.com/helm/chart-testing/v3/pkg/util"
2425
"github.com/stretchr/testify/assert"
2526
"github.com/stretchr/testify/mock"
@@ -152,6 +153,26 @@ func newTestingMock(cfg config.Configuration) Testing {
152153
accountValidator: fakeAccountValidator{},
153154
linter: fakeMockLinter,
154155
helm: new(fakeHelm),
156+
loadRules: func(dir string) (*ignore.Rules, error) {
157+
rules := ignore.Empty()
158+
if dir == "test_charts/foo" {
159+
var err error
160+
rules, err = ignore.Parse(strings.NewReader("Chart.yaml\n"))
161+
if err != nil {
162+
return nil, err
163+
}
164+
rules.AddDefaults()
165+
}
166+
if dir == "test_chart_at_multi_level/foo/baz" {
167+
var err error
168+
rules, err = ignore.Parse(strings.NewReader("Chart.yaml\n"))
169+
if err != nil {
170+
return nil, err
171+
}
172+
rules.AddDefaults()
173+
}
174+
return rules, nil
175+
},
155176
}
156177
}
157178

@@ -165,6 +186,19 @@ func TestComputeChangedChartDirectories(t *testing.T) {
165186
assert.Nil(t, err)
166187
}
167188

189+
func TestComputeChangedChartDirectoriesWithHelmignore(t *testing.T) {
190+
cfg := config.Configuration{
191+
ExcludedCharts: []string{"excluded"},
192+
ChartDirs: []string{"test_charts", "."},
193+
UseHelmignore: true,
194+
}
195+
ct := newTestingMock(cfg)
196+
actual, err := ct.ComputeChangedChartDirectories()
197+
expected := []string{"test_charts/bar", "test_chart_at_root"}
198+
assert.Nil(t, err)
199+
assert.ElementsMatch(t, expected, actual)
200+
}
201+
168202
func TestComputeChangedChartDirectoriesWithMultiLevelChart(t *testing.T) {
169203
cfg := config.Configuration{
170204
ExcludedCharts: []string{"excluded"},
@@ -180,6 +214,19 @@ func TestComputeChangedChartDirectoriesWithMultiLevelChart(t *testing.T) {
180214
assert.Nil(t, err)
181215
}
182216

217+
func TestComputeChangedChartDirectoriesWithMultiLevelChartWithHelmIgnore(t *testing.T) {
218+
cfg := config.Configuration{
219+
ExcludedCharts: []string{"excluded"},
220+
ChartDirs: []string{"test_chart_at_multi_level/foo"},
221+
UseHelmignore: true,
222+
}
223+
ct := newTestingMock(cfg)
224+
actual, err := ct.ComputeChangedChartDirectories()
225+
expected := []string{"test_chart_at_multi_level/foo/bar"}
226+
assert.Nil(t, err)
227+
assert.ElementsMatch(t, expected, actual)
228+
}
229+
183230
func TestReadAllChartDirectories(t *testing.T) {
184231
actual, err := ct.ReadAllChartDirectories()
185232
expected := []string{

pkg/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type Configuration struct {
7272
KubectlTimeout time.Duration `mapstructure:"kubectl-timeout"`
7373
PrintLogs bool `mapstructure:"print-logs"`
7474
GithubGroups bool `mapstructure:"github-groups"`
75+
UseHelmignore bool `mapstructure:"use-helmignore"`
7576
}
7677

7778
func LoadConfiguration(cfgFile string, cmd *cobra.Command, printConfig bool) (*Configuration, error) {

pkg/config/config_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func loadAndAssertConfigFromFile(t *testing.T, configFile string) {
6060
require.Equal(t, true, cfg.ExcludeDeprecated)
6161
require.Equal(t, 120*time.Second, cfg.KubectlTimeout)
6262
require.Equal(t, true, cfg.SkipCleanUp)
63+
require.Equal(t, true, cfg.UseHelmignore)
6364
}
6465

6566
func Test_findConfigFile(t *testing.T) {

pkg/config/test_config.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@
3131
"release-label": "release",
3232
"exclude-deprecated": true,
3333
"kubectl-timeout": "120s",
34-
"skip-clean-up": true
34+
"skip-clean-up": true,
35+
"use-helmignore": true
3536
}

pkg/config/test_config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ release-label: release
2727
exclude-deprecated: true
2828
kubectl-timeout: 120s
2929
skip-clean-up: true
30+
use-helmignore: true
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
toto
2+
tutu

pkg/ignore/chart_no_helmignore/Chart.yaml

Whitespace-only changes.

pkg/ignore/ignore.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Copyright The Helm Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package ignore
18+
19+
import (
20+
"io/fs"
21+
"os"
22+
"path/filepath"
23+
"testing/fstest"
24+
)
25+
26+
func LoadRules(dir string) (*Rules, error) {
27+
rules, err := ParseFile(filepath.Join(dir, HelmIgnore))
28+
if err != nil && !os.IsNotExist(err) {
29+
return nil, err
30+
}
31+
if rules == nil {
32+
rules = Empty()
33+
}
34+
rules.AddDefaults()
35+
return rules, nil
36+
}
37+
38+
func FilterFiles(files []string, rules *Rules) ([]string, error) {
39+
fsys := fstest.MapFS{}
40+
for _, file := range files {
41+
fsys[file] = &fstest.MapFile{}
42+
}
43+
44+
filteredFiles := []string{}
45+
46+
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
47+
if err != nil {
48+
return err
49+
}
50+
51+
fi, err := d.Info()
52+
if err != nil {
53+
return err
54+
}
55+
56+
// Normalize to / since it will also work on Windows
57+
path = filepath.ToSlash(path)
58+
59+
if fi.IsDir() {
60+
// Directory-based ignore rules should involve skipping the entire
61+
// contents of that directory.
62+
if rules.Ignore(path, fi) {
63+
return filepath.SkipDir
64+
}
65+
return nil
66+
}
67+
68+
// If a .helmignore file matches, skip this file.
69+
if rules.Ignore(path, fi) {
70+
return nil
71+
}
72+
73+
filteredFiles = append(filteredFiles, path)
74+
return nil
75+
})
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
return filteredFiles, nil
81+
}

pkg/ignore/ignore_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ignore
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestLoadRulesNoHelmignore(t *testing.T) {
11+
r, err := LoadRules("chart_no_helmignore")
12+
assert.Nil(t, err)
13+
// default pattern
14+
assert.Len(t, r.patterns, 1)
15+
}
16+
17+
func TestLoadRulesHelmignore(t *testing.T) {
18+
r, err := LoadRules("chart_helmignore")
19+
assert.Nil(t, err)
20+
assert.Len(t, r.patterns, 3)
21+
}
22+
23+
func TestFilter(t *testing.T) {
24+
rules, err := Parse(strings.NewReader("/bar/\nREADME.md\n"))
25+
assert.Nil(t, err)
26+
files := []string{"Chart.yaml", "bar/xxx", "template/svc.yaml", "baz/bar/biz.txt", "README.md"}
27+
actual, err := FilterFiles(files, rules)
28+
assert.Nil(t, err)
29+
expected := []string{"Chart.yaml", "baz/bar/biz.txt", "template/svc.yaml"}
30+
assert.ElementsMatch(t, expected, actual)
31+
}

0 commit comments

Comments
 (0)