mirror of
https://github.com/helm/chart-testing.git
synced 2026-02-05 09:45:14 +01:00
Support execution of additional commands during ct lint (#283)
* support execution of additional commands for lint
Given that helm unittest is installed locally or mounted into the chart
testing container one could use this to run helm unittest for each
chart.
```
additional-commands:
- helm unittest --helm3 -f tests/*.yaml {{ .Path }}
```
The command is treated as a go template. Like this it's possible to get
the path to the chart as in the example. It would also be open for
further extension if needed.
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
* Use sh to execute command
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
Co-authored-by: Reinhard Nägele <unguiculus@gmail.com>
* rename function to NewCmdTemplateExecutor
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
* add error handling
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
* add documentation
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
* use go-shellwords to split rendered command
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
* Update pkg/tool/cmdexecutor.go
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
Co-authored-by: Reinhard Nägele <unguiculus@gmail.com>
* add unit tests
Signed-off-by: Torsten Walter <mail@torstenwalter.de>
Co-authored-by: Reinhard Nägele <unguiculus@gmail.com>
This commit is contained in:
@@ -131,6 +131,13 @@ type Linter interface {
|
||||
Yamale(yamlFile string, schemaFile string) error
|
||||
}
|
||||
|
||||
// CmdExecutor is the interface
|
||||
//
|
||||
// RunCommand renders cmdTemplate as go template using data and executes the resulting command
|
||||
type CmdExecutor interface {
|
||||
RunCommand(cmdTemplate string, data interface{}) error
|
||||
}
|
||||
|
||||
// DirectoryLister is the interface
|
||||
//
|
||||
// ListChildDirs lists direct child directories of parentDir given they pass the test function
|
||||
@@ -224,6 +231,7 @@ type Testing struct {
|
||||
kubectl Kubectl
|
||||
git Git
|
||||
linter Linter
|
||||
cmdExecutor CmdExecutor
|
||||
accountValidator AccountValidator
|
||||
directoryLister DirectoryLister
|
||||
chartUtils ChartUtils
|
||||
@@ -253,6 +261,7 @@ func NewTesting(config config.Configuration) (Testing, error) {
|
||||
git: tool.NewGit(procExec),
|
||||
kubectl: tool.NewKubectl(procExec),
|
||||
linter: tool.NewLinter(procExec),
|
||||
cmdExecutor: tool.NewCmdTemplateExecutor(procExec),
|
||||
accountValidator: tool.AccountValidator{},
|
||||
directoryLister: util.DirectoryLister{},
|
||||
chartUtils: util.ChartUtils{},
|
||||
@@ -451,6 +460,13 @@ func (t *Testing) LintChart(chart *Chart) TestResult {
|
||||
}
|
||||
}
|
||||
|
||||
for _, cmd := range t.config.AdditionalCommands {
|
||||
if err := t.cmdExecutor.RunCommand(cmd, chart); err != nil {
|
||||
result.Error = err
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Lint with defaults if no values files are specified.
|
||||
if len(valuesFiles) == 0 {
|
||||
valuesFiles = append(valuesFiles, "")
|
||||
|
||||
@@ -109,6 +109,15 @@ func (h fakeHelm) Version() (string, error) {
|
||||
return "v3.0.0", nil
|
||||
}
|
||||
|
||||
type fakeCmdExecutor struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (c *fakeCmdExecutor) RunCommand(cmdTemplate string, data interface{}) error {
|
||||
c.Called(cmdTemplate, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
var ct Testing
|
||||
|
||||
func init() {
|
||||
@@ -417,3 +426,47 @@ func TestChart_HasCIValuesFile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChart_AdditionalCommandsAreRun(t *testing.T) {
|
||||
type testData struct {
|
||||
name string
|
||||
cfg config.Configuration
|
||||
callsRunCommand int
|
||||
}
|
||||
|
||||
testCases := []testData{
|
||||
{
|
||||
name: "no additional commands",
|
||||
cfg: config.Configuration{},
|
||||
callsRunCommand: 0,
|
||||
},
|
||||
{
|
||||
name: "one command",
|
||||
cfg: config.Configuration{
|
||||
AdditionalCommands: []string{"helm unittest --helm3 -f tests/*.yaml {{ .Path }}"},
|
||||
},
|
||||
callsRunCommand: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple commands",
|
||||
cfg: config.Configuration{
|
||||
AdditionalCommands: []string{"echo", "helm unittest --helm3 -f tests/*.yaml {{ .Path }}"},
|
||||
},
|
||||
callsRunCommand: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testData := range testCases {
|
||||
t.Run(testData.name, func(t *testing.T) {
|
||||
fakeCmdExecutor := new(fakeCmdExecutor)
|
||||
fakeCmdExecutor.On("RunCommand", mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
ct := newTestingMock(testData.cfg)
|
||||
ct.cmdExecutor = fakeCmdExecutor
|
||||
|
||||
ct.LintChart(&Chart{})
|
||||
|
||||
fakeCmdExecutor.AssertNumberOfCalls(t, "RunCommand", testData.callsRunCommand)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ type Configuration struct {
|
||||
ValidateMaintainers bool `mapstructure:"validate-maintainers"`
|
||||
ValidateChartSchema bool `mapstructure:"validate-chart-schema"`
|
||||
ValidateYaml bool `mapstructure:"validate-yaml"`
|
||||
AdditionalCommands []string `mapstructure:"additional-commands"`
|
||||
CheckVersionIncrement bool `mapstructure:"check-version-increment"`
|
||||
ProcessAllCharts bool `mapstructure:"all"`
|
||||
Charts []string `mapstructure:"charts"`
|
||||
|
||||
38
pkg/tool/cmdexecutor.go
Normal file
38
pkg/tool/cmdexecutor.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/mattn/go-shellwords"
|
||||
)
|
||||
|
||||
type ProcessExecutor interface {
|
||||
RunProcess(executable string, execArgs ...interface{}) error
|
||||
}
|
||||
|
||||
type CmdTemplateExecutor struct {
|
||||
exec ProcessExecutor
|
||||
}
|
||||
|
||||
func NewCmdTemplateExecutor(exec ProcessExecutor) CmdTemplateExecutor {
|
||||
return CmdTemplateExecutor{
|
||||
exec: exec,
|
||||
}
|
||||
}
|
||||
|
||||
func (t CmdTemplateExecutor) RunCommand(cmdTemplate string, data interface{}) error {
|
||||
var template = template.Must(template.New("command").Parse(cmdTemplate))
|
||||
var b strings.Builder
|
||||
if err := template.Execute(&b, data); err != nil {
|
||||
return err
|
||||
}
|
||||
rendered := b.String()
|
||||
|
||||
words, err := shellwords.Parse(rendered)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, args := words[0], words[1:]
|
||||
return t.exec.RunProcess(name, args)
|
||||
}
|
||||
76
pkg/tool/cmdexecutor_test.go
Normal file
76
pkg/tool/cmdexecutor_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type fakeProcessExecutor struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (c *fakeProcessExecutor) RunProcess(executable string, execArgs ...interface{}) error {
|
||||
c.Called(executable, execArgs[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCmdTemplateExecutor_RunCommand(t *testing.T) {
|
||||
type args struct {
|
||||
cmdTemplate string
|
||||
data interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
validate func(t *testing.T, executor *fakeProcessExecutor)
|
||||
}{
|
||||
{
|
||||
name: "command without arguments",
|
||||
args: args{
|
||||
cmdTemplate: "echo",
|
||||
data: nil,
|
||||
},
|
||||
validate: func(t *testing.T, executor *fakeProcessExecutor) {
|
||||
executor.AssertCalled(t, "RunProcess", "echo", []string{})
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "command with args",
|
||||
args: args{
|
||||
cmdTemplate: "echo hello world",
|
||||
},
|
||||
validate: func(t *testing.T, executor *fakeProcessExecutor) {
|
||||
executor.AssertCalled(t, "RunProcess", "echo", []string{"hello", "world"})
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "interpolate args",
|
||||
args: args{
|
||||
cmdTemplate: "helm unittest --helm3 -f tests/*.yaml {{ .Path }}",
|
||||
data: map[string]string{"Path": "charts/my-chart"},
|
||||
},
|
||||
validate: func(t *testing.T, executor *fakeProcessExecutor) {
|
||||
executor.AssertCalled(t, "RunProcess", "helm", []string{"unittest", "--helm3", "-f", "tests/*.yaml", "charts/my-chart"})
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
processExecutor := new(fakeProcessExecutor)
|
||||
processExecutor.On("RunProcess", mock.Anything, mock.Anything).Return(nil)
|
||||
templateExecutor := CmdTemplateExecutor{
|
||||
exec: processExecutor,
|
||||
}
|
||||
if err := templateExecutor.RunCommand(tt.args.cmdTemplate, tt.args.data); (err != nil) != tt.wantErr {
|
||||
t.Errorf("RunCommand() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
tt.validate(t, processExecutor)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user