Skip to content

Commit eeafe45

Browse files
committed
feat/Add OCI Regisrty repo smurf selm
1 parent 4d31fbd commit eeafe45

File tree

1 file changed

+122
-59
lines changed

1 file changed

+122
-59
lines changed

internal/helm/install.go

Lines changed: 122 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -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
110109
func 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
131131
func 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
207256
func 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

Comments
 (0)