github.com/verrazzano/verrazzano@v1.7.1/tools/charts-manager/vcm/pkg/helm/helm.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package helm
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  
    10  	"github.com/verrazzano/verrazzano/tools/vz/pkg/helpers"
    11  	"helm.sh/helm/v3/pkg/action"
    12  	"helm.sh/helm/v3/pkg/cli"
    13  	"helm.sh/helm/v3/pkg/getter"
    14  	"helm.sh/helm/v3/pkg/registry"
    15  	helmrepo "helm.sh/helm/v3/pkg/repo"
    16  )
    17  
    18  // HelmConfig represents any helm and related operations done for a helm chart.
    19  type HelmConfig interface {
    20  	AddAndUpdateChartRepo(string, string) (string, error)
    21  	DownloadChart(string, string, string, string, string) error
    22  	GetChartProvenance(string, string, string) (*ChartProvenance, error)
    23  }
    24  
    25  // VerrazzanoHelmConfig is default implementation of HelmConfig.
    26  type VerrazzanoHelmConfig struct {
    27  	settings     *cli.EnvSettings
    28  	helmRepoFile *helmrepo.File
    29  	vzHelper     helpers.VZHelper
    30  }
    31  
    32  // ChartProvenance represents the upstream provenance information about a chart version by reading/creating
    33  // the helm repository config and cache.
    34  type ChartProvenance struct {
    35  	// UpstreamVersion is version of the upstream chart.
    36  	UpstreamVersion string `yaml:"upstreamVersion"`
    37  	// UpstreamChartLocalPath contains relative path to upstream chart directory.
    38  	UpstreamChartLocalPath string `yaml:"upstreamChartLocalPath"`
    39  	// UpstreamIndexEntry is the index.yaml entry for the upstream chart.
    40  	UpstreamIndexEntry *helmrepo.ChartVersion `yaml:"upstreamIndexEntry"`
    41  }
    42  
    43  // NewHelmConfig initializes the default HelmConfig instance.
    44  func NewHelmConfig(vzHelper helpers.VZHelper) (*VerrazzanoHelmConfig, error) {
    45  	helmConfig := &VerrazzanoHelmConfig{vzHelper: vzHelper}
    46  	helmConfig.settings = cli.New()
    47  	if helmConfig.settings.RepositoryConfig == "" {
    48  		helmConfig.settings.RepositoryConfig = "/tmp/vz_helm_repo.yaml"
    49  	}
    50  
    51  	if helmConfig.settings.RepositoryCache == "" {
    52  		helmConfig.settings.RepositoryCache = "/tmp/vz_helm_repo_cache"
    53  	}
    54  
    55  	err := os.MkdirAll(helmConfig.settings.RepositoryCache, 0755)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	if _, err = os.Stat(helmConfig.settings.RepositoryConfig); err != nil {
    61  		err = helmrepo.NewFile().WriteFile(helmConfig.settings.RepositoryConfig, 0o644)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  	}
    66  
    67  	helmConfig.helmRepoFile, err = helmrepo.LoadFile(helmConfig.settings.RepositoryConfig)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	return helmConfig, nil
    72  }
    73  
    74  // AddAndUpdateChartRepo creates/updates the local helm repo for the given repoURL and
    75  // downloads the index file.
    76  func (h VerrazzanoHelmConfig) AddAndUpdateChartRepo(chart string, repoURL string) (string, error) {
    77  	repoEntry, err := h.getRepoEntry(repoURL)
    78  	if err != nil {
    79  		return "", err
    80  	}
    81  
    82  	if repoEntry == nil {
    83  		repoEntry = &helmrepo.Entry{
    84  			Name: fmt.Sprintf("%s-provider", chart),
    85  			URL:  repoURL,
    86  		}
    87  		fmt.Fprintf(h.vzHelper.GetOutputStream(), "Adding helm repo %s.\n", repoEntry.Name)
    88  	} else {
    89  		fmt.Fprintf(h.vzHelper.GetOutputStream(), "Using helm repo %s\n", repoEntry.Name)
    90  	}
    91  
    92  	chartRepo, err := helmrepo.NewChartRepository(repoEntry, getter.All(h.settings))
    93  	if err != nil {
    94  		return "", err
    95  	}
    96  
    97  	_, err = chartRepo.DownloadIndexFile()
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  
   102  	h.helmRepoFile.Update(repoEntry)
   103  	return repoEntry.Name, h.helmRepoFile.WriteFile(h.settings.RepositoryConfig, 0o644)
   104  }
   105  
   106  // DownloadChart pulls a chart from the remote helm repo and untars into the chart ditrectory.
   107  func (h VerrazzanoHelmConfig) DownloadChart(chart string, repo string, version string, targetVersion string, chartDir string) error {
   108  	registryClient, err := registry.NewClient(
   109  		registry.ClientOptDebug(h.settings.Debug),
   110  		registry.ClientOptCredentialsFile(h.settings.RegistryConfig),
   111  	)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	config := &action.Configuration{
   117  		RegistryClient: registryClient,
   118  	}
   119  
   120  	pull := action.NewPullWithOpts(action.WithConfig(config))
   121  	pull.Untar = true
   122  	pull.UntarDir = fmt.Sprintf("%s/%s/%s", chartDir, chart, targetVersion)
   123  	pull.Settings = cli.New()
   124  	pull.Version = version
   125  	err = os.RemoveAll(pull.UntarDir)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	out, err := pull.Run(fmt.Sprintf("%s/%s", repo, chart))
   131  	if out != "" {
   132  		fmt.Fprintln(h.vzHelper.GetOutputStream(), out)
   133  	}
   134  
   135  	return err
   136  }
   137  
   138  // GetChartProvenance creates the provenance data for a chart version against its upstream.
   139  func (h VerrazzanoHelmConfig) GetChartProvenance(chart string, repo string, version string) (*ChartProvenance, error) {
   140  	repoEntry, err := h.getRepoEntry(repo)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	indexPath := fmt.Sprintf("%s/%s-index.yaml", h.settings.RepositoryCache, repoEntry.Name)
   146  	if _, err = os.Stat(indexPath); err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	indexFile, err := helmrepo.LoadIndexFile(indexPath)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	chartVersion, err := indexFile.Get(chart, version)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return &ChartProvenance{
   161  		UpstreamVersion:        version,
   162  		UpstreamChartLocalPath: fmt.Sprintf("upstreams/%s", version),
   163  		UpstreamIndexEntry:     chartVersion,
   164  	}, nil
   165  }
   166  
   167  func (h VerrazzanoHelmConfig) getRepoEntry(repoURL string) (*helmrepo.Entry, error) {
   168  	for _, repoEntry := range h.helmRepoFile.Repositories {
   169  		if repoEntry.URL == repoURL {
   170  			return repoEntry, nil
   171  		}
   172  	}
   173  
   174  	return nil, fmt.Errorf("could not find repo entry")
   175  }