running update dependencies for local charts
This commit is contained in:
		
							parent
							
								
									cef2d26c06
								
							
						
					
					
						commit
						1b302db7f8
					
				|  | @ -101,7 +101,7 @@ 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) | ||||||
|      charts  sync charts from state file (helm upgrade --install) |      charts  sync charts from state file (helm upgrade --install) | ||||||
|      diff    diff charts from state file against env (helm diff) |      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) |      delete  delete charts from state file (helm delete) | ||||||
| 
 | 
 | ||||||
| GLOBAL OPTIONS: | GLOBAL OPTIONS: | ||||||
|  | @ -119,9 +119,10 @@ GLOBAL OPTIONS: | ||||||
| 
 | 
 | ||||||
| ### sync | ### 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 | ### diff | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -46,6 +46,12 @@ func (helm *execer) UpdateRepo() error { | ||||||
| 	return err | 	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 { | 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...)...) | ||||||
| 	helm.write(out) | 	helm.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) { | func Test_DecryptSecret(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	helm := MockExecer(&buffer, "dev") | 	helm := MockExecer(&buffer, "dev") | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ type Interface interface { | ||||||
| 
 | 
 | ||||||
| 	AddRepo(name, repository, certfile, keyfile string) error | 	AddRepo(name, repository, certfile, keyfile string) error | ||||||
| 	UpdateRepo() error | 	UpdateRepo() 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 | ||||||
| 	DeleteRelease(name string) error | 	DeleteRelease(name string) error | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								main.go
								
								
								
								
							
							
						
						
									
										15
									
								
								main.go
								
								
								
								
							|  | @ -163,7 +163,7 @@ func main() { | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:  "sync", | 			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{ | 			Flags: []cli.Flag{ | ||||||
| 				cli.StringSliceFlag{ | 				cli.StringSliceFlag{ | ||||||
| 					Name:  "values", | 					Name:  "values", | ||||||
|  | @ -193,14 +193,21 @@ func main() { | ||||||
| 					os.Exit(1) | 					os.Exit(1) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				values := c.StringSlice("values") |  | ||||||
| 				workers := c.Int("concurrency") |  | ||||||
| 
 |  | ||||||
| 				args := c.String("args") | 				args := c.String("args") | ||||||
| 				if len(args) > 0 { | 				if len(args) > 0 { | ||||||
| 					helm.SetExtraArgs(strings.Split(args, " ")...) | 					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) | 				errs := state.SyncReleases(helm, values, workers) | ||||||
| 				return clean(state, errs) | 				return clean(state, errs) | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
|  | @ -184,7 +184,8 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 					continue | 					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 | 					errQueue <- err | ||||||
| 				} | 				} | ||||||
| 				doneQueue <- true | 				doneQueue <- true | ||||||
|  | @ -331,18 +332,38 @@ func (state *HelmState) FilterReleases(labels []string) error { | ||||||
| 	return nil | 	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.
 | // 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
 | // - Any single (or double character) followed by a `/` will be considered a local file reference and
 | ||||||
| // 	 be constructed relative to the `base path`.
 | // 	 be constructed relative to the `base path`.
 | ||||||
| // - Everything else is assumed to be an absolute path or an actual <repository>/<chart> reference.
 | // - Everything else is assumed to be an absolute path or an actual <repository>/<chart> reference.
 | ||||||
| func normalizeChart(basePath, chart string) string { | func normalizeChart(basePath, chart string) string { | ||||||
| 	regex, _ := regexp.Compile("^[.]?./") | 	if !isLocalChart(chart) { | ||||||
| 	if !regex.MatchString(chart) { |  | ||||||
| 		return chart | 		return chart | ||||||
| 	} | 	} | ||||||
| 	return filepath.Join(basePath, 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) { | func flagsForRelease(helm helmexec.Interface, basePath string, release *ReleaseSpec) ([]string, error) { | ||||||
| 	flags := []string{} | 	flags := []string{} | ||||||
| 	if release.Version != "" { | 	if release.Version != "" { | ||||||
|  |  | ||||||
|  | @ -3,6 +3,10 @@ package state | ||||||
| import ( | import ( | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestReadFromYaml(t *testing.T) { | func TestReadFromYaml(t *testing.T) { | ||||||
|  | @ -218,3 +222,180 @@ 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 { | ||||||
|  | 	fmt.Println(chart) | ||||||
|  | 	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