Skip to content

Commit

Permalink
feat: add --output-dir on template command (roboll#693)
Browse files Browse the repository at this point in the history
It generates templates in a subdirectory named "stateFileName-stateFileHash-releaseName"
  • Loading branch information
olivierboudet authored and mumoshu committed Jul 11, 2019
1 parent 63b5040 commit 2f9f520
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 16 deletions.
8 changes: 8 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ func main() {
Name: "values",
Usage: "additional value files to be merged into the command",
},
cli.StringFlag{
Name: "output-dir",
Usage: "output directory to pass to helm template (helm template --output-dir)",
},
cli.IntFlag{
Name: "concurrency",
Value: 0,
Expand Down Expand Up @@ -440,6 +444,10 @@ func (c configImpl) Args() string {
return c.c.String("args")
}

func (c configImpl) OutputDir() string {
return c.c.String("output-dir")
}

func (c configImpl) Concurrency() int {
return c.c.Int("concurrency")
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type App struct {
chdir func(string) error

remote *remote.Remote

helmExecer helmexec.Interface
}

func New(conf ConfigProvider) *App {
Expand All @@ -59,6 +61,9 @@ func New(conf ConfigProvider) *App {
FileOrDir: conf.FileOrDir(),
ValuesFiles: conf.ValuesFiles(),
Set: conf.Set(),
helmExecer: helmexec.New(conf.Logger(), conf.KubeContext(), &helmexec.ShellRunner{
Logger: conf.Logger(),
}),
})
}

Expand Down Expand Up @@ -274,7 +279,7 @@ func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*sta

ctx := context{a, st}

helm := helmexec.New(a.Logger, a.KubeContext)
helm := a.helmExecer

if err != nil {
switch stateLoadErr := err.(type) {
Expand Down
158 changes: 158 additions & 0 deletions pkg/app/app_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package app

import (
"bytes"
"fmt"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/state"
"github.com/roboll/helmfile/pkg/testhelper"
"os"
"path/filepath"
"reflect"
"regexp"
"testing"

"go.uber.org/zap"
"gotest.tools/env"
)

Expand Down Expand Up @@ -1738,3 +1741,158 @@ services:
}
}
}

type configImpl struct {
}

func (c configImpl) Values() []string {
return []string{}
}

func (c configImpl) Args() string {
return "some args"
}

func (c configImpl) SkipDeps() bool {
return true
}

func (c configImpl) OutputDir() string {
return "output/subdir"
}

func (c configImpl) Concurrency() int {
return 1
}

// Mocking the command-line runner

type mockRunner struct {
output []byte
err error
}

func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
return []byte{}, nil
}

func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface {
execer := helmexec.New(logger, kubeContext, &mockRunner{})
return execer
}

// mocking helmexec.Interface

type listKey struct {
filter string
flags string
}

type mockHelmExec struct {
templated []mockTemplates

updateDepsCallbacks map[string]func(string) error
}

type mockTemplates struct {
flags []string
}

func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error {
helm.templated = append(helm.templated, mockTemplates{flags: flags})
return nil
}

func (helm *mockHelmExec) UpdateDeps(chart string) error {
return nil
}

func (helm *mockHelmExec) BuildDeps(chart string) error {
return nil
}

func (helm *mockHelmExec) SetExtraArgs(args ...string) {
return
}
func (helm *mockHelmExec) SetHelmBinary(bin string) {
return
}
func (helm *mockHelmExec) AddRepo(name, repository, certfile, keyfile, username, password string) error {
return nil
}
func (helm *mockHelmExec) UpdateRepo() error {
return nil
}
func (helm *mockHelmExec) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error {
return nil
}
func (helm *mockHelmExec) DiffRelease(context helmexec.HelmContext, name, chart string, flags ...string) error {
return nil
}
func (helm *mockHelmExec) ReleaseStatus(context helmexec.HelmContext, release string, flags ...string) error {
return nil
}
func (helm *mockHelmExec) DeleteRelease(context helmexec.HelmContext, name string, flags ...string) error {
return nil
}
func (helm *mockHelmExec) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) {
return "", nil
}
func (helm *mockHelmExec) DecryptSecret(context helmexec.HelmContext, name string, flags ...string) (string, error) {
return "", nil
}
func (helm *mockHelmExec) TestRelease(context helmexec.HelmContext, name string, flags ...string) error {
return nil
}
func (helm *mockHelmExec) Fetch(chart string, flags ...string) error {
return nil
}
func (helm *mockHelmExec) Lint(chart string, flags ...string) error {
return nil
}

