feat: `helmfile build --embed-values` to embed release values and secrets into the output (#1436)
This commit is contained in:
		
							parent
							
								
									5ca7ce15bc
								
							
						
					
					
						commit
						0fc0869671
					
				
							
								
								
									
										11
									
								
								main.go
								
								
								
								
							
							
						
						
									
										11
									
								
								main.go
								
								
								
								
							|  | @ -467,7 +467,12 @@ func main() { | ||||||
| 		{ | 		{ | ||||||
| 			Name:  "build", | 			Name:  "build", | ||||||
| 			Usage: "output compiled helmfile state(s) as YAML", | 			Usage: "output compiled helmfile state(s) as YAML", | ||||||
| 			Flags: []cli.Flag{}, | 			Flags: []cli.Flag{ | ||||||
|  | 				cli.BoolFlag{ | ||||||
|  | 					Name:  "embed-values", | ||||||
|  | 					Usage: "Read all the values files for every release and embed into the output helmfile.yaml", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
| 			Action: action(func(run *app.App, c configImpl) error { | 			Action: action(func(run *app.App, c configImpl) error { | ||||||
| 				return run.PrintState(c) | 				return run.PrintState(c) | ||||||
| 			}), | 			}), | ||||||
|  | @ -674,6 +679,10 @@ func (c configImpl) Context() int { | ||||||
| 	return c.c.Int("context") | 	return c.c.Int("context") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c configImpl) EmbedValues() bool { | ||||||
|  | 	return c.c.Bool("embed-values") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c configImpl) Logger() *zap.SugaredLogger { | func (c configImpl) Logger() *zap.SugaredLogger { | ||||||
| 	return c.c.App.Metadata["logger"].(*zap.SugaredLogger) | 	return c.c.App.Metadata["logger"].(*zap.SugaredLogger) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -387,12 +387,35 @@ func (a *App) PrintState(c StateConfigProvider) error { | ||||||
| 		err := run.withPreparedCharts("build", state.ChartPrepareOptions{ | 		err := run.withPreparedCharts("build", state.ChartPrepareOptions{ | ||||||
| 			SkipRepos: true, | 			SkipRepos: true, | ||||||
| 		}, func() { | 		}, func() { | ||||||
| 			state, err := run.state.ToYaml() | 			if c.EmbedValues() { | ||||||
|  | 				for i := range run.state.Releases { | ||||||
|  | 					r := run.state.Releases[i] | ||||||
|  | 
 | ||||||
|  | 					values, err := run.state.LoadYAMLForEmbedding(r.Values, r.MissingFileHandler, r.ValuesPathPrefix) | ||||||
|  | 					if err != nil { | ||||||
|  | 						errs = []error{err} | ||||||
|  | 						return | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					run.state.Releases[i].Values = values | ||||||
|  | 
 | ||||||
|  | 					secrets, err := run.state.LoadYAMLForEmbedding(r.Secrets, r.MissingFileHandler, r.ValuesPathPrefix) | ||||||
|  | 					if err != nil { | ||||||
|  | 						errs = []error{err} | ||||||
|  | 						return | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					run.state.Releases[i].Secrets = secrets | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			stateYaml, err := run.state.ToYaml() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				errs = []error{err} | 				errs = []error{err} | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			fmt.Printf("---\n#  Source: %s\n\n%+v", run.state.FilePath, state) | 
 | ||||||
|  | 			fmt.Printf("---\n#  Source: %s\n\n%+v", run.state.FilePath, stateYaml) | ||||||
| 
 | 
 | ||||||
| 			errs = []error{} | 			errs = []error{} | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | @ -2272,6 +2272,10 @@ func (c configImpl) Concurrency() int { | ||||||
| 	return 1 | 	return 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c configImpl) EmbedValues() bool { | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c configImpl) Output() string { | func (c configImpl) Output() string { | ||||||
| 	return c.output | 	return c.output | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -144,6 +144,7 @@ type StatusesConfigProvider interface { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type StateConfigProvider interface { | type StateConfigProvider interface { | ||||||
|  | 	EmbedValues() bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type concurrencyConfig interface { | type concurrencyConfig interface { | ||||||
|  |  | ||||||
|  | @ -133,12 +133,15 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for i, ts := range result.Secrets { | 	for i, t := range result.Secrets { | ||||||
| 		s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | 		switch ts := t.(type) { | ||||||
| 		if err != nil { | 		case string: | ||||||
| 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".secrets[%d] = \"%s\": %v", r.Name, i, ts, err) | 			s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".secrets[%d] = \"%s\": %v", r.Name, i, ts, err) | ||||||
|  | 			} | ||||||
|  | 			result.Secrets[i] = s.String() | ||||||
| 		} | 		} | ||||||
| 		result.Secrets[i] = s.String() |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(result.SetValuesTemplate) > 0 { | 	if len(result.SetValuesTemplate) > 0 { | ||||||
|  |  | ||||||
|  | @ -211,7 +211,7 @@ type ReleaseSpec struct { | ||||||
| 	Namespace string            `yaml:"namespace,omitempty"` | 	Namespace string            `yaml:"namespace,omitempty"` | ||||||
| 	Labels    map[string]string `yaml:"labels,omitempty"` | 	Labels    map[string]string `yaml:"labels,omitempty"` | ||||||
| 	Values    []interface{}     `yaml:"values,omitempty"` | 	Values    []interface{}     `yaml:"values,omitempty"` | ||||||
| 	Secrets   []string          `yaml:"secrets,omitempty"` | 	Secrets   []interface{}     `yaml:"secrets,omitempty"` | ||||||
| 	SetValues []SetValue        `yaml:"set,omitempty"` | 	SetValues []SetValue        `yaml:"set,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	ValuesTemplate    []interface{} `yaml:"valuesTemplate,omitempty"` | 	ValuesTemplate    []interface{} `yaml:"valuesTemplate,omitempty"` | ||||||
|  | @ -2262,11 +2262,41 @@ func (st *HelmState) generateVanillaValuesFiles(release *ReleaseSpec) ([]string, | ||||||
| func (st *HelmState) generateSecretValuesFiles(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) ([]string, error) { | func (st *HelmState) generateSecretValuesFiles(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) ([]string, error) { | ||||||
| 	var generatedFiles []string | 	var generatedFiles []string | ||||||
| 
 | 
 | ||||||
| 	for _, value := range release.Secrets { | 	for _, v := range release.Secrets { | ||||||
| 		paths, skip, err := st.storage().resolveFile(release.MissingFileHandler, "secrets", release.ValuesPathPrefix+value) | 		var ( | ||||||
| 		if err != nil { | 			paths []string | ||||||
| 			return nil, err | 			skip  bool | ||||||
|  | 			err   error | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 		switch value := v.(type) { | ||||||
|  | 		case string: | ||||||
|  | 			paths, skip, err = st.storage().resolveFile(release.MissingFileHandler, "secrets", release.ValuesPathPrefix+value) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		default: | ||||||
|  | 			bs, err := yaml.Marshal(value) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			path, err := ioutil.TempFile(os.TempDir(), "helmfile-embdedded-secrets-*.yaml.enc") | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			_ = path.Close() | ||||||
|  | 			defer func() { | ||||||
|  | 				_ = os.Remove(path.Name()) | ||||||
|  | 			}() | ||||||
|  | 
 | ||||||
|  | 			if err := ioutil.WriteFile(path.Name(), bs, 0644); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			paths = []string{path.Name()} | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		if skip { | 		if skip { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | @ -2546,3 +2576,42 @@ func (st *HelmState) ToYaml() (string, error) { | ||||||
| 		return string(result), nil | 		return string(result), nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (st *HelmState) LoadYAMLForEmbedding(entries []interface{}, missingFileHandler *string, pathPrefix string) ([]interface{}, error) { | ||||||
|  | 	var result []interface{} | ||||||
|  | 
 | ||||||
|  | 	for _, v := range entries { | ||||||
|  | 		switch t := v.(type) { | ||||||
|  | 		case string: | ||||||
|  | 			var values map[string]interface{} | ||||||
|  | 
 | ||||||
|  | 			paths, skip, err := st.storage().resolveFile(missingFileHandler, "values", pathPrefix+t) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			if skip { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(paths) > 1 { | ||||||
|  | 				return nil, fmt.Errorf("glob patterns in release values and secrets is not supported yet. please submit a feature request if necessary") | ||||||
|  | 			} | ||||||
|  | 			yamlOrTemplatePath := paths[0] | ||||||
|  | 
 | ||||||
|  | 			yamlBytes, err := st.RenderValuesFileToBytes(yamlOrTemplatePath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("failed to render values files \"%s\": %v", t, err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if err := yaml.Unmarshal(yamlBytes, &values); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			result = append(result, values) | ||||||
|  | 		default: | ||||||
|  | 			result = append(result, v) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ func TestHelmState_executeTemplates(t *testing.T) { | ||||||
| 				Name:           "test-app", | 				Name:           "test-app", | ||||||
| 				Namespace:      "test-namespace-{{ .Release.Name }}", | 				Namespace:      "test-namespace-{{ .Release.Name }}", | ||||||
| 				ValuesTemplate: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"}, | 				ValuesTemplate: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"}, | ||||||
| 				Secrets:        []string{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"}, | 				Secrets:        []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"}, | ||||||
| 				Labels:         map[string]string{"id": "{{ .Release.Name }}"}, | 				Labels:         map[string]string{"id": "{{ .Release.Name }}"}, | ||||||
| 			}, | 			}, | ||||||
| 			want: ReleaseSpec{ | 			want: ReleaseSpec{ | ||||||
|  | @ -45,7 +45,7 @@ func TestHelmState_executeTemplates(t *testing.T) { | ||||||
| 				Name:      "test-app", | 				Name:      "test-app", | ||||||
| 				Namespace: "test-namespace-test-app", | 				Namespace: "test-namespace-test-app", | ||||||
| 				Values:    []interface{}{"config/test_env/test-app/values.yaml"}, | 				Values:    []interface{}{"config/test_env/test-app/values.yaml"}, | ||||||
| 				Secrets:   []string{"config/test_env/test-app/secrets.yaml"}, | 				Secrets:   []interface{}{"config/test_env/test-app/secrets.yaml"}, | ||||||
| 				Labels:    map[string]string{"id": "test-app"}, | 				Labels:    map[string]string{"id": "test-app"}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue