Skip to content

Commit 07c8e3c

Browse files
committed
feat/Add OCI Regisrty repo smurf selm
1 parent 0581c77 commit 07c8e3c

File tree

1 file changed

+173
-51
lines changed

1 file changed

+173
-51
lines changed

internal/helm/install.go

Lines changed: 173 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package helm
33
import (
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
132132
func 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
172196
func 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\nOutput: %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

Comments
 (0)