diff --git a/api/client/trust.go b/api/client/trust.go index d06db5d938..220c3dbe32 100644 --- a/api/client/trust.go +++ b/api/client/trust.go @@ -21,6 +21,7 @@ import ( "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/cliconfig" "github.com/docker/docker/distribution" + "github.com/docker/docker/dockerversion" "github.com/docker/docker/pkg/jsonmessage" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/reference" @@ -139,7 +140,7 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut } // Skip configuration headers since request is not going to Docker daemon - modifiers := registry.DockerHeaders(http.Header{}) + modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(), http.Header{}) authTransport := transport.NewTransport(base, modifiers...) pingClient := &http.Client{ Transport: authTransport, diff --git a/daemon/daemon.go b/daemon/daemon.go index 9e0e77e350..81548a00de 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -40,6 +40,7 @@ import ( "github.com/docker/docker/distribution" dmetadata "github.com/docker/docker/distribution/metadata" "github.com/docker/docker/distribution/xfer" + "github.com/docker/docker/dockerversion" derr "github.com/docker/docker/errors" "github.com/docker/docker/image" "github.com/docker/docker/image/tarexport" @@ -1452,7 +1453,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, // AuthenticateToRegistry checks the validity of credentials in authConfig func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error) { - return daemon.RegistryService.Auth(authConfig) + return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent()) } // SearchRegistryForImages queries the registry for images matching @@ -1460,7 +1461,7 @@ func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (stri func (daemon *Daemon) SearchRegistryForImages(term string, authConfig *types.AuthConfig, headers map[string][]string) (*registrytypes.SearchResults, error) { - return daemon.RegistryService.Search(term, authConfig, headers) + return daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(), headers) } // IsShuttingDown tells whether the daemon is shutting down or not diff --git a/distribution/pull.go b/distribution/pull.go index 5f38a67673..d9ecdfdbc9 100644 --- a/distribution/pull.go +++ b/distribution/pull.go @@ -20,7 +20,6 @@ import ( // ImagePullConfig stores pull configuration. type ImagePullConfig struct { // MetaHeaders stores HTTP headers with metadata about the image - // (DockerHeaders with prefix X-Meta- in the request). MetaHeaders map[string][]string // AuthConfig holds authentication credentials for authenticating with // the registry. diff --git a/distribution/pull_v1.go b/distribution/pull_v1.go index 25e63343bb..312f7e30e2 100644 --- a/distribution/pull_v1.go +++ b/distribution/pull_v1.go @@ -14,6 +14,7 @@ import ( "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/distribution/metadata" "github.com/docker/docker/distribution/xfer" + "github.com/docker/docker/dockerversion" "github.com/docker/docker/image" "github.com/docker/docker/image/v1" "github.com/docker/docker/layer" @@ -47,10 +48,10 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error { tr := transport.NewTransport( // TODO(tiborvass): was ReceiveTimeout registry.NewTransport(tlsConfig), - registry.DockerHeaders(p.config.MetaHeaders)..., + registry.DockerHeaders(dockerversion.DockerUserAgent(), p.config.MetaHeaders)..., ) client := registry.HTTPClient(tr) - v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders) + v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(), p.config.MetaHeaders) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return fallbackError{err: err} diff --git a/distribution/push.go b/distribution/push.go index 445f6bb6bd..113aa14e1c 100644 --- a/distribution/push.go +++ b/distribution/push.go @@ -22,7 +22,6 @@ import ( // ImagePushConfig stores push configuration. type ImagePushConfig struct { // MetaHeaders store HTTP headers with metadata about the image - // (DockerHeaders with prefix X-Meta- in the request). MetaHeaders map[string][]string // AuthConfig holds authentication credentials for authenticating with // the registry. diff --git a/distribution/push_v1.go b/distribution/push_v1.go index 6ae2e43ead..8be1df97f1 100644 --- a/distribution/push_v1.go +++ b/distribution/push_v1.go @@ -8,6 +8,7 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/distribution/metadata" + "github.com/docker/docker/dockerversion" "github.com/docker/docker/image" "github.com/docker/docker/image/v1" "github.com/docker/docker/layer" @@ -38,10 +39,10 @@ func (p *v1Pusher) Push(ctx context.Context) error { tr := transport.NewTransport( // TODO(tiborvass): was NoTimeout registry.NewTransport(tlsConfig), - registry.DockerHeaders(p.config.MetaHeaders)..., + registry.DockerHeaders(dockerversion.DockerUserAgent(), p.config.MetaHeaders)..., ) client := registry.HTTPClient(tr) - v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders) + v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(), p.config.MetaHeaders) if err != nil { logrus.Debugf("Could not get v1 endpoint: %v", err) return fallbackError{err: err} diff --git a/distribution/registry.go b/distribution/registry.go index 1d4a2c4efe..42b400bd67 100644 --- a/distribution/registry.go +++ b/distribution/registry.go @@ -14,6 +14,7 @@ import ( "github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/distribution/xfer" + "github.com/docker/docker/dockerversion" "github.com/docker/docker/registry" "github.com/docker/engine-api/types" "golang.org/x/net/context" @@ -67,7 +68,7 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end DisableKeepAlives: true, } - modifiers := registry.DockerHeaders(metaHeaders) + modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(), metaHeaders) authTransport := transport.NewTransport(base, modifiers...) pingClient := &http.Client{ Transport: authTransport, diff --git a/dockerversion/useragent.go b/dockerversion/useragent.go new file mode 100644 index 0000000000..47cc2d491d --- /dev/null +++ b/dockerversion/useragent.go @@ -0,0 +1,24 @@ +package dockerversion + +import ( + "runtime" + + "github.com/docker/docker/pkg/parsers/kernel" + "github.com/docker/docker/pkg/useragent" +) + +// DockerUserAgent is the User-Agent the Docker client uses to identify itself. +// It is populated from version information of different components. +func DockerUserAgent() string { + httpVersion := make([]useragent.VersionInfo, 0, 6) + httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version}) + httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()}) + httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit}) + if kernelVersion, err := kernel.GetKernelVersion(); err == nil { + httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()}) + } + httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS}) + httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH}) + + return useragent.AppendVersions("", httpVersion...) +} diff --git a/registry/endpoint.go b/registry/endpoint.go index 258a9c2854..ef00431f43 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -45,12 +45,12 @@ func scanForAPIVersion(address string) (string, APIVersion) { // NewEndpoint parses the given address to return a registry endpoint. v can be used to // specify a specific endpoint version -func NewEndpoint(index *registrytypes.IndexInfo, metaHeaders http.Header, v APIVersion) (*Endpoint, error) { +func NewEndpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders http.Header, v APIVersion) (*Endpoint, error) { tlsConfig, err := newTLSConfig(index.Name, index.Secure) if err != nil { return nil, err } - endpoint, err := newEndpoint(GetAuthConfigKey(index), tlsConfig, metaHeaders) + endpoint, err := newEndpoint(GetAuthConfigKey(index), tlsConfig, userAgent, metaHeaders) if err != nil { return nil, err } @@ -91,7 +91,7 @@ func validateEndpoint(endpoint *Endpoint) error { return nil } -func newEndpoint(address string, tlsConfig *tls.Config, metaHeaders http.Header) (*Endpoint, error) { +func newEndpoint(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*Endpoint, error) { var ( endpoint = new(Endpoint) trimmedAddress string @@ -112,7 +112,7 @@ func newEndpoint(address string, tlsConfig *tls.Config, metaHeaders http.Header) // TODO(tiborvass): make sure a ConnectTimeout transport is used tr := NewTransport(tlsConfig) - endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...)) + endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(userAgent, metaHeaders)...)) return endpoint, nil } diff --git a/registry/endpoint_test.go b/registry/endpoint_test.go index ee301dbd88..4677e0c9e5 100644 --- a/registry/endpoint_test.go +++ b/registry/endpoint_test.go @@ -19,7 +19,7 @@ func TestEndpointParse(t *testing.T) { {"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"}, } for _, td := range testData { - e, err := newEndpoint(td.str, nil, nil) + e, err := newEndpoint(td.str, nil, "", nil) if err != nil { t.Errorf("%q: %s", td.str, err) } diff --git a/registry/registry.go b/registry/registry.go index 643fa56e6a..f4ddc15a0e 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -21,9 +21,6 @@ import ( "github.com/docker/distribution/registry/api/v2" "github.com/docker/distribution/registry/client" "github.com/docker/distribution/registry/client/transport" - "github.com/docker/docker/dockerversion" - "github.com/docker/docker/pkg/parsers/kernel" - "github.com/docker/docker/pkg/useragent" "github.com/docker/go-connections/tlsconfig" ) @@ -34,23 +31,7 @@ var ( errLoginRequired = errors.New("Authentication is required.") ) -// dockerUserAgent is the User-Agent the Docker client uses to identify itself. -// It is populated on init(), comprising version information of different components. -var dockerUserAgent string - func init() { - httpVersion := make([]useragent.VersionInfo, 0, 6) - httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: dockerversion.Version}) - httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()}) - httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: dockerversion.GitCommit}) - if kernelVersion, err := kernel.GetKernelVersion(); err == nil { - httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()}) - } - httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS}) - httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH}) - - dockerUserAgent = useragent.AppendVersions("", httpVersion...) - if runtime.GOOS != "linux" { V2Only = true } @@ -130,12 +111,13 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { return nil } -// DockerHeaders returns request modifiers that ensure requests have -// the User-Agent header set to dockerUserAgent and that metaHeaders -// are added. -func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier { - modifiers := []transport.RequestModifier{ - transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}), +// DockerHeaders returns request modifiers with a User-Agent and metaHeaders +func DockerHeaders(userAgent string, metaHeaders http.Header) []transport.RequestModifier { + modifiers := []transport.RequestModifier{} + if userAgent != "" { + modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{ + "User-Agent": []string{userAgent}, + })) } if metaHeaders != nil { modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders)) diff --git a/registry/registry_test.go b/registry/registry_test.go index 7630d9a523..98a3aa1c8b 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -25,12 +25,13 @@ const ( func spawnTestRegistrySession(t *testing.T) *Session { authConfig := &types.AuthConfig{} - endpoint, err := NewEndpoint(makeIndex("/v1/"), nil, APIVersionUnknown) + endpoint, err := NewEndpoint(makeIndex("/v1/"), "", nil, APIVersionUnknown) if err != nil { t.Fatal(err) } + userAgent := "docker test client" var tr http.RoundTripper = debugTransport{NewTransport(nil), t.Log} - tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...) + tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(userAgent, nil)...) client := HTTPClient(tr) r, err := NewSession(client, authConfig, endpoint) if err != nil { @@ -52,7 +53,7 @@ func spawnTestRegistrySession(t *testing.T) *Session { func TestPingRegistryEndpoint(t *testing.T) { testPing := func(index *registrytypes.IndexInfo, expectedStandalone bool, assertMessage string) { - ep, err := NewEndpoint(index, nil, APIVersionUnknown) + ep, err := NewEndpoint(index, "", nil, APIVersionUnknown) if err != nil { t.Fatal(err) } @@ -72,7 +73,7 @@ func TestPingRegistryEndpoint(t *testing.T) { func TestEndpoint(t *testing.T) { // Simple wrapper to fail test if err != nil expandEndpoint := func(index *registrytypes.IndexInfo) *Endpoint { - endpoint, err := NewEndpoint(index, nil, APIVersionUnknown) + endpoint, err := NewEndpoint(index, "", nil, APIVersionUnknown) if err != nil { t.Fatal(err) } @@ -81,7 +82,7 @@ func TestEndpoint(t *testing.T) { assertInsecureIndex := func(index *registrytypes.IndexInfo) { index.Secure = true - _, err := NewEndpoint(index, nil, APIVersionUnknown) + _, err := NewEndpoint(index, "", nil, APIVersionUnknown) assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index") assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index") index.Secure = false @@ -89,7 +90,7 @@ func TestEndpoint(t *testing.T) { assertSecureIndex := func(index *registrytypes.IndexInfo) { index.Secure = true - _, err := NewEndpoint(index, nil, APIVersionUnknown) + _, err := NewEndpoint(index, "", nil, APIVersionUnknown) assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index") assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index") index.Secure = false @@ -155,7 +156,7 @@ func TestEndpoint(t *testing.T) { } for _, address := range badEndpoints { index.Name = address - _, err := NewEndpoint(index, nil, APIVersionUnknown) + _, err := NewEndpoint(index, "", nil, APIVersionUnknown) checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint") } } diff --git a/registry/service.go b/registry/service.go index dbdf173113..861cdb4645 100644 --- a/registry/service.go +++ b/registry/service.go @@ -28,7 +28,7 @@ func NewService(options *Options) *Service { // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was successful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(authConfig *types.AuthConfig) (string, error) { +func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (string, error) { addr := authConfig.ServerAddress if addr == "" { // Use the official registry address if not specified. @@ -45,7 +45,7 @@ func (s *Service) Auth(authConfig *types.AuthConfig) (string, error) { endpointVersion = APIVersion2 } - endpoint, err := NewEndpoint(index, nil, endpointVersion) + endpoint, err := NewEndpoint(index, userAgent, nil, endpointVersion) if err != nil { return "", err } @@ -72,7 +72,7 @@ func splitReposSearchTerm(reposName string) (string, string) { // Search queries the public registry for images matching the specified // search terms, and returns the results. -func (s *Service) Search(term string, authConfig *types.AuthConfig, headers map[string][]string) (*registrytypes.SearchResults, error) { +func (s *Service) Search(term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) { if err := validateNoSchema(term); err != nil { return nil, err } @@ -85,7 +85,7 @@ func (s *Service) Search(term string, authConfig *types.AuthConfig, headers map[ } // *TODO: Search multiple indexes. - endpoint, err := NewEndpoint(index, http.Header(headers), APIVersionUnknown) + endpoint, err := NewEndpoint(index, userAgent, http.Header(headers), APIVersionUnknown) if err != nil { return nil, err } @@ -129,8 +129,8 @@ type APIEndpoint struct { } // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint -func (e APIEndpoint) ToV1Endpoint(metaHeaders http.Header) (*Endpoint, error) { - return newEndpoint(e.URL, e.TLSConfig, metaHeaders) +func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*Endpoint, error) { + return newEndpoint(e.URL, e.TLSConfig, userAgent, metaHeaders) } // TLSConfig constructs a client TLS configuration based on server defaults