Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding secret processing for helmfile via helm-secrets plugin #53

Merged
merged 3 commits into from
Mar 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ releases:
namespace: vault # target namespace
chart: roboll/vault-secret-manager # the chart being installed to create this release, referenced by `repository/chart` syntax
values: [ vault.yaml ] # value files (--values)
secrets:
- vault_secret.yaml # will attempt to decrypt it using helm-secrets plugin
set: # values (--set)
- name: address
value: https://vault.example.com
Expand Down Expand Up @@ -83,10 +85,17 @@ GLOBAL OPTIONS:
The `helmfile diff` sub-command executes the [helm-diff](https://github.com/databus23/helm-diff) plugin across all of
the charts/releases defined in the manifest.

Under the covers Helmfile is simply using the `helm diff` plugin, so that needs to be installed prior. For Helm 2.3+
To supply the diff functionality Helmfile needs the `helm diff` plugin installed. For Helm 2.3+
you should be able to simply execute `helm plugin install https://github.com/databus23/helm-diff`. For more details
please look at their [documentation](https://github.com/databus23/helm-diff#helm-diff-plugin).

### secrets

The `secrets` parameter in a `helmfile.yaml` causes the [helm-secrets](https://github.com/futuresimple/helm-secrets) plugin to be executed to decrypt the file.

To supply the secret functionality Helmfile needs the `helm secrets` plugin installed. For Helm 2.3+
you should be able to simply execute `helm plugin install https://github.com/futuresimple/helm-secrets
`.

## Paths Overview
Using manifest files in conjunction with command line argument can be a bit confusing.
Expand Down
8 changes: 8 additions & 0 deletions helmexec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ func (helm *execer) SyncRelease(name, chart string, flags ...string) error {
return err
}

func (helm *execer) DecryptSecret(name string) (string, error) {
out, err := helm.exec(append([]string{"secrets", "dec", name})...)
if helm.writer != nil {
helm.writer.Write(out)
}
return name + ".dec", err
}

func (helm *execer) DiffRelease(name, chart string, flags ...string) error {
out, err := helm.exec(append([]string{"diff", name, chart}, flags...)...)
if helm.writer != nil {
Expand Down
2 changes: 2 additions & 0 deletions helmexec/helmexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ type Interface interface {
SyncRelease(name, chart string, flags ...string) error
DiffRelease(name, chart string, flags ...string) error
DeleteRelease(name string) error

DecryptSecret(name string) (string, error)
}
75 changes: 40 additions & 35 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"io"
"log"
"os"
"os/signal"
"strings"
"syscall"

"github.com/roboll/helmfile/helmexec"
"github.com/roboll/helmfile/state"
Expand Down Expand Up @@ -67,13 +69,8 @@ func main() {
helm.SetExtraArgs(strings.Split(args, " ")...)
}

if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
for _, err := range errs {
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
}
return nil
errs := state.SyncRepos(helm)
return clean(state, errs)
},
},
{
Expand Down Expand Up @@ -109,13 +106,8 @@ func main() {
values := c.StringSlice("values")
workers := c.Int("concurrency")

if errs := state.SyncReleases(helm, values, workers); errs != nil && len(errs) > 0 {
for _, err := range errs {
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
}
return nil
errs := state.SyncReleases(helm, values, workers)
return clean(state, errs)
},
},
{
Expand Down Expand Up @@ -158,13 +150,8 @@ func main() {

values := c.StringSlice("values")

if errs := state.DiffReleases(helm, values); errs != nil && len(errs) > 0 {
for _, err := range errs {
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
}
return nil
errs := state.DiffReleases(helm, values)
return clean(state, errs)
},
},
{
Expand Down Expand Up @@ -197,13 +184,8 @@ func main() {
values := c.StringSlice("values")
workers := c.Int("concurrency")

if errs := state.SyncReleases(helm, values, workers); errs != nil && len(errs) > 0 {
for _, err := range errs {
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
}
return nil
errs := state.SyncReleases(helm, values, workers)
return clean(state, errs)
},
},
{
Expand All @@ -215,13 +197,8 @@ func main() {
return err
}

if errs := state.DeleteReleases(helm); errs != nil && len(errs) > 0 {
for _, err := range errs {
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
}
return nil
errs := state.DeleteReleases(helm)
return clean(state, errs)
},
},
}
Expand Down Expand Up @@ -267,5 +244,33 @@ func before(c *cli.Context) (*state.HelmState, helmexec.Interface, error) {
writer = os.Stdout
}

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigs

errs := []error{fmt.Errorf("Recived [%s] to shutdown ", sig)}
clean(st, errs)
}()

return st, helmexec.NewHelmExec(writer, kubeContext), nil
}

func clean(state *state.HelmState, errs []error) error {
if errs == nil {
errs = []error{}
}

cleanErrs := state.Clean()
if cleanErrs != nil {
errs = append(errs, cleanErrs...)
}

if errs != nil && len(errs) > 0 {
for _, err := range errs {
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
}
return nil
}
51 changes: 46 additions & 5 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ type ReleaseSpec struct {
Name string `yaml:"name"`
Namespace string `yaml:"namespace"`
Values []string `yaml:"values"`
Secrets []string `yaml:"secrets"`
SetValues []SetValue `yaml:"set"`

// The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality
EnvValues []SetValue `yaml:"env"`

// generatedValues are values that need cleaned up on exit
generatedValues []string
}

type SetValue struct {
Expand Down Expand Up @@ -157,7 +161,7 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additonalValues []
go func() {
for release := range jobQueue {
releaseWithDefaults := state.applyDefaultsTo(release)
flags, flagsErr := flagsForRelease(state.BaseChartPath, &releaseWithDefaults)
flags, flagsErr := flagsForRelease(helm, state.BaseChartPath, &releaseWithDefaults)
if flagsErr != nil {
errQueue <- flagsErr
doneQueue <- true
Expand Down Expand Up @@ -214,15 +218,17 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additonalValues []
var wg sync.WaitGroup
errs := []error{}

for _, release := range state.Releases {
for i := 0; i < len(state.Releases); i++ {
release := &state.Releases[i]
wg.Add(1)
go func(wg *sync.WaitGroup, release ReleaseSpec) {
go func(wg *sync.WaitGroup, release *ReleaseSpec) {
// Plugin command doesn't support explicit namespace
release.Namespace = ""
flags, flagsErr := flagsForRelease(state.BaseChartPath, &release)
flags, flagsErr := flagsForRelease(helm, state.BaseChartPath, release)
if flagsErr != nil {
errs = append(errs, flagsErr)
}

for _, value := range additonalValues {
valfile, err := filepath.Abs(value)
if err != nil {
Expand Down Expand Up @@ -269,6 +275,26 @@ func (state *HelmState) DeleteReleases(helm helmexec.Interface) []error {
return nil
}

// Clean will remove any generated secrets
func (state *HelmState) Clean() []error {
errs := []error{}

for _, release := range state.Releases {
for _, value := range release.generatedValues {
err := os.Remove(value)
if err != nil {
errs = append(errs, err)
}
}
}

if len(errs) != 0 {
return errs
}

return nil
}

// normalizeChart allows for the distinction between a file path reference and repository references.
// - Any single (or double character) followed by a `/` will be considered a local file reference and
// be constructed relative to the `base path`.
Expand All @@ -281,7 +307,7 @@ func normalizeChart(basePath, chart string) string {
return filepath.Join(basePath, chart)
}

func flagsForRelease(basePath string, release *ReleaseSpec) ([]string, error) {
func flagsForRelease(helm helmexec.Interface, basePath string, release *ReleaseSpec) ([]string, error) {
flags := []string{}
if release.Version != "" {
flags = append(flags, "--version", release.Version)
Expand All @@ -300,6 +326,21 @@ func flagsForRelease(basePath string, release *ReleaseSpec) ([]string, error) {
}
flags = append(flags, "--values", valfileRendered)
}
for _, value := range release.Secrets {
valfile := filepath.Join(basePath, value)
path, err := renderTemplateString(valfile)
if err != nil {
return nil, err
}

valfileRendered, err := helm.DecryptSecret(path)
if err != nil {
return nil, err
}

release.generatedValues = append(release.generatedValues, valfileRendered)
flags = append(flags, "--values", valfileRendered)
}
if len(release.SetValues) > 0 {
val := []string{}
for _, set := range release.SetValues {
Expand Down