Browse code

[api/client] Tag resolved digest from Dockerfile

Builds where the base images have been resolved to trusted digest
references will now be tagged with the original tag reference from
the Dockerfile on a successful build.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)

Josh Hawn authored on 2015/07/25 08:35:11
Showing 2 changed files
... ...
@@ -115,8 +115,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
115 115
 	}
116 116
 
117 117
 	// Resolve the FROM lines in the Dockerfile to trusted digest references
118
-	// using Notary.
119
-	newDockerfile, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference)
118
+	// using Notary. On a successful build, we must tag the resolved digests
119
+	// to the original name specified in the Dockerfile.
120
+	newDockerfile, resolvedTags, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference)
120 121
 	if err != nil {
121 122
 		return fmt.Errorf("unable to process Dockerfile: %v", err)
122 123
 	}
... ...
@@ -291,7 +292,20 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
291 291
 		}
292 292
 		return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
293 293
 	}
294
-	return err
294
+
295
+	if err != nil {
296
+		return err
297
+	}
298
+
299
+	// Since the build was successful, now we must tag any of the resolved
300
+	// images from the above Dockerfile rewrite.
301
+	for _, resolved := range resolvedTags {
302
+		if err := cli.tagTrusted(resolved.repoInfo, resolved.digestRef, resolved.tagRef); err != nil {
303
+			return err
304
+		}
305
+	}
306
+
307
+	return nil
295 308
 }
296 309
 
297 310
 // getDockerfileRelPath uses the given context directory for a `docker build`
... ...
@@ -483,14 +497,21 @@ func (td *trustedDockerfile) Close() error {
483 483
 	return os.Remove(td.File.Name())
484 484
 }
485 485
 
486
+// resolvedTag records the repository, tag, and resolved digest reference
487
+// from a Dockerfile rewrite.
488
+type resolvedTag struct {
489
+	repoInfo          *registry.RepositoryInfo
490
+	digestRef, tagRef registry.Reference
491
+}
492
+
486 493
 // rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
487 494
 // "FROM <image>" instructions to a digest reference. `translator` is a
488 495
 // function that takes a repository name and tag reference and returns a
489 496
 // trusted digest reference.
490
-func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, err error) {
497
+func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
491 498
 	dockerfile, err := os.Open(dockerfileName)
492 499
 	if err != nil {
493
-		return nil, fmt.Errorf("unable to open Dockerfile: %v", err)
500
+		return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err)
494 501
 	}
495 502
 	defer dockerfile.Close()
496 503
 
... ...
@@ -499,7 +520,7 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist
499 499
 	// Make a tempfile to store the rewritten Dockerfile.
500 500
 	tempFile, err := ioutil.TempFile("", "trusted-dockerfile-")
501 501
 	if err != nil {
502
-		return nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err)
502
+		return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err)
503 503
 	}
504 504
 
505 505
 	trustedFile := &trustedDockerfile{
... ...
@@ -525,21 +546,32 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist
525 525
 			if tag == "" {
526 526
 				tag = tags.DEFAULTTAG
527 527
 			}
528
+
529
+			repoInfo, err := registry.ParseRepositoryInfo(repo)
530
+			if err != nil {
531
+				return nil, nil, fmt.Errorf("unable to parse repository info: %v", err)
532
+			}
533
+
528 534
 			ref := registry.ParseReference(tag)
529 535
 
530 536
 			if !ref.HasDigest() && isTrusted() {
531 537
 				trustedRef, err := translator(repo, ref)
532 538
 				if err != nil {
533
-					return nil, err
539
+					return nil, nil, err
534 540
 				}
535 541
 
536 542
 				line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo)))
543
+				resolvedTags = append(resolvedTags, &resolvedTag{
544
+					repoInfo:  repoInfo,
545
+					digestRef: trustedRef,
546
+					tagRef:    ref,
547
+				})
537 548
 			}
538 549
 		}
539 550
 
540 551
 		n, err := fmt.Fprintln(tempFile, line)
541 552
 		if err != nil {
542
-			return nil, err
553
+			return nil, nil, err
543 554
 		}
544 555
 
545 556
 		trustedFile.size += int64(n)
... ...
@@ -547,7 +579,7 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist
547 547
 
548 548
 	tempFile.Seek(0, os.SEEK_SET)
549 549
 
550
-	return trustedFile, scanner.Err()
550
+	return trustedFile, resolvedTags, scanner.Err()
551 551
 }
552 552
 
553 553
 // replaceDockerfileTarWrapper wraps the given input tar archive stream and
... ...
@@ -5370,8 +5370,15 @@ func (s *DockerTrustSuite) TestTrustedBuild(c *check.C) {
5370 5370
 		c.Fatalf("Unexpected output on trusted build:\n%s", out)
5371 5371
 	}
5372 5372
 
5373
-	// Build command does not create untrusted tag
5374
-	//dockerCmd(c, "rmi", repoName)
5373
+	// We should also have a tag reference for the image.
5374
+	if out, exitCode := dockerCmd(c, "inspect", repoName); exitCode != 0 {
5375
+		c.Fatalf("unexpected exit code inspecting image %q: %d: %s", repoName, exitCode, out)
5376
+	}
5377
+
5378
+	// We should now be able to remove the tag reference.
5379
+	if out, exitCode := dockerCmd(c, "rmi", repoName); exitCode != 0 {
5380
+		c.Fatalf("unexpected exit code inspecting image %q: %d: %s", repoName, exitCode, out)
5381
+	}
5375 5382
 }
5376 5383
 
5377 5384
 func (s *DockerTrustSuite) TestTrustedBuildUntrustedTag(c *check.C) {