mirror of https://github.com/docker/docs.git
Pass upstream client's user agent through to registry on operations beyond pulls
This adds support for the passthrough on build, push, login, and search. Revamp the integration test to cover these cases and make it more robust. Use backticks instead of quoted strings for backslash-heavy string contstands. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
278d3962a8
commit
c44e7a3e63
|
@ -140,7 +140,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
|
||||||
if customHeaders == nil {
|
if customHeaders == nil {
|
||||||
customHeaders = map[string]string{}
|
customHeaders = map[string]string{}
|
||||||
}
|
}
|
||||||
customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
|
customHeaders["User-Agent"] = clientUserAgent()
|
||||||
|
|
||||||
verStr := api.DefaultVersion.String()
|
verStr := api.DefaultVersion.String()
|
||||||
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
||||||
|
@ -209,3 +209,7 @@ func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, er
|
||||||
Transport: tr,
|
Transport: tr,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clientUserAgent() string {
|
||||||
|
return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/distribution"
|
"github.com/docker/docker/distribution"
|
||||||
"github.com/docker/docker/dockerversion"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
|
@ -152,7 +151,7 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip configuration headers since request is not going to Docker daemon
|
// Skip configuration headers since request is not going to Docker daemon
|
||||||
modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(""), http.Header{})
|
modifiers := registry.DockerHeaders(clientUserAgent(), http.Header{})
|
||||||
authTransport := transport.NewTransport(base, modifiers...)
|
authTransport := transport.NewTransport(base, modifiers...)
|
||||||
pingClient := &http.Client{
|
pingClient := &http.Client{
|
||||||
Transport: authTransport,
|
Transport: authTransport,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/docker/docker/builder"
|
"github.com/docker/docker/builder"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
"io"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backend abstracts an image builder whose only purpose is to build an image referenced by an imageID.
|
// Backend abstracts an image builder whose only purpose is to build an image referenced by an imageID.
|
||||||
|
@ -14,5 +16,5 @@ type Backend interface {
|
||||||
// by the caller.
|
// by the caller.
|
||||||
//
|
//
|
||||||
// TODO: make this return a reference instead of string
|
// TODO: make this return a reference instead of string
|
||||||
Build(config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error)
|
Build(clientCtx context.Context, config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
|
||||||
closeNotifier = notifier.CloseNotify()
|
closeNotifier = notifier.CloseNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
imgID, err := br.backend.Build(buildOptions,
|
imgID, err := br.backend.Build(ctx, buildOptions,
|
||||||
builder.DockerIgnoreContext{ModifiableContext: context},
|
builder.DockerIgnoreContext{ModifiableContext: context},
|
||||||
stdout, stderr, out,
|
stdout, stderr, out,
|
||||||
closeNotifier)
|
closeNotifier)
|
||||||
|
|
|
@ -39,6 +39,6 @@ type importExportBackend interface {
|
||||||
|
|
||||||
type registryBackend interface {
|
type registryBackend interface {
|
||||||
PullImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
|
PullImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
|
||||||
PushImage(ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
|
PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
|
||||||
SearchRegistryForImages(term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
|
SearchRegistryForImages(ctx context.Context, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,7 +228,7 @@ func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter,
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
if err := s.backend.PushImage(ref, metaHeaders, authConfig, output); err != nil {
|
if err := s.backend.PushImage(ctx, ref, metaHeaders, authConfig, output); err != nil {
|
||||||
if !output.Flushed() {
|
if !output.Flushed() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -373,7 +373,7 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
|
||||||
headers[k] = v
|
headers[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query, err := s.backend.SearchRegistryForImages(r.Form.Get("term"), config, headers)
|
query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("term"), config, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
"github.com/docker/engine-api/types/events"
|
"github.com/docker/engine-api/types/events"
|
||||||
"github.com/docker/engine-api/types/filters"
|
"github.com/docker/engine-api/types/filters"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Backend is the methods that need to be implemented to provide
|
// Backend is the methods that need to be implemented to provide
|
||||||
|
@ -13,5 +14,5 @@ type Backend interface {
|
||||||
SystemVersion() types.Version
|
SystemVersion() types.Version
|
||||||
SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
|
SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
|
||||||
UnsubscribeFromEvents(chan interface{})
|
UnsubscribeFromEvents(chan interface{})
|
||||||
AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error)
|
AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ func (s *systemRouter) postAuth(ctx context.Context, w http.ResponseWriter, r *h
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
status, token, err := s.backend.AuthenticateToRegistry(config)
|
status, token, err := s.backend.AuthenticateToRegistry(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
"github.com/docker/engine-api/types/container"
|
"github.com/docker/engine-api/types/container"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -109,7 +110,7 @@ type Backend interface {
|
||||||
// Tag an image with newTag
|
// Tag an image with newTag
|
||||||
TagImage(newTag reference.Named, imageName string) error
|
TagImage(newTag reference.Named, imageName string) error
|
||||||
// Pull tells Docker to pull image referenced by `name`.
|
// Pull tells Docker to pull image referenced by `name`.
|
||||||
PullOnBuild(name string, authConfigs map[string]types.AuthConfig, output io.Writer) (Image, error)
|
PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (Image, error)
|
||||||
// ContainerAttach attaches to container.
|
// ContainerAttach attaches to container.
|
||||||
ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error
|
ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error
|
||||||
// ContainerCreate creates a new Docker container and returns potential warnings
|
// ContainerCreate creates a new Docker container and returns potential warnings
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
"github.com/docker/engine-api/types/container"
|
"github.com/docker/engine-api/types/container"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validCommitCommands = map[string]bool{
|
var validCommitCommands = map[string]bool{
|
||||||
|
@ -54,6 +55,7 @@ type Builder struct {
|
||||||
|
|
||||||
docker builder.Backend
|
docker builder.Backend
|
||||||
context builder.Context
|
context builder.Context
|
||||||
|
clientCtx context.Context
|
||||||
|
|
||||||
dockerfile *parser.Node
|
dockerfile *parser.Node
|
||||||
runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
|
runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
|
||||||
|
@ -86,7 +88,7 @@ func NewBuildManager(b builder.Backend) (bm *BuildManager) {
|
||||||
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
|
// NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
|
||||||
// If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
|
// If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
|
||||||
// will be read from the Context passed to Build().
|
// will be read from the Context passed to Build().
|
||||||
func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
|
func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, context builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = new(types.ImageBuildOptions)
|
config = new(types.ImageBuildOptions)
|
||||||
}
|
}
|
||||||
|
@ -94,6 +96,7 @@ func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, contex
|
||||||
config.BuildArgs = make(map[string]string)
|
config.BuildArgs = make(map[string]string)
|
||||||
}
|
}
|
||||||
b = &Builder{
|
b = &Builder{
|
||||||
|
clientCtx: clientCtx,
|
||||||
options: config,
|
options: config,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
|
@ -158,8 +161,8 @@ func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build creates a NewBuilder, which builds the image.
|
// Build creates a NewBuilder, which builds the image.
|
||||||
func (bm *BuildManager) Build(config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error) {
|
func (bm *BuildManager) Build(clientCtx context.Context, config *types.ImageBuildOptions, context builder.Context, stdout io.Writer, stderr io.Writer, out io.Writer, clientGone <-chan bool) (string, error) {
|
||||||
b, err := NewBuilder(config, bm.backend, context, nil)
|
b, err := NewBuilder(clientCtx, config, bm.backend, context, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -291,7 +294,7 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := NewBuilder(nil, nil, nil, nil)
|
b, err := NewBuilder(context.Background(), nil, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,7 +206,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
|
||||||
// TODO: shouldn't we error out if error is different from "not found" ?
|
// TODO: shouldn't we error out if error is different from "not found" ?
|
||||||
}
|
}
|
||||||
if image == nil {
|
if image == nil {
|
||||||
image, err = b.docker.PullOnBuild(name, b.options.AuthConfigs, b.Output)
|
image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1030,7 +1030,7 @@ func (daemon *Daemon) PullImage(ctx context.Context, ref reference.Named, metaHe
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullOnBuild tells Docker to pull image referenced by `name`.
|
// PullOnBuild tells Docker to pull image referenced by `name`.
|
||||||
func (daemon *Daemon) PullOnBuild(name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) {
|
func (daemon *Daemon) PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) {
|
||||||
ref, err := reference.ParseNamed(name)
|
ref, err := reference.ParseNamed(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1052,7 +1052,7 @@ func (daemon *Daemon) PullOnBuild(name string, authConfigs map[string]types.Auth
|
||||||
pullRegistryAuth = &resolvedConfig
|
pullRegistryAuth = &resolvedConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := daemon.PullImage(context.Background(), ref, nil, pullRegistryAuth, output); err != nil {
|
if err := daemon.PullImage(ctx, ref, nil, pullRegistryAuth, output); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return daemon.GetImage(name)
|
return daemon.GetImage(name)
|
||||||
|
@ -1069,14 +1069,14 @@ func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushImage initiates a push operation on the repository named localName.
|
// PushImage initiates a push operation on the repository named localName.
|
||||||
func (daemon *Daemon) PushImage(ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
|
func (daemon *Daemon) PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
|
||||||
// Include a buffer so that slow client connections don't affect
|
// Include a buffer so that slow client connections don't affect
|
||||||
// transfer performance.
|
// transfer performance.
|
||||||
progressChan := make(chan progress.Progress, 100)
|
progressChan := make(chan progress.Progress, 100)
|
||||||
|
|
||||||
writesDone := make(chan struct{})
|
writesDone := make(chan struct{})
|
||||||
|
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
ctx, cancelFunc := context.WithCancel(ctx)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
writeDistributionProgress(cancelFunc, outStream, progressChan)
|
writeDistributionProgress(cancelFunc, outStream, progressChan)
|
||||||
|
@ -1502,16 +1502,16 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticateToRegistry checks the validity of credentials in authConfig
|
// AuthenticateToRegistry checks the validity of credentials in authConfig
|
||||||
func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error) {
|
func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) {
|
||||||
return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(""))
|
return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchRegistryForImages queries the registry for images matching
|
// SearchRegistryForImages queries the registry for images matching
|
||||||
// term. authConfig is used to login.
|
// term. authConfig is used to login.
|
||||||
func (daemon *Daemon) SearchRegistryForImages(term string,
|
func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, term string,
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig,
|
||||||
headers map[string][]string) (*registrytypes.SearchResults, error) {
|
headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||||
return daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(""), headers)
|
return daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsShuttingDown tells whether the daemon is shutting down or not
|
// IsShuttingDown tells whether the daemon is shutting down or not
|
||||||
|
|
|
@ -49,10 +49,10 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
|
||||||
tr := transport.NewTransport(
|
tr := transport.NewTransport(
|
||||||
// TODO(tiborvass): was ReceiveTimeout
|
// TODO(tiborvass): was ReceiveTimeout
|
||||||
registry.NewTransport(tlsConfig),
|
registry.NewTransport(tlsConfig),
|
||||||
registry.DockerHeaders(dockerversion.DockerUserAgent(""), p.config.MetaHeaders)...,
|
registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
|
||||||
)
|
)
|
||||||
client := registry.HTTPClient(tr)
|
client := registry.HTTPClient(tr)
|
||||||
v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(""), p.config.MetaHeaders)
|
v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
||||||
return fallbackError{err: err}
|
return fallbackError{err: err}
|
||||||
|
|
|
@ -38,10 +38,10 @@ func (p *v1Pusher) Push(ctx context.Context) error {
|
||||||
tr := transport.NewTransport(
|
tr := transport.NewTransport(
|
||||||
// TODO(tiborvass): was NoTimeout
|
// TODO(tiborvass): was NoTimeout
|
||||||
registry.NewTransport(tlsConfig),
|
registry.NewTransport(tlsConfig),
|
||||||
registry.DockerHeaders(dockerversion.DockerUserAgent(""), p.config.MetaHeaders)...,
|
registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
|
||||||
)
|
)
|
||||||
client := registry.HTTPClient(tr)
|
client := registry.HTTPClient(tr)
|
||||||
v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(""), p.config.MetaHeaders)
|
v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
logrus.Debugf("Could not get v1 endpoint: %v", err)
|
||||||
return fallbackError{err: err}
|
return fallbackError{err: err}
|
||||||
|
|
|
@ -37,8 +37,6 @@ func (dcs dumbCredentialStore) SetRefreshToken(*url.URL, string, string) {
|
||||||
// providing timeout settings and authentication support, and also verifies the
|
// providing timeout settings and authentication support, and also verifies the
|
||||||
// remote API version.
|
// remote API version.
|
||||||
func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) {
|
func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) {
|
||||||
upstreamUA := dockerversion.GetUserAgentFromContext(ctx)
|
|
||||||
|
|
||||||
repoName := repoInfo.FullName()
|
repoName := repoInfo.FullName()
|
||||||
// If endpoint does not support CanonicalName, use the RemoteName instead
|
// If endpoint does not support CanonicalName, use the RemoteName instead
|
||||||
if endpoint.TrimHostname {
|
if endpoint.TrimHostname {
|
||||||
|
@ -59,7 +57,7 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(upstreamUA), metaHeaders)
|
modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), metaHeaders)
|
||||||
authTransport := transport.NewTransport(base, modifiers...)
|
authTransport := transport.NewTransport(base, modifiers...)
|
||||||
|
|
||||||
challengeManager, foundVersion, err := registry.PingV2Registry(endpoint, authTransport)
|
challengeManager, foundVersion, err := registry.PingV2Registry(endpoint, authTransport)
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
// DockerUserAgent is the User-Agent the Docker client uses to identify itself.
|
// DockerUserAgent is the User-Agent the Docker client uses to identify itself.
|
||||||
// In accordance with RFC 7231 (5.5.3) is of the form:
|
// In accordance with RFC 7231 (5.5.3) is of the form:
|
||||||
// [docker client's UA] UpstreamClient([upstream client's UA])
|
// [docker client's UA] UpstreamClient([upstream client's UA])
|
||||||
func DockerUserAgent(upstreamUA string) string {
|
func DockerUserAgent(ctx context.Context) string {
|
||||||
httpVersion := make([]useragent.VersionInfo, 0, 6)
|
httpVersion := make([]useragent.VersionInfo, 0, 6)
|
||||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
|
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: "go", Version: runtime.Version()})
|
||||||
|
@ -25,6 +25,7 @@ func DockerUserAgent(upstreamUA string) string {
|
||||||
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
|
httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
|
||||||
|
|
||||||
dockerUA := useragent.AppendVersions("", httpVersion...)
|
dockerUA := useragent.AppendVersions("", httpVersion...)
|
||||||
|
upstreamUA := getUserAgentFromContext(ctx)
|
||||||
if len(upstreamUA) > 0 {
|
if len(upstreamUA) > 0 {
|
||||||
ret := insertUpstreamUserAgent(upstreamUA, dockerUA)
|
ret := insertUpstreamUserAgent(upstreamUA, dockerUA)
|
||||||
return ret
|
return ret
|
||||||
|
@ -32,8 +33,8 @@ func DockerUserAgent(upstreamUA string) string {
|
||||||
return dockerUA
|
return dockerUA
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
|
// getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
|
||||||
func GetUserAgentFromContext(ctx context.Context) string {
|
func getUserAgentFromContext(ctx context.Context) string {
|
||||||
var upstreamUA string
|
var upstreamUA string
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
var ki interface{} = ctx.Value(httputils.UAStringKey)
|
var ki interface{} = ctx.Value(httputils.UAStringKey)
|
||||||
|
@ -51,7 +52,7 @@ func escapeStr(s string, charsToEscape string) string {
|
||||||
appended := false
|
appended := false
|
||||||
for _, escapeableRune := range charsToEscape {
|
for _, escapeableRune := range charsToEscape {
|
||||||
if currRune == escapeableRune {
|
if currRune == escapeableRune {
|
||||||
ret += "\\" + string(currRune)
|
ret += `\` + string(currRune)
|
||||||
appended = true
|
appended = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ func escapeStr(s string, charsToEscape string) string {
|
||||||
// string of the form:
|
// string of the form:
|
||||||
// $dockerUA UpstreamClient($upstreamUA)
|
// $dockerUA UpstreamClient($upstreamUA)
|
||||||
func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string {
|
func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string {
|
||||||
charsToEscape := "();\\" //["\\", ";", "(", ")"]string
|
charsToEscape := `();\`
|
||||||
upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape)
|
upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape)
|
||||||
return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped)
|
return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,17 +10,17 @@ import (
|
||||||
|
|
||||||
// unescapeBackslashSemicolonParens unescapes \;()
|
// unescapeBackslashSemicolonParens unescapes \;()
|
||||||
func unescapeBackslashSemicolonParens(s string) string {
|
func unescapeBackslashSemicolonParens(s string) string {
|
||||||
re := regexp.MustCompile("\\\\;")
|
re := regexp.MustCompile(`\\;`)
|
||||||
ret := re.ReplaceAll([]byte(s), []byte(";"))
|
ret := re.ReplaceAll([]byte(s), []byte(";"))
|
||||||
|
|
||||||
re = regexp.MustCompile("\\\\\\(")
|
re = regexp.MustCompile(`\\\(`)
|
||||||
ret = re.ReplaceAll([]byte(ret), []byte("("))
|
ret = re.ReplaceAll([]byte(ret), []byte("("))
|
||||||
|
|
||||||
re = regexp.MustCompile("\\\\\\)")
|
re = regexp.MustCompile(`\\\)`)
|
||||||
ret = re.ReplaceAll([]byte(ret), []byte(")"))
|
ret = re.ReplaceAll([]byte(ret), []byte(")"))
|
||||||
|
|
||||||
re = regexp.MustCompile("\\\\\\\\")
|
re = regexp.MustCompile(`\\\\`)
|
||||||
ret = re.ReplaceAll([]byte(ret), []byte("\\"))
|
ret = re.ReplaceAll([]byte(ret), []byte(`\`))
|
||||||
|
|
||||||
return string(ret)
|
return string(ret)
|
||||||
}
|
}
|
||||||
|
@ -46,14 +46,7 @@ func regexpCheckUA(c *check.C, ua string) {
|
||||||
c.Assert(bMatchUpstreamUA, check.Equals, true, check.Commentf("(Upstream) Docker Client User-Agent malformed"))
|
c.Assert(bMatchUpstreamUA, check.Equals, true, check.Commentf("(Upstream) Docker Client User-Agent malformed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestUserAgentPassThroughOnPull verifies that when an image is pulled from
|
func registerUserAgentHandler(reg *testRegistry, result *string) {
|
||||||
// a registry, the registry should see a User-Agent string of the form
|
|
||||||
// [docker engine UA] UptreamClientSTREAM-CLIENT([client UA])
|
|
||||||
func (s *DockerRegistrySuite) TestUserAgentPassThroughOnPull(c *check.C) {
|
|
||||||
reg, err := newTestRegistry(c)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
expectUpstreamUA := false
|
|
||||||
|
|
||||||
reg.registerHandler("/v2/", func(w http.ResponseWriter, r *http.Request) {
|
reg.registerHandler("/v2/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
var ua string
|
var ua string
|
||||||
|
@ -62,29 +55,66 @@ func (s *DockerRegistrySuite) TestUserAgentPassThroughOnPull(c *check.C) {
|
||||||
ua = v[0]
|
ua = v[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Assert(ua, check.Not(check.Equals), "", check.Commentf("No User-Agent found in request"))
|
*result = ua
|
||||||
if r.URL.Path == "/v2/busybox/manifests/latest" {
|
|
||||||
if expectUpstreamUA {
|
|
||||||
regexpCheckUA(c, ua)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
repoName := fmt.Sprintf("%s/busybox", reg.hostport)
|
// TestUserAgentPassThroughOnPull verifies that when an image is pulled from
|
||||||
err = s.d.Start("--insecure-registry", reg.hostport, "--disable-legacy-registry=true")
|
// a registry, the registry should see a User-Agent string of the form
|
||||||
|
// [docker engine UA] UptreamClientSTREAM-CLIENT([client UA])
|
||||||
|
func (s *DockerRegistrySuite) TestUserAgentPassThrough(c *check.C) {
|
||||||
|
var (
|
||||||
|
buildUA string
|
||||||
|
pullUA string
|
||||||
|
pushUA string
|
||||||
|
loginUA string
|
||||||
|
)
|
||||||
|
|
||||||
|
buildReg, err := newTestRegistry(c)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
registerUserAgentHandler(buildReg, &buildUA)
|
||||||
|
buildRepoName := fmt.Sprintf("%s/busybox", buildReg.hostport)
|
||||||
|
|
||||||
|
pullReg, err := newTestRegistry(c)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
registerUserAgentHandler(pullReg, &pullUA)
|
||||||
|
pullRepoName := fmt.Sprintf("%s/busybox", pullReg.hostport)
|
||||||
|
|
||||||
|
pushReg, err := newTestRegistry(c)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
registerUserAgentHandler(pushReg, &pushUA)
|
||||||
|
pushRepoName := fmt.Sprintf("%s/busybox", pushReg.hostport)
|
||||||
|
|
||||||
|
loginReg, err := newTestRegistry(c)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
registerUserAgentHandler(loginReg, &loginUA)
|
||||||
|
|
||||||
|
err = s.d.Start(
|
||||||
|
"--insecure-registry", buildReg.hostport,
|
||||||
|
"--insecure-registry", pullReg.hostport,
|
||||||
|
"--insecure-registry", pushReg.hostport,
|
||||||
|
"--insecure-registry", loginReg.hostport,
|
||||||
|
"--disable-legacy-registry=true")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
dockerfileName, cleanup, err := makefile(fmt.Sprintf("FROM %s/busybox", reg.hostport))
|
dockerfileName, cleanup1, err := makefile(fmt.Sprintf("FROM %s", buildRepoName))
|
||||||
c.Assert(err, check.IsNil, check.Commentf("Unable to create test dockerfile"))
|
c.Assert(err, check.IsNil, check.Commentf("Unable to create test dockerfile"))
|
||||||
defer cleanup()
|
defer cleanup1()
|
||||||
|
|
||||||
s.d.Cmd("build", "--file", dockerfileName, ".")
|
s.d.Cmd("build", "--file", dockerfileName, ".")
|
||||||
|
regexpCheckUA(c, buildUA)
|
||||||
|
|
||||||
s.d.Cmd("run", repoName)
|
s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", loginReg.hostport)
|
||||||
s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", reg.hostport)
|
regexpCheckUA(c, loginUA)
|
||||||
s.d.Cmd("tag", "busybox", repoName)
|
|
||||||
s.d.Cmd("push", repoName)
|
|
||||||
|
|
||||||
expectUpstreamUA = true
|
s.d.Cmd("pull", pullRepoName)
|
||||||
s.d.Cmd("pull", repoName)
|
regexpCheckUA(c, pullUA)
|
||||||
|
|
||||||
|
dockerfileName, cleanup2, err := makefile(`FROM scratch
|
||||||
|
ENV foo bar`)
|
||||||
|
c.Assert(err, check.IsNil, check.Commentf("Unable to create test dockerfile"))
|
||||||
|
defer cleanup2()
|
||||||
|
s.d.Cmd("build", "-t", pushRepoName, "--file", dockerfileName, ".")
|
||||||
|
|
||||||
|
s.d.Cmd("push", pushRepoName)
|
||||||
|
regexpCheckUA(c, pushUA)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue