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:
Erin Call 2019-12-12 10:20:11 -08:00
parent 4cbb4922fb
commit 13c663e906
No known key found for this signature in database
GPG key ID: 4071FF6C15B8DAD1
14 changed files with 646 additions and 119 deletions

View file

@ -2,6 +2,7 @@ FROM alpine/helm
MAINTAINER Erin Call <erin@liffft.com>
COPY build/drone-helm /bin/drone-helm
COPY kubeconfig /root/.kube/config.tpl
LABEL description="Helm 3 plugin for Drone 3"
LABEL base="alpine/helm"

View file

@ -7,7 +7,7 @@ TODO:
* [x] Make `cmd/drone-helm/main.go` actually invoke `helm`
* [x] Make `golint` part of the build process (and make it pass)
* [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 `helm lint`
* [ ] Implement `helm delete`

View file

@ -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"`

View 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)
}

View file

@ -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}
}

View file

@ -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)
}

View file

@ -6,17 +6,12 @@ import (
// Config contains configuration applicable to all helm commands
type Config struct {
Debug bool
KubeConfig string
Values string
StringValues string
ValuesFiles []string
Namespace string
Token string
SkipTLSVerify bool
Certificate string
APIServer string
ServiceAccount string
Stdout io.Writer
Stderr io.Writer
Debug bool
KubeConfig string
Values string
StringValues string
ValuesFiles []string
Namespace string
Stdout io.Writer
Stderr io.Writer
}

View file

@ -10,7 +10,7 @@ type Help struct {
}
// Execute executes the `helm help` command.
func (h *Help) Execute() error {
func (h *Help) Execute(_ Config) error {
return h.cmd.Run()
}

View file

@ -50,7 +50,7 @@ func (suite *HelpTestSuite) TestPrepare() {
h := Help{}
err := h.Prepare(cfg)
suite.Require().Nil(err)
h.Execute()
h.Execute(cfg)
}
func (suite *HelpTestSuite) TestPrepareDebugFlag() {

93
internal/run/initkube.go Normal file
View 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
}

View 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
}

View file

@ -19,13 +19,19 @@ type Upgrade struct {
}
// Execute executes the `helm upgrade` command.
func (u *Upgrade) Execute() error {
func (u *Upgrade) Execute(_ Config) error {
return u.cmd.Run()
}
// Prepare gets the Upgrade ready to execute.
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 {
args = append([]string{"--debug"}, args...)

View file

@ -41,7 +41,8 @@ func (suite *UpgradeTestSuite) TestPrepare() {
command = func(path string, args ...string) cmd {
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
}
@ -54,9 +55,44 @@ func (suite *UpgradeTestSuite) TestPrepare() {
Run().
Times(1)
err := u.Prepare(Config{})
cfg := Config{
KubeConfig: "/root/.kube/config",
}
err := u.Prepare(cfg)
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() {
@ -68,9 +104,10 @@ func (suite *UpgradeTestSuite) TestPrepareDebugFlag() {
stdout := strings.Builder{}
stderr := strings.Builder{}
cfg := Config{
Debug: true,
Stdout: &stdout,
Stderr: &stderr,
Debug: true,
KubeConfig: "/root/.kube/config",
Stdout: &stdout,
Stderr: &stderr,
}
command = func(path string, args ...string) cmd {
@ -88,7 +125,8 @@ func (suite *UpgradeTestSuite) TestPrepareDebugFlag() {
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("", stdout.String())
}

39
kubeconfig Normal file
View 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 }}