Fix terminology (#35)

In a backward compatible manner, so that (I believe) we could move the discussion made in #25 forward.

Fixes #25
This commit is contained in:
KUOKA Yusuke 2018-03-03 00:14:43 +09:00 committed by GitHub
parent efdede1658
commit 0fc74ea771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 151 additions and 68 deletions

View File

@ -14,7 +14,7 @@ Helmfile is a declarative spec for deploying helm charts. It lets you...
To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, `helm` must be installed. To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, `helm` must be installed.
The default helmfile is `charts.yaml`: The default helmfile is `helmfile.yaml`:
``` ```
repositories: repositories:
@ -23,11 +23,11 @@ repositories:
context: kube-context # kube-context (--kube-context) context: kube-context # kube-context (--kube-context)
charts: releases:
# Published chart example # Published chart example
- name: vault # helm deployment name - name: vault # name of this release
namespace: vault # target namespace namespace: vault # target namespace
chart: roboll/vault-secret-manager # chart reference (repository) chart: roboll/vault-secret-manager # the chart being installed to create this release, referenced by `repository/chart` syntax
values: [ vault.yaml ] # value files (--values) values: [ vault.yaml ] # value files (--values)
set: # values (--set) set: # values (--set)
- name: address - name: address
@ -38,9 +38,9 @@ charts:
value: "{{ env \"PLATFORM_ID\" }}.my-domain.com" # Interpolate environment variable with a fixed string value: "{{ env \"PLATFORM_ID\" }}.my-domain.com" # Interpolate environment variable with a fixed string
# Local chart example # Local chart example
- name: grafana # helm deployment name - name: grafana # name of this release
namespace: another # target namespace namespace: another # target namespace
chart: ../my-charts/grafana # chart reference (relative path to manifest) chart: ../my-charts/grafana # the chart being installed to create this release, referenced by relative path to local chart
values: values:
- "../../my-values/grafana/values.yaml" # Values file (relative path to manifest) - "../../my-values/grafana/values.yaml" # Values file (relative path to manifest)
- "./values/{{ env \"PLATFORM_ENV\" }}/config.yaml" # Values file taken from path with environment variable. $PLATFORM_ENV must be set in the calling environment. - "./values/{{ env \"PLATFORM_ENV\" }}/config.yaml" # Values file taken from path with environment variable. $PLATFORM_ENV must be set in the calling environment.
@ -59,7 +59,7 @@ NAME:
helmfile - helmfile -
USAGE: USAGE:
main [global options] command [command options] [arguments...] helmfile [global options] command [command options] [arguments...]
COMMANDS: COMMANDS:
repos sync repositories from state file (helm repo add && helm repo update) repos sync repositories from state file (helm repo add && helm repo update)
@ -69,7 +69,7 @@ COMMANDS:
delete delete charts from state file (helm delete) delete delete charts from state file (helm delete)
GLOBAL OPTIONS: GLOBAL OPTIONS:
--file FILE, -f FILE load config from FILE (default: "charts.yaml") --file FILE, -f FILE load config from FILE (default: "helmfile.yaml")
--quiet, -q silence output --quiet, -q silence output
--kube-context value Set kubectl context. Uses current context by default --kube-context value Set kubectl context. Uses current context by default
--help, -h show help --help, -h show help

View File

@ -1,5 +1,5 @@
charts: charts:
# Published chart example # Published chart example
- name: grafana # helm deployment name - name: grafana # helm release name
namespace: grafana # target namespace namespace: grafana # target namespace
chart: stable/grafana # chart reference (repository) chart: stable/grafana # chart reference (repository)

View File

@ -46,7 +46,7 @@ func (helm *execer) UpdateRepo() error {
return err return err
} }
func (helm *execer) SyncChart(name, chart string, flags ...string) error { func (helm *execer) SyncRelease(name, chart string, flags ...string) error {
out, err := helm.exec(append([]string{"upgrade", "--install", name, chart}, flags...)...) out, err := helm.exec(append([]string{"upgrade", "--install", name, chart}, flags...)...)
if helm.writer != nil { if helm.writer != nil {
helm.writer.Write(out) helm.writer.Write(out)
@ -54,7 +54,7 @@ func (helm *execer) SyncChart(name, chart string, flags ...string) error {
return err return err
} }
func (helm *execer) DiffChart(name, chart string, flags ...string) error { func (helm *execer) DiffRelease(name, chart string, flags ...string) error {
out, err := helm.exec(append([]string{"diff", name, chart}, flags...)...) out, err := helm.exec(append([]string{"diff", name, chart}, flags...)...)
if helm.writer != nil { if helm.writer != nil {
helm.writer.Write(out) helm.writer.Write(out)
@ -62,7 +62,7 @@ func (helm *execer) DiffChart(name, chart string, flags ...string) error {
return err return err
} }
func (helm *execer) DeleteChart(name string) error { func (helm *execer) DeleteRelease(name string) error {
out, err := helm.exec("delete", "--purge", name) out, err := helm.exec("delete", "--purge", name)
if helm.writer != nil { if helm.writer != nil {
helm.writer.Write(out) helm.writer.Write(out)

View File

@ -6,7 +6,7 @@ type Interface interface {
AddRepo(name, repository string) error AddRepo(name, repository string) error
UpdateRepo() error UpdateRepo() error
SyncChart(name, chart string, flags ...string) error SyncRelease(name, chart string, flags ...string) error
DiffChart(name, chart string, flags ...string) error DiffRelease(name, chart string, flags ...string) error
DeleteChart(name string) error DeleteRelease(name string) error
} }

33
main.go
View File

@ -13,7 +13,8 @@ import (
) )
const ( const (
helmfile = "charts.yaml" DefaultHelmfile = "helmfile.yaml"
DeprecatedHelmfile = "charts.yaml"
) )
var Version string var Version string
@ -27,7 +28,7 @@ func main() {
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "file, f", Name: "file, f",
Value: helmfile, Value: DefaultHelmfile,
Usage: "load config from `FILE`", Usage: "load config from `FILE`",
}, },
cli.BoolFlag{ cli.BoolFlag{
@ -108,7 +109,7 @@ func main() {
values := c.StringSlice("values") values := c.StringSlice("values")
workers := c.Int("concurrency") workers := c.Int("concurrency")
if errs := state.SyncCharts(helm, values, workers); errs != nil && len(errs) > 0 { if errs := state.SyncReleases(helm, values, workers); errs != nil && len(errs) > 0 {
for _, err := range errs { for _, err := range errs {
fmt.Printf("err: %s\n", err.Error()) fmt.Printf("err: %s\n", err.Error())
} }
@ -157,7 +158,7 @@ func main() {
values := c.StringSlice("values") values := c.StringSlice("values")
if errs := state.DiffCharts(helm, values); errs != nil && len(errs) > 0 { if errs := state.DiffReleases(helm, values); errs != nil && len(errs) > 0 {
for _, err := range errs { for _, err := range errs {
fmt.Printf("err: %s\n", err.Error()) fmt.Printf("err: %s\n", err.Error())
} }
@ -196,7 +197,7 @@ func main() {
values := c.StringSlice("values") values := c.StringSlice("values")
workers := c.Int("concurrency") workers := c.Int("concurrency")
if errs := state.SyncCharts(helm, values, workers); errs != nil && len(errs) > 0 { if errs := state.SyncReleases(helm, values, workers); errs != nil && len(errs) > 0 {
for _, err := range errs { for _, err := range errs {
fmt.Printf("err: %s\n", err.Error()) fmt.Printf("err: %s\n", err.Error())
} }
@ -214,7 +215,7 @@ func main() {
return err return err
} }
if errs := state.DeleteCharts(helm); errs != nil && len(errs) > 0 { if errs := state.DeleteReleases(helm); errs != nil && len(errs) > 0 {
for _, err := range errs { for _, err := range errs {
fmt.Printf("err: %s\n", err.Error()) fmt.Printf("err: %s\n", err.Error())
} }
@ -238,28 +239,36 @@ func before(c *cli.Context) (*state.HelmState, helmexec.Interface, error) {
kubeContext := c.GlobalString("kube-context") kubeContext := c.GlobalString("kube-context")
namespace := c.GlobalString("namespace") namespace := c.GlobalString("namespace")
state, err := state.ReadFromFile(file) st, err := state.ReadFromFile(file)
if err != nil && strings.Contains(err.Error(), fmt.Sprintf("open %s:", DefaultHelmfile)) {
var fallbackErr error
st, fallbackErr = state.ReadFromFile(DeprecatedHelmfile)
if fallbackErr != nil {
return nil, nil, fmt.Errorf("failed to read %s and %s: %v", file, DeprecatedHelmfile, err)
}
log.Printf("warn: charts.yaml is loaded: charts.yaml is deprecated in favor of helmfile.yaml. See https://github.com/roboll/helmfile/issues/25 for more information")
}
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if state.Context != "" { if st.Context != "" {
if kubeContext != "" { if kubeContext != "" {
log.Printf("err: Cannot use option --kube-context and set attribute context.") log.Printf("err: Cannot use option --kube-context and set attribute context.")
os.Exit(1) os.Exit(1)
} }
kubeContext = state.Context kubeContext = st.Context
} }
if namespace != "" { if namespace != "" {
if state.Namespace != "" { if st.Namespace != "" {
log.Printf("err: Cannot use option --namespace and set attribute namespace.") log.Printf("err: Cannot use option --namespace and set attribute namespace.")
os.Exit(1) os.Exit(1)
} }
state.Namespace = namespace st.Namespace = namespace
} }
var writer io.Writer var writer io.Writer
if !quiet { if !quiet {
writer = os.Stdout writer = os.Stdout
} }
return state, helmexec.NewHelmExec(writer, kubeContext), nil return st, helmexec.NewHelmExec(writer, kubeContext), nil
} }

View File

@ -13,17 +13,19 @@ import (
"github.com/roboll/helmfile/helmexec" "github.com/roboll/helmfile/helmexec"
"bytes" "bytes"
yaml "gopkg.in/yaml.v1"
"path" "path"
"regexp" "regexp"
yaml "gopkg.in/yaml.v1"
) )
type HelmState struct { type HelmState struct {
BaseChartPath string BaseChartPath string
Context string `yaml:"context"` Context string `yaml:"context"`
DeprecatedReleases []ReleaseSpec `yaml:"charts"`
Namespace string `yaml:"namespace"` Namespace string `yaml:"namespace"`
Repositories []RepositorySpec `yaml:"repositories"` Repositories []RepositorySpec `yaml:"repositories"`
Charts []ChartSpec `yaml:"charts"` Releases []ReleaseSpec `yaml:"releases"`
} }
type RepositorySpec struct { type RepositorySpec struct {
@ -31,11 +33,13 @@ type RepositorySpec struct {
URL string `yaml:"url"` URL string `yaml:"url"`
} }
type ChartSpec struct { type ReleaseSpec struct {
// Chart is the name of the chart being installed to create this release
Chart string `yaml:"chart"` Chart string `yaml:"chart"`
Version string `yaml:"version"` Version string `yaml:"version"`
Verify bool `yaml:"verify"` Verify bool `yaml:"verify"`
// Name is the name of this release
Name string `yaml:"name"` Name string `yaml:"name"`
Namespace string `yaml:"namespace"` Namespace string `yaml:"namespace"`
Values []string `yaml:"values"` Values []string `yaml:"values"`
@ -55,13 +59,25 @@ func ReadFromFile(file string) (*HelmState, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return readFromYaml(content, file)
}
func readFromYaml(content []byte, file string) (*HelmState, error) {
var state HelmState var state HelmState
state.BaseChartPath, _ = filepath.Abs(path.Dir(file)) state.BaseChartPath, _ = filepath.Abs(path.Dir(file))
if err := yaml.Unmarshal(content, &state); err != nil { if err := yaml.Unmarshal(content, &state); err != nil {
return nil, err return nil, err
} }
if len(state.DeprecatedReleases) > 0 {
if len(state.Releases) > 0 {
return nil, fmt.Errorf("failed to parse %s: you can't specify both `charts` and `releases` sections", file)
}
state.Releases = state.DeprecatedReleases
state.DeprecatedReleases = []ReleaseSpec{}
}
return &state, nil return &state, nil
} }
@ -100,7 +116,7 @@ func renderTemplateString(s string) (string, error) {
return tplString.String(), nil return tplString.String(), nil
} }
func (state *HelmState) applyDefaultsTo(spec ChartSpec) ChartSpec { func (state *HelmState) applyDefaultsTo(spec ReleaseSpec) ReleaseSpec {
spec.Namespace = state.Namespace spec.Namespace = state.Namespace
return spec return spec
} }
@ -130,21 +146,21 @@ func (state *HelmState) SyncRepos(helm helmexec.Interface) []error {
return nil return nil
} }
func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []string, workerLimit int) []error { func (state *HelmState) SyncReleases(helm helmexec.Interface, additonalValues []string, workerLimit int) []error {
errs := []error{} errs := []error{}
jobQueue := make(chan ChartSpec) jobQueue := make(chan ReleaseSpec)
doneQueue := make(chan bool) doneQueue := make(chan bool)
errQueue := make(chan error) errQueue := make(chan error)
if workerLimit < 1 { if workerLimit < 1 {
workerLimit = len(state.Charts) workerLimit = len(state.Releases)
} }
for w := 1; w <= workerLimit; w++ { for w := 1; w <= workerLimit; w++ {
go func() { go func() {
for chart := range jobQueue { for release := range jobQueue {
chartWithDefaults := state.applyDefaultsTo(chart) releaseWithDefaults := state.applyDefaultsTo(release)
flags, flagsErr := flagsForChart(state.BaseChartPath, &chartWithDefaults) flags, flagsErr := flagsForRelease(state.BaseChartPath, &releaseWithDefaults)
if flagsErr != nil { if flagsErr != nil {
errQueue <- flagsErr errQueue <- flagsErr
doneQueue <- true doneQueue <- true
@ -166,7 +182,7 @@ func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []st
continue continue
} }
if err := helm.SyncChart(chart.Name, normalizeChart(state.BaseChartPath, chart.Chart), flags...); err != nil { if err := helm.SyncRelease(release.Name, normalizeChart(state.BaseChartPath, release.Chart), flags...); err != nil {
errQueue <- err errQueue <- err
} }
doneQueue <- true doneQueue <- true
@ -175,13 +191,13 @@ func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []st
} }
go func() { go func() {
for _, chart := range state.Charts { for _, release := range state.Releases {
jobQueue <- chart jobQueue <- release
} }
close(jobQueue) close(jobQueue)
}() }()
for i := 0; i < len(state.Charts); { for i := 0; i < len(state.Releases); {
select { select {
case err := <-errQueue: case err := <-errQueue:
errs = append(errs, err) errs = append(errs, err)
@ -197,16 +213,16 @@ func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []st
return nil return nil
} }
func (state *HelmState) DiffCharts(helm helmexec.Interface, additonalValues []string) []error { func (state *HelmState) DiffReleases(helm helmexec.Interface, additonalValues []string) []error {
var wg sync.WaitGroup var wg sync.WaitGroup
errs := []error{} errs := []error{}
for _, chart := range state.Charts { for _, release := range state.Releases {
wg.Add(1) wg.Add(1)
go func(wg *sync.WaitGroup, chart ChartSpec) { go func(wg *sync.WaitGroup, release ReleaseSpec) {
// Plugin command doesn't support explicit namespace // Plugin command doesn't support explicit namespace
chart.Namespace = "" release.Namespace = ""
flags, flagsErr := flagsForChart(state.BaseChartPath, &chart) flags, flagsErr := flagsForRelease(state.BaseChartPath, &release)
if flagsErr != nil { if flagsErr != nil {
errs = append(errs, flagsErr) errs = append(errs, flagsErr)
} }
@ -218,12 +234,12 @@ func (state *HelmState) DiffCharts(helm helmexec.Interface, additonalValues []st
flags = append(flags, "--values", valfile) flags = append(flags, "--values", valfile)
} }
if len(errs) == 0 { if len(errs) == 0 {
if err := helm.DiffChart(chart.Name, normalizeChart(state.BaseChartPath, chart.Chart), flags...); err != nil { if err := helm.DiffRelease(release.Name, normalizeChart(state.BaseChartPath, release.Chart), flags...); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
} }
wg.Done() wg.Done()
}(&wg, chart) }(&wg, release)
} }
wg.Wait() wg.Wait()
@ -234,18 +250,18 @@ func (state *HelmState) DiffCharts(helm helmexec.Interface, additonalValues []st
return nil return nil
} }
func (state *HelmState) DeleteCharts(helm helmexec.Interface) []error { func (state *HelmState) DeleteReleases(helm helmexec.Interface) []error {
var wg sync.WaitGroup var wg sync.WaitGroup
errs := []error{} errs := []error{}
for _, chart := range state.Charts { for _, release := range state.Releases {
wg.Add(1) wg.Add(1)
go func(wg *sync.WaitGroup, chart ChartSpec) { go func(wg *sync.WaitGroup, release ReleaseSpec) {
if err := helm.DeleteChart(chart.Name); err != nil { if err := helm.DeleteRelease(release.Name); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
wg.Done() wg.Done()
}(&wg, chart) }(&wg, release)
} }
wg.Wait() wg.Wait()
@ -268,18 +284,18 @@ func normalizeChart(basePath, chart string) string {
return filepath.Join(basePath, chart) return filepath.Join(basePath, chart)
} }
func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) { func flagsForRelease(basePath string, release *ReleaseSpec) ([]string, error) {
flags := []string{} flags := []string{}
if chart.Version != "" { if release.Version != "" {
flags = append(flags, "--version", chart.Version) flags = append(flags, "--version", release.Version)
} }
if chart.Verify { if release.Verify {
flags = append(flags, "--verify") flags = append(flags, "--verify")
} }
if chart.Namespace != "" { if release.Namespace != "" {
flags = append(flags, "--namespace", chart.Namespace) flags = append(flags, "--namespace", release.Namespace)
} }
for _, value := range chart.Values { for _, value := range release.Values {
valfile := filepath.Join(basePath, value) valfile := filepath.Join(basePath, value)
valfileRendered, err := renderTemplateString(valfile) valfileRendered, err := renderTemplateString(valfile)
if err != nil { if err != nil {
@ -287,9 +303,9 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) {
} }
flags = append(flags, "--values", valfileRendered) flags = append(flags, "--values", valfileRendered)
} }
if len(chart.SetValues) > 0 { if len(release.SetValues) > 0 {
val := []string{} val := []string{}
for _, set := range chart.SetValues { for _, set := range release.SetValues {
renderedValue, err := renderTemplateString(set.Value) renderedValue, err := renderTemplateString(set.Value)
if err != nil { if err != nil {
return nil, err return nil, err
@ -303,10 +319,10 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) {
* START 'env' section for backwards compatibility * START 'env' section for backwards compatibility
***********/ ***********/
// The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality // The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality
if len(chart.EnvValues) > 0 { if len(release.EnvValues) > 0 {
val := []string{} val := []string{}
envValErrs := []string{} envValErrs := []string{}
for _, set := range chart.EnvValues { for _, set := range release.EnvValues {
value, isSet := os.LookupEnv(set.Value) value, isSet := os.LookupEnv(set.Value)
if isSet { if isSet {
val = append(val, fmt.Sprintf("%s=%s", set.Name, value)) val = append(val, fmt.Sprintf("%s=%s", set.Name, value))

58
state/state_test.go Normal file
View File

@ -0,0 +1,58 @@
package state
import (
"testing"
)
func TestReadFromYaml(t *testing.T) {
yamlFile := "example/path/to/yaml/file"
yamlContent := []byte(`releases:
- name: myrelease
chart: mychart
`)
state, err := readFromYaml(yamlContent, yamlFile)
if err != nil {
t.Errorf("unxpected error: %v", err)
}
if state.Releases[0].Name != "myrelease" {
t.Errorf("unexpected release name: expected=myrelease actual=%s", state.Releases[0].Name)
}
if state.Releases[0].Chart != "mychart" {
t.Errorf("unexpected chart name: expected=mychart actual=%s", state.Releases[0].Chart)
}
}
func TestReadFromYaml_DeprecatedReleaseReferences(t *testing.T) {
yamlFile := "example/path/to/yaml/file"
yamlContent := []byte(`charts:
- name: myrelease
chart: mychart
`)
state, err := readFromYaml(yamlContent, yamlFile)
if err != nil {
t.Errorf("unxpected error: %v", err)
}
if state.Releases[0].Name != "myrelease" {
t.Errorf("unexpected release name: expected=myrelease actual=%s", state.Releases[0].Name)
}
if state.Releases[0].Chart != "mychart" {
t.Errorf("unexpected chart name: expected=mychart actual=%s", state.Releases[0].Chart)
}
}
func TestReadFromYaml_ConflictingReleasesConfig(t *testing.T) {
yamlFile := "example/path/to/yaml/file"
yamlContent := []byte(`charts:
- name: myrelease1
chart: mychart1
releases:
- name: myrelease2
chart: mychart2
`)
_, err := readFromYaml(yamlContent, yamlFile)
if err == nil {
t.Error("expected error")
}
}