generatebundlefile/helm.go (143 lines of code) (raw):
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/go-logr/logr"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/registry"
"sigs.k8s.io/yaml"
)
// helmDriver implements PackageDriver to install packages from Helm charts.
type helmDriver struct {
cfg *action.Configuration
log logr.Logger
settings *cli.EnvSettings
}
func NewHelm(log logr.Logger, authfile string) (*helmDriver, error) {
settings := cli.New()
options := registry.ClientOptCredentialsFile(authfile)
client, err := registry.NewClient(options)
if err != nil {
return nil, fmt.Errorf("creating registry client while initializing helm driver: %w", err)
}
cfg := &action.Configuration{RegistryClient: client}
err = cfg.Init(settings.RESTClientGetter(), settings.Namespace(),
os.Getenv("HELM_DRIVER"), helmLog(log))
if err != nil {
return nil, fmt.Errorf("initializing helm driver: %w", err)
}
return &helmDriver{
cfg: cfg,
log: log,
settings: settings,
}, nil
}
// PullHelmChart will take in a a remote Helm URI and attempt to pull down the chart if it exists.
func (d *helmDriver) PullHelmChart(name, version string) (string, error) {
BundleLog.Info("Pulling Helm chart", "URI", name, "version", version)
if name == "" || version == "" {
return "", fmt.Errorf("empty input for PullHelmChart, check flags")
}
install := action.NewInstall(d.cfg)
install.ChartPathOptions.Version = version
if !strings.HasPrefix(name, "oci://") {
name = fmt.Sprintf("oci://%s", name)
}
chartPath, err := install.LocateChart(name, d.settings)
if err != nil || chartPath == "" {
return "", fmt.Errorf("running the Helm LocateChart command, you might need run an AWS ECR Login: %w", err)
}
return chartPath, nil
}
// helmLog wraps logr.Logger to make it compatible with helm's DebugLog.
func helmLog(log logr.Logger) action.DebugLog {
return func(template string, args ...interface{}) {
log.Info(fmt.Sprintf(template, args...))
}
}
// UnTarHelmChart will attempt to move the helm chart out of the helm cache, by untaring it to the pwd and creating the filesystem to unpack it into.
func UnTarHelmChart(chartRef, chartPath, dest string) error {
if chartRef == "" || chartPath == "" || dest == "" {
return fmt.Errorf("empty input value given for UnTarHelmChart")
}
_, err := os.Stat(dest)
if os.IsNotExist(err) {
if _, err := os.Stat(chartPath); err != nil {
if err := os.MkdirAll(chartPath, 0o755); err != nil {
return errors.Wrap(err, "failed to untar (mkdir)")
}
} else {
return errors.Errorf("failed to untar: a file or directory with the name %s already exists", dest)
}
} else {
if err != nil { // Checks directory check errors such as permission issues to read
return errors.Errorf("failed UnTarHelmChart: %s", err)
}
}
// Untar the files, and create the directory structure
return chartutil.ExpandFile(dest, chartRef)
}
// hasRequires checks for the existence of the requires.yaml within the helm directory
func hasRequires(helmdir string) (string, error) {
requires := filepath.Join(helmdir, "requires.yaml")
info, err := os.Stat(requires)
if os.IsNotExist(err) {
return "", err
}
if info.IsDir() {
return "", fmt.Errorf("found Dir, not requires.yaml file")
}
return requires, nil
}
// validateHelmRequires runs the parse file into struct function, and validations
func validateHelmRequires(fileName string) (*Requires, error) {
helmrequires := &Requires{}
err := parseHelmRequires(fileName, helmrequires)
if err != nil {
return nil, err
}
err = validateHelmRequiresContent(helmrequires)
if err != nil {
return nil, err
}
return helmrequires, err
}
// validateHelmRequiresContent loops over the validation tests
func validateHelmRequiresContent(helmrequires *Requires) error {
for _, v := range helmRequiresValidations {
if err := v(helmrequires); err != nil {
return err
}
}
return nil
}
var helmRequiresValidations = []func(*Requires) error{
validateHelmRequiresName,
}
func validateHelmRequiresName(helmrequires *Requires) error {
err := helmrequires.validateHelmRequiresNotEmpty()
if err != nil {
return err
}
return nil
}
// validateHelmRequiresNotEmpty checks that it has at least one image in the spec
func (helmrequires *Requires) validateHelmRequiresNotEmpty() error {
// Check if Projects are listed
if len(helmrequires.Spec.Images) < 1 {
return fmt.Errorf("should use non-empty list of images for requires")
}
return nil
}
// parseHelmRequires will attempt to unpack the requires.yaml into the Go struct `Requires`
func parseHelmRequires(fileName string, helmrequires *Requires) error {
content, err := os.ReadFile(fileName)
if err != nil {
return fmt.Errorf("unable to read file due to: %v", err)
}
for _, c := range strings.Split(string(content), YamlSeparator) {
if err = yaml.Unmarshal([]byte(c), helmrequires); err != nil {
return fmt.Errorf("unable to parse %s\nyaml: %s\n %v", fileName, string(c), err)
}
err = yaml.UnmarshalStrict([]byte(c), helmrequires)
if err != nil {
return fmt.Errorf("unable to UnmarshalStrict %s\nyaml: %s\n %v", helmrequires, string(c), err)
}
return nil
}
return fmt.Errorf("cluster spec file %s is invalid or does not contain kind %v", fileName, helmrequires)
}