pkg/executables/helm.go (206 lines of code) (raw):
package executables
import (
"bytes"
"context"
"fmt"
"strings"
"sigs.k8s.io/yaml"
"github.com/aws/eks-anywhere/pkg/helm"
"github.com/aws/eks-anywhere/pkg/logger"
)
const (
helmPath = "helm"
insecureSkipVerifyFlag = "--insecure-skip-tls-verify"
)
type Helm struct {
executable Executable
helmConfig *helm.Config // Embed HelmOptions in Helm struct
env map[string]string
}
// NewHelm returns a new Helm executable client.
func NewHelm(executable Executable, opts ...helm.Opt) *Helm {
helmConfig := &helm.Config{
Insecure: false,
}
for _, o := range opts {
o(helmConfig)
}
env := map[string]string{
"HELM_EXPERIMENTAL_OCI": "1",
}
mergeMaps(env, helmConfig.ProxyConfig)
h := &Helm{
executable: executable,
helmConfig: helmConfig,
env: env,
}
return h
}
// mergeMaps joins the default and the provided maps together, then return the
// new map.
func mergeMaps(defaultEnv, newEnv map[string]string) {
for k, v := range newEnv {
defaultEnv[k] = v
}
}
func (h *Helm) Template(ctx context.Context, ociURI, version, namespace string, values interface{}, kubeVersion string) ([]byte, error) {
valuesYaml, err := yaml.Marshal(values)
if err != nil {
return nil, fmt.Errorf("failed marshalling values for helm template: %v", err)
}
params := []string{"template", h.url(ociURI), "--version", version, "--namespace", namespace, "--kube-version", kubeVersion}
params = h.addInsecureFlagIfProvided(params)
params = append(params, "-f", "-")
result, err := h.executable.Command(ctx, params...).WithStdIn(valuesYaml).WithEnvVars(h.env).Run()
if err != nil {
return nil, err
}
return result.Bytes(), nil
}
func (h *Helm) PullChart(ctx context.Context, ociURI, version string) error {
params := []string{"pull", h.url(ociURI), "--version", version}
params = h.addInsecureFlagIfProvided(params)
_, err := h.executable.Command(ctx, params...).
WithEnvVars(h.env).Run()
return err
}
// ShowValues get the values of a chart.
func (h *Helm) ShowValues(ctx context.Context, ociURI, version string) (bytes.Buffer, error) {
params := []string{"show", "values", h.url(ociURI), "--version", version}
out, err := h.executable.Command(ctx, params...).
WithEnvVars(h.env).Run()
return out, err
}
func (h *Helm) PushChart(ctx context.Context, chart, registry string) error {
logger.Info("Pushing", "chart", chart)
params := []string{"push", chart, registry}
params = h.addInsecureFlagIfProvided(params)
_, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).Run()
return err
}
func (h *Helm) RegistryLogin(ctx context.Context, registry, username, password string) error {
logger.Info("Logging in to helm registry", "registry", registry)
params := []string{"registry", "login", registry, "--username", username, "--password-stdin"}
if h.helmConfig.Insecure {
params = append(params, "--insecure")
}
_, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).WithStdIn([]byte(password)).Run()
return err
}
func (h *Helm) SaveChart(ctx context.Context, ociURI, version, folder string) error {
params := []string{"pull", h.url(ociURI), "--version", version, "--destination", folder}
params = h.addInsecureFlagIfProvided(params)
_, err := h.executable.Command(ctx, params...).
WithEnvVars(h.env).Run()
return err
}
func (h *Helm) InstallChartFromName(ctx context.Context, ociURI, kubeConfig, name, version string) error {
// Using upgrade --install will install the chart if it doesn't exist, but
// upgrades it otherwise, making this more idempotent than install, which
// would error out if the chart is already installed, and has no similar
// "--upgrade" flag.
params := []string{"upgrade", "--install", name, ociURI, "--version", version, "--kubeconfig", kubeConfig}
params = h.addInsecureFlagIfProvided(params)
_, err := h.executable.Command(ctx, params...).
WithEnvVars(h.env).Run()
return err
}
// InstallChart installs a helm chart to the target cluster.
//
// If kubeconfigFilePath is the empty string, it won't be passed at all.
func (h *Helm) InstallChart(ctx context.Context, chart, ociURI, version, kubeconfigFilePath, namespace, valueFilePath string, skipCRDs bool, values []string) error {
valueArgs := GetHelmValueArgs(values)
params := []string{"upgrade", "--install", chart, ociURI, "--version", version}
if skipCRDs {
params = append(params, "--skip-crds")
}
params = append(params, valueArgs...)
if kubeconfigFilePath != "" {
params = append(params, "--kubeconfig", kubeconfigFilePath)
}
if len(namespace) > 0 {
params = append(params, "--create-namespace", "--namespace", namespace)
}
if valueFilePath != "" {
params = append(params, "-f", valueFilePath)
}
params = h.addInsecureFlagIfProvided(params)
logger.Info("Installing helm chart on cluster", "chart", chart, "version", version)
_, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).Run()
return err
}
// Delete removes an installation.
func (h *Helm) Delete(ctx context.Context, kubeconfigFilePath, installName, namespace string) error {
params := []string{
"delete", installName,
"--kubeconfig", kubeconfigFilePath,
}
if namespace != "" {
params = append(params, "--namespace", namespace)
}
params = h.addInsecureFlagIfProvided(params)
if _, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).Run(); err != nil {
return fmt.Errorf("deleting helm installation %w", err)
}
logger.V(6).Info("Deleted helm installation", "name", installName, "namespace", namespace)
return nil
}
// ListCharts lists helm charts filtered on the given regex filter.
func (h *Helm) ListCharts(ctx context.Context, kubeconfigFilePath, filter string) ([]string, error) {
params := []string{"list", "-q", "--kubeconfig", kubeconfigFilePath}
if len(filter) > 0 {
params = append(params, "--filter", filter)
}
out, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).Run()
if err != nil {
return nil, err
}
charts := strings.FieldsFunc(out.String(), func(c rune) bool {
return c == '\n'
})
return charts, nil
}
func (h *Helm) addInsecureFlagIfProvided(params []string) []string {
if h.helmConfig.Insecure {
return append(params, insecureSkipVerifyFlag)
}
return params
}
func (h *Helm) url(originalURL string) string {
registryMirror := h.helmConfig.RegistryMirror
return registryMirror.ReplaceRegistry(originalURL)
}
func GetHelmValueArgs(values []string) []string {
valueArgs := []string{}
for _, value := range values {
valueArgs = append(valueArgs, "--set", value)
}
return valueArgs
}
// UpgradeInstallChartWithValuesFile runs a helm upgrade --install with the provided values file and waits for the
// chart deployment to be ready. This will upgrade the chart if it exists and install if it does not.
func (h *Helm) UpgradeInstallChartWithValuesFile(ctx context.Context, chart, ociURI, version, kubeconfigFilePath, namespace, valuesFilePath string, opts ...helm.Opt) error {
params := []string{
"upgrade",
"--install",
chart, ociURI,
"--version", version,
"--values", valuesFilePath,
"--kubeconfig", kubeconfigFilePath,
"--wait",
}
if len(namespace) > 0 {
params = append(params, "--namespace", namespace)
}
// TODO: we should not update the receiver here, so this needs to change.
// This is not thread safe.
// https://github.com/aws/eks-anywhere/issues/7176
for _, opt := range opts {
opt(h.helmConfig)
}
mergeMaps(h.env, h.helmConfig.ProxyConfig)
params = h.addInsecureFlagIfProvided(params)
params = append(params, h.helmConfig.ExtraFlags...)
_, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).Run()
return err
}
// Uninstall runs a helm uninstall for the given chart in given namespace.
func (h *Helm) Uninstall(ctx context.Context, chart, kubeconfigFilePath, namespace string, opts ...helm.Opt) error {
params := []string{
"uninstall", chart,
"--kubeconfig", kubeconfigFilePath,
"--wait",
}
if len(namespace) > 0 {
params = append(params, "--namespace", namespace)
}
for _, opt := range opts {
opt(h.helmConfig)
}
params = h.addInsecureFlagIfProvided(params)
params = append(params, h.helmConfig.ExtraFlags...)
logger.Info("Uninstalling helm chart on cluster", "chart", chart)
_, err := h.executable.Command(ctx, params...).WithEnvVars(h.env).Run()
return err
}