From 5adcfa53f40aa6ae77f9c5e37f424cada251dd63 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 31 Jan 2025 12:12:23 -0800 Subject: [PATCH] Add `User-Agent` to all our outgoing requests This is something we should've been doing all along because we've been contributing to the thundering herd of useless `Go-http-client/1.1` agent values hitting Docker Hub. --- registry/client.go | 7 +++++++ registry/user-agent.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 registry/user-agent.go diff --git a/registry/client.go b/registry/client.go index 4b9f0c4..c279e31 100644 --- a/registry/client.go +++ b/registry/client.go @@ -30,6 +30,13 @@ func Client(host string, opts *ociclient.Options) (ociregistry.Interface, error) clientOptions.Transport = http.DefaultTransport } + // make sure we set User-Agent explicitly; this is first so that everything else has an explicit layer at the bottom setting User-Agent so we don't miss any requests + // IMPORTANT: this wrapper stays first! (https://github.com/cue-labs/oci/issues/37#issuecomment-2628321222) + clientOptions.Transport = &userAgentRoundTripper{ + roundTripper: clientOptions.Transport, + userAgent: "https://github.com/docker-library/meta-scripts", // TODO allow this to be modified via environment variable + } + // if we have a rate limiter configured for this registry, shim it in if limiter, ok := registryRateLimiters[host]; ok { clientOptions.Transport = &rateLimitedRetryingRoundTripper{ diff --git a/registry/user-agent.go b/registry/user-agent.go new file mode 100644 index 0000000..1c818c9 --- /dev/null +++ b/registry/user-agent.go @@ -0,0 +1,31 @@ +package registry + +// https://github.com/docker-library/meta-scripts/issues/111 +// https://github.com/cue-labs/oci/issues/37 + +import ( + "fmt" + "maps" + "net/http" +) + +// an implementation of [net/http.RoundTripper] that transparently injects User-Agent (as a wrapper around another [net/http.RoundTripper]) +type userAgentRoundTripper struct { + roundTripper http.RoundTripper + userAgent string +} + +func (d *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + // if d is nil or if d.roundTripper is nil, we'll just let the runtime panic because those are both 100% coding errors in the consuming code + + if d.userAgent == "" { + // arguably we could `panic` here too since this is *also* a coding error, but it'd be pretty reasonable to source this from an environment variable so `panic` is perhaps a bit user-hostile + return nil, fmt.Errorf("missing userAgent in userAgentRoundTripper! (request %s)", req.URL) + } + + // https://github.com/cue-lang/cue/blob/0a43336cccf3b6fc632e976912d74fb2c9670557/internal/cueversion/transport.go#L27-L34 + reqClone := *req + reqClone.Header = maps.Clone(reqClone.Header) + reqClone.Header.Set("User-Agent", d.userAgent) + return d.roundTripper.RoundTrip(&reqClone) +}