Search code examples
gokuberneteskubernetes-helm

Download Helm Chart using Go


I wondered if anyone had ever successfully used the Go Helm provider. I've been in a very frustrating day trying to get my Argo-CD helm chart to download and install using the go helm provider:

https://pkg.go.dev/github.com/mittwald/go-helm-client#ChartSpec

I have this code. I got some of it from Stack Overflow. (I've tried stuff that's commented out, and I end up at the same spot each time.)

package main

import (
    "fmt"
    "os"

    //helmclient "github.com/mittwald/go-helm-client"

    "helm.sh/helm/v3/pkg/action"
    "helm.sh/helm/v3/pkg/chart/loader"
    "helm.sh/helm/v3/pkg/cli"
    "helm.sh/helm/v3/pkg/kube"
    _ "k8s.io/client-go/plugin/pkg/client/auth"
)

func main() {
    //chartname := "argo-cd"
    mychartOptions := &action.ChartPathOptions{
        RepoURL: "https://argoproj.github.io/argo-helm",
        Version: "6.9.1",
    }
    //var outputBuffer bytes.Buffer
    settings := cli.New()
    /*
        opt := &helmclient.Options{
            Namespace:        "argo-cd", // Change this to the namespace you wish the client to operate in.
            RepositoryCache:  "/tmp/.helmcache",
            RepositoryConfig: "/tmp/.helmrepo",
            Debug:            true,
            Linting:          true,
            DebugLog:         func(format string, v ...interface{}) {},
            Output:           &outputBuffer, // Not mandatory, leave open for default os.Stdout
        }
    */

    //myHelmClient, err := helmclient.New(opt)
    //if err != nil {
    //  panic(err)
    //}

    //myHelmChart, _, err := myHelmClient.GetChart(chartname, mychartOptions)
    //if err != nil {
    //  panic(err)
    //}
    getChart, err := action.NewPull().LocateChart(mychartOptions.RepoURL, settings)
    if err != nil {
        panic(err)
    }
    //chartPath := myHelmChart.ChartFullPath()
    //myChartName := myHelmChart.Name()
    //fmt.Printf("%v\n", chartPath)
    //fmt.Printf("%v\n", myChartName)
    fmt.Printf("%v\n", getChart)
    chart, err := loader.Load(getChart)
    if err != nil {
        panic(err)
    }

    kubeconfigPath := "C:\\Users\\jcontent\\.kube\\config"
    releaseName := "argo-cd"
    releaseNamespace := "argo-cd"
    actionConfig := new(action.Configuration)
    if err := actionConfig.Init(kube.GetConfig(kubeconfigPath, "", releaseNamespace), releaseNamespace, os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {
        fmt.Sprintf(format, v)
    }); err != nil {
        panic(err)
    }

    iCli := action.NewInstall(actionConfig)
    iCli.Namespace = releaseNamespace
    iCli.ReleaseName = releaseName
    rel, err := iCli.Run(chart, nil)
    if err != nil {
        panic(err)
    }
    fmt.Println("Successfully installed release: ", rel.Name)
}

Yet all I keep getting to is this error no matter what I try when trying to download the helm chart and put it into a helm repo.

panic: file 'C:\Users\jcontent\AppData\Local\Temp\helm\repository\argo-helm' does not appear to be a gzipped archive; got 'text/html; charset=utf-8'

goroutine 1 [running]:
main.main()
        C:/DevOps/argo-cd-demo/deployHelm/deployhelm.go:56 +0x2ee
exit status 2

I'm trying to download the helm chart using various helm Go libraries. The Stackoverflow page that I've looked at is Samples on kubernetes helm golang client

Yet I can't get any examples to work when you try and download the helm chart dynamically.

Any help would be amazing at this point, as I want to throw my machine out the window.


