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) | ||||
|      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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -46,6 +46,12 @@ func (helm *execer) UpdateRepo() error { | |||
| 	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...)...) | ||||
| 	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) { | ||||
| 	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,10 @@ package state | |||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| 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