@@ -3,6 +3,7 @@ package helm
33import (
44 "fmt"
55 "os"
6+ "os/exec"
67 "path/filepath"
78 "strings"
89 "time"
@@ -127,13 +128,17 @@ func LoadChart(chartRef, repoURL, version string, settings *cli.EnvSettings) (*c
127128 return loader .Load (chartRef )
128129}
129130
130- // LoadOCIChart loads a chart from an OCI registry
131131// LoadOCIChart loads a chart from an OCI registry
132132func LoadOCIChart (chartRef , version string , settings * cli.EnvSettings , debug bool ) (* chart.Chart , error ) {
133133 if debug {
134134 pterm .Printf ("Loading OCI chart: %s (version: %s)\n " , chartRef , version )
135135 }
136136
137+ // Ensure cache directory exists
138+ if err := ensureHelmCacheDir (settings .RepositoryCache ); err != nil {
139+ return nil , fmt .Errorf ("failed to create helm cache directory: %w" , err )
140+ }
141+
137142 // Create registry client
138143 registryClient , err := newRegistryClient (debug )
139144 if err != nil {
@@ -154,36 +159,67 @@ func LoadOCIChart(chartRef, version string, settings *cli.EnvSettings, debug boo
154159
155160 // Run the pull command
156161 fmt .Printf ("⬇️ Pulling OCI chart: %s...\n " , chartRef )
157- _ , err = pull .Run (chartRef )
162+ downloadedFile , err : = pull .Run (chartRef )
158163 if err != nil {
159- // Check if error is about file not found (might be a temp file issue)
160- if strings . Contains ( err . Error (), "no such file or directory" ) {
161- // Try to find the actual downloaded file
162- return findAndLoadChartFromCache ( chartRef , settings , debug )
164+ // The error might be about the file path, not the pull itself
165+ if debug {
166+ fmt . Printf ( "⚠️ Pull returned error but may have succeeded: %v \n " , err )
167+ fmt . Printf ( "⚠️ Downloaded file path from pull.Run(): %s \n " , downloadedFile )
163168 }
164- return nil , fmt .Errorf ("failed to pull OCI chart: %w" , err )
169+
170+ // Continue to try loading the chart anyway
171+ return findAndLoadChartFromCache (chartRef , settings , debug )
172+ }
173+
174+ if debug {
175+ fmt .Printf ("✅ Pull reported success, downloaded to: %s\n " , downloadedFile )
165176 }
166177
167178 // Try to find and load the chart
168179 return findAndLoadChartFromCache (chartRef , settings , debug )
169180}
170181
182+ // Helper function to ensure helm cache directory exists
183+ func ensureHelmCacheDir (cacheDir string ) error {
184+ if _ , err := os .Stat (cacheDir ); os .IsNotExist (err ) {
185+ fmt .Printf ("📁 Creating helm cache directory: %s\n " , cacheDir )
186+ if err := os .MkdirAll (cacheDir , 0755 ); err != nil {
187+ return fmt .Errorf ("failed to create directory %s: %w" , cacheDir , err )
188+ }
189+ } else if err != nil {
190+ return fmt .Errorf ("failed to check cache directory: %w" , err )
191+ }
192+ return nil
193+ }
194+
171195// Helper function to find and load chart from cache
172196func findAndLoadChartFromCache (chartRef string , settings * cli.EnvSettings , debug bool ) (* chart.Chart , error ) {
197+ // Ensure directory exists (double-check)
198+ if err := ensureHelmCacheDir (settings .RepositoryCache ); err != nil {
199+ return nil , err
200+ }
201+
173202 // List all files in cache directory
174203 files , err := os .ReadDir (settings .RepositoryCache )
175204 if err != nil {
176- return nil , fmt .Errorf ("failed to read cache directory: %w" , err )
205+ return nil , fmt .Errorf ("failed to read cache directory %s : %w" , settings . RepositoryCache , err )
177206 }
178207
179208 if debug {
180209 fmt .Printf ("📁 Searching for chart in cache directory: %s\n " , settings .RepositoryCache )
181210 fmt .Printf ("📁 Files found (%d):\n " , len (files ))
182211 for i , file := range files {
183- fmt .Printf (" %d. %s (dir: %v)\n " , i + 1 , file .Name (), file .IsDir ())
212+ info , _ := file .Info ()
213+ fmt .Printf (" %d. %s (size: %d)\n " , i + 1 , file .Name (), info .Size ())
184214 }
185215 }
186216
217+ // If no files found, try a different approach
218+ if len (files ) == 0 {
219+ fmt .Println ("⚠️ No files found in cache, attempting direct helm CLI pull..." )
220+ return pullWithHelmCLI (chartRef , settings , debug )
221+ }
222+
187223 // Extract chart name from OCI reference
188224 ref := strings .TrimPrefix (chartRef , "oci://" )
189225 baseName := filepath .Base (ref )
@@ -198,74 +234,153 @@ func findAndLoadChartFromCache(chartRef string, settings *cli.EnvSettings, debug
198234 fmt .Printf ("🔍 Looking for chart matching: %s\n " , chartName )
199235 }
200236
201- // Look for .tgz files
202- var tgzFiles []string
237+ // Look for .tgz files (most common)
203238 for _ , file := range files {
204- if ! file .IsDir () && strings .HasSuffix (file .Name (), ".tgz" ) {
239+ if ! file .IsDir () && strings .HasSuffix (strings . ToLower ( file .Name () ), ".tgz" ) {
205240 fullPath := filepath .Join (settings .RepositoryCache , file .Name ())
206- tgzFiles = append (tgzFiles , fullPath )
207241
208242 if debug {
209- fmt .Printf (" Found .tgz: %s\n " , file .Name ())
243+ fmt .Printf (" Trying .tgz file: %s\n " , file .Name ())
244+ }
245+
246+ chartObj , err := loader .Load (fullPath )
247+ if err == nil {
248+ if debug {
249+ fmt .Printf ("✅ Successfully loaded chart from: %s\n " , fullPath )
250+ }
251+ return chartObj , nil
252+ }
253+
254+ if debug {
255+ fmt .Printf ("❌ Failed to load as chart: %v\n " , err )
210256 }
211257 }
212258 }
213259
214- // If no .tgz files found, check for any files that might be the chart
215- if len (tgzFiles ) == 0 {
216- if debug {
217- fmt .Println ("⚠️ No .tgz files found, checking all files..." )
218- }
260+ // Try any file (might not have .tgz extension)
261+ for _ , file := range files {
262+ if ! file .IsDir () {
263+ fullPath := filepath .Join (settings .RepositoryCache , file .Name ())
219264
220- // Look for any file containing the chart name
221- for _ , file := range files {
222- if ! file .IsDir () && strings .Contains (strings .ToLower (file .Name ()), strings .ToLower (chartName )) {
223- fullPath := filepath .Join (settings .RepositoryCache , file .Name ())
224- tgzFiles = append (tgzFiles , fullPath )
265+ if debug {
266+ fmt .Printf (" Trying any file: %s\n " , file .Name ())
267+ }
225268
269+ chartObj , err := loader .Load (fullPath )
270+ if err == nil {
226271 if debug {
227- fmt .Printf (" Found matching file : %s\n " , file . Name () )
272+ fmt .Printf ("✅ Successfully loaded chart from : %s\n " , fullPath )
228273 }
274+ return chartObj , nil
229275 }
230276 }
231277 }
232278
233- // Try to load each potential file
234- for _ , filePath := range tgzFiles {
235- if debug {
236- fmt .Printf ("🔄 Attempting to load: %s\n " , filePath )
237- }
279+ return nil , fmt .Errorf ("no valid chart file found in cache directory: %s" , settings .RepositoryCache )
280+ }
238281
239- chartObj , err := loader .Load (filePath )
240- if err == nil {
241- if debug {
242- fmt .Printf ("✅ Successfully loaded chart from: %s\n " , filePath )
243- }
244- return chartObj , nil
282+ // Fallback function using helm CLI directly
283+ func pullWithHelmCLI (chartRef string , settings * cli.EnvSettings , debug bool ) (* chart.Chart , error ) {
284+ fmt .Printf ("🔄 Using helm CLI for OCI pull...\n " )
285+
286+ // Ensure cache directory exists
287+ if err := ensureHelmCacheDir (settings .RepositoryCache ); err != nil {
288+ return nil , err
289+ }
290+
291+ // Create a temporary directory
292+ tempDir , err := os .MkdirTemp ("" , "helm-oci-*" )
293+ if err != nil {
294+ return nil , fmt .Errorf ("failed to create temp directory: %w" , err )
295+ }
296+ defer os .RemoveAll (tempDir )
297+
298+ // Build helm command
299+ args := []string {"pull" , chartRef , "--destination" , tempDir }
300+
301+ // Add version if specified
302+ if strings .Contains (chartRef , ":" ) {
303+ // Version might be in the chartRef itself
304+ fmt .Printf ("📦 Chart reference includes version/tag\n " )
305+ } else {
306+ // Parse version from chartRef or use default
307+ ref := strings .TrimPrefix (chartRef , "oci://" )
308+ if idx := strings .LastIndex (ref , ":" ); idx != - 1 {
309+ version := ref [idx + 1 :]
310+ args = append (args , "--version" , version )
245311 }
312+ }
246313
247- if debug {
248- fmt .Printf ("❌ Failed to load %s: %v\n " , filePath , err )
314+ if debug {
315+ args = append (args , "--debug" )
316+ }
317+
318+ // Execute helm pull
319+ cmd := exec .Command ("helm" , args ... )
320+ cmd .Env = os .Environ ()
321+
322+ // Enable OCI experimental feature
323+ cmd .Env = append (cmd .Env , "HELM_EXPERIMENTAL_OCI=1" )
324+
325+ // Handle GitHub Container Registry authentication
326+ if strings .Contains (chartRef , "ghcr.io" ) {
327+ fmt .Println ("🔑 Detected GHCR registry" )
328+ if token := os .Getenv ("GITHUB_TOKEN" ); token != "" {
329+ fmt .Println ("🔑 Using GITHUB_TOKEN for authentication" )
330+ // Helm should automatically use GITHUB_TOKEN for ghcr.io
331+ cmd .Env = append (cmd .Env , "GITHUB_TOKEN=" + token )
249332 }
250333 }
251334
252- // Last resort: try to find any file that can be loaded as a chart
253- if len (tgzFiles ) == 0 {
254- for _ , file := range files {
255- if ! file .IsDir () {
256- filePath := filepath .Join (settings .RepositoryCache , file .Name ())
257- chartObj , err := loader .Load (filePath )
258- if err == nil {
259- if debug {
260- fmt .Printf ("✅ Successfully loaded chart from unexpected file: %s\n " , filePath )
261- }
262- return chartObj , nil
335+ output , err := cmd .CombinedOutput ()
336+ if debug {
337+ fmt .Printf ("📋 Helm CLI output:\n %s\n " , output )
338+ }
339+
340+ if err != nil {
341+ return nil , fmt .Errorf ("helm CLI pull failed: %w\n Output: %s" , err , output )
342+ }
343+
344+ // Find and load the downloaded chart
345+ files , err := os .ReadDir (tempDir )
346+ if err != nil {
347+ return nil , fmt .Errorf ("failed to read temp directory: %w" , err )
348+ }
349+
350+ for _ , file := range files {
351+ if ! file .IsDir () {
352+ fullPath := filepath .Join (tempDir , file .Name ())
353+ fmt .Printf ("📦 Attempting to load: %s\n " , file .Name ())
354+
355+ chartObj , err := loader .Load (fullPath )
356+ if err == nil {
357+ fmt .Printf ("✅ Successfully loaded chart\n " )
358+
359+ // Copy to cache directory for future use
360+ cachePath := filepath .Join (settings .RepositoryCache , file .Name ())
361+ if err := copyFile (fullPath , cachePath ); err == nil && debug {
362+ fmt .Printf ("📁 Copied to cache: %s\n " , cachePath )
263363 }
364+
365+ return chartObj , nil
366+ }
367+
368+ if debug {
369+ fmt .Printf ("❌ Failed to load: %v\n " , err )
264370 }
265371 }
266372 }
267373
268- return nil , fmt .Errorf ("no chart file found in cache directory: %s" , settings .RepositoryCache )
374+ return nil , fmt .Errorf ("no chart file found after helm pull" )
375+ }
376+
377+ // Helper function to copy file
378+ func copyFile (src , dst string ) error {
379+ input , err := os .ReadFile (src )
380+ if err != nil {
381+ return err
382+ }
383+ return os .WriteFile (dst , input , 0644 )
269384}
270385
271386// newRegistryClient creates a registry client for OCI operations
@@ -320,7 +435,14 @@ func helmHome() string {
320435 return home
321436 }
322437 userHome , _ := os .UserHomeDir ()
323- return filepath .Join (userHome , ".helm" )
438+ helmPath := filepath .Join (userHome , ".helm" )
439+
440+ // Ensure directory exists
441+ if _ , err := os .Stat (helmPath ); os .IsNotExist (err ) {
442+ os .MkdirAll (helmPath , 0755 )
443+ }
444+
445+ return helmPath
324446}
325447
326448// loadFromLocalRepo loads a chart from a local repository
0 commit comments