diff --git a/pkg/action/pull.go b/pkg/action/pull.go index a2f53af0d..644812ca7 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -88,6 +88,7 @@ func (p *Pull) Run(chartRef string) (string, error) { RegistryClient: p.cfg.RegistryClient, RepositoryConfig: p.Settings.RepositoryConfig, RepositoryCache: p.Settings.RepositoryCache, + Debug: p.Settings.Debug, } if registry.IsOCI(chartRef) { diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 04c56e614..b3b2d324c 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -21,6 +21,8 @@ import ( "io" "io/fs" "net/url" + "net/http" + "net/http/httputil" "os" "path/filepath" "strings" @@ -69,11 +71,44 @@ type ChartDownloader struct { Getters getter.Providers // Options provide parameters to be passed along to the Getter being initialized. Options []getter.Option + Debug bool //Added to capture the --debug flag RegistryClient *registry.Client RepositoryConfig string RepositoryCache string } +type debugTransport struct { + *http.Transport + out io.Writer +} + +func (t *debugTransport) RoundTrip(req *http.Request) (*http.Response, error) { + + fmt.Fprintf(t.out, "DEBUG: HTTP request to %s\n", req.URL.String()) + // Log the request + reqDump, err := httputil.DumpRequestOut(req, false) + if err == nil { + fmt.Fprintf(t.out, "%s\n", reqDump) + } + // Perform the request + resp, err := t.Transport.RoundTrip(req) + if err != nil { + fmt.Fprintf(t.out, "HTTP request failed: %v\n", err) + return nil, err + } + // Log the response + respDump, err := httputil.DumpResponse(resp, false) + if err == nil { + fmt.Fprintf(t.out, "HTTP Response: \n%s\n", respDump) + } + if resp.StatusCode >= 300 && resp.StatusCode < 400 { + location, _ := resp.Header["Location"] + fmt.Fprintf(t.out, "DEBUG: Redirect to: %v\n", location) + } + + return resp, err +} + // DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. // // If Verify is set to VerifyNever, the verification will be nil. @@ -88,18 +123,37 @@ type ChartDownloader struct { func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *provenance.Verification, error) { u, err := c.ResolveChartVersion(ref, version) if err != nil { + fmt.Fprintf(c.Out, "DEBUG: Failed to resolve chart version: %v\n", err) return "", nil, err } - + fmt.Fprintf(c.Out, "DEBUG: Resolved chart URL: %s\n", u.String()) g, err := c.Getters.ByScheme(u.Scheme) if err != nil { + fmt.Fprintf(c.Out, "DEBUG: Failed to get getter for scheme %s: %v\n", u.Scheme, err) return "", nil, err } + fmt.Fprintf(c.Out, "DEBUG: Using getter for scheme: %s\n", u.Scheme) c.Options = append(c.Options, getter.WithAcceptHeader("application/gzip,application/octet-stream")) + // If debug is enabled, wrap the getter's HTTP client with a debug transport + if c.Debug { + dt := &debugTransport{ + Transport: http.DefaultTransport.(*http.Transport).Clone(), + out: c.Out, + } + dt.Transport.DisableKeepAlives = true + c.Options = append(c.Options, getter.WithClient(&http.Client{ + Transport: dt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + fmt.Fprintf(c.Out, "DEBUG: Following redirect to %s\n", req.URL.String()) + return nil + }, + })) + } data, err := g.Get(u.String(), c.Options...) if err != nil { + fmt.Fprintf(c.Out, "DEBUG: Failed to fetch chart: %v\n", err) return "", nil, err } @@ -117,6 +171,21 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven // If provenance is requested, verify it. ver := &provenance.Verification{} if c.Verify > VerifyNever { + // If debug is enabled, use the same debug transport for provenance file + if c.Debug { + dt := &debugTransport{ + Transport: http.DefaultTransport.(*http.Transport).Clone(), + out: c.Out, + } + dt.Transport.DisableKeepAlives = true + c.Options = append(c.Options, getter.WithClient(&http.Client{ + Transport: dt, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + fmt.Fprintf(c.Out, "DEBUG: Following redirect to %s\n", req.URL.String()) + return nil + }, + })) + } body, err := g.Get(u.String() + ".prov") if err != nil { if c.Verify == VerifyAlways { diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 5605e043f..3637caedd 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -47,6 +47,7 @@ type options struct { registryClient *registry.Client timeout time.Duration transport *http.Transport + client *http.Client } // Option allows specifying various settings configurable by the user for overriding the defaults diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 925df201e..e98379fb4 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -35,6 +35,7 @@ type HTTPGetter struct { once sync.Once } + // Get performs a Get from repo.Getter and returns the body. func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { for _, opt := range options { @@ -80,9 +81,13 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { } } - client, err := g.httpClient() - if err != nil { - return nil, err + client := g.opts.client + if client == nil { + var err error + client, err = g.httpClient() + if err != nil { + return nil, err + } } resp, err := client.Do(req) @@ -155,3 +160,10 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) { return client, nil } + +// WithClient sets the HTTP client for the getter +func WithClient(client *http.Client) Option { + return func(opts *options) { + opts.client = client + } +}