Browse Source

Merge pull request #21373 from aaronlehmann/client-user-agent-registry-operations

Pass upstream client's user agent through to registry on operations beyond pulls
Arnaud Porterie 9 years ago
parent
commit
9f327b4c28

+ 5 - 1
api/client/cli.go

@@ -140,7 +140,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
 		if customHeaders == nil {
 			customHeaders = map[string]string{}
 		}
-		customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
+		customHeaders["User-Agent"] = clientUserAgent()
 
 		verStr := api.DefaultVersion.String()
 		if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
@@ -209,3 +209,7 @@ func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, er
 		Transport: tr,
 	}, nil
 }
+
+func clientUserAgent() string {
+	return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
+}

+ 1 - 2
api/client/trust.go

@@ -23,7 +23,6 @@ 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"
@@ -152,7 +151,7 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut
 	}
 
 	// 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...)
 	pingClient := &http.Client{
 		Transport: authTransport,

+ 4 - 2
api/server/router/build/backend.go

@@ -1,9 +1,11 @@
 package build
 
 import (
+	"io"
+
 	"github.com/docker/docker/builder"
 	"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.
@@ -14,5 +16,5 @@ type Backend interface {
 	// by the caller.
 	//
 	// 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)
 }

+ 1 - 1
api/server/router/build/build_routes.go

@@ -171,7 +171,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
 		closeNotifier = notifier.CloseNotify()
 	}
 
-	imgID, err := br.backend.Build(buildOptions,
+	imgID, err := br.backend.Build(ctx, buildOptions,
 		builder.DockerIgnoreContext{ModifiableContext: context},
 		stdout, stderr, out,
 		closeNotifier)

+ 2 - 2
api/server/router/image/backend.go

@@ -39,6 +39,6 @@ type importExportBackend interface {
 
 type registryBackend interface {
 	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
-	SearchRegistryForImages(term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
+	PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
+	SearchRegistryForImages(ctx context.Context, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
 }

+ 2 - 2
api/server/router/image/image_routes.go

@@ -228,7 +228,7 @@ func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter,
 
 	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() {
 			return err
 		}
@@ -373,7 +373,7 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
 			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 {
 		return err
 	}

+ 2 - 1
api/server/router/system/backend.go

@@ -4,6 +4,7 @@ import (
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/events"
 	"github.com/docker/engine-api/types/filters"
+	"golang.org/x/net/context"
 )
 
 // Backend is the methods that need to be implemented to provide
@@ -13,5 +14,5 @@ type Backend interface {
 	SystemVersion() types.Version
 	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
 	UnsubscribeFromEvents(chan interface{})
-	AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error)
+	AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error)
 }

+ 1 - 1
api/server/router/system/system_routes.go

@@ -115,7 +115,7 @@ func (s *systemRouter) postAuth(ctx context.Context, w http.ResponseWriter, r *h
 	if err != nil {
 		return err
 	}
-	status, token, err := s.backend.AuthenticateToRegistry(config)
+	status, token, err := s.backend.AuthenticateToRegistry(ctx, config)
 	if err != nil {
 		return err
 	}

+ 2 - 1
builder/builder.go

@@ -12,6 +12,7 @@ import (
 	"github.com/docker/docker/reference"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
+	"golang.org/x/net/context"
 )
 
 const (
@@ -109,7 +110,7 @@ type Backend interface {
 	// Tag an image with newTag
 	TagImage(newTag reference.Named, imageName string) error
 	// 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.
 	ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error
 	// ContainerCreate creates a new Docker container and returns potential warnings

+ 9 - 6
builder/dockerfile/builder.go

@@ -17,6 +17,7 @@ import (
 	"github.com/docker/docker/reference"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
+	"golang.org/x/net/context"
 )
 
 var validCommitCommands = map[string]bool{
@@ -52,8 +53,9 @@ type Builder struct {
 	Stderr io.Writer
 	Output io.Writer
 
-	docker  builder.Backend
-	context builder.Context
+	docker    builder.Backend
+	context   builder.Context
+	clientCtx context.Context
 
 	dockerfile       *parser.Node
 	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.
 // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
 // 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 {
 		config = new(types.ImageBuildOptions)
 	}
@@ -94,6 +96,7 @@ func NewBuilder(config *types.ImageBuildOptions, backend builder.Backend, contex
 		config.BuildArgs = make(map[string]string)
 	}
 	b = &Builder{
+		clientCtx:        clientCtx,
 		options:          config,
 		Stdout:           os.Stdout,
 		Stderr:           os.Stderr,
@@ -158,8 +161,8 @@ func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
 }
 
 // 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) {
-	b, err := NewBuilder(config, bm.backend, context, nil)
+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(clientCtx, config, bm.backend, context, nil)
 	if err != nil {
 		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 {
 		return nil, err
 	}

+ 1 - 1
builder/dockerfile/dispatchers.go

@@ -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" ?
 		}
 		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 {
 				return err
 			}

+ 8 - 8
daemon/daemon.go

@@ -1030,7 +1030,7 @@ func (daemon *Daemon) PullImage(ctx context.Context, ref reference.Named, metaHe
 }
 
 // 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)
 	if err != nil {
 		return nil, err
@@ -1052,7 +1052,7 @@ func (daemon *Daemon) PullOnBuild(name string, authConfigs map[string]types.Auth
 		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 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.
-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
 	// transfer performance.
 	progressChan := make(chan progress.Progress, 100)
 
 	writesDone := make(chan struct{})
 
-	ctx, cancelFunc := context.WithCancel(context.Background())
+	ctx, cancelFunc := context.WithCancel(ctx)
 
 	go func() {
 		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
-func (daemon *Daemon) AuthenticateToRegistry(authConfig *types.AuthConfig) (string, string, error) {
-	return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(""))
+func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) {
+	return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(ctx))
 }
 
 // SearchRegistryForImages queries the registry for images matching
 // term. authConfig is used to login.
-func (daemon *Daemon) SearchRegistryForImages(term string,
+func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, term string,
 	authConfig *types.AuthConfig,
 	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

+ 2 - 2
distribution/pull_v1.go

@@ -49,10 +49,10 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
 	tr := transport.NewTransport(
 		// TODO(tiborvass): was ReceiveTimeout
 		registry.NewTransport(tlsConfig),
-		registry.DockerHeaders(dockerversion.DockerUserAgent(""), p.config.MetaHeaders)...,
+		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
 	)
 	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 {
 		logrus.Debugf("Could not get v1 endpoint: %v", err)
 		return fallbackError{err: err}

+ 2 - 2
distribution/push_v1.go

@@ -38,10 +38,10 @@ func (p *v1Pusher) Push(ctx context.Context) error {
 	tr := transport.NewTransport(
 		// TODO(tiborvass): was NoTimeout
 		registry.NewTransport(tlsConfig),
-		registry.DockerHeaders(dockerversion.DockerUserAgent(""), p.config.MetaHeaders)...,
+		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
 	)
 	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 {
 		logrus.Debugf("Could not get v1 endpoint: %v", err)
 		return fallbackError{err: err}

+ 1 - 3
distribution/registry.go

@@ -37,8 +37,6 @@ func (dcs dumbCredentialStore) SetRefreshToken(*url.URL, string, string) {
 // providing timeout settings and authentication support, and also verifies the
 // 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) {
-	upstreamUA := dockerversion.GetUserAgentFromContext(ctx)
-
 	repoName := repoInfo.FullName()
 	// If endpoint does not support CanonicalName, use the RemoteName instead
 	if endpoint.TrimHostname {
@@ -59,7 +57,7 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 		DisableKeepAlives: true,
 	}
 
-	modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(upstreamUA), metaHeaders)
+	modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), metaHeaders)
 	authTransport := transport.NewTransport(base, modifiers...)
 
 	challengeManager, foundVersion, err := registry.PingV2Registry(endpoint, authTransport)

+ 6 - 5
dockerversion/useragent.go

@@ -13,7 +13,7 @@ import (
 // DockerUserAgent is the User-Agent the Docker client uses to identify itself.
 // In accordance with RFC 7231 (5.5.3) is of the form:
 //    [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 = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: 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})
 
 	dockerUA := useragent.AppendVersions("", httpVersion...)
+	upstreamUA := getUserAgentFromContext(ctx)
 	if len(upstreamUA) > 0 {
 		ret := insertUpstreamUserAgent(upstreamUA, dockerUA)
 		return ret
@@ -32,8 +33,8 @@ func DockerUserAgent(upstreamUA string) string {
 	return dockerUA
 }
 
-// GetUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
-func GetUserAgentFromContext(ctx context.Context) string {
+// getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists
+func getUserAgentFromContext(ctx context.Context) string {
 	var upstreamUA string
 	if ctx != nil {
 		var ki interface{} = ctx.Value(httputils.UAStringKey)
@@ -51,7 +52,7 @@ func escapeStr(s string, charsToEscape string) string {
 		appended := false
 		for _, escapeableRune := range charsToEscape {
 			if currRune == escapeableRune {
-				ret += "\\" + string(currRune)
+				ret += `\` + string(currRune)
 				appended = true
 				break
 			}
@@ -67,7 +68,7 @@ func escapeStr(s string, charsToEscape string) string {
 // string of the form:
 //   $dockerUA UpstreamClient($upstreamUA)
 func insertUpstreamUserAgent(upstreamUA string, dockerUA string) string {
-	charsToEscape := "();\\" //["\\", ";", "(", ")"]string
+	charsToEscape := `();\`
 	upstreamUAEscaped := escapeStr(upstreamUA, charsToEscape)
 	return fmt.Sprintf("%s UpstreamClient(%s)", dockerUA, upstreamUAEscaped)
 }

+ 60 - 30
integration-cli/docker_cli_registry_user_agent_test.go

@@ -10,17 +10,17 @@ import (
 
 // unescapeBackslashSemicolonParens unescapes \;()
 func unescapeBackslashSemicolonParens(s string) string {
-	re := regexp.MustCompile("\\\\;")
+	re := regexp.MustCompile(`\\;`)
 	ret := re.ReplaceAll([]byte(s), []byte(";"))
 
-	re = regexp.MustCompile("\\\\\\(")
+	re = regexp.MustCompile(`\\\(`)
 	ret = re.ReplaceAll([]byte(ret), []byte("("))
 
-	re = regexp.MustCompile("\\\\\\)")
+	re = regexp.MustCompile(`\\\)`)
 	ret = re.ReplaceAll([]byte(ret), []byte(")"))
 
-	re = regexp.MustCompile("\\\\\\\\")
-	ret = re.ReplaceAll([]byte(ret), []byte("\\"))
+	re = regexp.MustCompile(`\\\\`)
+	ret = re.ReplaceAll([]byte(ret), []byte(`\`))
 
 	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"))
 }
 
-// TestUserAgentPassThroughOnPull verifies that when an image is pulled from
-// 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
-
+func registerUserAgentHandler(reg *testRegistry, result *string) {
 	reg.registerHandler("/v2/", func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(404)
 		var ua string
@@ -62,29 +55,66 @@ func (s *DockerRegistrySuite) TestUserAgentPassThroughOnPull(c *check.C) {
 				ua = v[0]
 			}
 		}
-		c.Assert(ua, check.Not(check.Equals), "", check.Commentf("No User-Agent found in request"))
-		if r.URL.Path == "/v2/busybox/manifests/latest" {
-			if expectUpstreamUA {
-				regexpCheckUA(c, ua)
-			}
-		}
+		*result = ua
 	})
+}
+
+// TestUserAgentPassThroughOnPull verifies that when an image is pulled from
+// 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)
 
-	repoName := fmt.Sprintf("%s/busybox", reg.hostport)
-	err = s.d.Start("--insecure-registry", reg.hostport, "--disable-legacy-registry=true")
+	pullReg, err := newTestRegistry(c)
 	c.Assert(err, check.IsNil)
+	registerUserAgentHandler(pullReg, &pullUA)
+	pullRepoName := fmt.Sprintf("%s/busybox", pullReg.hostport)
 
-	dockerfileName, cleanup, err := makefile(fmt.Sprintf("FROM %s/busybox", reg.hostport))
-	c.Assert(err, check.IsNil, check.Commentf("Unable to create test dockerfile"))
-	defer cleanup()
+	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)
+
+	dockerfileName, cleanup1, err := makefile(fmt.Sprintf("FROM %s", buildRepoName))
+	c.Assert(err, check.IsNil, check.Commentf("Unable to create test dockerfile"))
+	defer cleanup1()
 	s.d.Cmd("build", "--file", dockerfileName, ".")
+	regexpCheckUA(c, buildUA)
+
+	s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", loginReg.hostport)
+	regexpCheckUA(c, loginUA)
 
-	s.d.Cmd("run", repoName)
-	s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", reg.hostport)
-	s.d.Cmd("tag", "busybox", repoName)
-	s.d.Cmd("push", repoName)
+	s.d.Cmd("pull", pullRepoName)
+	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, ".")
 
-	expectUpstreamUA = true
-	s.d.Cmd("pull", repoName)
+	s.d.Cmd("push", pushRepoName)
+	regexpCheckUA(c, pushUA)
 }