Solution

  • I actually figured out how to do this in the end using the Helm provider with Go.

    Here: https://pkg.go.dev/github.com/mittwald/[email protected]

    In my question, I was partly there, but where I got that code from the loader.load package did not download the helm chart as in a whole package. It was spitting out a yaml file of the chart. Then, when passing that to the client.Run, it would not work because it was expecting all the Helm chart contents, not just a yaml file.

    This left me with no choice but to scrap the whole thing and start again.

    I got this to work by messing around with the AddOrUpdateChartRepo function. With Argo CD, I had to do this.

    chartRepo := repo.Entry{
            Name:               "argo",
            URL:                "https://argoproj.github.io/argo-helm",
            PassCredentialsAll: true,
        }

    Then that would add the chart into the Kubernetes Helm Repo by using the AddOrUpdateChartRepo function like so:

    if err := myHelmClient.AddOrUpdateChartRepo(chartRepo); err != nil {
            log.Fatal(err)
        } else {
            fmt.Printf("Added Chart Repo %s\n", chartRepo.Name)
        }

    Yet to get that to actually work, what you have to do, like many of the Go SDKs, is build a client. I saw examples of how to do this by reading various .Config files or using the Kubernetes SDK package to build the client, but then I noticed on one of the examples of the Helm client package a .Options Struct I then went away and messed about with this Struct and in the end, managed to build a helmclient by passing that struct to another function that is overlooked in that package just called .New()

    The code looks like the following to build a client:

    // Builds Helm Chart Client ////
        opt := &helmclient.Options{
            Namespace:        "argo-cd", // Change this to the namespace you wish the client to operate in.
            RepositoryCache:  "/tmp/.helmcache",
            RepositoryConfig: "/tmp/.helmrepo",
            Debug:            false,
            Linting:          true,
            DebugLog:         func(format string, v ...interface{}) {},
            Output:           &outputBuffer, // Not mandatory, leave open for default os.Stdout
        }
    
        myHelmClient, err := helmclient.New(opt)
        if err != nil {
            panic(err)
        }

    So now we're halfway there: The Helm Client chart has been processed and added to the Repo. Yet there is an important step I initially missed: When you run Helm with helm repo add, it adds the chart but then runs an Update on the chart repo to ensure the chart is in place. Yet, for some reason, the Go SDK does not do that. This means when you later try to refer to the repo you just added, the Program will come back and say that it can not find it. I found the function to update the repo, which is simply UpdateChartRepos:.

    // Now Run Update Chart Repos
        if err := myHelmClient.UpdateChartRepos(); err != nil {
            log.Fatal(err)
        } else {
            fmt.Printf("Updating Chart Repo\n")
        }

    With all that in place, we can now do helm install but using the Go Helm SDK.

    What you have to do here is mess about with another struct called ChartSpec. Here, you pass all the options you want for Go to first find the chart and then run the equivalent of helm install —-set. So, for Argo CD, you do something like this.

    // Now install the chart from the repo thats just been created./////////////////////
        chartSpec := helmclient.ChartSpec{
            ReleaseName:     "argo-cd-chart",
            ChartName:       "argo/argo-cd",
            Version:         "6.9.2",
            Namespace:       "argo-cd",
            CreateNamespace: true,
            ValuesOptions: values.Options{
                StringValues: []string{
                    "controller.status.processors=20",
                    "controller.operation.processors=10",
                    "controller.self.heal.timeout.seconds=5",
                    "controller.repo.server.timeout.seconds=60",
                },
                Values: []string{"./values-dev.yaml"},
            },
            SkipCRDs: true,
            Wait:     true,
        }

    Once you're happy with the ChartSpec, you pass that ChartSpec to the following function InstallChart using the' helmclient' you built earlier. Like this:

        ctx := context.Background()
        mycontext, _ := context.WithTimeout(ctx, 80000*time.Second)
        myInstalledHelmChart, err := myHelmClient.InstallChart(mycontext, &chartSpec, nil)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Status of Chart Install %v,\n", *myInstalledHelmChart.Info)

    You also need to use the context package for Argo CD for it to install the chart.

    That's how you use Go to install a helm chart for anyone looking for this.

    Here is the complete code for reference.

    package main
    
    import (
        "bytes"
        "context"
        "fmt"
        "log"
        "os"
        "path/filepath"
        "time"
    
        helmclient "github.com/mittwald/go-helm-client"
        "github.com/mittwald/go-helm-client/values"
        "helm.sh/helm/v3/pkg/repo"
    )
    
    func main() {
        // First add the ArgoCD Chart to a repo in the cluster
        helmrepo := fmt.Sprintf("https://github.com/jasric89")
        var outputBuffer bytes.Buffer
        
        // Builds Helm Chart Client ////
        opt := &helmclient.Options{
            Namespace:        "argo-cd", // Change this to the namespace you wish the client to operate in.
            RepositoryCache:  "/tmp/.helmcache",
            RepositoryConfig: "/tmp/.helmrepo",
            Debug:            false,
            Linting:          true,
            DebugLog:         func(format string, v ...interface{}) {},
            Output:           &outputBuffer, // Not mandatory, leave open for default os.Stdout
        }
    
        myHelmClient, err := helmclient.New(opt)
        if err != nil {
            panic(err)
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////
        //// Now use Helmchart client to create chart repo //////
        chartRepo := repo.Entry{
            Name:               "argo",
            URL:                "https://argoproj.github.io/argo-helm",
            PassCredentialsAll: true,
        }
    
        if err := myHelmClient.AddOrUpdateChartRepo(chartRepo); err != nil {
            log.Fatal(err)
        } else {
            fmt.Printf("Added Chart Repo %s\n", chartRepo.Name)
        }
    
        // Now Run Update Chart Repos
        if err := myHelmClient.UpdateChartRepos(); err != nil {
            log.Fatal(err)
        } else {
            fmt.Printf("Updating Chart Repo\n")
        }
    
        ///////////////////////////////////////////////////////////////////////////////////////////////////////
        // Now install the chart from the repo thats just been created./////////////////////
        chartSpec := helmclient.ChartSpec{
            ReleaseName:     "argo-cd-chart",
            ChartName:       "argo/argo-cd",
            Version:         "6.9.2",
            Namespace:       "argo-cd",
            CreateNamespace: true,
            ValuesOptions: values.Options{
                StringValues: []string{
                    "controller.status.processors=20",
                    "controller.operation.processors=10",
                    "controller.self.heal.timeout.seconds=5",
                    "controller.repo.server.timeout.seconds=60",
                },
                Values: []string{"./values-dev.yaml"},
            },
            SkipCRDs: true,
            Wait:     true,
        }
        ctx := context.Background()
        mycontext, _ := context.WithTimeout(ctx, 80000*time.Second)
        myInstalledHelmChart, err := myHelmClient.InstallChart(mycontext, &chartSpec, nil)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Status of Chart Install %v,\n", *myInstalledHelmChart.Info)
    
        ////////////////////////////////////////////////////////////////////////////////////////
    }