package helm import ( "fmt" "log" "os" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/registry" // [NEW] Required for OCI "helm.sh/helm/v3/pkg/storage/driver" "k8s.io/cli-runtime/pkg/genericclioptions" ) type Config struct { Namespace string ReleaseName string RepoURL string ChartName string Version string Values map[string]interface{} } func Apply(cfg Config) error { settings := cli.New() // 1. Initialize Action Config actionConfig := new(action.Configuration) getter := genericclioptions.NewConfigFlags(false) if err := actionConfig.Init(getter, cfg.Namespace, os.Getenv("HELM_DRIVER"), log.Printf); err != nil { return fmt.Errorf("failed to init helm config: %w", err) } // 2. [NEW] Initialize OCI Registry Client // This tells Helm how to talk to ghcr.io, docker.io, etc. registryClient, err := registry.NewClient( registry.ClientOptDebug(true), registry.ClientOptEnableCache(true), registry.ClientOptCredentialsFile(settings.RegistryConfig), // Uses ~/.config/helm/registry/config.json ) if err != nil { return fmt.Errorf("failed to init registry client: %w", err) } actionConfig.RegistryClient = registryClient // 3. Setup Install Action client := action.NewInstall(actionConfig) client.Version = cfg.Version client.Namespace = cfg.Namespace client.ReleaseName = cfg.ReleaseName client.CreateNamespace = true if cfg.RepoURL != "" { client.RepoURL = cfg.RepoURL } // 4. Locate Chart (Now supports oci:// because RegistryClient is set) cp, err := client.ChartPathOptions.LocateChart(cfg.ChartName, settings) if err != nil { return fmt.Errorf("failed to locate chart %s: %w", cfg.ChartName, err) } chart, err := loader.Load(cp) if err != nil { return fmt.Errorf("failed to load chart: %w", err) } // 5. Install or Upgrade histClient := action.NewHistory(actionConfig) histClient.Max = 1 if _, err := histClient.Run(cfg.ReleaseName); err == driver.ErrReleaseNotFound { fmt.Printf("Installing OCI Release %s...\n", cfg.ReleaseName) _, err := client.Run(chart, cfg.Values) return err } else if err != nil { return err } fmt.Printf("Upgrading OCI Release %s...\n", cfg.ReleaseName) upgrade := action.NewUpgrade(actionConfig) upgrade.Version = cfg.Version upgrade.Namespace = cfg.Namespace // Important: Upgrade also needs the RegistryClient, but it shares 'actionConfig' // so it is already set up. if cfg.RepoURL != "" { upgrade.RepoURL = cfg.RepoURL } _, err = upgrade.Run(cfg.ReleaseName, chart, cfg.Values) return err } func Uninstall(cfg Config) error { settings := cli.New() // 1. Initialize Action Config (Same as Apply) actionConfig := new(action.Configuration) getter := genericclioptions.NewConfigFlags(false) if err := actionConfig.Init(getter, cfg.Namespace, os.Getenv("HELM_DRIVER"), log.Printf); err != nil { return fmt.Errorf("failed to init helm config: %w", err) } // 2. Initialize OCI Registry Client (Crucial for OCI charts) registryClient, err := registry.NewClient( registry.ClientOptDebug(true), registry.ClientOptEnableCache(true), registry.ClientOptCredentialsFile(settings.RegistryConfig), ) if err != nil { return fmt.Errorf("failed to init registry client: %w", err) } actionConfig.RegistryClient = registryClient // 3. Run Uninstall client := action.NewUninstall(actionConfig) // Don't fail if it's already gone _, err = client.Run(cfg.ReleaseName) if err != nil && err != driver.ErrReleaseNotFound { return fmt.Errorf("failed to uninstall release: %w", err) } fmt.Printf("✅ Uninstalled Release %s\n", cfg.ReleaseName) return nil }