Merge pull request #77 from cmeury/sync-update-deps
Update dependencies of local charts when running 'sync'
This commit is contained in:
commit
1af8743bb6
|
|
@ -101,7 +101,7 @@ COMMANDS:
|
|||
repos sync repositories from state file (helm repo add && helm repo update)
|
||||
charts sync charts from state file (helm upgrade --install)
|
||||
diff diff charts from state file against env (helm diff)
|
||||
sync sync all resources from state file (repos && charts)
|
||||
sync sync all resources from state file (repos, charts and local chart deps)
|
||||
delete delete charts from state file (helm delete)
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
|
|
@ -119,9 +119,10 @@ GLOBAL OPTIONS:
|
|||
|
||||
### sync
|
||||
|
||||
The `helmfile sync` sub-command sync your cluster state as desired in your `helmfile`. The default helmfile is `helmfile.yaml`, but any yaml file can be passed by specifying a `--file path/to/your/yaml/file` flag.
|
||||
The `helmfile sync` sub-command sync your cluster state as described in your `helmfile`. The default helmfile is `helmfile.yaml`, but any yaml file can be passed by specifying a `--file path/to/your/yaml/file` flag.
|
||||
|
||||
Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values.
|
||||
Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the
|
||||
dependencies of any referenced local charts.
|
||||
|
||||
### diff
|
||||
|
||||
|
|
|
|||
|
|
@ -36,49 +36,43 @@ func (helm *execer) AddRepo(name, repository, certfile, keyfile string) error {
|
|||
args = append(args, "--cert-file", certfile, "--key-file", keyfile)
|
||||
}
|
||||
out, err := helm.exec(args...)
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
helm.write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) UpdateRepo() error {
|
||||
out, err := helm.exec("repo", "update")
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
helm.write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) UpdateDeps(chart string) error {
|
||||
out, err := helm.exec("dependency", "update", chart)
|
||||
helm.write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
helm.write(out)
|
||||
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)
|
||||
}
|
||||
helm.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 {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
helm.write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) DeleteRelease(name string) error {
|
||||
out, err := helm.exec("delete", "--purge", name)
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
helm.write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -90,8 +84,12 @@ func (helm *execer) exec(args ...string) ([]byte, error) {
|
|||
if helm.kubeContext != "" {
|
||||
cmdargs = append(cmdargs, "--kube-context", helm.kubeContext)
|
||||
}
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write([]byte(fmt.Sprintf("exec: helm %s\n", strings.Join(cmdargs, " "))))
|
||||
}
|
||||
helm.write([]byte(fmt.Sprintf("exec: helm %s\n", strings.Join(cmdargs, " "))))
|
||||
return helm.runner.Execute(command, cmdargs)
|
||||
}
|
||||
|
||||
func (helm *execer) write(out []byte) {
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,24 @@ func Test_SyncRelease(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_UpdateDeps(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
helm := MockExecer(&buffer, "dev")
|
||||
helm.UpdateDeps("./chart/foo")
|
||||
expected := "exec: helm dependency update ./chart/foo --kube-context dev\n"
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm.SetExtraArgs("--verify")
|
||||
helm.UpdateDeps("./chart/foo")
|
||||
expected = "exec: helm dependency update ./chart/foo --verify --kube-context dev\n"
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DecryptSecret(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
helm := MockExecer(&buffer, "dev")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ type Interface interface {
|
|||
|
||||
AddRepo(name, repository, certfile, keyfile string) error
|
||||
UpdateRepo() error
|
||||
|
||||
UpdateDeps(chart string) error
|
||||
SyncRelease(name, chart string, flags ...string) error
|
||||
DiffRelease(name, chart string, flags ...string) error
|
||||
DeleteRelease(name string) error
|
||||
|
|
|
|||
15
main.go
15
main.go
|
|
@ -163,7 +163,7 @@ func main() {
|
|||
},
|
||||
{
|
||||
Name: "sync",
|
||||
Usage: "sync all resources from state file (repos && charts)",
|
||||
Usage: "sync all resources from state file (repos, charts and local chart deps)",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
|
|
@ -193,14 +193,21 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
values := c.StringSlice("values")
|
||||
workers := c.Int("concurrency")
|
||||
|
||||
args := c.String("args")
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(strings.Split(args, " ")...)
|
||||
}
|
||||
|
||||
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Printf("err: %s\n", err.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
values := c.StringSlice("values")
|
||||
workers := c.Int("concurrency")
|
||||
|
||||
errs := state.SyncReleases(helm, values, workers)
|
||||
return clean(state, errs)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -184,7 +184,8 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
|
|||
continue
|
||||
}
|
||||
|
||||
if err := helm.SyncRelease(release.Name, normalizeChart(state.BaseChartPath, release.Chart), flags...); err != nil {
|
||||
chart := normalizeChart(state.BaseChartPath, release.Chart)
|
||||
if err := helm.SyncRelease(release.Name, chart, flags...); err != nil {
|
||||
errQueue <- err
|
||||
}
|
||||
doneQueue <- true
|
||||
|
|
@ -331,18 +332,38 @@ func (state *HelmState) FilterReleases(labels []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (state *HelmState) UpdateDeps(helm helmexec.Interface) []error {
|
||||
errs := []error{}
|
||||
|
||||
for _, release := range state.Releases {
|
||||
if isLocalChart(release.Chart) {
|
||||
if err := helm.UpdateDeps(normalizeChart(state.BaseChartPath, release.Chart)); 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`.
|
||||
// - Everything else is assumed to be an absolute path or an actual <repository>/<chart> reference.
|
||||
func normalizeChart(basePath, chart string) string {
|
||||
regex, _ := regexp.Compile("^[.]?./")
|
||||
if !regex.MatchString(chart) {
|
||||
if !isLocalChart(chart) {
|
||||
return chart
|
||||
}
|
||||
return filepath.Join(basePath, chart)
|
||||
}
|
||||
|
||||
func isLocalChart(chart string) bool {
|
||||
regex, _ := regexp.Compile("^[.]?./")
|
||||
return regex.MatchString(chart)
|
||||
}
|
||||
|
||||
func flagsForRelease(helm helmexec.Interface, basePath string, release *ReleaseSpec) ([]string, error) {
|
||||
flags := []string{}
|
||||
if release.Version != "" {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package state
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TestReadFromYaml(t *testing.T) {
|
||||
|
|
@ -218,3 +221,179 @@ func TestHelmState_applyDefaultsTo(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isLocalChart(t *testing.T) {
|
||||
type args struct {
|
||||
chart string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "local chart",
|
||||
args: args{
|
||||
chart: "./charts/nonstop",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "repo chart",
|
||||
args: args{
|
||||
chart: "stable/genius",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
chart: "",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "parent local path",
|
||||
args: args{
|
||||
chart: "../../dotty",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "parent-parent local path",
|
||||
args: args{
|
||||
chart: "../../dotty",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isLocalChart(tt.args.chart); got != tt.want {
|
||||
t.Errorf("isLocalChart() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_normalizeChart(t *testing.T) {
|
||||
type args struct {
|
||||
basePath string
|
||||
chart string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "construct local chart path",
|
||||
args: args{
|
||||
basePath: "/Users/jane/code/deploy/charts",
|
||||
chart: "./app",
|
||||
},
|
||||
want: "/Users/jane/code/deploy/charts/app",
|
||||
},
|
||||
{
|
||||
name: "repo path",
|
||||
args: args{
|
||||
basePath: "/Users/jane/code/deploy/charts",
|
||||
chart: "remote/app",
|
||||
},
|
||||
want: "remote/app",
|
||||
},
|
||||
{
|
||||
name: "construct local chart path, parent dir",
|
||||
args: args{
|
||||
basePath: "/Users/jane/code/deploy/charts",
|
||||
chart: "../app",
|
||||
},
|
||||
want: "/Users/jane/code/deploy/app",
|
||||
},
|
||||
{
|
||||
name: "too much parent levels",
|
||||
args: args{
|
||||
basePath: "/src",
|
||||
chart: "../../app",
|
||||
},
|
||||
want: "/app",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := normalizeChart(tt.args.basePath, tt.args.chart); got != tt.want {
|
||||
t.Errorf("normalizeChart() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mocking helmexec.Interface
|
||||
|
||||
type mockHelmExec struct {
|
||||
charts []string
|
||||
}
|
||||
|
||||
func (helm *mockHelmExec) UpdateDeps(chart string) error {
|
||||
if strings.Contains(chart, "error") {
|
||||
return errors.New("error")
|
||||
}
|
||||
helm.charts = append(helm.charts, chart)
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) SetExtraArgs(args ...string) {
|
||||
return
|
||||
}
|
||||
func (helm *mockHelmExec) AddRepo(name, repository, certfile, keyfile string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) UpdateRepo() error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) SyncRelease(name, chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) DiffRelease(name, chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) DeleteRelease(name string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) DecryptSecret(name string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func TestHelmState_UpdateDeps(t *testing.T) {
|
||||
state := &HelmState{
|
||||
BaseChartPath: "/src",
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./local",
|
||||
},
|
||||
{
|
||||
Chart: "../local",
|
||||
},
|
||||
{
|
||||
Chart: "../../local",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "./error",
|
||||
},
|
||||
},
|
||||
}
|
||||
want := []string{"/src/local", "/local", "/local"}
|
||||
helm := &mockHelmExec{}
|
||||
errs := state.UpdateDeps(helm)
|
||||
if !reflect.DeepEqual(helm.charts, want) {
|
||||
t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.charts, want)
|
||||
}
|
||||
if len(errs) != 1 {
|
||||
t.Errorf("HelmState.UpdateDeps() - expected an error, but got: %v", len(errs))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue