@@ -17,9 +17,14 @@ limitations under the License.
1717package helm
1818
1919import (
20+ "archive/tar"
21+ "compress/gzip"
2022 "encoding/json"
2123 "fmt"
24+ "io"
2225 "os"
26+ "os/exec"
27+ "path/filepath"
2328
2429 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
2530 "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
@@ -119,3 +124,86 @@ func ReleaseNamespace(namespace string, release latest.HelmRelease) (string, err
119124 }
120125 return "" , nil
121126}
127+
128+ // PullAndExtractChartFile pulls a remote Helm chart and extracts a file from it (e.g., values-prod.yaml).
129+ // chartRef: the remote chart reference (e.g., oci://...)
130+ // version: the chart version
131+ // fileInChart: the file to extract (e.g., values-prod.yaml)
132+ // Returns the path to the extracted file, and a cleanup function.
133+ func PullAndExtractChartFile (chartRef , version , fileInChart string ) (string , func (), error ) {
134+ tmpDir , err := os .MkdirTemp ("" , "skaffold-helm-pull-*" )
135+ if err != nil {
136+ return "" , nil , err
137+ }
138+ success := false
139+ cleanup := func () { os .RemoveAll (tmpDir ) }
140+ defer func () {
141+ if ! success {
142+ cleanup ()
143+ }
144+ }()
145+
146+ // Pull the chart
147+ pullArgs := []string {"pull" , chartRef , "--version" , version , "--destination" , tmpDir }
148+ cmd := exec .Command ("helm" , pullArgs ... )
149+ if out , err := cmd .CombinedOutput (); err != nil {
150+ return "" , nil , fmt .Errorf ("failed to pull chart: %v\n %s" , err , string (out ))
151+ }
152+
153+ // Find the .tgz file
154+ var tgzPath string
155+ dirEntries , err := os .ReadDir (tmpDir )
156+ if err != nil {
157+ return "" , nil , err
158+ }
159+ for _ , entry := range dirEntries {
160+ if filepath .Ext (entry .Name ()) == ".tgz" {
161+ tgzPath = filepath .Join (tmpDir , entry .Name ())
162+ break
163+ }
164+ }
165+ if tgzPath == "" {
166+ return "" , nil , fmt .Errorf ("no chart archive found after helm pull" )
167+ }
168+
169+ // Extract the requested file
170+ tgzFile , err := os .Open (tgzPath )
171+ if err != nil {
172+ return "" , nil , err
173+ }
174+ defer tgzFile .Close ()
175+ gzReader , err := gzip .NewReader (tgzFile )
176+ if err != nil {
177+ return "" , nil , err
178+ }
179+ tarReader := tar .NewReader (gzReader )
180+
181+ var extractedPath string
182+ for {
183+ hdr , err := tarReader .Next ()
184+ if err == io .EOF {
185+ break
186+ }
187+ if err != nil {
188+ return "" , nil , err
189+ }
190+ // Chart files are inside a top-level dir, e.g. united/values-prod.yaml
191+ if filepath .Base (hdr .Name ) == fileInChart {
192+ extractedPath = filepath .Join (tmpDir , fileInChart )
193+ outFile , err := os .Create (extractedPath )
194+ if err != nil {
195+ return "" , nil , err
196+ }
197+ defer outFile .Close ()
198+ if _ , err := io .Copy (outFile , tarReader ); err != nil {
199+ return "" , nil , err
200+ }
201+ break
202+ }
203+ }
204+ if extractedPath == "" {
205+ return "" , nil , fmt .Errorf ("file %s not found in chart" , fileInChart )
206+ }
207+ success = true
208+ return extractedPath , cleanup , nil
209+ }
0 commit comments