From 75c99683b5fffe3cf7f0b8bd4c2710228066bed6 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Mon, 30 Dec 2019 09:52:00 -0800 Subject: [PATCH] AddRepo step that calls `helm repo add` [#26] As with some of the other commands, I'm not sure `--namespace` is relevant here. Just rolling with the "at worst it doesn't hurt" theory. --- docs/parameter_reference.md | 2 +- internal/run/addrepo.go | 48 +++++++++++++ internal/run/addrepo_test.go | 134 +++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 internal/run/addrepo.go create mode 100644 internal/run/addrepo_test.go diff --git a/docs/parameter_reference.md b/docs/parameter_reference.md index 07d8107..426dda9 100644 --- a/docs/parameter_reference.md +++ b/docs/parameter_reference.md @@ -5,7 +5,7 @@ |---------------------|-----------------|---------| | helm_command | string | Indicates the operation to perform. Recommended, but not required. Valid options are `upgrade`, `uninstall`, `lint`, and `help`. | | update_dependencies | boolean | Calls `helm dependency update` before running the main command. **Not currently implemented**; see [#25](https://github.com/pelotech/drone-helm3/issues/25).| -| helm_repos | list\ | Calls `helm repo add $repo` before running the main command. Each string should be formatted as `repo_name=https://repo.url/`. **Not currently implemented**; see [#26](https://github.com/pelotech/drone-helm3/issues/26). | +| helm_repos | list\ | Calls `helm repo add $repo` before running the main command. Each string should be formatted as `repo_name=https://repo.url/`. | | namespace | string | Kubernetes namespace to use for this operation. | | prefix | string | Expect environment variables to be prefixed with the given string. For more details, see "Using the prefix setting" below. **Not currently implemented**; see [#19](https://github.com/pelotech/drone-helm3/issues/19). | | debug | boolean | Generate debug output within drone-helm3 and pass `--debug` to all helm commands. Use with care, since the debug output may include secrets. | diff --git a/internal/run/addrepo.go b/internal/run/addrepo.go new file mode 100644 index 0000000..4dc326a --- /dev/null +++ b/internal/run/addrepo.go @@ -0,0 +1,48 @@ +package run + +import ( + "fmt" +) + +// AddRepo is an execution step that calls `helm repo add` when executed. +type AddRepo struct { + Name string + URL string + cmd cmd +} + +// Execute executes the `helm repo add` command. +func (a *AddRepo) Execute(_ Config) error { + return a.cmd.Run() +} + +// Prepare gets the AddRepo ready to execute. +func (a *AddRepo) Prepare(cfg Config) error { + if a.Name == "" { + return fmt.Errorf("repo name is required") + } + if a.URL == "" { + return fmt.Errorf("repo URL 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, "repo", "add", a.Name, a.URL) + + a.cmd = command(helmBin, args...) + a.cmd.Stdout(cfg.Stdout) + a.cmd.Stderr(cfg.Stderr) + + if cfg.Debug { + fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", a.cmd.String()) + } + + return nil +} diff --git a/internal/run/addrepo_test.go b/internal/run/addrepo_test.go new file mode 100644 index 0000000..867c039 --- /dev/null +++ b/internal/run/addrepo_test.go @@ -0,0 +1,134 @@ +package run + +import ( + "fmt" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" + "strings" + "testing" +) + +type AddRepoTestSuite struct { + suite.Suite + ctrl *gomock.Controller + mockCmd *Mockcmd + originalCommand func(string, ...string) cmd + commandPath string + commandArgs []string +} + +func (suite *AddRepoTestSuite) BeforeTest(_, _ string) { + suite.ctrl = gomock.NewController(suite.T()) + suite.mockCmd = NewMockcmd(suite.ctrl) + + suite.originalCommand = command + command = func(path string, args ...string) cmd { + suite.commandPath = path + suite.commandArgs = args + return suite.mockCmd + } +} + +func (suite *AddRepoTestSuite) AfterTest(_, _ string) { + suite.ctrl.Finish() + command = suite.originalCommand +} + +func TestAddRepoTestSuite(t *testing.T) { + suite.Run(t, new(AddRepoTestSuite)) +} + +func (suite *AddRepoTestSuite) TestPrepareAndExecute() { + stdout := strings.Builder{} + stderr := strings.Builder{} + cfg := Config{ + Stdout: &stdout, + Stderr: &stderr, + } + a := AddRepo{ + Name: "edeath", + URL: "https://github.com/n_marks/e-death", + } + + suite.mockCmd.EXPECT(). + Stdout(&stdout). + Times(1) + suite.mockCmd.EXPECT(). + Stderr(&stderr). + Times(1) + + suite.Require().NoError(a.Prepare(cfg)) + suite.Equal(suite.commandPath, helmBin) + suite.Equal(suite.commandArgs, []string{"repo", "add", "edeath", "https://github.com/n_marks/e-death"}) + + suite.mockCmd.EXPECT(). + Run(). + Times(1) + + suite.Require().NoError(a.Execute(cfg)) + +} + +func (suite *AddRepoTestSuite) TestRequiredFields() { + // 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{} + a := AddRepo{ + Name: "idgen", + } + + err := a.Prepare(cfg) + suite.EqualError(err, "repo URL is required") + + a.Name = "" + a.URL = "https://github.com/n_marks/idgen" + + err = a.Prepare(cfg) + suite.EqualError(err, "repo name is required") +} + +func (suite *AddRepoTestSuite) TestNamespaceFlag() { + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + cfg := Config{ + Namespace: "alliteration", + } + a := AddRepo{ + Name: "edeath", + URL: "https://github.com/theater_guy/e-death", + } + + suite.NoError(a.Prepare(cfg)) + suite.Equal(suite.commandPath, helmBin) + suite.Equal(suite.commandArgs, []string{"--namespace", "alliteration", + "repo", "add", "edeath", "https://github.com/theater_guy/e-death"}) +} + +func (suite *AddRepoTestSuite) TestDebugFlag() { + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + + stderr := strings.Builder{} + + command = func(path string, args ...string) cmd { + suite.mockCmd.EXPECT(). + String(). + Return(fmt.Sprintf("%s %s", path, strings.Join(args, " "))) + + return suite.mockCmd + } + + cfg := Config{ + Debug: true, + Stderr: &stderr, + } + a := AddRepo{ + Name: "edeath", + URL: "https://github.com/the_bug/e-death", + } + + suite.Require().NoError(a.Prepare(cfg)) + suite.Equal(fmt.Sprintf("Generated command: '%s --debug "+ + "repo add edeath https://github.com/the_bug/e-death'\n", helmBin), stderr.String()) +}