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.
The default helmfile is `charts.yaml`:
The default helmfile is `helmfile.yaml`:
```
repositories:
@ -23,11 +23,11 @@ repositories:
context: kube-context # kube-context (--kube-context)
charts:
releases:
# Published chart example
- name: vault # helm deployment name
- name: vault # name of this release
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)
set: # values (--set)
- name: address
@ -38,9 +38,9 @@ charts:
value: "{{ env \"PLATFORM_ID\" }}.my-domain.com" # Interpolate environment variable with a fixed string
# Local chart example
- name: grafana # helm deployment name
- name: grafana # name of this release
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:
- "../../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.
@ -59,7 +59,7 @@ NAME:
helmfile -
USAGE:
main [global options] command [command options] [arguments...]
helmfile [global options] command [command options] [arguments...]
COMMANDS:
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)
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
--kube-context value Set kubectl context. Uses current context by default
--help, -h show help

View File

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

View File

@ -46,7 +46,7 @@ func (helm *execer) UpdateRepo() error {
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...)...)
if helm.writer != nil {
helm.writer.Write(out)
@ -54,7 +54,7 @@ func (helm *execer) SyncChart(name, chart string, flags ...string) error {
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...)...)
if helm.writer != nil {
helm.writer.Write(out)
@ -62,7 +62,7 @@ func (helm *execer) DiffChart(name, chart string, flags ...string) error {
return err
}
func (helm *execer) DeleteChart(name string) error {
func (helm *execer) DeleteRelease(name string) error {
out, err := helm.exec("delete", "--purge", name)
if helm.writer != nil {
helm.writer.Write(out)

View File

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

33
main.go
View File

@ -13,7 +13,8 @@ import (
)
const (
helmfile = "charts.yaml"
DefaultHelmfile = "helmfile.yaml"
DeprecatedHelmfile = "charts.yaml"
)
var Version string
@ -27,7 +28,7 @@ func main() {
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "file, f",
Value: helmfile,
Value: DefaultHelmfile,
Usage: "load config from `FILE`",
},
cli.BoolFlag{
@ -108,7 +109,7 @@ func main() {
values := c.StringSlice("values")
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 {
fmt.Printf("err: %s\n", err.Error())
}
@ -157,7 +158,7 @@ func main() {
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 {
fmt.Printf("err: %s\n", err.Error())
}
@ -196,7 +197,7 @@ func main() {
values := c.StringSlice("values")
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 {
fmt.Printf("err: %s\n", err.Error())
}
@ -214,7 +215,7 @@ func main() {
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 {
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")
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 {
return nil, nil, err
}
if state.Context != "" {
if st.Context != "" {
if kubeContext != "" {
log.Printf("err: Cannot use option --kube-context and set attribute context.")
os.Exit(1)
}
kubeContext = state.Context
kubeContext = st.Context
}
if namespace != "" {
if state.Namespace != "" {
if st.Namespace != "" {
log.Printf("err: Cannot use option --namespace and set attribute namespace.")
os.Exit(1)
}
state.Namespace = namespace
st.Namespace = namespace
}
var writer io.Writer
if !quiet {
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"
"bytes"
yaml "gopkg.in/yaml.v1"
"path"
"regexp"
yaml "gopkg.in/yaml.v1"
)
type HelmState struct {
BaseChartPath string
Context string `yaml:"context"`
Namespace string `yaml:"namespace"`
Repositories []RepositorySpec `yaml:"repositories"`
Charts []ChartSpec `yaml:"charts"`
BaseChartPath string
Context string `yaml:"context"`
DeprecatedReleases []ReleaseSpec `yaml:"charts"`
Namespace string `yaml:"namespace"`
Repositories []RepositorySpec `yaml:"repositories"`
Releases []ReleaseSpec `yaml:"releases"`
}
type RepositorySpec struct {
@ -31,11 +33,13 @@ type RepositorySpec struct {
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"`
Version string `yaml:"version"`
Verify bool `yaml:"verify"`
// Name is the name of this release
Name string `yaml:"name"`
Namespace string `yaml:"namespace"`
Values []string `yaml:"values"`
@ -55,13 +59,25 @@ func ReadFromFile(file string) (*HelmState, error) {
if err != nil {
return nil, err
}
return readFromYaml(content, file)
}
func readFromYaml(content []byte, file string) (*HelmState, error) {
var state HelmState
state.BaseChartPath, _ = filepath.Abs(path.Dir(file))
if err := yaml.Unmarshal(content, &state); err != nil {
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
}
@ -100,7 +116,7 @@ func renderTemplateString(s string) (string, error) {
return tplString.String(), nil
}
func (state *HelmState) applyDefaultsTo(spec ChartSpec) ChartSpec {
func (state *HelmState) applyDefaultsTo(spec ReleaseSpec) ReleaseSpec {
spec.Namespace = state.Namespace
return spec
}
@ -130,21 +146,21 @@ func (state *HelmState) SyncRepos(helm helmexec.Interface) []error {
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{}
jobQueue := make(chan ChartSpec)
jobQueue := make(chan ReleaseSpec)
doneQueue := make(chan bool)
errQueue := make(chan error)
if workerLimit < 1 {
workerLimit = len(state.Charts)
workerLimit = len(state.Releases)
}
for w := 1; w <= workerLimit; w++ {
go func() {
for chart := range jobQueue {
chartWithDefaults := state.applyDefaultsTo(chart)
flags, flagsErr := flagsForChart(state.BaseChartPath, &chartWithDefaults)
for release := range jobQueue {
releaseWithDefaults := state.applyDefaultsTo(release)
flags, flagsErr := flagsForRelease(state.BaseChartPath, &releaseWithDefaults)
if flagsErr != nil {
errQueue <- flagsErr
doneQueue <- true
@ -166,7 +182,7 @@ func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []st
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
}
doneQueue <- true
@ -175,13 +191,13 @@ func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []st
}
go func() {
for _, chart := range state.Charts {
jobQueue <- chart
for _, release := range state.Releases {
jobQueue <- release
}
close(jobQueue)
}()
for i := 0; i < len(state.Charts); {
for i := 0; i < len(state.Releases); {
select {
case err := <-errQueue:
errs = append(errs, err)
@ -197,16 +213,16 @@ func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []st
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
errs := []error{}
for _, chart := range state.Charts {
for _, release := range state.Releases {
wg.Add(1)
go func(wg *sync.WaitGroup, chart ChartSpec) {
go func(wg *sync.WaitGroup, release ReleaseSpec) {
// Plugin command doesn't support explicit namespace
chart.Namespace = ""
flags, flagsErr := flagsForChart(state.BaseChartPath, &chart)
release.Namespace = ""
flags, flagsErr := flagsForRelease(state.BaseChartPath, &release)
if flagsErr != nil {
errs = append(errs, flagsErr)
}
@ -218,12 +234,12 @@ func (state *HelmState) DiffCharts(helm helmexec.Interface, additonalValues []st
flags = append(flags, "--values", valfile)
}
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)
}
}
wg.Done()
}(&wg, chart)
}(&wg, release)
}
wg.Wait()
@ -234,18 +250,18 @@ func (state *HelmState) DiffCharts(helm helmexec.Interface, additonalValues []st
return nil
}
func (state *HelmState) DeleteCharts(helm helmexec.Interface) []error {
func (state *HelmState) DeleteReleases(helm helmexec.Interface) []error {
var wg sync.WaitGroup
errs := []error{}
for _, chart := range state.Charts {
for _, release := range state.Releases {
wg.Add(1)
go func(wg *sync.WaitGroup, chart ChartSpec) {
if err := helm.DeleteChart(chart.Name); err != nil {
go func(wg *sync.WaitGroup, release ReleaseSpec) {
if err := helm.DeleteRelease(release.Name); err != nil {
errs = append(errs, err)
}
wg.Done()
}(&wg, chart)
}(&wg, release)
}
wg.Wait()
@ -268,18 +284,18 @@ func normalizeChart(basePath, chart string) string {
return filepath.Join(basePath, chart)
}
func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) {
func flagsForRelease(basePath string, release *ReleaseSpec) ([]string, error) {
flags := []string{}
if chart.Version != "" {
flags = append(flags, "--version", chart.Version)
if release.Version != "" {
flags = append(flags, "--version", release.Version)
}
if chart.Verify {
if release.Verify {
flags = append(flags, "--verify")
}
if chart.Namespace != "" {
flags = append(flags, "--namespace", chart.Namespace)
if release.Namespace != "" {
flags = append(flags, "--namespace", release.Namespace)
}
for _, value := range chart.Values {
for _, value := range release.Values {
valfile := filepath.Join(basePath, value)
valfileRendered, err := renderTemplateString(valfile)
if err != nil {
@ -287,9 +303,9 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) {
}
flags = append(flags, "--values", valfileRendered)
}
if len(chart.SetValues) > 0 {
if len(release.SetValues) > 0 {
val := []string{}
for _, set := range chart.SetValues {
for _, set := range release.SetValues {
renderedValue, err := renderTemplateString(set.Value)
if err != nil {
return nil, err
@ -303,10 +319,10 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) {
* START 'env' section for backwards compatibility
***********/
// 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{}
envValErrs := []string{}
for _, set := range chart.EnvValues {
for _, set := range release.EnvValues {
value, isSet := os.LookupEnv(set.Value)
if isSet {
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")
}
}