diff --git a/internal/helm/plan.go b/internal/helm/plan.go index ea184f3..61bc98e 100644 --- a/internal/helm/plan.go +++ b/internal/helm/plan.go @@ -62,7 +62,7 @@ func determineSteps(cfg Config) *func(Config) []Step { case "delete": panic("not implemented") case "lint": - panic("not implemented") + return &lint case "help": return &help default: @@ -116,6 +116,14 @@ var upgrade = func(cfg Config) []Step { return steps } +var lint = func(cfg Config) []Step { + lint := &run.Lint{ + Chart: cfg.Chart, + } + + return []Step{lint} +} + var help = func(cfg Config) []Step { help := &run.Help{} return []Step{help} diff --git a/internal/helm/plan_test.go b/internal/helm/plan_test.go index 716a27c..34708e6 100644 --- a/internal/helm/plan_test.go +++ b/internal/helm/plan_test.go @@ -137,6 +137,20 @@ func (suite *PlanTestSuite) TestUpgrade() { suite.Equal(expected, upgrade) } +func (suite *PlanTestSuite) TestLint() { + cfg := Config{ + Chart: "./flow", + } + + steps := lint(cfg) + suite.Equal(1, len(steps)) + + want := &run.Lint{ + Chart: "./flow", + } + suite.Equal(want, steps[0]) +} + func (suite *PlanTestSuite) TestDeterminePlanUpgradeCommand() { cfg := Config{ Command: "upgrade", @@ -156,6 +170,15 @@ func (suite *PlanTestSuite) TestDeterminePlanUpgradeFromDroneEvent() { } } +func (suite *PlanTestSuite) TestDeterminePlanLintCommand() { + cfg := Config{ + Command: "lint", + } + + stepsMaker := determineSteps(cfg) + suite.Same(&lint, stepsMaker) +} + func (suite *PlanTestSuite) TestDeterminePlanHelpCommand() { cfg := Config{ Command: "help", diff --git a/internal/run/lint.go b/internal/run/lint.go new file mode 100644 index 0000000..e2843ca --- /dev/null +++ b/internal/run/lint.go @@ -0,0 +1,56 @@ +package run + +import ( + "fmt" +) + +// Lint is an execution step that calls `helm lint` when executed. +type Lint struct { + Chart string + cmd cmd +} + +// Execute executes the `helm lint` command. +func (l *Lint) Execute(_ Config) error { + return l.cmd.Run() +} + +// Prepare gets the Lint ready to execute. +func (l *Lint) Prepare(cfg Config) error { + if l.Chart == "" { + return fmt.Errorf("chart is required") + } + + args := make([]string, 0) + + if cfg.Namespace != "" { + args = append(args, "--namespace", cfg.Namespace) + } + if cfg.Debug { + args = append(args, "--debug") + } + + args = append(args, "lint") + + if cfg.Values != "" { + args = append(args, "--set", cfg.Values) + } + if cfg.StringValues != "" { + args = append(args, "--set-string", cfg.StringValues) + } + for _, vFile := range cfg.ValuesFiles { + args = append(args, "--values", vFile) + } + + args = append(args, l.Chart) + + l.cmd = command(helmBin, args...) + l.cmd.Stdout(cfg.Stdout) + l.cmd.Stderr(cfg.Stderr) + + if cfg.Debug { + fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", l.cmd.String()) + } + + return nil +} diff --git a/internal/run/lint_test.go b/internal/run/lint_test.go new file mode 100644 index 0000000..9d683b4 --- /dev/null +++ b/internal/run/lint_test.go @@ -0,0 +1,169 @@ +package run + +import ( + "fmt" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" + "strings" + "testing" +) + +type LintTestSuite struct { + suite.Suite + ctrl *gomock.Controller + mockCmd *Mockcmd + originalCommand func(string, ...string) cmd +} + +func (suite *LintTestSuite) BeforeTest(_, _ string) { + suite.ctrl = gomock.NewController(suite.T()) + suite.mockCmd = NewMockcmd(suite.ctrl) + + suite.originalCommand = command + command = func(path string, args ...string) cmd { return suite.mockCmd } +} + +func (suite *LintTestSuite) AfterTest(_, _ string) { + command = suite.originalCommand +} + +func TestLintTestSuite(t *testing.T) { + suite.Run(t, new(LintTestSuite)) +} + +func (suite *LintTestSuite) TestPrepareAndExecute() { + defer suite.ctrl.Finish() + + stdout := strings.Builder{} + stderr := strings.Builder{} + + l := Lint{ + Chart: "./epic/mychart", + } + cfg := Config{ + Stdout: &stdout, + Stderr: &stderr, + } + + command = func(path string, args ...string) cmd { + suite.Equal(helmBin, path) + suite.Equal([]string{"lint", "./epic/mychart"}, args) + + return suite.mockCmd + } + + suite.mockCmd.EXPECT(). + Stdout(&stdout) + suite.mockCmd.EXPECT(). + Stderr(&stderr) + suite.mockCmd.EXPECT(). + Run(). + Times(1) + + err := l.Prepare(cfg) + suite.Require().Nil(err) + l.Execute(cfg) +} + +func (suite *LintTestSuite) TestPrepareRequiresChart() { + // These aren't really expected, but allowing them gives clearer test-failure messages + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + + cfg := Config{} + l := Lint{} + + err := l.Prepare(cfg) + suite.EqualError(err, "chart is required", "Chart should be mandatory") +} + +func (suite *LintTestSuite) TestPrepareWithLintFlags() { + defer suite.ctrl.Finish() + + cfg := Config{ + Values: "width=5", + StringValues: "version=2.0", + ValuesFiles: []string{"/usr/local/underrides", "/usr/local/overrides"}, + } + + l := Lint{ + Chart: "./uk/top_40", + } + + command = func(path string, args ...string) cmd { + suite.Equal(helmBin, path) + suite.Equal([]string{"lint", + "--set", "width=5", + "--set-string", "version=2.0", + "--values", "/usr/local/underrides", + "--values", "/usr/local/overrides", + "./uk/top_40"}, args) + + return suite.mockCmd + } + + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + + err := l.Prepare(cfg) + suite.Require().Nil(err) +} + +func (suite *LintTestSuite) TestPrepareWithDebugFlag() { + defer suite.ctrl.Finish() + + stderr := strings.Builder{} + + cfg := Config{ + Debug: true, + Stderr: &stderr, + } + + l := Lint{ + Chart: "./scotland/top_40", + } + + command = func(path string, args ...string) cmd { + suite.mockCmd.EXPECT(). + String(). + Return(fmt.Sprintf("%s %s", path, strings.Join(args, " "))) + + return suite.mockCmd + } + + suite.mockCmd.EXPECT().Stdout(gomock.Any()) + suite.mockCmd.EXPECT().Stderr(&stderr) + + err := l.Prepare(cfg) + suite.Require().Nil(err) + + want := fmt.Sprintf("Generated command: '%s --debug lint ./scotland/top_40'\n", helmBin) + suite.Equal(want, stderr.String()) +} + +func (suite *LintTestSuite) TestPrepareWithNamespaceFlag() { + defer suite.ctrl.Finish() + + cfg := Config{ + Namespace: "table-service", + } + + l := Lint{ + Chart: "./wales/top_40", + } + + actual := []string{} + command = func(path string, args ...string) cmd { + actual = args + return suite.mockCmd + } + + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + + err := l.Prepare(cfg) + suite.Require().Nil(err) + + expected := []string{"--namespace", "table-service", "lint", "./wales/top_40"} + suite.Equal(expected, actual) +}