Install list of charts in parallel with ok status

4/15/2021

I use the following code which works and installs helm charts. I got a list of charts and it installs each chart (via loop) and wait (upgradeAction.Wait = true, see below ) that the chart is up and running (using the wait=true flag of the helm) and then install the next one, the problem is that it takes a lot of time to wait that each chart is up-and-running and just then proceed to the next one, Is there a way to install all in parallel and just verify at the end (of all the charts installations) that it works (like how the wait works but for list of charts).

Here is the code:

mpfile, err := ioutil.TempFile(kp, kcp)
if err != nil {
    log.Error(err, "error")
}

defer os.Remove(tmpfile.Name()) 

if _, err := tmpfile.Write(cfg); err != nil {
    return err
}
if err := tmpfile.Close(); err != nil {
    return err
}

kcfgFilePath := tmpfile.Name()
settings := cli.New()
ac := new(action.Configuration)
clientGetter := genericclioptions.NewConfigFlags(false)
clientGetter.KubeConfig = &kcfgFilePath

for _, chartInstallation := range charts {
    chart, err := loader.Load(chartInstallation.Path)
    if err != nil {
        return err
    }

    releaseName := releaseName + "-" + chartInstallation.Name
    if err := ac.Init(clientGetter, settings.Namespace(), os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {
        
    }); err != nil {
        return err
    }
    releasePresent := true
    statusAction := action.NewStatus(ac)
    status, err := statusAction.Run(releaseName)
    if err != nil {
        if strings.Contains(err.Error(), driver.ErrReleaseNotFound.Error()) {
            releasePresent = false
        } else {
            return err
        }
    }

    if !releasePresent {
        // install chart
        installAction := action.NewInstall(ac)
        installAction.CreateNamespace = true
        installAction.Namespace = chartInstallation.Namespace
        installAction.ReleaseName = releaseName

        _, err := installAction.Run(chart, nil)
        if err != nil {
            return err
        }
        log.Info("chart installed: ", "releaseName", releaseName)
    }

    if status != nil {
        if releasePresent && status.Info.Status.String() == release.StatusFailed.String() { 
            upgradeAction := action.NewUpgrade(ac)
            // HERE IT WAIT FOR THE CHART TO VERIFY THAT EVERYTHING IS UP
            upgradeAction.Wait = true
            upgradeAction.ReuseValues = false
            upgradeAction.Recreate = false
            _, err := upgradeAction.Run(releaseName, chart, nil)
            if err != nil {
                return err
            }
        }
    }
}

If I change it to upgradeAction.Wait = false , It starts to install all the charts without waiting to each one health checks, however not sure how can I verify it at the end of all the charts installations

-- JJD
concurrency
go
kubernetes
kubernetes-helm
parallel-processing

1 Answer

4/16/2021

You could start goroutines for each chart you're installing (wrapping chart install code inside go routines) and then use sync.WaitGroup to wait all goroutines to finish. Something like this:

package main

import (
	"fmt"
	"os"
	"strings"
	"sync"
)

func main() {
	kcfgFilePath := tmpfile.Name()
	settings := cli.New()
	ac := new(action.Configuration)
	clientGetter := genericclioptions.NewConfigFlags(false)
	clientGetter.KubeConfig = &kcfgFilePath
	var wg sync.WaitGroup

	for _, chartInstallation := range charts {
        wg.Add(1)
		go installChart(&wg, chartInstallation.Path)
	}

	fmt.Println("Installing...")
	wg.Wait()
	fmt.Println("Installed!")
}

func installChart(wg *sync.WaitGroup, chartInstallationPath string) error {
	defer wg.Done()

	chart, err := loader.Load(chartInstallationPath)
	if err != nil {
		return err
	}

	releaseName := releaseName + "-" + chartInstallation.Name
	if err := ac.Init(clientGetter, settings.Namespace(), os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {

	}); err != nil {
		return err
	}
	releasePresent := true
	statusAction := action.NewStatus(ac)
	status, err := statusAction.Run(releaseName)
	if err != nil {
		if strings.Contains(err.Error(), driver.ErrReleaseNotFound.Error()) {
			releasePresent = false
		} else {
			return err
		}
	}

	if !releasePresent {
		// install chart
		installAction := action.NewInstall(ac)
		installAction.CreateNamespace = true
		installAction.Namespace = chartInstallation.Namespace
		installAction.ReleaseName = releaseName

		_, err := installAction.Run(chart, nil)
		if err != nil {
			return err
		}
		log.Info("chart installed: ", "releaseName", releaseName)
	}

	if status != nil {
		if releasePresent && status.Info.Status.String() == release.StatusFailed.String() {
			upgradeAction := action.NewUpgrade(ac)
			// HERE IT WAIT FOR THE CHART TO VERIFY THAT EVERYTHING IS UP
			upgradeAction.Wait = true
			upgradeAction.ReuseValues = false
			upgradeAction.Recreate = false
			_, err := upgradeAction.Run(releaseName, chart, nil)
			if err != nil {
				return err
			}
		}
	}
}

Here's a good resource for that: https://goinbigdata.com/golang-wait-for-all-goroutines-to-finish/

-- Jose Henrique Felipetto
Source: StackOverflow