Add option to limit concurrent helm calls (#24)
* Add option to throttle concurrent `helm repo update` calls Have added a new flag `--concurrency N` to `helmfile sync charts` that can be used to set a limit on the number of concurrent calls to helm. Implementation details: Switched `SyncCharts` from using a WaitGroup to using a pool of workers and a queue of jobs. To ensure that this is thread safe and an attempt is made to sync each chart at the end. Fixes #23 * Fix formatting and update CI to catch these errors Have fixed the formatting so that `make pristine` now passes. Have also added this to the Circle CI config to catch these errors in the future.
This commit is contained in:
		
							parent
							
								
									c00b869045
								
							
						
					
					
						commit
						effc747081
					
				|  | @ -18,6 +18,7 @@ dependencies: | |||
| test: | ||||
|   pre: | ||||
|     - cd "$WORK" && make check | ||||
|     - cd "$WORK" && make pristine | ||||
| 
 | ||||
|   override: | ||||
|     - cd "$WORK" && make test | ||||
|  |  | |||
							
								
								
									
										16
									
								
								main.go
								
								
								
								
							
							
						
						
									
										16
									
								
								main.go
								
								
								
								
							|  | @ -84,6 +84,11 @@ func main() { | |||
| 					Name:  "values", | ||||
| 					Usage: "additional value files to be merged into the command", | ||||
| 				}, | ||||
| 				cli.IntFlag{ | ||||
| 					Name:  "concurrency", | ||||
| 					Value: 0, | ||||
| 					Usage: "maximum number of concurrent helm processes to run, 0 is unlimited", | ||||
| 				}, | ||||
| 			}, | ||||
| 			Action: func(c *cli.Context) error { | ||||
| 				state, helm, err := before(c) | ||||
|  | @ -97,8 +102,9 @@ func main() { | |||
| 				} | ||||
| 
 | ||||
| 				values := c.StringSlice("values") | ||||
| 				workers := c.Int("concurrency") | ||||
| 
 | ||||
| 				if errs := state.SyncCharts(helm, values); errs != nil && len(errs) > 0 { | ||||
| 				if errs := state.SyncCharts(helm, values, workers); errs != nil && len(errs) > 0 { | ||||
| 					for _, err := range errs { | ||||
| 						fmt.Printf("err: %s\n", err.Error()) | ||||
| 					} | ||||
|  | @ -164,6 +170,11 @@ func main() { | |||
| 					Name:  "values", | ||||
| 					Usage: "additional value files to be merged into the command", | ||||
| 				}, | ||||
| 				cli.IntFlag{ | ||||
| 					Name:  "concurrency", | ||||
| 					Value: 0, | ||||
| 					Usage: "maximum number of concurrent helm processes to run, 0 is unlimited", | ||||
| 				}, | ||||
| 			}, | ||||
| 			Action: func(c *cli.Context) error { | ||||
| 				state, helm, err := before(c) | ||||
|  | @ -179,8 +190,9 @@ func main() { | |||
| 				} | ||||
| 
 | ||||
| 				values := c.StringSlice("values") | ||||
| 				workers := c.Int("concurrency") | ||||
| 
 | ||||
| 				if errs := state.SyncCharts(helm, values); errs != nil && len(errs) > 0 { | ||||
| 				if errs := state.SyncCharts(helm, values, workers); errs != nil && len(errs) > 0 { | ||||
| 					for _, err := range errs { | ||||
| 						fmt.Printf("err: %s\n", err.Error()) | ||||
| 					} | ||||
|  |  | |||
|  | @ -12,10 +12,10 @@ import ( | |||
| 
 | ||||
| 	"github.com/roboll/helmfile/helmexec" | ||||
| 
 | ||||
| 	"bytes" | ||||
| 	yaml "gopkg.in/yaml.v1" | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"bytes" | ||||
| ) | ||||
| 
 | ||||
| type HelmState struct { | ||||
|  | @ -31,9 +31,9 @@ type RepositorySpec struct { | |||
| } | ||||
| 
 | ||||
| type ChartSpec struct { | ||||
| 	Chart     string `yaml:"chart"` | ||||
| 	Version   string `yaml:"version"` | ||||
| 	Verify    bool   `yaml:"verify"` | ||||
| 	Chart   string `yaml:"chart"` | ||||
| 	Version string `yaml:"version"` | ||||
| 	Verify  bool   `yaml:"verify"` | ||||
| 
 | ||||
| 	Name      string     `yaml:"name"` | ||||
| 	Namespace string     `yaml:"namespace"` | ||||
|  | @ -64,13 +64,11 @@ func ReadFromFile(file string) (*HelmState, error) { | |||
| 	return &state, nil | ||||
| } | ||||
| 
 | ||||
| var /* const */ | ||||
| 	stringTemplateFuncMap = template.FuncMap{ | ||||
| 		"env": getEnvVar, | ||||
| 	} | ||||
| var stringTemplateFuncMap = template.FuncMap{ | ||||
| 	"env": getEnvVar, | ||||
| } | ||||
| 
 | ||||
| var /* const */ | ||||
| 	stringTemplate = template.New("stringTemplate").Funcs(stringTemplateFuncMap) | ||||
| var stringTemplate = template.New("stringTemplate").Funcs(stringTemplateFuncMap) | ||||
| 
 | ||||
| func getEnvVar(envVarName string) (string, error) { | ||||
| 	envVarValue, isSet := os.LookupEnv(envVarName) | ||||
|  | @ -124,33 +122,65 @@ func (state *HelmState) SyncRepos(helm helmexec.Interface) []error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []string) []error { | ||||
| 	var wg sync.WaitGroup | ||||
| func (state *HelmState) SyncCharts(helm helmexec.Interface, additonalValues []string, workerLimit int) []error { | ||||
| 	errs := []error{} | ||||
| 	jobQueue := make(chan ChartSpec) | ||||
| 	doneQueue := make(chan bool) | ||||
| 	errQueue := make(chan error) | ||||
| 
 | ||||
| 	for _, chart := range state.Charts { | ||||
| 		wg.Add(1) | ||||
| 		go func(wg *sync.WaitGroup, chart ChartSpec) { | ||||
| 			flags, flagsErr := flagsForChart(state.BaseChartPath, &chart) | ||||
| 			if flagsErr != nil { | ||||
| 				errs = append(errs, flagsErr) | ||||
| 			} | ||||
| 			for _, value := range additonalValues { | ||||
| 				valfile, err := filepath.Abs(value) | ||||
| 				if err != nil { | ||||
| 					errs = append(errs, err) | ||||
| 				} | ||||
| 				flags = append(flags, "--values", valfile) | ||||
| 			} | ||||
| 			if len(errs) == 0 { | ||||
| 				if err := helm.SyncChart(chart.Name, normalizeChart(state.BaseChartPath, chart.Chart), flags...); err != nil { | ||||
| 					errs = append(errs, err) | ||||
| 				} | ||||
| 			} | ||||
| 			wg.Done() | ||||
| 		}(&wg, chart) | ||||
| 	if workerLimit < 1 { | ||||
| 		workerLimit = len(state.Charts) | ||||
| 	} | ||||
| 
 | ||||
| 	for w := 1; w <= workerLimit; w++ { | ||||
| 		go func() { | ||||
| 			for chart := range jobQueue { | ||||
| 
 | ||||
| 				flags, flagsErr := flagsForChart(state.BaseChartPath, &chart) | ||||
| 				if flagsErr != nil { | ||||
| 					errQueue <- flagsErr | ||||
| 					doneQueue <- true | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 				haveValueErr := false | ||||
| 				for _, value := range additonalValues { | ||||
| 					valfile, err := filepath.Abs(value) | ||||
| 					if err != nil { | ||||
| 						errQueue <- err | ||||
| 						haveValueErr = true | ||||
| 					} | ||||
| 					flags = append(flags, "--values", valfile) | ||||
| 				} | ||||
| 
 | ||||
| 				if haveValueErr { | ||||
| 					doneQueue <- true | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 				if err := helm.SyncChart(chart.Name, normalizeChart(state.BaseChartPath, chart.Chart), flags...); err != nil { | ||||
| 					errQueue <- err | ||||
| 				} | ||||
| 				doneQueue <- true | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| 
 | ||||
| 	go func() { | ||||
| 		for _, chart := range state.Charts { | ||||
| 			jobQueue <- chart | ||||
| 		} | ||||
| 		close(jobQueue) | ||||
| 	}() | ||||
| 
 | ||||
| 	for i := 0; i < len(state.Charts); { | ||||
| 		select { | ||||
| 		case err := <-errQueue: | ||||
| 			errs = append(errs, err) | ||||
| 		case <-doneQueue: | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
| 	wg.Wait() | ||||
| 
 | ||||
| 	if len(errs) != 0 { | ||||
| 		return errs | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue