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:
parent
4cbb4922fb
commit
13c663e906
|
@ -2,6 +2,7 @@ FROM alpine/helm
|
||||||
MAINTAINER Erin Call <erin@liffft.com>
|
MAINTAINER Erin Call <erin@liffft.com>
|
||||||
|
|
||||||
COPY build/drone-helm /bin/drone-helm
|
COPY build/drone-helm /bin/drone-helm
|
||||||
|
COPY kubeconfig /root/.kube/config.tpl
|
||||||
|
|
||||||
LABEL description="Helm 3 plugin for Drone 3"
|
LABEL description="Helm 3 plugin for Drone 3"
|
||||||
LABEL base="alpine/helm"
|
LABEL base="alpine/helm"
|
||||||
|
|
|
@ -7,7 +7,7 @@ TODO:
|
||||||
* [x] Make `cmd/drone-helm/main.go` actually invoke `helm`
|
* [x] Make `cmd/drone-helm/main.go` actually invoke `helm`
|
||||||
* [x] Make `golint` part of the build process (and make it pass)
|
* [x] Make `golint` part of the build process (and make it pass)
|
||||||
* [x] Implement debug output
|
* [x] Implement debug output
|
||||||
* [ ] Flesh out `helm upgrade` until it's capable of working
|
* [x] Flesh out `helm upgrade` until it's capable of working
|
||||||
* [ ] Implement config settings for `upgrade`
|
* [ ] Implement config settings for `upgrade`
|
||||||
* [ ] Implement `helm lint`
|
* [ ] Implement `helm lint`
|
||||||
* [ ] Implement `helm delete`
|
* [ ] Implement `helm delete`
|
||||||
|
|
|
@ -25,7 +25,7 @@ type Config struct {
|
||||||
StringValues string `split_words:"true"`
|
StringValues string `split_words:"true"`
|
||||||
ValuesFiles []string `split_words:"true"`
|
ValuesFiles []string `split_words:"true"`
|
||||||
Namespace string ``
|
Namespace string ``
|
||||||
Token string `envconfig:"KUBERNETES_TOKEN"`
|
KubeToken string `envconfig:"KUBERNETES_TOKEN"`
|
||||||
SkipTLSVerify bool `envconfig:"SKIP_TLS_VERIFY"`
|
SkipTLSVerify bool `envconfig:"SKIP_TLS_VERIFY"`
|
||||||
Certificate string `envconfig:"KUBERNETES_CERTIFICATE"`
|
Certificate string `envconfig:"KUBERNETES_CERTIFICATE"`
|
||||||
APIServer string `envconfig:"API_SERVER"`
|
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
|
package helm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pelotech/drone-helm3/internal/run"
|
"github.com/pelotech/drone-helm3/internal/run"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const kubeConfigTemplate = "/root/.kube/config.tpl"
|
||||||
|
|
||||||
// A Step is one step in the plan.
|
// A Step is one step in the plan.
|
||||||
type Step interface {
|
type Step interface {
|
||||||
Prepare(run.Config) error
|
Prepare(run.Config) error
|
||||||
Execute() error
|
Execute(run.Config) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Plan is a series of steps to perform.
|
// A Plan is a series of steps to perform.
|
||||||
type Plan struct {
|
type Plan struct {
|
||||||
steps []Step
|
steps []Step
|
||||||
|
cfg Config
|
||||||
|
runCfg run.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlan makes a plan for running a helm operation.
|
// NewPlan makes a plan for running a helm operation.
|
||||||
func NewPlan(cfg Config) (*Plan, error) {
|
func NewPlan(cfg Config) (*Plan, error) {
|
||||||
runCfg := run.Config{
|
p := Plan{
|
||||||
|
cfg: cfg,
|
||||||
|
runCfg: run.Config{
|
||||||
Debug: cfg.Debug,
|
Debug: cfg.Debug,
|
||||||
KubeConfig: cfg.KubeConfig,
|
KubeConfig: cfg.KubeConfig,
|
||||||
Values: cfg.Values,
|
Values: cfg.Values,
|
||||||
StringValues: cfg.StringValues,
|
StringValues: cfg.StringValues,
|
||||||
ValuesFiles: cfg.ValuesFiles,
|
ValuesFiles: cfg.ValuesFiles,
|
||||||
Namespace: cfg.Namespace,
|
Namespace: cfg.Namespace,
|
||||||
Token: cfg.Token,
|
|
||||||
SkipTLSVerify: cfg.SkipTLSVerify,
|
|
||||||
Certificate: cfg.Certificate,
|
|
||||||
APIServer: cfg.APIServer,
|
|
||||||
ServiceAccount: cfg.ServiceAccount,
|
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p := Plan{}
|
p.steps = (*determineSteps(cfg))(cfg)
|
||||||
switch cfg.Command {
|
|
||||||
case "upgrade":
|
for i, step := range p.steps {
|
||||||
steps, err := upgrade(cfg, runCfg)
|
if cfg.Debug {
|
||||||
if err != nil {
|
fmt.Fprintf(os.Stderr, "calling %T.Prepare (step %d)\n", step, i)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
p.steps = steps
|
|
||||||
case "delete":
|
if err := step.Prepare(p.runCfg); err != nil {
|
||||||
return nil, errors.New("not implemented")
|
err = fmt.Errorf("while preparing %T step: %w", step, err)
|
||||||
case "lint":
|
|
||||||
return nil, errors.New("not implemented")
|
|
||||||
case "help":
|
|
||||||
steps, err := help(cfg, runCfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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
|
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
|
// Execute runs each step in the plan, aborting and reporting on error
|
||||||
func (p *Plan) Execute() error {
|
func (p *Plan) Execute() error {
|
||||||
for _, step := range p.steps {
|
for i, step := range p.steps {
|
||||||
if err := step.Execute(); err != nil {
|
if p.cfg.Debug {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func upgrade(cfg Config, runCfg run.Config) ([]Step, error) {
|
var upgrade = func(cfg Config) []Step {
|
||||||
steps := make([]Step, 0)
|
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,
|
Chart: cfg.Chart,
|
||||||
Release: cfg.Release,
|
Release: cfg.Release,
|
||||||
ChartVersion: cfg.ChartVersion,
|
ChartVersion: cfg.ChartVersion,
|
||||||
|
@ -91,23 +110,12 @@ func upgrade(cfg Config, runCfg run.Config) ([]Step, error) {
|
||||||
ReuseValues: cfg.ReuseValues,
|
ReuseValues: cfg.ReuseValues,
|
||||||
Timeout: cfg.Timeout,
|
Timeout: cfg.Timeout,
|
||||||
Force: cfg.Force,
|
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{}
|
help := &run.Help{}
|
||||||
|
return []Step{help}
|
||||||
if err := help.Prepare(runCfg); err != nil {
|
|
||||||
err = fmt.Errorf("while preparing help step: %w", err)
|
|
||||||
return []Step{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []Step{help}, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ package helm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pelotech/drone-helm3/internal/run"
|
"github.com/pelotech/drone-helm3/internal/run"
|
||||||
|
@ -16,48 +18,147 @@ func TestPlanTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(PlanTestSuite))
|
suite.Run(t, new(PlanTestSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PlanTestSuite) TestNewPlanUpgradeCommand() {
|
func (suite *PlanTestSuite) TestNewPlan() {
|
||||||
cfg := Config{
|
ctrl := gomock.NewController(suite.T())
|
||||||
Command: "upgrade",
|
stepOne := NewMockStep(ctrl)
|
||||||
Chart: "billboard_top_100",
|
stepTwo := NewMockStep(ctrl)
|
||||||
Release: "post_malone_circles",
|
|
||||||
|
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)
|
plan, err := NewPlan(cfg)
|
||||||
suite.Require().Nil(err)
|
suite.Require().Nil(err)
|
||||||
suite.Require().Equal(1, len(plan.steps))
|
suite.Equal(cfg, plan.cfg)
|
||||||
|
suite.Equal(runCfg, plan.runCfg)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PlanTestSuite) TestNewPlanUpgradeFromDroneEvent() {
|
func (suite *PlanTestSuite) TestNewPlanAbortsOnError() {
|
||||||
cfg := Config{
|
ctrl := gomock.NewController(suite.T())
|
||||||
Chart: "billboard_top_100",
|
stepOne := NewMockStep(ctrl)
|
||||||
Release: "lizzo_good_as_hell",
|
stepTwo := NewMockStep(ctrl)
|
||||||
}
|
|
||||||
|
|
||||||
upgradeEvents := []string{"push", "tag", "deployment", "pull_request", "promote", "rollback"}
|
origHelp := help
|
||||||
for _, event := range upgradeEvents {
|
help = func(cfg Config) []Step {
|
||||||
cfg.DroneEvent = event
|
return []Step{stepOne, stepTwo}
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
defer func() { help = origHelp }()
|
||||||
|
|
||||||
func (suite *PlanTestSuite) TestNewPlanHelpCommand() {
|
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
Command: "help",
|
Command: "help",
|
||||||
}
|
}
|
||||||
|
|
||||||
plan, err := NewPlan(cfg)
|
stepOne.EXPECT().
|
||||||
suite.Require().Nil(err)
|
Prepare(gomock.Any()).
|
||||||
suite.Equal(1, len(plan.steps))
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,6 @@ type Config struct {
|
||||||
StringValues string
|
StringValues string
|
||||||
ValuesFiles []string
|
ValuesFiles []string
|
||||||
Namespace string
|
Namespace string
|
||||||
Token string
|
|
||||||
SkipTLSVerify bool
|
|
||||||
Certificate string
|
|
||||||
APIServer string
|
|
||||||
ServiceAccount string
|
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ type Help struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute executes the `helm help` command.
|
// Execute executes the `helm help` command.
|
||||||
func (h *Help) Execute() error {
|
func (h *Help) Execute(_ Config) error {
|
||||||
return h.cmd.Run()
|
return h.cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (suite *HelpTestSuite) TestPrepare() {
|
||||||
h := Help{}
|
h := Help{}
|
||||||
err := h.Prepare(cfg)
|
err := h.Prepare(cfg)
|
||||||
suite.Require().Nil(err)
|
suite.Require().Nil(err)
|
||||||
h.Execute()
|
h.Execute(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *HelpTestSuite) TestPrepareDebugFlag() {
|
func (suite *HelpTestSuite) TestPrepareDebugFlag() {
|
||||||
|
|
93
internal/run/initkube.go
Normal file
93
internal/run/initkube.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package run
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
template *template.Template
|
||||||
|
configFile io.WriteCloser
|
||||||
|
values kubeValues
|
||||||
|
}
|
||||||
|
|
||||||
|
type kubeValues struct {
|
||||||
|
SkipTLSVerify bool
|
||||||
|
Certificate string
|
||||||
|
APIServer string
|
||||||
|
Namespace string
|
||||||
|
ServiceAccount string
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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", cfg.KubeConfig)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if i.APIServer == "" {
|
||||||
|
return errors.New("an API Server is needed to deploy")
|
||||||
|
}
|
||||||
|
if i.Token == "" {
|
||||||
|
return errors.New("token is needed to deploy")
|
||||||
|
}
|
||||||
|
if i.Certificate == "" && !i.SkipTLSVerify {
|
||||||
|
return errors.New("certificate is needed to deploy")
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.ServiceAccount == "" {
|
||||||
|
i.ServiceAccount = "helm"
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Debug {
|
||||||
|
fmt.Fprintf(cfg.Stderr, "loading kubeconfig template from %s\n", i.TemplateFile)
|
||||||
|
}
|
||||||
|
i.template, err = template.ParseFiles(i.TemplateFile)
|
||||||
|
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(cfg.KubeConfig); err != nil {
|
||||||
|
// non-nil err here isn't an actual error state; the kubeconfig just doesn't exist
|
||||||
|
fmt.Fprint(cfg.Stderr, "creating ")
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(cfg.Stderr, "truncating ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cfg.Stderr, "kubeconfig file at %s\n", cfg.KubeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.configFile, err = os.Create(cfg.KubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not open kubeconfig file for writing: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
184
internal/run/initkube_test.go
Normal file
184
internal/run/initkube_test.go
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
package run
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
// "github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InitKubeTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitKubeTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(InitKubeTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *InitKubeTestSuite) TestPrepareExecute() {
|
||||||
|
templateFile, err := tempfile("kubeconfig********.yml.tpl", `
|
||||||
|
certificate: {{ .Certificate }}
|
||||||
|
namespace: {{ .Namespace }}
|
||||||
|
`)
|
||||||
|
defer os.Remove(templateFile.Name())
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
configFile, err := tempfile("kubeconfig********.yml", "")
|
||||||
|
defer os.Remove(configFile.Name())
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
init := InitKube{
|
||||||
|
APIServer: "Sysadmin",
|
||||||
|
Certificate: "CCNA",
|
||||||
|
Token: "Aspire virtual currency",
|
||||||
|
TemplateFile: templateFile.Name(),
|
||||||
|
}
|
||||||
|
cfg := Config{
|
||||||
|
Namespace: "Cisco",
|
||||||
|
KubeConfig: configFile.Name(),
|
||||||
|
}
|
||||||
|
err = init.Prepare(cfg)
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
suite.IsType(&template.Template{}, init.template)
|
||||||
|
suite.NotNil(init.configFile)
|
||||||
|
|
||||||
|
err = init.Execute(cfg)
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
conf, err := ioutil.ReadFile(configFile.Name())
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
want := `
|
||||||
|
certificate: CCNA
|
||||||
|
namespace: Cisco
|
||||||
|
`
|
||||||
|
suite.Equal(want, string(conf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *InitKubeTestSuite) TestPrepareParseError() {
|
||||||
|
templateFile, err := tempfile("kubeconfig********.yml.tpl", `{{ NonexistentFunction }}`)
|
||||||
|
defer os.Remove(templateFile.Name())
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
init := InitKube{
|
||||||
|
APIServer: "Sysadmin",
|
||||||
|
Certificate: "CCNA",
|
||||||
|
Token: "Aspire virtual currency",
|
||||||
|
TemplateFile: templateFile.Name(),
|
||||||
|
}
|
||||||
|
err = init.Prepare(Config{})
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
err := init.Prepare(Config{})
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Regexp("could not load kubeconfig .* no such file or directory", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := Config{
|
||||||
|
KubeConfig: "/usr/foreign/exclude/kubeprofig",
|
||||||
|
}
|
||||||
|
err = init.Prepare(cfg)
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Regexp("could not open .* for writing: .* no such file or directory", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *InitKubeTestSuite) TestPrepareRequiredConfig() {
|
||||||
|
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)
|
||||||
|
|
||||||
|
// initial config with all required fields present
|
||||||
|
init := InitKube{
|
||||||
|
APIServer: "Sysadmin",
|
||||||
|
Certificate: "CCNA",
|
||||||
|
Token: "Aspire virtual currency",
|
||||||
|
TemplateFile: templateFile.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := Config{
|
||||||
|
KubeConfig: configFile.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.NoError(init.Prepare(cfg)) // consistency check; we should be starting in a happy state
|
||||||
|
|
||||||
|
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.Token = "Aspire virtual currency"
|
||||||
|
init.Certificate = ""
|
||||||
|
suite.Error(init.Prepare(cfg), "Certificate should be required.")
|
||||||
|
|
||||||
|
init.SkipTLSVerify = true
|
||||||
|
suite.NoError(init.Prepare(cfg), "Certificate should not be required if SkipTLSVerify is true")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *InitKubeTestSuite) TestPrepareDefaultsServiceAccount() {
|
||||||
|
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)
|
||||||
|
|
||||||
|
init := InitKube{
|
||||||
|
APIServer: "Sysadmin",
|
||||||
|
Certificate: "CCNA",
|
||||||
|
Token: "Aspire virtual currency",
|
||||||
|
TemplateFile: templateFile.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := Config{
|
||||||
|
KubeConfig: configFile.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
init.Prepare(cfg)
|
||||||
|
suite.Equal("helm", init.ServiceAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tempfile(name, contents string) (*os.File, error) {
|
||||||
|
file, err := ioutil.TempFile("", name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = file.Write([]byte(contents))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
|
@ -19,13 +19,19 @@ type Upgrade struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute executes the `helm upgrade` command.
|
// Execute executes the `helm upgrade` command.
|
||||||
func (u *Upgrade) Execute() error {
|
func (u *Upgrade) Execute(_ Config) error {
|
||||||
return u.cmd.Run()
|
return u.cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare gets the Upgrade ready to execute.
|
// Prepare gets the Upgrade ready to execute.
|
||||||
func (u *Upgrade) Prepare(cfg Config) error {
|
func (u *Upgrade) Prepare(cfg Config) error {
|
||||||
args := []string{"upgrade", "--install", u.Release, u.Chart}
|
args := []string{"--kubeconfig", cfg.KubeConfig}
|
||||||
|
|
||||||
|
if cfg.Namespace != "" {
|
||||||
|
args = append(args, "--namespace", cfg.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "upgrade", "--install", u.Release, u.Chart)
|
||||||
|
|
||||||
if cfg.Debug {
|
if cfg.Debug {
|
||||||
args = append([]string{"--debug"}, args...)
|
args = append([]string{"--debug"}, args...)
|
||||||
|
|
|
@ -41,7 +41,8 @@ func (suite *UpgradeTestSuite) TestPrepare() {
|
||||||
|
|
||||||
command = func(path string, args ...string) cmd {
|
command = func(path string, args ...string) cmd {
|
||||||
suite.Equal(helmBin, path)
|
suite.Equal(helmBin, path)
|
||||||
suite.Equal([]string{"upgrade", "--install", "jonas_brothers_only_human", "at40"}, args)
|
suite.Equal([]string{"--kubeconfig", "/root/.kube/config", "upgrade", "--install",
|
||||||
|
"jonas_brothers_only_human", "at40"}, args)
|
||||||
|
|
||||||
return suite.mockCmd
|
return suite.mockCmd
|
||||||
}
|
}
|
||||||
|
@ -54,9 +55,44 @@ func (suite *UpgradeTestSuite) TestPrepare() {
|
||||||
Run().
|
Run().
|
||||||
Times(1)
|
Times(1)
|
||||||
|
|
||||||
err := u.Prepare(Config{})
|
cfg := Config{
|
||||||
|
KubeConfig: "/root/.kube/config",
|
||||||
|
}
|
||||||
|
err := u.Prepare(cfg)
|
||||||
suite.Require().Nil(err)
|
suite.Require().Nil(err)
|
||||||
u.Execute()
|
u.Execute(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *UpgradeTestSuite) TestPrepareNamespaceFlag() {
|
||||||
|
defer suite.ctrl.Finish()
|
||||||
|
|
||||||
|
u := Upgrade{
|
||||||
|
Chart: "at40",
|
||||||
|
Release: "shaed_trampoline",
|
||||||
|
}
|
||||||
|
|
||||||
|
command = func(path string, args ...string) cmd {
|
||||||
|
suite.Equal(helmBin, path)
|
||||||
|
suite.Equal([]string{"--kubeconfig", "/root/.kube/config", "--namespace", "melt", "upgrade",
|
||||||
|
"--install", "shaed_trampoline", "at40"}, args)
|
||||||
|
|
||||||
|
return suite.mockCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.mockCmd.EXPECT().
|
||||||
|
Stdout(gomock.Any())
|
||||||
|
suite.mockCmd.EXPECT().
|
||||||
|
Stderr(gomock.Any())
|
||||||
|
suite.mockCmd.EXPECT().
|
||||||
|
Run()
|
||||||
|
|
||||||
|
cfg := Config{
|
||||||
|
Namespace: "melt",
|
||||||
|
KubeConfig: "/root/.kube/config",
|
||||||
|
}
|
||||||
|
err := u.Prepare(cfg)
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
u.Execute(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UpgradeTestSuite) TestPrepareDebugFlag() {
|
func (suite *UpgradeTestSuite) TestPrepareDebugFlag() {
|
||||||
|
@ -69,6 +105,7 @@ func (suite *UpgradeTestSuite) TestPrepareDebugFlag() {
|
||||||
stderr := strings.Builder{}
|
stderr := strings.Builder{}
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
Debug: true,
|
Debug: true,
|
||||||
|
KubeConfig: "/root/.kube/config",
|
||||||
Stdout: &stdout,
|
Stdout: &stdout,
|
||||||
Stderr: &stderr,
|
Stderr: &stderr,
|
||||||
}
|
}
|
||||||
|
@ -88,7 +125,8 @@ func (suite *UpgradeTestSuite) TestPrepareDebugFlag() {
|
||||||
|
|
||||||
u.Prepare(cfg)
|
u.Prepare(cfg)
|
||||||
|
|
||||||
want := fmt.Sprintf("Generated command: '%s --debug upgrade --install lewis_capaldi_someone_you_loved at40'\n", helmBin)
|
want := fmt.Sprintf("Generated command: '%s --debug --kubeconfig /root/.kube/config upgrade "+
|
||||||
|
"--install lewis_capaldi_someone_you_loved at40'\n", helmBin)
|
||||||
suite.Equal(want, stderr.String())
|
suite.Equal(want, stderr.String())
|
||||||
suite.Equal("", stdout.String())
|
suite.Equal("", stdout.String())
|
||||||
}
|
}
|
||||||
|
|
39
kubeconfig
Normal file
39
kubeconfig
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
{{- if eq .SkipTLSVerify true }}
|
||||||
|
insecure-skip-tls-verify: true
|
||||||
|
{{- else }}
|
||||||
|
certificate-authority-data: {{ .Certificate }}
|
||||||
|
{{- end}}
|
||||||
|
server: {{ .APIServer }}
|
||||||
|
name: helm
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: helm
|
||||||
|
{{- if .Namespace }}
|
||||||
|
namespace: {{ .Namespace }}
|
||||||
|
{{- end }}
|
||||||
|
user: {{ .ServiceAccount }}
|
||||||
|
name: helm
|
||||||
|
current-context: "helm"
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: {{ .ServiceAccount }}
|
||||||
|
user:
|
||||||
|
{{- if .Token }}
|
||||||
|
token: {{ .Token }}
|
||||||
|
{{- else if .EKSCluster }}
|
||||||
|
exec:
|
||||||
|
apiVersion: client.authentication.k8s.io/v1alpha1
|
||||||
|
command: aws-iam-authenticator
|
||||||
|
args:
|
||||||
|
- "token"
|
||||||
|
- "-i"
|
||||||
|
- "{{ .EKSCluster }}"
|
||||||
|
{{- if .EKSRoleARN }}
|
||||||
|
- "-r"
|
||||||
|
- "{{ .EKSRoleARN }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
Loading…
Reference in a new issue