func TestTemplate_SingleStateFile(t *testing.T) {
files := map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: myrelease1
chart: mychart1
- name: myrelease2
chart: mychart1
`,
}

var helm = &mockHelmExec{}
var wantReleases = []mockTemplates{
{[]string{"--name", "myrelease1", "--output-dir", "output/subdir/helmfile-[a-z0-9]{8}-myrelease1"}},
{[]string{"--name", "myrelease2", "--output-dir", "output/subdir/helmfile-[a-z0-9]{8}-myrelease2"}},
}

var buffer bytes.Buffer
logger := helmexec.NewLogger(&buffer, "debug")

app := appWithFs(&App{
glob: filepath.Glob,
abs: filepath.Abs,
KubeContext: "default",
Env: "default",
Logger: logger,
helmExecer: helm,
}, files)
app.Template(configImpl{})

for i := range wantReleases {
for j := range wantReleases[i].flags {
if j == 3 {
matched, _ := regexp.Match(wantReleases[i].flags[j], []byte(helm.templated[i].flags[j]))
if !matched {
t.Errorf("HelmState.TemplateReleases() = [%v], want %v", helm.templated[i].flags[j], wantReleases[i].flags[j])
}
} else if wantReleases[i].flags[j] != helm.templated[i].flags[j] {
t.Errorf("HelmState.TemplateReleases() = [%v], want %v", helm.templated[i].flags[j], wantReleases[i].flags[j])
}
}

}

}
1 change: 1 addition & 0 deletions pkg/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type TemplateConfigProvider interface {

Values() []string
SkipDeps() bool
OutputDir() string

concurrencyConfig
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func (r *Run) Template(c TemplateConfigProvider) []error {
}

args := argparser.GetArgs(c.Args(), state)
return state.TemplateReleases(helm, c.Values(), args, c.Concurrency())
return state.TemplateReleases(helm, c.OutputDir(), c.Values(), args, c.Concurrency())
}

func (r *Run) Test(c TestConfigProvider) []error {
Expand Down
6 changes: 2 additions & 4 deletions pkg/helmexec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,12 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
}

// New for running helm commands
func New(logger *zap.SugaredLogger, kubeContext string) *execer {
func New(logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
return &execer{
helmBinary: command,
logger: logger,
kubeContext: kubeContext,
runner: &ShellRunner{
logger: logger,
},
runner: runner,
}
}

Expand Down
32 changes: 27 additions & 5 deletions pkg/helmexec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
}

func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
execer := New(logger, kubeContext)
execer.runner = &mockRunner{}
execer := New(logger, kubeContext, &mockRunner{})
return execer
}

Expand All @@ -34,7 +33,9 @@ func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
func TestNewHelmExec(t *testing.T) {
buffer := bytes.NewBufferString("something")
logger := NewLogger(buffer, "debug")
helm := New(logger, "dev")
helm := New(logger, "dev", &ShellRunner{
Logger: logger,
})
if helm.kubeContext != "dev" {
t.Error("helmexec.New() - kubeContext")
}
Expand All @@ -47,7 +48,11 @@ func TestNewHelmExec(t *testing.T) {
}

func Test_SetExtraArgs(t *testing.T) {
helm := New(NewLogger(os.Stdout, "info"), "dev")
buffer := bytes.NewBufferString("something")
logger := NewLogger(buffer, "debug")
helm := New(NewLogger(os.Stdout, "info"), "dev", &ShellRunner{
Logger: logger,
})
helm.SetExtraArgs()
if len(helm.extra) != 0 {
t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field")
Expand All @@ -63,7 +68,11 @@ func Test_SetExtraArgs(t *testing.T) {
}

func Test_SetHelmBinary(t *testing.T) {
helm := New(NewLogger(os.Stdout, "info"), "dev")
buffer := bytes.NewBufferString("something")
logger := NewLogger(buffer, "debug")
helm := New(NewLogger(os.Stdout, "info"), "dev", &ShellRunner{
Logger: logger,
})
if helm.helmBinary != "helm" {
t.Error("helmexec.command - default command is not helm")
}
Expand Down Expand Up @@ -478,3 +487,16 @@ func Test_mergeEnv(t *testing.T) {
t.Errorf("mergeEnv()\nactual = %v\nexpect = %v", actual, expected)
}
}

func Test_Template(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.TemplateRelease("path/to/chart", "--values", "file.yml")
expected := `exec: helm template path/to/chart --values file.yml --kube-context dev
exec: helm template path/to/chart --values file.yml --kube-context dev:
`
if buffer.String() != expected {
t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}
4 changes: 2 additions & 2 deletions pkg/helmexec/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ type Runner interface {
type ShellRunner struct {
Dir string

logger *zap.SugaredLogger
Logger *zap.SugaredLogger
}

// Execute a shell command
func (shell ShellRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
preparedCmd := exec.Command(cmd, args...)
preparedCmd.Dir = shell.Dir
preparedCmd.Env = mergeEnv(os.Environ(), env)
return combinedOutput(preparedCmd, shell.logger)
return combinedOutput(preparedCmd, shell.Logger)
}

func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
Expand Down
4 changes: 3 additions & 1 deletion pkg/state/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ func (st *HelmState) loadEnvValues(name string, ctxEnv *environment.Environment,
}

if len(envSpec.Secrets) > 0 {
helm := helmexec.New(st.logger, "")
helm := helmexec.New(st.logger, "", &helmexec.ShellRunner{
Logger: st.logger,
})

var envSecretFiles []string
for _, urlOrPath := range envSpec.Secrets {
Expand Down
Loading

0 comments on commit 2f9f520

Please sign in to comment.