@@ -105,13 +105,12 @@ func HelmInstall(
105105 return handleInstallationSuccess (rel , namespace )
106106}
107107
108- // loadChart determines the chart source and loads it appropriately
109108// LoadChart determines the chart source and loads it appropriately
110109func LoadChart (chartRef , repoURL , version string , settings * cli.EnvSettings ) (* chart.Chart , error ) {
111110 // Check if it's an OCI registry reference
112111 if strings .HasPrefix (chartRef , "oci://" ) {
113112 fmt .Printf ("🐳 Loading OCI chart from registry...\n " )
114- return LoadOCIChart (chartRef , version , settings , false )
113+ return LoadOCIChart (chartRef , version , settings , false ) // You might want to make debug configurable
115114 }
116115
117116 if repoURL != "" {
@@ -124,101 +123,155 @@ func LoadChart(chartRef, repoURL, version string, settings *cli.EnvSettings) (*c
124123 return LoadFromLocalRepo (chartRef , version , settings )
125124 }
126125
126+ // Handle local chart file or directory
127127 return loader .Load (chartRef )
128128}
129129
130130// LoadOCIChart loads a chart from an OCI registry
131131func LoadOCIChart (chartRef , version string , settings * cli.EnvSettings , debug bool ) (* chart.Chart , error ) {
132- fmt .Printf ("🐳 Loading OCI chart: %s\n " , chartRef )
132+ if debug {
133+ pterm .Printf ("Loading OCI chart: %s (version: %s)\n " , chartRef , version )
134+ }
135+
136+ // Create registry client
137+ registryClient , err := newRegistryClient (debug )
138+ if err != nil {
139+ return nil , fmt .Errorf ("failed to create registry client: %w" , err )
140+ }
133141
134- // **Use Helm's built-in OCI handling - THIS ALWAYS WORKS**
135- cpo := action.ChartPathOptions {}
142+ // Extract chart name and reference
143+ ref := strings .TrimPrefix (chartRef , "oci://" )
144+ chartName := filepath .Base (ref )
145+
146+ // Handle tag/version in the reference
147+ var tag string
148+ if idx := strings .LastIndex (chartName , ":" ); idx != - 1 {
149+ tag = chartName [idx + 1 :]
150+ chartName = chartName [:idx ]
151+ }
152+
153+ // Use provided version if not in the reference
136154 if version != "" {
137- cpo . Version = version
155+ tag = version
138156 }
139157
140- // This handles ALL cases: OCI, HTTP, local repos
141- chartPath , err := cpo .LocateChart (chartRef , settings )
142- if err != nil {
143- // If LocateChart fails, try direct load as fallback
144- fmt .Printf ("⚠️ LocateChart failed, trying fallback...\n " )
158+ // Update the reference with the proper tag
159+ if tag != "" {
160+ chartRef = fmt .Sprintf ("oci://%s:%s" , strings .TrimSuffix (ref , ":" + tag ), tag )
161+ }
145162
146- // Remove oci:// prefix for direct loading
147- cleanRef := strings .TrimPrefix (chartRef , "oci://" )
163+ // Create action configuration with registry client
164+ actionConfig := & action.Configuration {
165+ RegistryClient : registryClient ,
166+ }
148167
149- // Try to load directly (Helm v3.8+ supports this)
150- chart , loadErr := loader .Load (cleanRef )
151- if loadErr == nil {
152- return chart , nil
153- }
168+ // Create pull action
169+ pull := action .NewPullWithOpts (action .WithConfig (actionConfig ))
170+ pull .Settings = settings
171+ pull .Version = version
172+ pull .Untar = true
173+ pull .UntarDir = settings .RepositoryCache
154174
155- return nil , fmt .Errorf ("failed to load OCI chart %s: %w" , chartRef , err )
175+ // Run the pull command
176+ fmt .Printf ("⬇️ Pulling OCI chart: %s...\n " , chartRef )
177+ output , err := pull .Run (chartRef )
178+ if err != nil {
179+ return nil , fmt .Errorf ("failed to pull OCI chart: %w" , err )
156180 }
157181
158182 if debug {
159- pterm .Printf ("Chart resolved to : %s\n " , chartPath )
183+ pterm .Printf ("Pull output : %s\n " , output )
160184 }
161185
162- // **CRITICAL: Verify the file/directory exists**
163- if _ , err := os .Stat (chartPath ); os .IsNotExist (err ) {
164- // File not found at expected location
165- fmt .Printf ("⚠️ Chart not found at %s, searching cache...\n " , chartPath )
166-
167- // Search in Helm cache
168- cacheDir := settings .RepositoryCache
169- files , _ := os .ReadDir (cacheDir )
170-
171- for _ , file := range files {
172- filename := file .Name ()
173- // Look for any .tgz file or directory with Chart.yaml
174- fullPath := filepath .Join (cacheDir , filename )
175-
176- if strings .HasSuffix (filename , ".tgz" ) {
177- // Try to load .tgz file
178- if chart , err := loader .Load (fullPath ); err == nil {
179- fmt .Printf ("✅ Loaded from cache: %s\n " , filename )
180- return chart , nil
181- }
182- } else if file .IsDir () {
183- // Check if directory contains Chart.yaml
184- chartYaml := filepath .Join (fullPath , "Chart.yaml" )
185- if _ , err := os .Stat (chartYaml ); err == nil {
186- if chart , err := loader .Load (fullPath ); err == nil {
187- fmt .Printf ("✅ Loaded from directory: %s\n " , filename )
188- return chart , nil
189- }
186+ // Parse the output to find the downloaded file
187+ // The output typically looks like: "Pulled: oci://registry/path/chart:tag"
188+ // or gives us the file path
189+ chartPath := ""
190+ if strings .Contains (output , "Pulled:" ) {
191+ // Extract path from output
192+ parts := strings .Split (output , "Pulled:" )
193+ if len (parts ) > 1 {
194+ chartPath = strings .TrimSpace (parts [1 ])
195+ // Remove the OCI prefix if present
196+ chartPath = strings .TrimPrefix (chartPath , "oci://" )
197+ }
198+ }
199+
200+ // If we couldn't parse from output, try to find the chart file
201+ if chartPath == "" {
202+ // Look for the chart file in the cache directory
203+ pattern := filepath .Join (settings .RepositoryCache , fmt .Sprintf ("%s-*.tgz" , chartName ))
204+ if version != "" {
205+ pattern = filepath .Join (settings .RepositoryCache , fmt .Sprintf ("%s-%s.tgz" , chartName , version ))
206+ }
207+
208+ matches , err := filepath .Glob (pattern )
209+ if err != nil || len (matches ) == 0 {
210+ // Last resort: try to find any .tgz file with chart name
211+ allFiles , _ := os .ReadDir (settings .RepositoryCache )
212+ for _ , file := range allFiles {
213+ if strings .Contains (file .Name (), chartName ) && strings .HasSuffix (file .Name (), ".tgz" ) {
214+ chartPath = filepath .Join (settings .RepositoryCache , file .Name ())
215+ break
190216 }
191217 }
218+ } else {
219+ chartPath = matches [0 ]
192220 }
221+ }
193222
194- return nil , fmt .Errorf ("chart not found. Try: helm pull %s --version %s" , chartRef , version )
223+ if chartPath == "" {
224+ // Try direct path construction as fallback
225+ expectedName := chartName
226+ if tag != "" {
227+ expectedName = fmt .Sprintf ("%s-%s.tgz" , chartName , tag )
228+ } else {
229+ expectedName = fmt .Sprintf ("%s.tgz" , chartName )
230+ }
231+ chartPath = filepath .Join (settings .RepositoryCache , expectedName )
195232 }
196233
197- fmt .Printf ("📦 Loading chart from: %s\n " , chartPath )
198- chart , err := loader .Load (chartPath )
199- if err != nil {
200- return nil , fmt .Errorf ("failed to load chart file: %w" , err )
234+ if debug {
235+ pterm .Printf ("Loading OCI chart from: %s\n " , chartPath )
236+ }
237+
238+ // Verify the file exists
239+ if _ , err := os .Stat (chartPath ); os .IsNotExist (err ) {
240+ // List files in cache directory for debugging
241+ if debug {
242+ files , _ := os .ReadDir (settings .RepositoryCache )
243+ pterm .Printf ("Files in cache directory:\n " )
244+ for _ , file := range files {
245+ pterm .Printf (" - %s\n " , file .Name ())
246+ }
247+ }
248+ return nil , fmt .Errorf ("chart file not found at expected location: %s" , chartPath )
201249 }
202250
203- return chart , nil
251+ fmt .Printf ("📦 Loading OCI chart into memory...\n " )
252+ return loader .Load (chartPath )
204253}
205254
206255// newRegistryClient creates a registry client for OCI operations
207256func newRegistryClient (debug bool ) (* registry.Client , error ) {
208257 // Create registry client options
209258 opts := []registry.ClientOption {
210- registry .ClientOptWriter (os .Stdout ),
259+ registry .ClientOptWriter (os .Stderr ), // Use stderr for debug output
260+ registry .ClientOptDebug (debug ),
211261 }
212262
213- // Try to load credentials from various locations
263+ // Try multiple credential sources
214264 helmConfig := helmHome ()
215- credentialFiles := []string {
265+
266+ // Check for Docker config in multiple locations
267+ possibleCredFiles := []string {
216268 filepath .Join (helmConfig , "config.json" ),
217269 filepath .Join (os .Getenv ("HOME" ), ".docker/config.json" ),
218270 "/etc/docker/config.json" ,
271+ filepath .Join (os .Getenv ("HOME" ), ".helm/registry/config.json" ),
219272 }
220273
221- for _ , credFile := range credentialFiles {
274+ for _ , credFile := range possibleCredFiles {
222275 if _ , err := os .Stat (credFile ); err == nil {
223276 opts = append (opts , registry .ClientOptCredentialsFile (credFile ))
224277 if debug {
@@ -228,8 +281,18 @@ func newRegistryClient(debug bool) (*registry.Client, error) {
228281 }
229282 }
230283
284+ // Also check for environment variables
285+ if auth := os .Getenv ("HELM_REGISTRY_CONFIG" ); auth != "" {
286+ opts = append (opts , registry .ClientOptCredentialsFile (auth ))
287+ }
288+
231289 // Create and return the registry client
232- return registry .NewClient (opts ... )
290+ client , err := registry .NewClient (opts ... )
291+ if err != nil {
292+ return nil , fmt .Errorf ("failed to create registry client: %w" , err )
293+ }
294+
295+ return client , nil
233296}
234297
235298// helmHome gets the Helm home directory
0 commit comments