feat: `helmfile template` (#284)
`helmfile template` runs `helm template` over releases within the helmfiles, and provide you a stream of generated yaml documents of Kubernetes resources via stdout. Resolves #283
This commit is contained in:
parent
8a90e5320c
commit
93c5d4c219
|
|
@ -6,9 +6,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -143,6 +144,12 @@ func (helm *execer) DecryptSecret(name string) (string, error) {
|
||||||
return tmpFile.Name(), err
|
return tmpFile.Name(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (helm *execer) TemplateRelease(chart string, flags ...string) error {
|
||||||
|
out, err := helm.exec(append([]string{"template", chart}, flags...)...)
|
||||||
|
helm.write(out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (helm *execer) DiffRelease(name, chart string, flags ...string) error {
|
func (helm *execer) DiffRelease(name, chart string, flags ...string) error {
|
||||||
helm.logger.Infof("Comparing %v %v", name, chart)
|
helm.logger.Infof("Comparing %v %v", name, chart)
|
||||||
out, err := helm.exec(append([]string{"diff", "upgrade", "--allow-unreleased", name, chart}, flags...)...)
|
out, err := helm.exec(append([]string{"diff", "upgrade", "--allow-unreleased", name, chart}, flags...)...)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ type Interface interface {
|
||||||
UpdateDeps(chart string) error
|
UpdateDeps(chart string) error
|
||||||
SyncRelease(name, chart string, flags ...string) error
|
SyncRelease(name, chart string, flags ...string) error
|
||||||
DiffRelease(name, chart string, flags ...string) error
|
DiffRelease(name, chart string, flags ...string) error
|
||||||
|
TemplateRelease(chart string, flags ...string) error
|
||||||
Fetch(chart string, flags ...string) error
|
Fetch(chart string, flags ...string) error
|
||||||
Lint(chart string, flags ...string) error
|
Lint(chart string, flags ...string) error
|
||||||
ReleaseStatus(name string) error
|
ReleaseStatus(name string) error
|
||||||
|
|
|
||||||
48
main.go
48
main.go
|
|
@ -11,6 +11,8 @@ import (
|
||||||
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/roboll/helmfile/args"
|
"github.com/roboll/helmfile/args"
|
||||||
"github.com/roboll/helmfile/environment"
|
"github.com/roboll/helmfile/environment"
|
||||||
"github.com/roboll/helmfile/helmexec"
|
"github.com/roboll/helmfile/helmexec"
|
||||||
|
|
@ -19,7 +21,6 @@ import (
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"io/ioutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -197,6 +198,31 @@ func main() {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "template",
|
||||||
|
Usage: "template releases from state file against env (helm template)",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "args",
|
||||||
|
Value: "",
|
||||||
|
Usage: "pass args to helm template",
|
||||||
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "values",
|
||||||
|
Usage: "additional value files to be merged into the command",
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "concurrency",
|
||||||
|
Value: 0,
|
||||||
|
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
|
||||||
|
return executeTemplateCommand(c, state, helm)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "lint",
|
Name: "lint",
|
||||||
Usage: "lint charts from state file (helm lint)",
|
Usage: "lint charts from state file (helm lint)",
|
||||||
|
|
@ -449,6 +475,26 @@ func executeSyncCommand(c *cli.Context, state *state.HelmState, helm helmexec.In
|
||||||
return state.SyncReleases(helm, values, workers)
|
return state.SyncReleases(helm, values, workers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executeTemplateCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface) []error {
|
||||||
|
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.GlobalString("helm-binary") != "" {
|
||||||
|
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
||||||
|
}
|
||||||
|
|
||||||
|
args := args.GetArgs(c.String("args"), state)
|
||||||
|
values := c.StringSlice("values")
|
||||||
|
workers := c.Int("concurrency")
|
||||||
|
|
||||||
|
return state.TemplateReleases(helm, values, workers, args)
|
||||||
|
}
|
||||||
|
|
||||||
func executeDiffCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface, detailedExitCode, suppressSecrets bool) []error {
|
func executeDiffCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface, detailedExitCode, suppressSecrets bool) []error {
|
||||||
args := args.GetArgs(c.String("args"), state)
|
args := args.GetArgs(c.String("args"), state)
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
|
|
||||||
117
state/state.go
117
state/state.go
|
|
@ -3,7 +3,6 @@ package state
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/roboll/helmfile/helmexec"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
@ -12,6 +11,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/roboll/helmfile/helmexec"
|
||||||
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/roboll/helmfile/environment"
|
"github.com/roboll/helmfile/environment"
|
||||||
|
|
@ -270,6 +271,111 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TemplateReleases wrapper for executing helm template on the releases
|
||||||
|
func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, args []string) []error {
|
||||||
|
var wgRelease sync.WaitGroup
|
||||||
|
var wgError sync.WaitGroup
|
||||||
|
errs := []error{}
|
||||||
|
jobQueue := make(chan *ReleaseSpec, len(state.Releases))
|
||||||
|
errQueue := make(chan error)
|
||||||
|
|
||||||
|
if workerLimit < 1 {
|
||||||
|
workerLimit = len(state.Releases)
|
||||||
|
}
|
||||||
|
|
||||||
|
wgRelease.Add(len(state.Releases))
|
||||||
|
|
||||||
|
// Create tmp directory and bail immediately if it fails
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
for w := 1; w <= workerLimit; w++ {
|
||||||
|
go func() {
|
||||||
|
for release := range jobQueue {
|
||||||
|
errs := []error{}
|
||||||
|
flags, err := state.flagsForTemplate(helm, release)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
for _, value := range additionalValues {
|
||||||
|
valfile, err := filepath.Abs(value)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(valfile); os.IsNotExist(err) {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
flags = append(flags, "--values", valfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
chartPath := ""
|
||||||
|
if pathExists(normalizeChart(state.basePath, release.Chart)) {
|
||||||
|
chartPath = normalizeChart(state.basePath, release.Chart)
|
||||||
|
} else {
|
||||||
|
fetchFlags := []string{}
|
||||||
|
if release.Version != "" {
|
||||||
|
chartPath = path.Join(dir, release.Name, release.Version, release.Chart)
|
||||||
|
fetchFlags = append(fetchFlags, "--version", release.Version)
|
||||||
|
} else {
|
||||||
|
chartPath = path.Join(dir, release.Name, "latest", release.Chart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only fetch chart if it is not already fetched
|
||||||
|
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
|
||||||
|
fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath)
|
||||||
|
if err := helm.Fetch(release.Chart, fetchFlags...); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chartPath = path.Join(chartPath, chartNameWithoutRepository(release.Chart))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
helm.SetExtraArgs(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) == 0 {
|
||||||
|
if err := helm.TemplateRelease(chartPath, flags...); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, err := range errs {
|
||||||
|
errQueue <- err
|
||||||
|
}
|
||||||
|
wgRelease.Done()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wgError.Add(1)
|
||||||
|
go func() {
|
||||||
|
for err := range errQueue {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
wgError.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < len(state.Releases); i++ {
|
||||||
|
jobQueue <- &state.Releases[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
close(jobQueue)
|
||||||
|
wgRelease.Wait()
|
||||||
|
|
||||||
|
close(errQueue)
|
||||||
|
wgError.Wait()
|
||||||
|
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DiffReleases wrapper for executing helm diff on the releases
|
// DiffReleases wrapper for executing helm diff on the releases
|
||||||
func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool) []error {
|
func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool) []error {
|
||||||
var wgRelease sync.WaitGroup
|
var wgRelease sync.WaitGroup
|
||||||
|
|
@ -691,6 +797,15 @@ func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, release *Releas
|
||||||
return append(flags, common...), nil
|
return append(flags, common...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (state *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
||||||
|
flags := []string{}
|
||||||
|
common, err := state.namespaceAndValuesFlags(helm, release)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(flags, common...), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (state *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
func (state *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
||||||
flags := []string{}
|
flags := []string{}
|
||||||
if release.Version != "" {
|
if release.Version != "" {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/roboll/helmfile/helmexec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/roboll/helmfile/helmexec"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = helmexec.NewLogger(os.Stdout, "warn")
|
var logger = helmexec.NewLogger(os.Stdout, "warn")
|
||||||
|
|
@ -537,7 +538,9 @@ func (helm *mockHelmExec) Fetch(chart string, flags ...string) error {
|
||||||
func (helm *mockHelmExec) Lint(chart string, flags ...string) error {
|
func (helm *mockHelmExec) Lint(chart string, flags ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func TestHelmState_SyncRepos(t *testing.T) {
|
func TestHelmState_SyncRepos(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue