Skip to content

Commit

Permalink
use .helmignore when identifying changed charts
Browse files Browse the repository at this point in the history
Signed-off-by: Cyril Jouve <[email protected]>
  • Loading branch information
jouve committed Oct 3, 2023
1 parent 7ca7aae commit 208a95d
Show file tree
Hide file tree
Showing 26 changed files with 585 additions and 6 deletions.
1 change: 1 addition & 0 deletions ct/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func addCommonFlags(flags *pflag.FlagSet) {
flags.Bool("github-groups", false, heredoc.Doc(`
Change the delimiters for github to create collapsible groups
for command output`))
flags.Bool("use-helmignore", false, "Use .helmignore when identifying changed charts")
}

func addCommonLintAndInstallFlags(flags *pflag.FlagSet) {
Expand Down
1 change: 1 addition & 0 deletions doc/ct_install.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ ct install [flags]
--target-branch string The name of the target branch used to identify changed charts (default "master")
--upgrade Whether to test an in-place upgrade of each chart from its previous revision if the
current version should not introduce a breaking change according to the SemVer spec
--use-helmignore Use .helmignore when identifying changed charts
```

### SEE ALSO
Expand Down
1 change: 1 addition & 0 deletions doc/ct_lint-and-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ ct lint-and-install [flags]
--target-branch string The name of the target branch used to identify changed charts (default "master")
--upgrade Whether to test an in-place upgrade of each chart from its previous revision if the
current version should not introduce a breaking change according to the SemVer spec
--use-helmignore Use .helmignore when identifying changed charts
--validate-chart-schema Enable schema validation of 'Chart.yaml' using Yamale (default true)
--validate-maintainers Enable validation of maintainer account names in chart.yml.
Works for GitHub, GitLab, and Bitbucket (default true)
Expand Down
1 change: 1 addition & 0 deletions doc/ct_lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ ct lint [flags]
--remote string The name of the Git remote used to identify changed charts (default "origin")
--since string The Git reference used to identify changed charts (default "HEAD")
--target-branch string The name of the target branch used to identify changed charts (default "master")
--use-helmignore Use .helmignore when identifying changed charts
--validate-chart-schema Enable schema validation of 'Chart.yaml' using Yamale (default true)
--validate-maintainers Enable validation of maintainer account names in chart.yml.
Works for GitHub, GitLab, and Bitbucket (default true)
Expand Down
1 change: 1 addition & 0 deletions doc/ct_list-changed.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ct list-changed [flags]
--remote string The name of the Git remote used to identify changed charts (default "origin")
--since string The Git reference used to identify changed charts (default "HEAD")
--target-branch string The name of the target branch used to identify changed charts (default "master")
--use-helmignore Use .helmignore when identifying changed charts
```

### SEE ALSO
Expand Down
31 changes: 26 additions & 5 deletions pkg/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/helm/chart-testing/v3/pkg/config"
"github.com/helm/chart-testing/v3/pkg/exec"
"github.com/helm/chart-testing/v3/pkg/ignore"
"github.com/helm/chart-testing/v3/pkg/tool"
"github.com/helm/chart-testing/v3/pkg/util"
)
Expand Down Expand Up @@ -242,6 +243,7 @@ type Testing struct {
directoryLister DirectoryLister
utils Utils
previousRevisionWorktree string
loadRules func(string) (*ignore.Rules, error)
}

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

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

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

changedChartDirs := []string{}
if t.config.UseHelmignore {
for chartDir, changedChartFiles := range changedChartFiles {
rules, err := t.loadRules(chartDir)
if err != nil {
return nil, err
}
filteredChartFiles, err := ignore.FilterFiles(changedChartFiles, rules)
if err != nil {
return nil, err
}
if len(filteredChartFiles) > 0 {
changedChartDirs = append(changedChartDirs, chartDir)
}
}
} else {
for chartDir := range changedChartFiles {
changedChartDirs = append(changedChartDirs, chartDir)
}
}

return changedChartDirs, nil
}

Expand Down
47 changes: 47 additions & 0 deletions pkg/chart/chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"testing"

"github.com/helm/chart-testing/v3/pkg/config"
"github.com/helm/chart-testing/v3/pkg/ignore"
"github.com/helm/chart-testing/v3/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -152,6 +153,26 @@ func newTestingMock(cfg config.Configuration) Testing {
accountValidator: fakeAccountValidator{},
linter: fakeMockLinter,
helm: new(fakeHelm),
loadRules: func(dir string) (*ignore.Rules, error) {
rules := ignore.Empty()
if dir == "test_charts/foo" {
var err error
rules, err = ignore.Parse(strings.NewReader("Chart.yaml\n"))
if err != nil {
return nil, err
}
rules.AddDefaults()
}
if dir == "test_chart_at_multi_level/foo/baz" {
var err error
rules, err = ignore.Parse(strings.NewReader("Chart.yaml\n"))
if err != nil {
return nil, err
}
rules.AddDefaults()
}
return rules, nil
},
}
}

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

