diff --git a/cmd/drone-helm/main.go b/cmd/drone-helm/main.go index 1d6c2b4..7c6e3ff 100644 --- a/cmd/drone-helm/main.go +++ b/cmd/drone-helm/main.go @@ -5,11 +5,12 @@ import ( "os" _ "github.com/joho/godotenv/autoload" + "github.com/pelotech/drone-helm3/internal/env" "github.com/pelotech/drone-helm3/internal/helm" ) func main() { - cfg, err := helm.NewConfig(os.Stdout, os.Stderr) + cfg, err := env.NewConfig(os.Stdout, os.Stderr) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err.Error()) diff --git a/internal/helm/config.go b/internal/env/config.go similarity index 99% rename from internal/helm/config.go rename to internal/env/config.go index b8ce354..dad997a 100644 --- a/internal/helm/config.go +++ b/internal/env/config.go @@ -1,4 +1,4 @@ -package helm +package env import ( "fmt" diff --git a/internal/helm/config_test.go b/internal/env/config_test.go similarity index 99% rename from internal/helm/config_test.go rename to internal/env/config_test.go index 13bf22a..40123dd 100644 --- a/internal/helm/config_test.go +++ b/internal/env/config_test.go @@ -1,4 +1,4 @@ -package helm +package env import ( "fmt" diff --git a/internal/helm/mock_step_test.go b/internal/helm/mock_step_test.go index 5387162..fa9c629 100644 --- a/internal/helm/mock_step_test.go +++ b/internal/helm/mock_step_test.go @@ -6,7 +6,6 @@ package helm import ( gomock "github.com/golang/mock/gomock" - run "github.com/pelotech/drone-helm3/internal/run" reflect "reflect" ) @@ -34,29 +33,29 @@ func (m *MockStep) EXPECT() *MockStepMockRecorder { } // Prepare mocks base method -func (m *MockStep) Prepare(arg0 run.Config) error { +func (m *MockStep) Prepare() error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Prepare", arg0) + ret := m.ctrl.Call(m, "Prepare") ret0, _ := ret[0].(error) return ret0 } // Prepare indicates an expected call of Prepare -func (mr *MockStepMockRecorder) Prepare(arg0 interface{}) *gomock.Call { +func (mr *MockStepMockRecorder) Prepare() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prepare", reflect.TypeOf((*MockStep)(nil).Prepare), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prepare", reflect.TypeOf((*MockStep)(nil).Prepare)) } // Execute mocks base method -func (m *MockStep) Execute(arg0 run.Config) error { +func (m *MockStep) Execute() error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Execute", arg0) + ret := m.ctrl.Call(m, "Execute") ret0, _ := ret[0].(error) return ret0 } // Execute indicates an expected call of Execute -func (mr *MockStepMockRecorder) Execute(arg0 interface{}) *gomock.Call { +func (mr *MockStepMockRecorder) Execute() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockStep)(nil).Execute), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockStep)(nil).Execute)) } diff --git a/internal/helm/plan.go b/internal/helm/plan.go index 2475734..c09bdc8 100644 --- a/internal/helm/plan.go +++ b/internal/helm/plan.go @@ -2,6 +2,7 @@ package helm import ( "fmt" + "github.com/pelotech/drone-helm3/internal/env" "github.com/pelotech/drone-helm3/internal/run" "os" ) @@ -13,27 +14,20 @@ const ( // A Step is one step in the plan. type Step interface { - Prepare(run.Config) error - Execute(run.Config) error + Prepare() error + Execute() error } // A Plan is a series of steps to perform. type Plan struct { - steps []Step - cfg Config - runCfg run.Config + steps []Step + cfg env.Config } // NewPlan makes a plan for running a helm operation. -func NewPlan(cfg Config) (*Plan, error) { +func NewPlan(cfg env.Config) (*Plan, error) { p := Plan{ cfg: cfg, - runCfg: run.Config{ - Debug: cfg.Debug, - Namespace: cfg.Namespace, - Stdout: cfg.Stdout, - Stderr: cfg.Stderr, - }, } p.steps = (*determineSteps(cfg))(cfg) @@ -43,7 +37,7 @@ func NewPlan(cfg Config) (*Plan, error) { fmt.Fprintf(os.Stderr, "calling %T.Prepare (step %d)\n", step, i) } - if err := step.Prepare(p.runCfg); err != nil { + if err := step.Prepare(); err != nil { err = fmt.Errorf("while preparing %T step: %w", step, err) return nil, err } @@ -54,7 +48,7 @@ func NewPlan(cfg Config) (*Plan, error) { // determineSteps is primarily for the tests' convenience: it allows testing the "which stuff should // we do" logic without building a config that meets all the steps' requirements. -func determineSteps(cfg Config) *func(Config) []Step { +func determineSteps(cfg env.Config) *func(env.Config) []Step { switch cfg.Command { case "upgrade": return &upgrade @@ -83,7 +77,7 @@ func (p *Plan) Execute() error { fmt.Fprintf(p.cfg.Stderr, "calling %T.Execute (step %d)\n", step, i) } - if err := step.Execute(p.runCfg); err != nil { + if err := step.Execute(); err != nil { return fmt.Errorf("while executing %T step: %w", step, err) } } @@ -91,99 +85,43 @@ func (p *Plan) Execute() error { return nil } -var upgrade = func(cfg Config) []Step { - steps := initKube(cfg) - steps = append(steps, addRepos(cfg)...) - if cfg.UpdateDependencies { - steps = append(steps, depUpdate(cfg)...) - } - steps = append(steps, &run.Upgrade{ - Chart: cfg.Chart, - Release: cfg.Release, - ChartVersion: cfg.ChartVersion, - DryRun: cfg.DryRun, - Wait: cfg.Wait, - Values: cfg.Values, - StringValues: cfg.StringValues, - ValuesFiles: cfg.ValuesFiles, - ReuseValues: cfg.ReuseValues, - Timeout: cfg.Timeout, - Force: cfg.Force, - Atomic: cfg.AtomicUpgrade, - CleanupOnFail: cfg.CleanupOnFail, - CAFile: cfg.RepoCAFile, - }) - - return steps -} - -var uninstall = func(cfg Config) []Step { - steps := initKube(cfg) - if cfg.UpdateDependencies { - steps = append(steps, depUpdate(cfg)...) - } - steps = append(steps, &run.Uninstall{ - Release: cfg.Release, - DryRun: cfg.DryRun, - KeepHistory: cfg.KeepHistory, - }) - - return steps -} - -var lint = func(cfg Config) []Step { - steps := addRepos(cfg) - if cfg.UpdateDependencies { - steps = append(steps, depUpdate(cfg)...) - } - steps = append(steps, &run.Lint{ - Chart: cfg.Chart, - Values: cfg.Values, - StringValues: cfg.StringValues, - ValuesFiles: cfg.ValuesFiles, - Strict: cfg.LintStrictly, - }) - - return steps -} - -var help = func(cfg Config) []Step { - help := &run.Help{ - HelmCommand: cfg.Command, - } - return []Step{help} -} - -func initKube(cfg Config) []Step { - return []Step{ - &run.InitKube{ - SkipTLSVerify: cfg.SkipTLSVerify, - Certificate: cfg.Certificate, - APIServer: cfg.APIServer, - ServiceAccount: cfg.ServiceAccount, - Token: cfg.KubeToken, - TemplateFile: kubeConfigTemplate, - ConfigFile: kubeConfigFile, - }, - } -} - -func addRepos(cfg Config) []Step { - steps := make([]Step, 0) +var upgrade = func(cfg env.Config) []Step { + var steps []Step + steps = append(steps, run.NewInitKube(cfg, kubeConfigTemplate, kubeConfigFile)) for _, repo := range cfg.AddRepos { - steps = append(steps, &run.AddRepo{ - Repo: repo, - CAFile: cfg.RepoCAFile, - }) + steps = append(steps, run.NewAddRepo(cfg, repo)) } + if cfg.UpdateDependencies { + steps = append(steps, run.NewDepUpdate(cfg)) + } + steps = append(steps, run.NewUpgrade(cfg)) return steps } -func depUpdate(cfg Config) []Step { - return []Step{ - &run.DepUpdate{ - Chart: cfg.Chart, - }, +var uninstall = func(cfg env.Config) []Step { + var steps []Step + steps = append(steps, run.NewInitKube(cfg, kubeConfigTemplate, kubeConfigFile)) + if cfg.UpdateDependencies { + steps = append(steps, run.NewDepUpdate(cfg)) } + steps = append(steps, run.NewUninstall(cfg)) + + return steps +} + +var lint = func(cfg env.Config) []Step { + var steps []Step + for _, repo := range cfg.AddRepos { + steps = append(steps, run.NewAddRepo(cfg, repo)) + } + if cfg.UpdateDependencies { + steps = append(steps, run.NewDepUpdate(cfg)) + } + steps = append(steps, run.NewLint(cfg)) + return steps +} + +var help = func(cfg env.Config) []Step { + return []Step{run.NewHelp(cfg)} } diff --git a/internal/helm/plan_test.go b/internal/helm/plan_test.go index f59fa01..77e913c 100644 --- a/internal/helm/plan_test.go +++ b/internal/helm/plan_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/pelotech/drone-helm3/internal/env" "github.com/pelotech/drone-helm3/internal/run" ) @@ -25,14 +26,14 @@ func (suite *PlanTestSuite) TestNewPlan() { stepTwo := NewMockStep(ctrl) origHelp := help - help = func(cfg Config) []Step { + help = func(cfg env.Config) []Step { return []Step{stepOne, stepTwo} } defer func() { help = origHelp }() stdout := strings.Builder{} stderr := strings.Builder{} - cfg := Config{ + cfg := env.Config{ Command: "help", Debug: false, Namespace: "outer", @@ -40,22 +41,14 @@ func (suite *PlanTestSuite) TestNewPlan() { Stderr: &stderr, } - runCfg := run.Config{ - Debug: false, - Namespace: "outer", - Stdout: &stdout, - Stderr: &stderr, - } - stepOne.EXPECT(). - Prepare(runCfg) + Prepare() stepTwo.EXPECT(). - Prepare(runCfg) + Prepare() plan, err := NewPlan(cfg) suite.Require().Nil(err) suite.Equal(cfg, plan.cfg) - suite.Equal(runCfg, plan.runCfg) } func (suite *PlanTestSuite) TestNewPlanAbortsOnError() { @@ -65,17 +58,17 @@ func (suite *PlanTestSuite) TestNewPlanAbortsOnError() { stepTwo := NewMockStep(ctrl) origHelp := help - help = func(cfg Config) []Step { + help = func(cfg env.Config) []Step { return []Step{stepOne, stepTwo} } defer func() { help = origHelp }() - cfg := Config{ + cfg := env.Config{ Command: "help", } stepOne.EXPECT(). - Prepare(gomock.Any()). + Prepare(). Return(fmt.Errorf("I'm starry Dave, aye, cat blew that")) _, err := NewPlan(cfg) @@ -89,18 +82,15 @@ func (suite *PlanTestSuite) TestExecute() { stepOne := NewMockStep(ctrl) stepTwo := NewMockStep(ctrl) - runCfg := run.Config{} - plan := Plan{ - steps: []Step{stepOne, stepTwo}, - runCfg: runCfg, + steps: []Step{stepOne, stepTwo}, } stepOne.EXPECT(). - Execute(runCfg). + Execute(). Times(1) stepTwo.EXPECT(). - Execute(runCfg). + Execute(). Times(1) suite.NoError(plan.Execute()) @@ -112,15 +102,12 @@ func (suite *PlanTestSuite) TestExecuteAbortsOnError() { stepOne := NewMockStep(ctrl) stepTwo := NewMockStep(ctrl) - runCfg := run.Config{} - plan := Plan{ - steps: []Step{stepOne, stepTwo}, - runCfg: runCfg, + steps: []Step{stepOne, stepTwo}, } stepOne.EXPECT(). - Execute(runCfg). + Execute(). Times(1). Return(fmt.Errorf("oh, he'll gnaw")) @@ -129,52 +116,14 @@ func (suite *PlanTestSuite) TestExecuteAbortsOnError() { } func (suite *PlanTestSuite) TestUpgrade() { - cfg := Config{ - ChartVersion: "seventeen", - DryRun: true, - Wait: true, - Values: "steadfastness,forthrightness", - StringValues: "tensile_strength,flexibility", - ValuesFiles: []string{"/root/price_inventory.yml"}, - ReuseValues: true, - Timeout: "go sit in the corner", - Chart: "billboard_top_100", - Release: "post_malone_circles", - Force: true, - AtomicUpgrade: true, - CleanupOnFail: true, - RepoCAFile: "state_licensure.repo.cert", - } - - steps := upgrade(cfg) + steps := upgrade(env.Config{}) suite.Require().Equal(2, len(steps), "upgrade should return 2 steps") - suite.Require().IsType(&run.InitKube{}, steps[0]) - - suite.Require().IsType(&run.Upgrade{}, steps[1]) - upgrade, _ := steps[1].(*run.Upgrade) - - expected := &run.Upgrade{ - Chart: cfg.Chart, - Release: cfg.Release, - ChartVersion: cfg.ChartVersion, - DryRun: true, - Wait: cfg.Wait, - Values: "steadfastness,forthrightness", - StringValues: "tensile_strength,flexibility", - ValuesFiles: []string{"/root/price_inventory.yml"}, - ReuseValues: cfg.ReuseValues, - Timeout: cfg.Timeout, - Force: cfg.Force, - Atomic: true, - CleanupOnFail: true, - CAFile: "state_licensure.repo.cert", - } - - suite.Equal(expected, upgrade) + suite.IsType(&run.InitKube{}, steps[0]) + suite.IsType(&run.Upgrade{}, steps[1]) } func (suite *PlanTestSuite) TestUpgradeWithUpdateDependencies() { - cfg := Config{ + cfg := env.Config{ UpdateDependencies: true, } steps := upgrade(cfg) @@ -184,7 +133,7 @@ func (suite *PlanTestSuite) TestUpgradeWithUpdateDependencies() { } func (suite *PlanTestSuite) TestUpgradeWithAddRepos() { - cfg := Config{ + cfg := env.Config{ AddRepos: []string{ "machine=https://github.com/harold_finch/themachine", }, @@ -195,47 +144,15 @@ func (suite *PlanTestSuite) TestUpgradeWithAddRepos() { } func (suite *PlanTestSuite) TestUninstall() { - cfg := Config{ - KubeToken: "b2YgbXkgYWZmZWN0aW9u", - SkipTLSVerify: true, - Certificate: "cHJvY2xhaW1zIHdvbmRlcmZ1bCBmcmllbmRzaGlw", - APIServer: "98.765.43.21", - ServiceAccount: "greathelm", - DryRun: true, - Timeout: "think about what you did", - Release: "jetta_id_love_to_change_the_world", - KeepHistory: true, - } - - steps := uninstall(cfg) + steps := uninstall(env.Config{}) suite.Require().Equal(2, len(steps), "uninstall should return 2 steps") - suite.Require().IsType(&run.InitKube{}, steps[0]) - init, _ := steps[0].(*run.InitKube) - var expected Step = &run.InitKube{ - SkipTLSVerify: true, - Certificate: "cHJvY2xhaW1zIHdvbmRlcmZ1bCBmcmllbmRzaGlw", - APIServer: "98.765.43.21", - ServiceAccount: "greathelm", - Token: "b2YgbXkgYWZmZWN0aW9u", - TemplateFile: kubeConfigTemplate, - ConfigFile: kubeConfigFile, - } - - suite.Equal(expected, init) - - suite.Require().IsType(&run.Uninstall{}, steps[1]) - actual, _ := steps[1].(*run.Uninstall) - expected = &run.Uninstall{ - Release: "jetta_id_love_to_change_the_world", - DryRun: true, - KeepHistory: true, - } - suite.Equal(expected, actual) + suite.IsType(&run.InitKube{}, steps[0]) + suite.IsType(&run.Uninstall{}, steps[1]) } func (suite *PlanTestSuite) TestUninstallWithUpdateDependencies() { - cfg := Config{ + cfg := env.Config{ UpdateDependencies: true, } steps := uninstall(cfg) @@ -244,94 +161,14 @@ func (suite *PlanTestSuite) TestUninstallWithUpdateDependencies() { suite.IsType(&run.DepUpdate{}, steps[1]) } -func (suite *PlanTestSuite) TestInitKube() { - cfg := Config{ - KubeToken: "cXVlZXIgY2hhcmFjdGVyCg==", - SkipTLSVerify: true, - Certificate: "b2Ygd29rZW5lc3MK", - APIServer: "123.456.78.9", - ServiceAccount: "helmet", - } - - steps := initKube(cfg) - suite.Require().Equal(1, len(steps), "initKube should return one step") - suite.Require().IsType(&run.InitKube{}, steps[0]) - init, _ := steps[0].(*run.InitKube) - - expected := &run.InitKube{ - SkipTLSVerify: true, - Certificate: "b2Ygd29rZW5lc3MK", - APIServer: "123.456.78.9", - ServiceAccount: "helmet", - Token: "cXVlZXIgY2hhcmFjdGVyCg==", - TemplateFile: kubeConfigTemplate, - ConfigFile: kubeConfigFile, - } - suite.Equal(expected, init) -} - -func (suite *PlanTestSuite) TestDepUpdate() { - cfg := Config{ - UpdateDependencies: true, - Chart: "scatterplot", - } - - steps := depUpdate(cfg) - suite.Require().Equal(1, len(steps), "depUpdate should return one step") - suite.Require().IsType(&run.DepUpdate{}, steps[0]) - update, _ := steps[0].(*run.DepUpdate) - - expected := &run.DepUpdate{ - Chart: "scatterplot", - } - suite.Equal(expected, update) -} - -func (suite *PlanTestSuite) TestAddRepos() { - cfg := Config{ - AddRepos: []string{ - "first=https://add.repos/one", - "second=https://add.repos/two", - }, - RepoCAFile: "state_licensure.repo.cert", - } - steps := addRepos(cfg) - suite.Require().Equal(2, len(steps), "addRepos should add one step per repo") - suite.Require().IsType(&run.AddRepo{}, steps[0]) - suite.Require().IsType(&run.AddRepo{}, steps[1]) - first := steps[0].(*run.AddRepo) - second := steps[1].(*run.AddRepo) - - suite.Equal(first.Repo, "first=https://add.repos/one") - suite.Equal(second.Repo, "second=https://add.repos/two") - suite.Equal(first.CAFile, "state_licensure.repo.cert") - suite.Equal(second.CAFile, "state_licensure.repo.cert") -} - func (suite *PlanTestSuite) TestLint() { - cfg := Config{ - Chart: "./flow", - Values: "steadfastness,forthrightness", - StringValues: "tensile_strength,flexibility", - ValuesFiles: []string{"/root/price_inventory.yml"}, - LintStrictly: true, - } - - steps := lint(cfg) - suite.Equal(1, len(steps)) - - want := &run.Lint{ - Chart: "./flow", - Values: "steadfastness,forthrightness", - StringValues: "tensile_strength,flexibility", - ValuesFiles: []string{"/root/price_inventory.yml"}, - Strict: true, - } - suite.Equal(want, steps[0]) + steps := lint(env.Config{}) + suite.Require().Equal(1, len(steps)) + suite.IsType(&run.Lint{}, steps[0]) } func (suite *PlanTestSuite) TestLintWithUpdateDependencies() { - cfg := Config{ + cfg := env.Config{ UpdateDependencies: true, } steps := lint(cfg) @@ -340,7 +177,7 @@ func (suite *PlanTestSuite) TestLintWithUpdateDependencies() { } func (suite *PlanTestSuite) TestLintWithAddRepos() { - cfg := Config{ + cfg := env.Config{ AddRepos: []string{"friendczar=https://github.com/logan_pierce/friendczar"}, } steps := lint(cfg) @@ -349,7 +186,7 @@ func (suite *PlanTestSuite) TestLintWithAddRepos() { } func (suite *PlanTestSuite) TestDeterminePlanUpgradeCommand() { - cfg := Config{ + cfg := env.Config{ Command: "upgrade", } stepsMaker := determineSteps(cfg) @@ -357,7 +194,7 @@ func (suite *PlanTestSuite) TestDeterminePlanUpgradeCommand() { } func (suite *PlanTestSuite) TestDeterminePlanUpgradeFromDroneEvent() { - cfg := Config{} + cfg := env.Config{} upgradeEvents := []string{"push", "tag", "deployment", "pull_request", "promote", "rollback"} for _, event := range upgradeEvents { @@ -368,7 +205,7 @@ func (suite *PlanTestSuite) TestDeterminePlanUpgradeFromDroneEvent() { } func (suite *PlanTestSuite) TestDeterminePlanUninstallCommand() { - cfg := Config{ + cfg := env.Config{ Command: "uninstall", } stepsMaker := determineSteps(cfg) @@ -377,7 +214,7 @@ func (suite *PlanTestSuite) TestDeterminePlanUninstallCommand() { // helm_command = delete is provided as an alias for backward-compatibility with drone-helm func (suite *PlanTestSuite) TestDeterminePlanDeleteCommand() { - cfg := Config{ + cfg := env.Config{ Command: "delete", } stepsMaker := determineSteps(cfg) @@ -385,7 +222,7 @@ func (suite *PlanTestSuite) TestDeterminePlanDeleteCommand() { } func (suite *PlanTestSuite) TestDeterminePlanDeleteFromDroneEvent() { - cfg := Config{ + cfg := env.Config{ DroneEvent: "delete", } stepsMaker := determineSteps(cfg) @@ -393,7 +230,7 @@ func (suite *PlanTestSuite) TestDeterminePlanDeleteFromDroneEvent() { } func (suite *PlanTestSuite) TestDeterminePlanLintCommand() { - cfg := Config{ + cfg := env.Config{ Command: "lint", } @@ -402,7 +239,7 @@ func (suite *PlanTestSuite) TestDeterminePlanLintCommand() { } func (suite *PlanTestSuite) TestDeterminePlanHelpCommand() { - cfg := Config{ + cfg := env.Config{ Command: "help", } diff --git a/internal/run/addrepo.go b/internal/run/addrepo.go index 40f8740..cfb87c2 100644 --- a/internal/run/addrepo.go +++ b/internal/run/addrepo.go @@ -2,55 +2,58 @@ package run import ( "fmt" + "github.com/pelotech/drone-helm3/internal/env" "strings" ) // AddRepo is an execution step that calls `helm repo add` when executed. type AddRepo struct { - Repo string - CAFile string + *config + repo string + caFile string cmd cmd } +// NewAddRepo creates an AddRepo for the given repo-spec. No validation is performed at this time. +func NewAddRepo(cfg env.Config, repo string) *AddRepo { + return &AddRepo{ + config: newConfig(cfg), + repo: repo, + caFile: cfg.RepoCAFile, + } +} + // Execute executes the `helm repo add` command. -func (a *AddRepo) Execute(_ Config) error { +func (a *AddRepo) Execute() error { return a.cmd.Run() } // Prepare gets the AddRepo ready to execute. -func (a *AddRepo) Prepare(cfg Config) error { - if a.Repo == "" { +func (a *AddRepo) Prepare() error { + if a.repo == "" { return fmt.Errorf("repo is required") } - split := strings.SplitN(a.Repo, "=", 2) + split := strings.SplitN(a.repo, "=", 2) if len(split) != 2 { - return fmt.Errorf("bad repo spec '%s'", a.Repo) + return fmt.Errorf("bad repo spec '%s'", a.repo) } name := split[0] url := split[1] - args := make([]string, 0) - - if cfg.Namespace != "" { - args = append(args, "--namespace", cfg.Namespace) - } - if cfg.Debug { - args = append(args, "--debug") - } - + args := a.globalFlags() args = append(args, "repo", "add") - if a.CAFile != "" { - args = append(args, "--ca-file", a.CAFile) + if a.caFile != "" { + args = append(args, "--ca-file", a.caFile) } args = append(args, name, url) a.cmd = command(helmBin, args...) - a.cmd.Stdout(cfg.Stdout) - a.cmd.Stderr(cfg.Stderr) + a.cmd.Stdout(a.stdout) + a.cmd.Stderr(a.stderr) - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", a.cmd.String()) + if a.debug { + fmt.Fprintf(a.stderr, "Generated command: '%s'\n", a.cmd.String()) } return nil diff --git a/internal/run/addrepo_test.go b/internal/run/addrepo_test.go index 4a8445c..6633981 100644 --- a/internal/run/addrepo_test.go +++ b/internal/run/addrepo_test.go @@ -1,8 +1,8 @@ package run import ( - "fmt" "github.com/golang/mock/gomock" + "github.com/pelotech/drone-helm3/internal/env" "github.com/stretchr/testify/suite" "strings" "testing" @@ -38,16 +38,21 @@ func TestAddRepoTestSuite(t *testing.T) { suite.Run(t, new(AddRepoTestSuite)) } +func (suite *AddRepoTestSuite) TestNewAddRepo() { + repo := NewAddRepo(env.Config{}, "picompress=https://github.com/caleb_phipps/picompress") + suite.Require().NotNil(repo) + suite.Equal("picompress=https://github.com/caleb_phipps/picompress", repo.repo) + suite.NotNil(repo.config) +} + func (suite *AddRepoTestSuite) TestPrepareAndExecute() { stdout := strings.Builder{} stderr := strings.Builder{} - cfg := Config{ + cfg := env.Config{ Stdout: &stdout, Stderr: &stderr, } - a := AddRepo{ - Repo: "edeath=https://github.com/n_marks/e-death", - } + a := NewAddRepo(cfg, "edeath=https://github.com/n_marks/e-death") suite.mockCmd.EXPECT(). Stdout(&stdout). @@ -56,7 +61,7 @@ func (suite *AddRepoTestSuite) TestPrepareAndExecute() { Stderr(&stderr). Times(1) - suite.Require().NoError(a.Prepare(cfg)) + suite.Require().NoError(a.Prepare()) suite.Equal(helmBin, suite.commandPath) suite.Equal([]string{"repo", "add", "edeath", "https://github.com/n_marks/e-death"}, suite.commandArgs) @@ -64,7 +69,7 @@ func (suite *AddRepoTestSuite) TestPrepareAndExecute() { Run(). Times(1) - suite.Require().NoError(a.Execute(cfg)) + suite.Require().NoError(a.Execute()) } @@ -72,83 +77,34 @@ func (suite *AddRepoTestSuite) TestPrepareRepoIsRequired() { // 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{} + a := NewAddRepo(env.Config{}, "") - err := a.Prepare(cfg) + err := a.Prepare() suite.EqualError(err, "repo is required") } func (suite *AddRepoTestSuite) TestPrepareMalformedRepo() { - a := AddRepo{ - Repo: "dwim", - } - err := a.Prepare(Config{}) + a := NewAddRepo(env.Config{}, "dwim") + err := a.Prepare() suite.EqualError(err, "bad repo spec 'dwim'") } func (suite *AddRepoTestSuite) TestPrepareWithEqualSignInURL() { suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - a := AddRepo{ - Repo: "samaritan=https://github.com/arthur_claypool/samaritan?version=2.1", - } - suite.NoError(a.Prepare(Config{})) + a := NewAddRepo(env.Config{}, "samaritan=https://github.com/arthur_claypool/samaritan?version=2.1") + suite.NoError(a.Prepare()) suite.Contains(suite.commandArgs, "https://github.com/arthur_claypool/samaritan?version=2.1") } func (suite *AddRepoTestSuite) TestRepoAddFlags() { suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - cfg := Config{} - a := AddRepo{ - Repo: "machine=https://github.com/harold_finch/themachine", - CAFile: "./helm/reporepo.cert", + cfg := env.Config{ + RepoCAFile: "./helm/reporepo.cert", } - suite.NoError(a.Prepare(cfg)) + a := NewAddRepo(cfg, "machine=https://github.com/harold_finch/themachine") + suite.NoError(a.Prepare()) suite.Equal([]string{"repo", "add", "--ca-file", "./helm/reporepo.cert", "machine", "https://github.com/harold_finch/themachine"}, suite.commandArgs) } - -func (suite *AddRepoTestSuite) TestNamespaceFlag() { - suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() - suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - cfg := Config{ - Namespace: "alliteration", - } - a := AddRepo{ - Repo: "edeath=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{ - Repo: "edeath=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()) -} diff --git a/internal/run/config.go b/internal/run/config.go index b237f18..268482c 100644 --- a/internal/run/config.go +++ b/internal/run/config.go @@ -1,13 +1,33 @@ package run import ( + "github.com/pelotech/drone-helm3/internal/env" "io" ) -// Config contains configuration applicable to all helm commands -type Config struct { - Debug bool - Namespace string - Stdout io.Writer - Stderr io.Writer +type config struct { + debug bool + namespace string + stdout io.Writer + stderr io.Writer +} + +func newConfig(cfg env.Config) *config { + return &config{ + debug: cfg.Debug, + namespace: cfg.Namespace, + stdout: cfg.Stdout, + stderr: cfg.Stderr, + } +} + +func (cfg *config) globalFlags() []string { + flags := []string{} + if cfg.debug { + flags = append(flags, "--debug") + } + if cfg.namespace != "" { + flags = append(flags, "--namespace", cfg.namespace) + } + return flags } diff --git a/internal/run/config_test.go b/internal/run/config_test.go new file mode 100644 index 0000000..88dbfc4 --- /dev/null +++ b/internal/run/config_test.go @@ -0,0 +1,48 @@ +package run + +import ( + "github.com/pelotech/drone-helm3/internal/env" + "github.com/stretchr/testify/suite" + "strings" + "testing" +) + +type ConfigTestSuite struct { + suite.Suite +} + +func TestConfigTestSuite(t *testing.T) { + suite.Run(t, new(ConfigTestSuite)) +} + +func (suite *ConfigTestSuite) TestNewConfig() { + stdout := &strings.Builder{} + stderr := &strings.Builder{} + envCfg := env.Config{ + Namespace: "private", + Debug: true, + Stdout: stdout, + Stderr: stderr, + } + cfg := newConfig(envCfg) + suite.Require().NotNil(cfg) + suite.Equal(&config{ + namespace: "private", + debug: true, + stdout: stdout, + stderr: stderr, + }, cfg) +} + +func (suite *ConfigTestSuite) TestGlobalFlags() { + cfg := config{ + debug: true, + namespace: "public", + } + flags := cfg.globalFlags() + suite.Equal([]string{"--debug", "--namespace", "public"}, flags) + + cfg = config{} + flags = cfg.globalFlags() + suite.Equal([]string{}, flags) +} diff --git a/internal/run/depupdate.go b/internal/run/depupdate.go index a9b6c91..551e7e7 100644 --- a/internal/run/depupdate.go +++ b/internal/run/depupdate.go @@ -2,42 +2,44 @@ package run import ( "fmt" + "github.com/pelotech/drone-helm3/internal/env" ) // DepUpdate is an execution step that calls `helm dependency update` when executed. type DepUpdate struct { - Chart string + *config + chart string cmd cmd } +// NewDepUpdate creates a DepUpdate using fields from the given Config. No validation is performed at this time. +func NewDepUpdate(cfg env.Config) *DepUpdate { + return &DepUpdate{ + config: newConfig(cfg), + chart: cfg.Chart, + } +} + // Execute executes the `helm upgrade` command. -func (d *DepUpdate) Execute(_ Config) error { +func (d *DepUpdate) Execute() error { return d.cmd.Run() } // Prepare gets the DepUpdate ready to execute. -func (d *DepUpdate) Prepare(cfg Config) error { - if d.Chart == "" { +func (d *DepUpdate) Prepare() error { + if d.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, "dependency", "update", d.Chart) + args := d.globalFlags() + args = append(args, "dependency", "update", d.chart) d.cmd = command(helmBin, args...) - d.cmd.Stdout(cfg.Stdout) - d.cmd.Stderr(cfg.Stderr) + d.cmd.Stdout(d.stdout) + d.cmd.Stderr(d.stderr) - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", d.cmd.String()) + if d.debug { + fmt.Fprintf(d.stderr, "Generated command: '%s'\n", d.cmd.String()) } return nil diff --git a/internal/run/depupdate_test.go b/internal/run/depupdate_test.go index 315b351..ef553a5 100644 --- a/internal/run/depupdate_test.go +++ b/internal/run/depupdate_test.go @@ -1,8 +1,8 @@ package run import ( - "fmt" "github.com/golang/mock/gomock" + "github.com/pelotech/drone-helm3/internal/env" "github.com/stretchr/testify/suite" "strings" "testing" @@ -31,12 +31,21 @@ func TestDepUpdateTestSuite(t *testing.T) { suite.Run(t, new(DepUpdateTestSuite)) } +func (suite *DepUpdateTestSuite) TestNewDepUpdate() { + cfg := env.Config{ + Chart: "scatterplot", + } + d := NewDepUpdate(cfg) + suite.Equal("scatterplot", d.chart) +} + func (suite *DepUpdateTestSuite) TestPrepareAndExecute() { defer suite.ctrl.Finish() stdout := strings.Builder{} stderr := strings.Builder{} - cfg := Config{ + cfg := env.Config{ + Chart: "your_top_songs_2019", Stdout: &stdout, Stderr: &stderr, } @@ -55,74 +64,18 @@ func (suite *DepUpdateTestSuite) TestPrepareAndExecute() { Run(). Times(1) - d := DepUpdate{ - Chart: "your_top_songs_2019", - } + d := NewDepUpdate(cfg) - suite.Require().NoError(d.Prepare(cfg)) - suite.NoError(d.Execute(cfg)) -} - -func (suite *DepUpdateTestSuite) TestPrepareNamespaceFlag() { - defer suite.ctrl.Finish() - - cfg := Config{ - Namespace: "spotify", - } - - command = func(path string, args ...string) cmd { - suite.Equal([]string{"--namespace", "spotify", "dependency", "update", "your_top_songs_2019"}, args) - - return suite.mockCmd - } - suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() - suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - - d := DepUpdate{ - Chart: "your_top_songs_2019", - } - - suite.Require().NoError(d.Prepare(cfg)) -} - -func (suite *DepUpdateTestSuite) TestPrepareDebugFlag() { - defer suite.ctrl.Finish() - - stdout := strings.Builder{} - stderr := strings.Builder{} - cfg := Config{ - Debug: true, - Stdout: &stdout, - Stderr: &stderr, - } - - 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()).AnyTimes() - suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - - d := DepUpdate{ - Chart: "your_top_songs_2019", - } - - suite.Require().NoError(d.Prepare(cfg)) - - want := fmt.Sprintf("Generated command: '%s --debug dependency update your_top_songs_2019'\n", helmBin) - suite.Equal(want, stderr.String()) - suite.Equal("", stdout.String()) + suite.Require().NoError(d.Prepare()) + suite.NoError(d.Execute()) } func (suite *DepUpdateTestSuite) TestPrepareChartRequired() { - d := DepUpdate{} + d := NewDepUpdate(env.Config{}) suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - err := d.Prepare(Config{}) + err := d.Prepare() suite.EqualError(err, "chart is required") } diff --git a/internal/run/help.go b/internal/run/help.go index f2d6c59..f5609cd 100644 --- a/internal/run/help.go +++ b/internal/run/help.go @@ -2,39 +2,47 @@ package run import ( "fmt" + "github.com/pelotech/drone-helm3/internal/env" ) // Help is a step in a helm Plan that calls `helm help`. type Help struct { - HelmCommand string + *config + helmCommand string cmd cmd } +// NewHelp creates a Help using fields from the given Config. No validation is performed at this time. +func NewHelp(cfg env.Config) *Help { + return &Help{ + config: newConfig(cfg), + helmCommand: cfg.Command, + } +} + // Execute executes the `helm help` command. -func (h *Help) Execute(cfg Config) error { +func (h *Help) Execute() error { if err := h.cmd.Run(); err != nil { return fmt.Errorf("while running '%s': %w", h.cmd.String(), err) } - if h.HelmCommand == "help" { + if h.helmCommand == "help" { return nil } - return fmt.Errorf("unknown command '%s'", h.HelmCommand) + return fmt.Errorf("unknown command '%s'", h.helmCommand) } // Prepare gets the Help ready to execute. -func (h *Help) Prepare(cfg Config) error { - args := []string{"help"} - if cfg.Debug { - args = append([]string{"--debug"}, args...) - } +func (h *Help) Prepare() error { + args := h.globalFlags() + args = append(args, "help") h.cmd = command(helmBin, args...) - h.cmd.Stdout(cfg.Stdout) - h.cmd.Stderr(cfg.Stderr) + h.cmd.Stdout(h.stdout) + h.cmd.Stderr(h.stderr) - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", h.cmd.String()) + if h.debug { + fmt.Fprintf(h.stderr, "Generated command: '%s'\n", h.cmd.String()) } return nil diff --git a/internal/run/help_test.go b/internal/run/help_test.go index 19c49d2..d85cc35 100644 --- a/internal/run/help_test.go +++ b/internal/run/help_test.go @@ -1,8 +1,8 @@ package run import ( - "fmt" "github.com/golang/mock/gomock" + "github.com/pelotech/drone-helm3/internal/env" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "strings" @@ -17,6 +17,15 @@ func TestHelpTestSuite(t *testing.T) { suite.Run(t, new(HelpTestSuite)) } +func (suite *HelpTestSuite) TestNewHelp() { + cfg := env.Config{ + Command: "everybody dance NOW!!", + } + help := NewHelp(cfg) + suite.Require().NotNil(help) + suite.Equal("everybody dance NOW!!", help.helmCommand) +} + func (suite *HelpTestSuite) TestPrepare() { ctrl := gomock.NewController(suite.T()) defer ctrl.Finish() @@ -39,13 +48,13 @@ func (suite *HelpTestSuite) TestPrepare() { mCmd.EXPECT(). Stderr(&stderr) - cfg := Config{ + cfg := env.Config{ Stdout: &stdout, Stderr: &stderr, } - h := Help{} - err := h.Prepare(cfg) + h := NewHelp(cfg) + err := h.Prepare() suite.NoError(err) } @@ -53,41 +62,15 @@ func (suite *HelpTestSuite) TestExecute() { ctrl := gomock.NewController(suite.T()) defer ctrl.Finish() mCmd := NewMockcmd(ctrl) - originalCommand := command - command = func(_ string, _ ...string) cmd { - return mCmd - } - defer func() { command = originalCommand }() mCmd.EXPECT(). Run(). Times(2) - cfg := Config{} - help := Help{ - HelmCommand: "help", - cmd: mCmd, - } - suite.NoError(help.Execute(cfg)) + help := NewHelp(env.Config{Command: "help"}) + help.cmd = mCmd + suite.NoError(help.Execute()) - help.HelmCommand = "get down on friday" - suite.EqualError(help.Execute(cfg), "unknown command 'get down on friday'") -} - -func (suite *HelpTestSuite) TestPrepareDebugFlag() { - help := Help{} - - stdout := strings.Builder{} - stderr := strings.Builder{} - cfg := Config{ - Debug: true, - Stdout: &stdout, - Stderr: &stderr, - } - - help.Prepare(cfg) - - want := fmt.Sprintf("Generated command: '%s --debug help'\n", helmBin) - suite.Equal(want, stderr.String()) - suite.Equal("", stdout.String()) + help.helmCommand = "get down on friday" + suite.EqualError(help.Execute(), "unknown command 'get down on friday'") } diff --git a/internal/run/initkube.go b/internal/run/initkube.go index fc0fb11..bf4de3f 100644 --- a/internal/run/initkube.go +++ b/internal/run/initkube.go @@ -3,6 +3,7 @@ package run import ( "errors" "fmt" + "github.com/pelotech/drone-helm3/internal/env" "io" "os" "text/template" @@ -10,17 +11,12 @@ import ( // InitKube is a step in a helm Plan that initializes the kubernetes config file. type InitKube struct { - SkipTLSVerify bool - Certificate string - APIServer string - ServiceAccount string - Token string - TemplateFile string - ConfigFile string - - template *template.Template - configFile io.WriteCloser - values kubeValues + *config + templateFilename string + configFilename string + template *template.Template + configFile io.WriteCloser + values kubeValues } type kubeValues struct { @@ -32,58 +28,66 @@ type kubeValues struct { Token string } +// NewInitKube creates a InitKube using the given Config and filepaths. No validation is performed at this time. +func NewInitKube(cfg env.Config, templateFile, configFile string) *InitKube { + return &InitKube{ + config: newConfig(cfg), + values: kubeValues{ + SkipTLSVerify: cfg.SkipTLSVerify, + Certificate: cfg.Certificate, + APIServer: cfg.APIServer, + Namespace: cfg.Namespace, + ServiceAccount: cfg.ServiceAccount, + Token: cfg.KubeToken, + }, + templateFilename: templateFile, + configFilename: configFile, + } +} + // Execute generates a kubernetes config file from drone-helm3's template. -func (i *InitKube) Execute(cfg Config) error { - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "writing kubeconfig file to %s\n", i.ConfigFile) +func (i *InitKube) Execute() error { + if i.debug { + fmt.Fprintf(i.stderr, "writing kubeconfig file to %s\n", i.configFilename) } defer i.configFile.Close() return i.template.Execute(i.configFile, i.values) } // Prepare ensures all required configuration is present and that the config file is writable. -func (i *InitKube) Prepare(cfg Config) error { +func (i *InitKube) Prepare() error { var err error - if i.APIServer == "" { + if i.values.APIServer == "" { return errors.New("an API Server is needed to deploy") } - if i.Token == "" { + if i.values.Token == "" { return errors.New("token is needed to deploy") } - if i.ServiceAccount == "" { - i.ServiceAccount = "helm" + if i.values.ServiceAccount == "" { + i.values.ServiceAccount = "helm" } - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "loading kubeconfig template from %s\n", i.TemplateFile) + if i.debug { + fmt.Fprintf(i.stderr, "loading kubeconfig template from %s\n", i.templateFilename) } - i.template, err = template.ParseFiles(i.TemplateFile) + i.template, err = template.ParseFiles(i.templateFilename) if err != nil { return fmt.Errorf("could not load kubeconfig template: %w", err) } - i.values = kubeValues{ - SkipTLSVerify: i.SkipTLSVerify, - Certificate: i.Certificate, - APIServer: i.APIServer, - ServiceAccount: i.ServiceAccount, - Token: i.Token, - Namespace: cfg.Namespace, - } - - if cfg.Debug { - if _, err := os.Stat(i.ConfigFile); err != nil { + if i.debug { + if _, err := os.Stat(i.configFilename); err != nil { // non-nil err here isn't an actual error state; the kubeconfig just doesn't exist - fmt.Fprint(cfg.Stderr, "creating ") + fmt.Fprint(i.stderr, "creating ") } else { - fmt.Fprint(cfg.Stderr, "truncating ") + fmt.Fprint(i.stderr, "truncating ") } - fmt.Fprintf(cfg.Stderr, "kubeconfig file at %s\n", i.ConfigFile) + fmt.Fprintf(i.stderr, "kubeconfig file at %s\n", i.configFilename) } - i.configFile, err = os.Create(i.ConfigFile) + i.configFile, err = os.Create(i.configFilename) if err != nil { return fmt.Errorf("could not open kubeconfig file for writing: %w", err) } diff --git a/internal/run/initkube_test.go b/internal/run/initkube_test.go index 72452a8..8e13552 100644 --- a/internal/run/initkube_test.go +++ b/internal/run/initkube_test.go @@ -1,10 +1,13 @@ package run import ( + "fmt" + "github.com/pelotech/drone-helm3/internal/env" "github.com/stretchr/testify/suite" yaml "gopkg.in/yaml.v2" "io/ioutil" "os" + "strings" "testing" "text/template" ) @@ -17,6 +20,30 @@ func TestInitKubeTestSuite(t *testing.T) { suite.Run(t, new(InitKubeTestSuite)) } +func (suite *InitKubeTestSuite) TestNewInitKube() { + cfg := env.Config{ + SkipTLSVerify: true, + Certificate: "cHJvY2xhaW1zIHdvbmRlcmZ1bCBmcmllbmRzaGlw", + APIServer: "98.765.43.21", + ServiceAccount: "greathelm", + KubeToken: "b2YgbXkgYWZmZWN0aW9u", + Stderr: &strings.Builder{}, + Debug: true, + } + + init := NewInitKube(cfg, "conf.tpl", "conf.yml") + suite.Equal(kubeValues{ + SkipTLSVerify: true, + Certificate: "cHJvY2xhaW1zIHdvbmRlcmZ1bCBmcmllbmRzaGlw", + APIServer: "98.765.43.21", + ServiceAccount: "greathelm", + Token: "b2YgbXkgYWZmZWN0aW9u", + }, init.values) + suite.Equal("conf.tpl", init.templateFilename) + suite.Equal("conf.yml", init.configFilename) + suite.NotNil(init.config) +} + func (suite *InitKubeTestSuite) TestPrepareExecute() { templateFile, err := tempfile("kubeconfig********.yml.tpl", ` certificate: {{ .Certificate }} @@ -29,23 +56,20 @@ namespace: {{ .Namespace }} defer os.Remove(configFile.Name()) suite.Require().Nil(err) - init := InitKube{ - APIServer: "Sysadmin", - Certificate: "CCNA", - Token: "Aspire virtual currency", - TemplateFile: templateFile.Name(), - ConfigFile: configFile.Name(), + cfg := env.Config{ + APIServer: "Sysadmin", + Certificate: "CCNA", + KubeToken: "Aspire virtual currency", + Namespace: "Cisco", } - cfg := Config{ - Namespace: "Cisco", - } - err = init.Prepare(cfg) + init := NewInitKube(cfg, templateFile.Name(), configFile.Name()) + err = init.Prepare() suite.Require().Nil(err) suite.IsType(&template.Template{}, init.template) suite.NotNil(init.configFile) - err = init.Execute(cfg) + err = init.Execute() suite.Require().Nil(err) conf, err := ioutil.ReadFile(configFile.Name()) @@ -63,19 +87,16 @@ func (suite *InitKubeTestSuite) TestExecuteGeneratesConfig() { defer os.Remove(configFile.Name()) suite.Require().NoError(err) - cfg := Config{ - Namespace: "marshmallow", - } - init := InitKube{ - ConfigFile: configFile.Name(), - TemplateFile: "../../assets/kubeconfig.tpl", // the actual kubeconfig template + cfg := env.Config{ APIServer: "https://kube.cluster/peanut", ServiceAccount: "chef", - Token: "eWVhaCB3ZSB0b2tpbic=", + KubeToken: "eWVhaCB3ZSB0b2tpbic=", Certificate: "d293LCB5b3UgYXJlIHNvIGNvb2wgZm9yIHNtb2tpbmcgd2VlZCDwn5mE", + Namespace: "marshmallow", } - suite.Require().NoError(init.Prepare(cfg)) - suite.Require().NoError(init.Execute(cfg)) + init := NewInitKube(cfg, "../../assets/kubeconfig.tpl", configFile.Name()) // the actual kubeconfig template + suite.Require().NoError(init.Prepare()) + suite.Require().NoError(init.Execute()) contents, err := ioutil.ReadFile(configFile.Name()) suite.Require().NoError(err) @@ -98,11 +119,11 @@ func (suite *InitKubeTestSuite) TestExecuteGeneratesConfig() { suite.NoError(yaml.UnmarshalStrict(contents, &conf)) // test the other branch of the certificate/SkipTLSVerify conditional - init.SkipTLSVerify = true - init.Certificate = "" + init.values.SkipTLSVerify = true + init.values.Certificate = "" - suite.Require().NoError(init.Prepare(cfg)) - suite.Require().NoError(init.Execute(cfg)) + suite.Require().NoError(init.Prepare()) + suite.Require().NoError(init.Execute()) contents, err = ioutil.ReadFile(configFile.Name()) suite.Require().NoError(err) suite.Contains(string(contents), "insecure-skip-tls-verify: true") @@ -116,25 +137,25 @@ func (suite *InitKubeTestSuite) TestPrepareParseError() { defer os.Remove(templateFile.Name()) suite.Require().Nil(err) - init := InitKube{ - APIServer: "Sysadmin", - Certificate: "CCNA", - Token: "Aspire virtual currency", - TemplateFile: templateFile.Name(), + cfg := env.Config{ + APIServer: "Sysadmin", + Certificate: "CCNA", + KubeToken: "Aspire virtual currency", } - err = init.Prepare(Config{}) + init := NewInitKube(cfg, templateFile.Name(), "") + err = init.Prepare() suite.Error(err) suite.Regexp("could not load kubeconfig .* function .* not defined", err) } func (suite *InitKubeTestSuite) TestPrepareNonexistentTemplateFile() { - init := InitKube{ - APIServer: "Sysadmin", - Certificate: "CCNA", - Token: "Aspire virtual currency", - TemplateFile: "/usr/foreign/exclude/kubeprofig.tpl", + cfg := env.Config{ + APIServer: "Sysadmin", + Certificate: "CCNA", + KubeToken: "Aspire virtual currency", } - err := init.Prepare(Config{}) + init := NewInitKube(cfg, "/usr/foreign/exclude/kubeprofig.tpl", "") + err := init.Prepare() suite.Error(err) suite.Regexp("could not load kubeconfig .* no such file or directory", err) } @@ -143,16 +164,14 @@ func (suite *InitKubeTestSuite) TestPrepareCannotOpenDestinationFile() { templateFile, err := tempfile("kubeconfig********.yml.tpl", "hurgity burgity") defer os.Remove(templateFile.Name()) suite.Require().Nil(err) - init := InitKube{ - APIServer: "Sysadmin", - Certificate: "CCNA", - Token: "Aspire virtual currency", - TemplateFile: templateFile.Name(), - ConfigFile: "/usr/foreign/exclude/kubeprofig", + cfg := env.Config{ + APIServer: "Sysadmin", + Certificate: "CCNA", + KubeToken: "Aspire virtual currency", } + init := NewInitKube(cfg, templateFile.Name(), "/usr/foreign/exclude/kubeprofig") - cfg := Config{} - err = init.Prepare(cfg) + err = init.Prepare() suite.Error(err) suite.Regexp("could not open .* for writing: .* no such file or directory", err) } @@ -167,24 +186,21 @@ func (suite *InitKubeTestSuite) TestPrepareRequiredConfig() { suite.Require().Nil(err) // initial config with all required fields present - init := InitKube{ - APIServer: "Sysadmin", - Certificate: "CCNA", - Token: "Aspire virtual currency", - TemplateFile: templateFile.Name(), - ConfigFile: configFile.Name(), + cfg := env.Config{ + APIServer: "Sysadmin", + Certificate: "CCNA", + KubeToken: "Aspire virtual currency", } - cfg := Config{} + init := NewInitKube(cfg, templateFile.Name(), configFile.Name()) + suite.NoError(init.Prepare()) // consistency check; we should be starting in a happy state - suite.NoError(init.Prepare(cfg)) // consistency check; we should be starting in a happy state + init.values.APIServer = "" + suite.Error(init.Prepare(), "APIServer should be required.") - init.APIServer = "" - suite.Error(init.Prepare(cfg), "APIServer should be required.") - - init.APIServer = "Sysadmin" - init.Token = "" - suite.Error(init.Prepare(cfg), "Token should be required.") + init.values.APIServer = "Sysadmin" + init.values.Token = "" + suite.Error(init.Prepare(), "Token should be required.") } func (suite *InitKubeTestSuite) TestPrepareDefaultsServiceAccount() { @@ -196,18 +212,43 @@ func (suite *InitKubeTestSuite) TestPrepareDefaultsServiceAccount() { defer os.Remove(configFile.Name()) suite.Require().Nil(err) - init := InitKube{ - APIServer: "Sysadmin", - Certificate: "CCNA", - Token: "Aspire virtual currency", - TemplateFile: templateFile.Name(), - ConfigFile: configFile.Name(), + cfg := env.Config{ + APIServer: "Sysadmin", + Certificate: "CCNA", + KubeToken: "Aspire virtual currency", } + init := NewInitKube(cfg, templateFile.Name(), configFile.Name()) - cfg := Config{} + init.Prepare() + suite.Equal("helm", init.values.ServiceAccount) +} - init.Prepare(cfg) - suite.Equal("helm", init.ServiceAccount) +func (suite *InitKubeTestSuite) TestDebugOutput() { + templateFile, err := tempfile("kubeconfig********.yml.tpl", "hurgity burgity") + defer os.Remove(templateFile.Name()) + suite.Require().Nil(err) + + configFile, err := tempfile("kubeconfig********.yml", "") + defer os.Remove(configFile.Name()) + suite.Require().Nil(err) + + stdout := &strings.Builder{} + stderr := &strings.Builder{} + cfg := env.Config{ + APIServer: "http://my.kube.server/", + KubeToken: "QSBzaW5nbGUgcm9zZQ==", + Debug: true, + Stdout: stdout, + Stderr: stderr, + } + init := NewInitKube(cfg, templateFile.Name(), configFile.Name()) + suite.NoError(init.Prepare()) + + suite.Contains(stderr.String(), fmt.Sprintf("loading kubeconfig template from %s\n", templateFile.Name())) + suite.Contains(stderr.String(), fmt.Sprintf("truncating kubeconfig file at %s\n", configFile.Name())) + + suite.NoError(init.Execute()) + suite.Contains(stderr.String(), fmt.Sprintf("writing kubeconfig file to %s\n", configFile.Name())) } func tempfile(name, contents string) (*os.File, error) { diff --git a/internal/run/lint.go b/internal/run/lint.go index db4e13b..7752f8d 100644 --- a/internal/run/lint.go +++ b/internal/run/lint.go @@ -2,61 +2,67 @@ package run import ( "fmt" + "github.com/pelotech/drone-helm3/internal/env" ) // Lint is an execution step that calls `helm lint` when executed. type Lint struct { - Chart string - Values string - StringValues string - ValuesFiles []string - Strict bool + *config + chart string + values string + stringValues string + valuesFiles []string + strict bool cmd cmd } +// NewLint creates a Lint using fields from the given Config. No validation is performed at this time. +func NewLint(cfg env.Config) *Lint { + return &Lint{ + config: newConfig(cfg), + chart: cfg.Chart, + values: cfg.Values, + stringValues: cfg.StringValues, + valuesFiles: cfg.ValuesFiles, + strict: cfg.LintStrictly, + } +} + // Execute executes the `helm lint` command. -func (l *Lint) Execute(_ Config) error { +func (l *Lint) Execute() error { return l.cmd.Run() } // Prepare gets the Lint ready to execute. -func (l *Lint) Prepare(cfg Config) error { - if l.Chart == "" { +func (l *Lint) Prepare() 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 := l.globalFlags() args = append(args, "lint") - if l.Values != "" { - args = append(args, "--set", l.Values) + if l.values != "" { + args = append(args, "--set", l.values) } - if l.StringValues != "" { - args = append(args, "--set-string", l.StringValues) + if l.stringValues != "" { + args = append(args, "--set-string", l.stringValues) } - for _, vFile := range l.ValuesFiles { + for _, vFile := range l.valuesFiles { args = append(args, "--values", vFile) } - if l.Strict { + if l.strict { args = append(args, "--strict") } - args = append(args, l.Chart) + args = append(args, l.chart) l.cmd = command(helmBin, args...) - l.cmd.Stdout(cfg.Stdout) - l.cmd.Stderr(cfg.Stderr) + l.cmd.Stdout(l.stdout) + l.cmd.Stderr(l.stderr) - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", l.cmd.String()) + if l.debug { + fmt.Fprintf(l.stderr, "Generated command: '%s'\n", l.cmd.String()) } return nil diff --git a/internal/run/lint_test.go b/internal/run/lint_test.go index f46ad63..42fd923 100644 --- a/internal/run/lint_test.go +++ b/internal/run/lint_test.go @@ -1,8 +1,8 @@ package run import ( - "fmt" "github.com/golang/mock/gomock" + "github.com/pelotech/drone-helm3/internal/env" "github.com/stretchr/testify/suite" "strings" "testing" @@ -31,19 +31,36 @@ func TestLintTestSuite(t *testing.T) { suite.Run(t, new(LintTestSuite)) } +func (suite *LintTestSuite) TestNewLint() { + cfg := env.Config{ + Chart: "./flow", + Values: "steadfastness,forthrightness", + StringValues: "tensile_strength,flexibility", + ValuesFiles: []string{"/root/price_inventory.yml"}, + LintStrictly: true, + } + lint := NewLint(cfg) + suite.Require().NotNil(lint) + suite.Equal("./flow", lint.chart) + suite.Equal("steadfastness,forthrightness", lint.values) + suite.Equal("tensile_strength,flexibility", lint.stringValues) + suite.Equal([]string{"/root/price_inventory.yml"}, lint.valuesFiles) + suite.Equal(true, lint.strict) + suite.NotNil(lint.config) +} + func (suite *LintTestSuite) TestPrepareAndExecute() { defer suite.ctrl.Finish() stdout := strings.Builder{} stderr := strings.Builder{} - l := Lint{ - Chart: "./epic/mychart", - } - cfg := Config{ + cfg := env.Config{ + Chart: "./epic/mychart", Stdout: &stdout, Stderr: &stderr, } + l := NewLint(cfg) command = func(path string, args ...string) cmd { suite.Equal(helmBin, path) @@ -52,6 +69,7 @@ func (suite *LintTestSuite) TestPrepareAndExecute() { return suite.mockCmd } + suite.mockCmd.EXPECT().String().AnyTimes() suite.mockCmd.EXPECT(). Stdout(&stdout) suite.mockCmd.EXPECT(). @@ -60,9 +78,9 @@ func (suite *LintTestSuite) TestPrepareAndExecute() { Run(). Times(1) - err := l.Prepare(cfg) + err := l.Prepare() suite.Require().Nil(err) - l.Execute(cfg) + l.Execute() } func (suite *LintTestSuite) TestPrepareRequiresChart() { @@ -70,25 +88,22 @@ func (suite *LintTestSuite) TestPrepareRequiresChart() { suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - cfg := Config{} - l := Lint{} - - err := l.Prepare(cfg) + l := NewLint(env.Config{}) + err := l.Prepare() suite.EqualError(err, "chart is required", "Chart should be mandatory") } func (suite *LintTestSuite) TestPrepareWithLintFlags() { defer suite.ctrl.Finish() - cfg := Config{} - - l := Lint{ + cfg := env.Config{ Chart: "./uk/top_40", Values: "width=5", StringValues: "version=2.0", ValuesFiles: []string{"/usr/local/underrides", "/usr/local/overrides"}, - Strict: true, + LintStrictly: true, } + l := NewLint(cfg) command = func(path string, args ...string) cmd { suite.Equal(helmBin, path) @@ -105,66 +120,8 @@ func (suite *LintTestSuite) TestPrepareWithLintFlags() { suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().String().AnyTimes() - err := l.Prepare(cfg) + err := l.Prepare() 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) -} diff --git a/internal/run/uninstall.go b/internal/run/uninstall.go index 5c5c654..d0cce60 100644 --- a/internal/run/uninstall.go +++ b/internal/run/uninstall.go @@ -2,53 +2,57 @@ package run import ( "fmt" + "github.com/pelotech/drone-helm3/internal/env" ) // Uninstall is an execution step that calls `helm uninstall` when executed. type Uninstall struct { - Release string - DryRun bool - KeepHistory bool + *config + release string + dryRun bool + keepHistory bool cmd cmd } +// NewUninstall creates an Uninstall using fields from the given Config. No validation is performed at this time. +func NewUninstall(cfg env.Config) *Uninstall { + return &Uninstall{ + config: newConfig(cfg), + release: cfg.Release, + dryRun: cfg.DryRun, + keepHistory: cfg.KeepHistory, + } +} + // Execute executes the `helm uninstall` command. -func (u *Uninstall) Execute(_ Config) error { +func (u *Uninstall) Execute() error { return u.cmd.Run() } // Prepare gets the Uninstall ready to execute. -func (u *Uninstall) Prepare(cfg Config) error { - if u.Release == "" { +func (u *Uninstall) Prepare() error { + if u.release == "" { return fmt.Errorf("release is required") } - args := make([]string, 0) - - if cfg.Namespace != "" { - args = append(args, "--namespace", cfg.Namespace) - } - if cfg.Debug { - args = append(args, "--debug") - } - + args := u.globalFlags() args = append(args, "uninstall") - if u.DryRun { + if u.dryRun { args = append(args, "--dry-run") } - if u.KeepHistory { + if u.keepHistory { args = append(args, "--keep-history") } - args = append(args, u.Release) + args = append(args, u.release) u.cmd = command(helmBin, args...) - u.cmd.Stdout(cfg.Stdout) - u.cmd.Stderr(cfg.Stderr) + u.cmd.Stdout(u.stdout) + u.cmd.Stderr(u.stderr) - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", u.cmd.String()) + if u.debug { + fmt.Fprintf(u.stderr, "Generated command: '%s'\n", u.cmd.String()) } return nil diff --git a/internal/run/uninstall_test.go b/internal/run/uninstall_test.go index 6ac7cc9..b52ebfd 100644 --- a/internal/run/uninstall_test.go +++ b/internal/run/uninstall_test.go @@ -1,10 +1,9 @@ package run import ( - "fmt" "github.com/golang/mock/gomock" + "github.com/pelotech/drone-helm3/internal/env" "github.com/stretchr/testify/suite" - "strings" "testing" ) @@ -35,12 +34,26 @@ func TestUninstallTestSuite(t *testing.T) { suite.Run(t, new(UninstallTestSuite)) } +func (suite *UninstallTestSuite) TestNewUninstall() { + cfg := env.Config{ + DryRun: true, + Release: "jetta_id_love_to_change_the_world", + KeepHistory: true, + } + u := NewUninstall(cfg) + suite.Equal("jetta_id_love_to_change_the_world", u.release) + suite.Equal(true, u.dryRun) + suite.Equal(true, u.keepHistory) + suite.NotNil(u.config) +} + func (suite *UninstallTestSuite) TestPrepareAndExecute() { defer suite.ctrl.Finish() - u := Uninstall{ + cfg := env.Config{ Release: "zayde_wølf_king", } + u := NewUninstall(cfg) actual := []string{} command = func(path string, args ...string) cmd { @@ -58,92 +71,49 @@ func (suite *UninstallTestSuite) TestPrepareAndExecute() { Run(). Times(1) - cfg := Config{} - suite.NoError(u.Prepare(cfg)) + suite.NoError(u.Prepare()) expected := []string{"uninstall", "zayde_wølf_king"} suite.Equal(expected, actual) - u.Execute(cfg) + u.Execute() } func (suite *UninstallTestSuite) TestPrepareDryRunFlag() { - u := Uninstall{ + cfg := env.Config{ Release: "firefox_ak_wildfire", DryRun: true, } - cfg := Config{} + u := NewUninstall(cfg) suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - suite.NoError(u.Prepare(cfg)) + suite.NoError(u.Prepare()) expected := []string{"uninstall", "--dry-run", "firefox_ak_wildfire"} suite.Equal(expected, suite.actualArgs) } func (suite *UninstallTestSuite) TestPrepareKeepHistoryFlag() { - u := Uninstall{ + cfg := env.Config{ Release: "perturbator_sentient", KeepHistory: true, } - cfg := Config{} + u := NewUninstall(cfg) suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - suite.NoError(u.Prepare(cfg)) + suite.NoError(u.Prepare()) expected := []string{"uninstall", "--keep-history", "perturbator_sentient"} suite.Equal(expected, suite.actualArgs) } -func (suite *UninstallTestSuite) TestPrepareNamespaceFlag() { - u := Uninstall{ - Release: "carly_simon_run_away_with_me", - } - cfg := Config{ - Namespace: "emotion", - } - - suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() - suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - - suite.NoError(u.Prepare(cfg)) - expected := []string{"--namespace", "emotion", "uninstall", "carly_simon_run_away_with_me"} - suite.Equal(expected, suite.actualArgs) -} - -func (suite *UninstallTestSuite) TestPrepareDebugFlag() { - u := Uninstall{ - Release: "just_a_band_huff_and_puff", - } - stderr := strings.Builder{} - cfg := Config{ - Debug: true, - Stderr: &stderr, - } - - 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()).AnyTimes() - suite.mockCmd.EXPECT().Stderr(&stderr).AnyTimes() - - suite.NoError(u.Prepare(cfg)) - suite.Equal(fmt.Sprintf("Generated command: '%s --debug "+ - "uninstall just_a_band_huff_and_puff'\n", helmBin), stderr.String()) -} - func (suite *UninstallTestSuite) TestPrepareRequiresRelease() { // 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() - u := Uninstall{} - err := u.Prepare(Config{}) + u := NewUninstall(env.Config{}) + err := u.Prepare() suite.EqualError(err, "release is required", "Uninstall.Release should be mandatory") } diff --git a/internal/run/upgrade.go b/internal/run/upgrade.go index 41c8ab0..80ccac4 100644 --- a/internal/run/upgrade.go +++ b/internal/run/upgrade.go @@ -2,98 +2,113 @@ package run import ( "fmt" + "github.com/pelotech/drone-helm3/internal/env" ) // Upgrade is an execution step that calls `helm upgrade` when executed. type Upgrade struct { - Chart string - Release string + *config + chart string + release string - ChartVersion string - DryRun bool - Wait bool - Values string - StringValues string - ValuesFiles []string - ReuseValues bool - Timeout string - Force bool - Atomic bool - CleanupOnFail bool - CAFile string + chartVersion string + dryRun bool + wait bool + values string + stringValues string + valuesFiles []string + reuseValues bool + timeout string + force bool + atomic bool + cleanupOnFail bool + caFile string cmd cmd } +// NewUpgrade creates an Upgrade using fields from the given Config. No validation is performed at this time. +func NewUpgrade(cfg env.Config) *Upgrade { + return &Upgrade{ + config: newConfig(cfg), + chart: cfg.Chart, + release: cfg.Release, + chartVersion: cfg.ChartVersion, + dryRun: cfg.DryRun, + wait: cfg.Wait, + values: cfg.Values, + stringValues: cfg.StringValues, + valuesFiles: cfg.ValuesFiles, + reuseValues: cfg.ReuseValues, + timeout: cfg.Timeout, + force: cfg.Force, + atomic: cfg.AtomicUpgrade, + cleanupOnFail: cfg.CleanupOnFail, + caFile: cfg.RepoCAFile, + } +} + // Execute executes the `helm upgrade` command. -func (u *Upgrade) Execute(_ Config) error { +func (u *Upgrade) Execute() error { return u.cmd.Run() } // Prepare gets the Upgrade ready to execute. -func (u *Upgrade) Prepare(cfg Config) error { - if u.Chart == "" { +func (u *Upgrade) Prepare() error { + if u.chart == "" { return fmt.Errorf("chart is required") } - if u.Release == "" { + if u.release == "" { return fmt.Errorf("release is required") } - args := make([]string, 0) - - if cfg.Namespace != "" { - args = append(args, "--namespace", cfg.Namespace) - } - if cfg.Debug { - args = append(args, "--debug") - } - + args := u.globalFlags() args = append(args, "upgrade", "--install") - if u.ChartVersion != "" { - args = append(args, "--version", u.ChartVersion) + if u.chartVersion != "" { + args = append(args, "--version", u.chartVersion) } - if u.DryRun { + if u.dryRun { args = append(args, "--dry-run") } - if u.Wait { + if u.wait { args = append(args, "--wait") } - if u.ReuseValues { + if u.reuseValues { args = append(args, "--reuse-values") } - if u.Timeout != "" { - args = append(args, "--timeout", u.Timeout) + if u.timeout != "" { + args = append(args, "--timeout", u.timeout) } - if u.Force { + if u.force { args = append(args, "--force") } - if u.Atomic { + if u.atomic { args = append(args, "--atomic") } - if u.CleanupOnFail { + if u.cleanupOnFail { args = append(args, "--cleanup-on-fail") } - if u.Values != "" { - args = append(args, "--set", u.Values) + if u.values != "" { + args = append(args, "--set", u.values) } - if u.StringValues != "" { - args = append(args, "--set-string", u.StringValues) + if u.stringValues != "" { + args = append(args, "--set-string", u.stringValues) } - for _, vFile := range u.ValuesFiles { + for _, vFile := range u.valuesFiles { args = append(args, "--values", vFile) } - if u.CAFile != "" { - args = append(args, "--ca-file", u.CAFile) + if u.caFile != "" { + args = append(args, "--ca-file", u.caFile) } - args = append(args, u.Release, u.Chart) + args = append(args, u.release, u.chart) u.cmd = command(helmBin, args...) - u.cmd.Stdout(cfg.Stdout) - u.cmd.Stderr(cfg.Stderr) + u.cmd.Stdout(u.stdout) + u.cmd.Stderr(u.stderr) - if cfg.Debug { - fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", u.cmd.String()) + if u.debug { + fmt.Fprintf(u.stderr, "Generated command: '%s'\n", u.cmd.String()) } return nil diff --git a/internal/run/upgrade_test.go b/internal/run/upgrade_test.go index bcc950c..770a15e 100644 --- a/internal/run/upgrade_test.go +++ b/internal/run/upgrade_test.go @@ -3,6 +3,7 @@ package run import ( "fmt" "github.com/golang/mock/gomock" + "github.com/pelotech/drone-helm3/internal/env" "github.com/stretchr/testify/suite" "strings" "testing" @@ -31,13 +32,48 @@ func TestUpgradeTestSuite(t *testing.T) { suite.Run(t, new(UpgradeTestSuite)) } +func (suite *UpgradeTestSuite) TestNewUpgrade() { + cfg := env.Config{ + ChartVersion: "seventeen", + DryRun: true, + Wait: true, + Values: "steadfastness,forthrightness", + StringValues: "tensile_strength,flexibility", + ValuesFiles: []string{"/root/price_inventory.yml"}, + ReuseValues: true, + Timeout: "go sit in the corner", + Chart: "billboard_top_100", + Release: "post_malone_circles", + Force: true, + AtomicUpgrade: true, + CleanupOnFail: true, + } + + up := NewUpgrade(cfg) + suite.Equal(cfg.Chart, up.chart) + suite.Equal(cfg.Release, up.release) + suite.Equal(cfg.ChartVersion, up.chartVersion) + suite.Equal(true, up.dryRun) + suite.Equal(cfg.Wait, up.wait) + suite.Equal("steadfastness,forthrightness", up.values) + suite.Equal("tensile_strength,flexibility", up.stringValues) + suite.Equal([]string{"/root/price_inventory.yml"}, up.valuesFiles) + suite.Equal(cfg.ReuseValues, up.reuseValues) + suite.Equal(cfg.Timeout, up.timeout) + suite.Equal(cfg.Force, up.force) + suite.Equal(true, up.atomic) + suite.Equal(true, up.cleanupOnFail) + suite.NotNil(up.config) +} + func (suite *UpgradeTestSuite) TestPrepareAndExecute() { defer suite.ctrl.Finish() - u := Upgrade{ + cfg := env.Config{ Chart: "at40", Release: "jonas_brothers_only_human", } + u := NewUpgrade(cfg) command = func(path string, args ...string) cmd { suite.Equal(helmBin, path) @@ -54,19 +90,20 @@ func (suite *UpgradeTestSuite) TestPrepareAndExecute() { Run(). Times(1) - cfg := Config{} - err := u.Prepare(cfg) + err := u.Prepare() suite.Require().Nil(err) - u.Execute(cfg) + u.Execute() } func (suite *UpgradeTestSuite) TestPrepareNamespaceFlag() { defer suite.ctrl.Finish() - u := Upgrade{ - Chart: "at40", - Release: "shaed_trampoline", + cfg := env.Config{ + Namespace: "melt", + Chart: "at40", + Release: "shaed_trampoline", } + u := NewUpgrade(cfg) command = func(path string, args ...string) cmd { suite.Equal(helmBin, path) @@ -78,17 +115,14 @@ func (suite *UpgradeTestSuite) TestPrepareNamespaceFlag() { suite.mockCmd.EXPECT().Stdout(gomock.Any()) suite.mockCmd.EXPECT().Stderr(gomock.Any()) - cfg := Config{ - Namespace: "melt", - } - err := u.Prepare(cfg) + err := u.Prepare() suite.Require().Nil(err) } func (suite *UpgradeTestSuite) TestPrepareWithUpgradeFlags() { defer suite.ctrl.Finish() - u := Upgrade{ + cfg := env.Config{ Chart: "hot_ac", Release: "maroon_5_memories", ChartVersion: "radio_edit", @@ -100,12 +134,11 @@ func (suite *UpgradeTestSuite) TestPrepareWithUpgradeFlags() { ReuseValues: true, Timeout: "sit_in_the_corner", Force: true, - Atomic: true, + AtomicUpgrade: true, CleanupOnFail: true, - CAFile: "local_ca.cert", + RepoCAFile: "local_ca.cert", } - - cfg := Config{} + u := NewUpgrade(cfg) command = func(path string, args ...string) cmd { suite.Equal(helmBin, path) @@ -131,7 +164,7 @@ func (suite *UpgradeTestSuite) TestPrepareWithUpgradeFlags() { suite.mockCmd.EXPECT().Stdout(gomock.Any()) suite.mockCmd.EXPECT().Stderr(gomock.Any()) - err := u.Prepare(cfg) + err := u.Prepare() suite.Require().Nil(err) } @@ -140,34 +173,30 @@ func (suite *UpgradeTestSuite) TestRequiresChartAndRelease() { suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() - u := Upgrade{ - Release: "seth_everman_unskippable_cutscene", - } + u := NewUpgrade(env.Config{}) + u.release = "seth_everman_unskippable_cutscene" - err := u.Prepare(Config{}) + err := u.Prepare() suite.EqualError(err, "chart is required", "Chart should be mandatory") - u = Upgrade{ - Chart: "billboard_top_zero", - } + u.release = "" + u.chart = "billboard_top_zero" - err = u.Prepare(Config{}) + err = u.Prepare() suite.EqualError(err, "release is required", "Release should be mandatory") } func (suite *UpgradeTestSuite) TestPrepareDebugFlag() { - u := Upgrade{ - Chart: "at40", - Release: "lewis_capaldi_someone_you_loved", - } - stdout := strings.Builder{} stderr := strings.Builder{} - cfg := Config{ - Debug: true, - Stdout: &stdout, - Stderr: &stderr, + cfg := env.Config{ + Chart: "at40", + Release: "lewis_capaldi_someone_you_loved", + Debug: true, + Stdout: &stdout, + Stderr: &stderr, } + u := NewUpgrade(cfg) command = func(path string, args ...string) cmd { suite.mockCmd.EXPECT(). @@ -182,7 +211,7 @@ func (suite *UpgradeTestSuite) TestPrepareDebugFlag() { suite.mockCmd.EXPECT(). Stderr(&stderr) - u.Prepare(cfg) + u.Prepare() want := fmt.Sprintf("Generated command: '%s --debug upgrade "+ "--install lewis_capaldi_someone_you_loved at40'\n", helmBin)