Initialize kubernetes config on upgrade
This change revealed more about how the system needs to work, so there
are some supporting changes:
* helm.upgrade and helm.help are now vars rather than raw functions.
This allows unit tests to target the "which step should we run"
logic directly by comparing function pointers, rather than having to
configure/prepare a fully-valid Plan and then infer the logic’s
correctness based on the Plan’s state.
* configuration that's specific to kubeconfig initialization is now part
of the InitKube struct rather than run.Config, since other steps
shouldn’t need access to those settings (particularly the secrets).
* Step.Execute now receives a run.Config so it can log debug output.
This commit is contained in:
@@ -25,7 +25,7 @@ type Config struct {
|
||||
StringValues string `split_words:"true"`
|
||||
ValuesFiles []string `split_words:"true"`
|
||||
Namespace string ``
|
||||
Token string `envconfig:"KUBERNETES_TOKEN"`
|
||||
KubeToken string `envconfig:"KUBERNETES_TOKEN"`
|
||||
SkipTLSVerify bool `envconfig:"SKIP_TLS_VERIFY"`
|
||||
Certificate string `envconfig:"KUBERNETES_CERTIFICATE"`
|
||||
APIServer string `envconfig:"API_SERVER"`
|
||||
|
||||
62
internal/helm/mock_step_test.go
Normal file
62
internal/helm/mock_step_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./internal/helm/plan.go
|
||||
|
||||
// Package mock_helm is a generated GoMock package.
|
||||
package helm
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
run "github.com/pelotech/drone-helm3/internal/run"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockStep is a mock of Step interface
|
||||
type MockStep struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStepMockRecorder
|
||||
}
|
||||
|
||||
// MockStepMockRecorder is the mock recorder for MockStep
|
||||
type MockStepMockRecorder struct {
|
||||
mock *MockStep
|
||||
}
|
||||
|
||||
// NewMockStep creates a new mock instance
|
||||
func NewMockStep(ctrl *gomock.Controller) *MockStep {
|
||||
mock := &MockStep{ctrl: ctrl}
|
||||
mock.recorder = &MockStepMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockStep) EXPECT() *MockStepMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Prepare mocks base method
|
||||
func (m *MockStep) Prepare(arg0 run.Config) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Prepare", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Prepare indicates an expected call of Prepare
|
||||
func (mr *MockStepMockRecorder) Prepare(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prepare", reflect.TypeOf((*MockStep)(nil).Prepare), arg0)
|
||||
}
|
||||
|
||||
// Execute mocks base method
|
||||
func (m *MockStep) Execute(arg0 run.Config) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Execute", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Execute indicates an expected call of Execute
|
||||
func (mr *MockStepMockRecorder) Execute(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockStep)(nil).Execute), arg0)
|
||||
}
|
||||
@@ -1,89 +1,108 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/pelotech/drone-helm3/internal/run"
|
||||
"os"
|
||||
)
|
||||
|
||||
const kubeConfigTemplate = "/root/.kube/config.tpl"
|
||||
|
||||
// A Step is one step in the plan.
|
||||
type Step interface {
|
||||
Prepare(run.Config) error
|
||||
Execute() error
|
||||
Execute(run.Config) error
|
||||
}
|
||||
|
||||
// A Plan is a series of steps to perform.
|
||||
type Plan struct {
|
||||
steps []Step
|
||||
steps []Step
|
||||
cfg Config
|
||||
runCfg run.Config
|
||||
}
|
||||
|
||||
// NewPlan makes a plan for running a helm operation.
|
||||
func NewPlan(cfg Config) (*Plan, error) {
|
||||
runCfg := run.Config{
|
||||
Debug: cfg.Debug,
|
||||
KubeConfig: cfg.KubeConfig,
|
||||
Values: cfg.Values,
|
||||
StringValues: cfg.StringValues,
|
||||
ValuesFiles: cfg.ValuesFiles,
|
||||
Namespace: cfg.Namespace,
|
||||
Token: cfg.Token,
|
||||
SkipTLSVerify: cfg.SkipTLSVerify,
|
||||
Certificate: cfg.Certificate,
|
||||
APIServer: cfg.APIServer,
|
||||
ServiceAccount: cfg.ServiceAccount,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
p := Plan{
|
||||
cfg: cfg,
|
||||
runCfg: run.Config{
|
||||
Debug: cfg.Debug,
|
||||
KubeConfig: cfg.KubeConfig,
|
||||
Values: cfg.Values,
|
||||
StringValues: cfg.StringValues,
|
||||
ValuesFiles: cfg.ValuesFiles,
|
||||
Namespace: cfg.Namespace,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
},
|
||||
}
|
||||
|
||||
p := Plan{}
|
||||
switch cfg.Command {
|
||||
case "upgrade":
|
||||
steps, err := upgrade(cfg, runCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
p.steps = (*determineSteps(cfg))(cfg)
|
||||
|
||||
for i, step := range p.steps {
|
||||
if cfg.Debug {
|
||||
fmt.Fprintf(os.Stderr, "calling %T.Prepare (step %d)\n", step, i)
|
||||
}
|
||||
p.steps = steps
|
||||
case "delete":
|
||||
return nil, errors.New("not implemented")
|
||||
case "lint":
|
||||
return nil, errors.New("not implemented")
|
||||
case "help":
|
||||
steps, err := help(cfg, runCfg)
|
||||
if err != nil {
|
||||
|
||||
if err := step.Prepare(p.runCfg); err != nil {
|
||||
err = fmt.Errorf("while preparing %T step: %w", step, err)
|
||||
return nil, err
|
||||
}
|
||||
p.steps = steps
|
||||
default:
|
||||
switch cfg.DroneEvent {
|
||||
case "push", "tag", "deployment", "pull_request", "promote", "rollback":
|
||||
steps, err := upgrade(cfg, runCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.steps = steps
|
||||
default:
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
switch cfg.Command {
|
||||
case "upgrade":
|
||||
return &upgrade
|
||||
case "delete":
|
||||
panic("not implemented")
|
||||
case "lint":
|
||||
panic("not implemented")
|
||||
case "help":
|
||||
return &help
|
||||
default:
|
||||
switch cfg.DroneEvent {
|
||||
case "push", "tag", "deployment", "pull_request", "promote", "rollback":
|
||||
return &upgrade
|
||||
default:
|
||||
panic("not implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute runs each step in the plan, aborting and reporting on error
|
||||
func (p *Plan) Execute() error {
|
||||
for _, step := range p.steps {
|
||||
if err := step.Execute(); err != nil {
|
||||
return err
|
||||
for i, step := range p.steps {
|
||||
if p.cfg.Debug {
|
||||
fmt.Fprintf(os.Stderr, "calling %T.Execute (step %d)\n", step, i)
|
||||
}
|
||||
|
||||
if err := step.Execute(p.runCfg); err != nil {
|
||||
return fmt.Errorf("in execution step %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func upgrade(cfg Config, runCfg run.Config) ([]Step, error) {
|
||||
var upgrade = func(cfg Config) []Step {
|
||||
steps := make([]Step, 0)
|
||||
upgrade := &run.Upgrade{
|
||||
|
||||
steps = append(steps, &run.InitKube{
|
||||
SkipTLSVerify: cfg.SkipTLSVerify,
|
||||
Certificate: cfg.Certificate,
|
||||
APIServer: cfg.APIServer,
|
||||
ServiceAccount: cfg.ServiceAccount,
|
||||
Token: cfg.KubeToken,
|
||||
TemplateFile: kubeConfigTemplate,
|
||||
})
|
||||
|
||||
steps = append(steps, &run.Upgrade{
|
||||
Chart: cfg.Chart,
|
||||
Release: cfg.Release,
|
||||
ChartVersion: cfg.ChartVersion,
|
||||
@@ -91,23 +110,12 @@ func upgrade(cfg Config, runCfg run.Config) ([]Step, error) {
|
||||
ReuseValues: cfg.ReuseValues,
|
||||
Timeout: cfg.Timeout,
|
||||
Force: cfg.Force,
|
||||
}
|
||||
if err := upgrade.Prepare(runCfg); err != nil {
|
||||
err = fmt.Errorf("while preparing upgrade step: %w", err)
|
||||
return steps, err
|
||||
}
|
||||
steps = append(steps, upgrade)
|
||||
})
|
||||
|
||||
return steps, nil
|
||||
return steps
|
||||
}
|
||||
|
||||
func help(cfg Config, runCfg run.Config) ([]Step, error) {
|
||||
var help = func(cfg Config) []Step {
|
||||
help := &run.Help{}
|
||||
|
||||
if err := help.Prepare(runCfg); err != nil {
|
||||
err = fmt.Errorf("while preparing help step: %w", err)
|
||||
return []Step{}, err
|
||||
}
|
||||
|
||||
return []Step{help}, nil
|
||||
return []Step{help}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package helm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/pelotech/drone-helm3/internal/run"
|
||||
@@ -16,48 +18,147 @@ func TestPlanTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(PlanTestSuite))
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestNewPlanUpgradeCommand() {
|
||||
cfg := Config{
|
||||
Command: "upgrade",
|
||||
Chart: "billboard_top_100",
|
||||
Release: "post_malone_circles",
|
||||
func (suite *PlanTestSuite) TestNewPlan() {
|
||||
ctrl := gomock.NewController(suite.T())
|
||||
stepOne := NewMockStep(ctrl)
|
||||
stepTwo := NewMockStep(ctrl)
|
||||
|
||||
origHelp := help
|
||||
help = func(cfg Config) []Step {
|
||||
return []Step{stepOne, stepTwo}
|
||||
}
|
||||
defer func() { help = origHelp }()
|
||||
|
||||
cfg := Config{
|
||||
Command: "help",
|
||||
Debug: false,
|
||||
KubeConfig: "/branch/.sfere/profig",
|
||||
Values: "steadfastness,forthrightness",
|
||||
StringValues: "tensile_strength,flexibility",
|
||||
ValuesFiles: []string{"/root/price_inventory.yml"},
|
||||
Namespace: "outer",
|
||||
}
|
||||
|
||||
runCfg := run.Config{
|
||||
Debug: false,
|
||||
KubeConfig: "/branch/.sfere/profig",
|
||||
Values: "steadfastness,forthrightness",
|
||||
StringValues: "tensile_strength,flexibility",
|
||||
ValuesFiles: []string{"/root/price_inventory.yml"},
|
||||
Namespace: "outer",
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
}
|
||||
|
||||
stepOne.EXPECT().
|
||||
Prepare(runCfg)
|
||||
stepTwo.EXPECT().
|
||||
Prepare(runCfg)
|
||||
|
||||
plan, err := NewPlan(cfg)
|
||||
suite.Require().Nil(err)
|
||||
suite.Require().Equal(1, len(plan.steps))
|
||||
|
||||
suite.Require().IsType(&run.Upgrade{}, plan.steps[0])
|
||||
step, _ := plan.steps[0].(*run.Upgrade)
|
||||
|
||||
suite.Equal("billboard_top_100", step.Chart)
|
||||
suite.Equal("post_malone_circles", step.Release)
|
||||
suite.Equal(cfg, plan.cfg)
|
||||
suite.Equal(runCfg, plan.runCfg)
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestNewPlanUpgradeFromDroneEvent() {
|
||||
cfg := Config{
|
||||
Chart: "billboard_top_100",
|
||||
Release: "lizzo_good_as_hell",
|
||||
}
|
||||
func (suite *PlanTestSuite) TestNewPlanAbortsOnError() {
|
||||
ctrl := gomock.NewController(suite.T())
|
||||
stepOne := NewMockStep(ctrl)
|
||||
stepTwo := NewMockStep(ctrl)
|
||||
|
||||
upgradeEvents := []string{"push", "tag", "deployment", "pull_request", "promote", "rollback"}
|
||||
for _, event := range upgradeEvents {
|
||||
cfg.DroneEvent = event
|
||||
plan, err := NewPlan(cfg)
|
||||
suite.Require().Nil(err)
|
||||
suite.Require().Equal(1, len(plan.steps), fmt.Sprintf("for event type '%s'", event))
|
||||
suite.IsType(&run.Upgrade{}, plan.steps[0], fmt.Sprintf("for event type '%s'", event))
|
||||
origHelp := help
|
||||
help = func(cfg Config) []Step {
|
||||
return []Step{stepOne, stepTwo}
|
||||
}
|
||||
}
|
||||
defer func() { help = origHelp }()
|
||||
|
||||
func (suite *PlanTestSuite) TestNewPlanHelpCommand() {
|
||||
cfg := Config{
|
||||
Command: "help",
|
||||
}
|
||||
|
||||
plan, err := NewPlan(cfg)
|
||||
suite.Require().Nil(err)
|
||||
suite.Equal(1, len(plan.steps))
|
||||
stepOne.EXPECT().
|
||||
Prepare(gomock.Any()).
|
||||
Return(fmt.Errorf("I'm starry Dave, aye, cat blew that"))
|
||||
|
||||
suite.Require().IsType(&run.Help{}, plan.steps[0])
|
||||
_, err := NewPlan(cfg)
|
||||
suite.Require().NotNil(err)
|
||||
suite.EqualError(err, "while preparing *helm.MockStep step: I'm starry Dave, aye, cat blew that")
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestUpgrade() {
|
||||
cfg := Config{
|
||||
KubeToken: "cXVlZXIgY2hhcmFjdGVyCg==",
|
||||
SkipTLSVerify: true,
|
||||
Certificate: "b2Ygd29rZW5lc3MK",
|
||||
APIServer: "123.456.78.9",
|
||||
ServiceAccount: "helmet",
|
||||
ChartVersion: "seventeen",
|
||||
Wait: true,
|
||||
ReuseValues: true,
|
||||
Timeout: "go sit in the corner",
|
||||
Chart: "billboard_top_100",
|
||||
Release: "post_malone_circles",
|
||||
Force: true,
|
||||
}
|
||||
|
||||
steps := upgrade(cfg)
|
||||
|
||||
suite.Equal(2, len(steps))
|
||||
|
||||
suite.Require().IsType(&run.InitKube{}, steps[0])
|
||||
init, _ := steps[0].(*run.InitKube)
|
||||
|
||||
var expected Step = &run.InitKube{
|
||||
SkipTLSVerify: cfg.SkipTLSVerify,
|
||||
Certificate: cfg.Certificate,
|
||||
APIServer: cfg.APIServer,
|
||||
ServiceAccount: cfg.ServiceAccount,
|
||||
Token: cfg.KubeToken,
|
||||
TemplateFile: kubeConfigTemplate,
|
||||
}
|
||||
|
||||
suite.Equal(expected, init)
|
||||
|
||||
suite.Require().IsType(&run.Upgrade{}, steps[1])
|
||||
upgrade, _ := steps[1].(*run.Upgrade)
|
||||
|
||||
expected = &run.Upgrade{
|
||||
Chart: cfg.Chart,
|
||||
Release: cfg.Release,
|
||||
ChartVersion: cfg.ChartVersion,
|
||||
Wait: cfg.Wait,
|
||||
ReuseValues: cfg.ReuseValues,
|
||||
Timeout: cfg.Timeout,
|
||||
Force: cfg.Force,
|
||||
}
|
||||
|
||||
suite.Equal(expected, upgrade)
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestDeterminePlanUpgradeCommand() {
|
||||
cfg := Config{
|
||||
Command: "upgrade",
|
||||
}
|
||||
stepsMaker := determineSteps(cfg)
|
||||
suite.Same(&upgrade, stepsMaker)
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestDeterminePlanUpgradeFromDroneEvent() {
|
||||
cfg := Config{}
|
||||
|
||||
upgradeEvents := []string{"push", "tag", "deployment", "pull_request", "promote", "rollback"}
|
||||
for _, event := range upgradeEvents {
|
||||
cfg.DroneEvent = event
|
||||
stepsMaker := determineSteps(cfg)
|
||||
suite.Same(&upgrade, stepsMaker, fmt.Sprintf("for event type '%s'", event))
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestDeterminePlanHelpCommand() {
|
||||
cfg := Config{
|
||||
Command: "help",
|
||||
}
|
||||
|
||||
stepsMaker := determineSteps(cfg)
|
||||
suite.Same(&help, stepsMaker)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user