func TestComputeChangedChartDirectoriesWithHelmignore(t *testing.T) {
cfg := config.Configuration{
ExcludedCharts: []string{"excluded"},
ChartDirs: []string{"test_charts", "."},
UseHelmignore: true,
}
ct := newTestingMock(cfg)
actual, err := ct.ComputeChangedChartDirectories()
expected := []string{"test_charts/bar", "test_chart_at_root"}
assert.Nil(t, err)
assert.ElementsMatch(t, expected, actual)
}

func TestComputeChangedChartDirectoriesWithMultiLevelChart(t *testing.T) {
cfg := config.Configuration{
ExcludedCharts: []string{"excluded"},
Expand All @@ -180,6 +214,19 @@ func TestComputeChangedChartDirectoriesWithMultiLevelChart(t *testing.T) {
assert.Nil(t, err)
}

func TestComputeChangedChartDirectoriesWithMultiLevelChartWithHelmIgnore(t *testing.T) {
cfg := config.Configuration{
ExcludedCharts: []string{"excluded"},
ChartDirs: []string{"test_chart_at_multi_level/foo"},
UseHelmignore: true,
}
ct := newTestingMock(cfg)
actual, err := ct.ComputeChangedChartDirectories()
expected := []string{"test_chart_at_multi_level/foo/bar"}
assert.Nil(t, err)
assert.ElementsMatch(t, expected, actual)
}

func TestReadAllChartDirectories(t *testing.T) {
actual, err := ct.ReadAllChartDirectories()
expected := []string{
Expand Down
1 change: 1 addition & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type Configuration struct {
KubectlTimeout time.Duration `mapstructure:"kubectl-timeout"`
PrintLogs bool `mapstructure:"print-logs"`
GithubGroups bool `mapstructure:"github-groups"`
UseHelmignore bool `mapstructure:"use-helmignore"`
}

func LoadConfiguration(cfgFile string, cmd *cobra.Command, printConfig bool) (*Configuration, error) {
Expand Down
1 change: 1 addition & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func loadAndAssertConfigFromFile(t *testing.T, configFile string) {
require.Equal(t, true, cfg.ExcludeDeprecated)
require.Equal(t, 120*time.Second, cfg.KubectlTimeout)
require.Equal(t, true, cfg.SkipCleanUp)
require.Equal(t, true, cfg.UseHelmignore)
}

func Test_findConfigFile(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/test_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
"release-label": "release",
"exclude-deprecated": true,
"kubectl-timeout": "120s",
"skip-clean-up": true
"skip-clean-up": true,
"use-helmignore": true
}
1 change: 1 addition & 0 deletions pkg/config/test_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ release-label: release
exclude-deprecated: true
kubectl-timeout: 120s
skip-clean-up: true
use-helmignore: true
2 changes: 2 additions & 0 deletions pkg/ignore/chart_helmignore/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
toto
tutu
Empty file.
81 changes: 81 additions & 0 deletions pkg/ignore/ignore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ignore

import (
"io/fs"
"os"
"path/filepath"
"testing/fstest"
)

func LoadRules(dir string) (*Rules, error) {
rules, err := ParseFile(filepath.Join(dir, HelmIgnore))
if err != nil && !os.IsNotExist(err) {
return nil, err
}
if rules == nil {
rules = Empty()
}
rules.AddDefaults()
return rules, nil
}

func FilterFiles(files []string, rules *Rules) ([]string, error) {
fsys := fstest.MapFS{}
for _, file := range files {
fsys[file] = &fstest.MapFile{}
}

filteredFiles := []string{}

err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

fi, err := d.Info()
if err != nil {
return err
}

// Normalize to / since it will also work on Windows
path = filepath.ToSlash(path)

if fi.IsDir() {
// Directory-based ignore rules should involve skipping the entire
// contents of that directory.
if rules.Ignore(path, fi) {
return filepath.SkipDir
}
return nil
}

// If a .helmignore file matches, skip this file.
if rules.Ignore(path, fi) {
return nil
}

filteredFiles = append(filteredFiles, path)
return nil
})
if err != nil {
return nil, err
}

return filteredFiles, nil
}
31 changes: 31 additions & 0 deletions pkg/ignore/ignore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ignore

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestLoadRulesNoHelmignore(t *testing.T) {
r, err := LoadRules("chart_no_helmignore")
assert.Nil(t, err)
// default pattern
assert.Len(t, r.patterns, 1)
}

func TestLoadRulesHelmignore(t *testing.T) {
r, err := LoadRules("chart_helmignore")
assert.Nil(t, err)
assert.Len(t, r.patterns, 3)
}

func TestFilter(t *testing.T) {
rules, err := Parse(strings.NewReader("/bar/\nREADME.md\n"))
assert.Nil(t, err)
files := []string{"Chart.yaml", "bar/xxx", "template/svc.yaml", "baz/bar/biz.txt", "README.md"}
actual, err := FilterFiles(files, rules)
assert.Nil(t, err)
expected := []string{"Chart.yaml", "baz/bar/biz.txt", "template/svc.yaml"}
assert.ElementsMatch(t, expected, actual)
}
Loading

0 comments on commit 208a95d

Please sign in to comment.