瀏覽代碼

Merge pull request #8827 from jlhawn/build_implied_from_scratch

Make `FROM scratch` a special cased 'no-base' spec
Tibor Vass 10 年之前
父節點
當前提交
610842f906

+ 6 - 1
api/client/commands.go

@@ -1696,7 +1696,12 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 
 
 		ports.ReadListFrom([]byte(out.Get("Ports")))
 		ports.ReadListFrom([]byte(out.Get("Ports")))
 
 
-		fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand,
+		image := out.Get("Image")
+		if image == "" {
+			image = "<no image>"
+		}
+
+		fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand,
 			units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))),
 			units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))),
 			out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
 			out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
 
 

+ 13 - 1
builder/dispatchers.go

@@ -21,6 +21,12 @@ import (
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 )
 )
 
 
+const (
+	// NoBaseImageSpecifier is the symbol used by the FROM
+	// command to specify that no base image is to be used.
+	NoBaseImageSpecifier string = "scratch"
+)
+
 // dispatch with no layer / parsing. This is effectively not a command.
 // dispatch with no layer / parsing. This is effectively not a command.
 func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
 func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
 	return nil
 	return nil
@@ -115,6 +121,12 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
 
 
 	name := args[0]
 	name := args[0]
 
 
+	if name == NoBaseImageSpecifier {
+		b.image = ""
+		b.noBaseImage = true
+		return nil
+	}
+
 	image, err := b.Daemon.Repositories().LookupImage(name)
 	image, err := b.Daemon.Repositories().LookupImage(name)
 	if b.Pull {
 	if b.Pull {
 		image, err = b.pullImage(name)
 		image, err = b.pullImage(name)
@@ -191,7 +203,7 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
 // RUN [ "echo", "hi" ] # echo hi
 // RUN [ "echo", "hi" ] # echo hi
 //
 //
 func run(b *Builder, args []string, attributes map[string]bool, original string) error {
 func run(b *Builder, args []string, attributes map[string]bool, original string) error {
-	if b.image == "" {
+	if b.image == "" && !b.noBaseImage {
 		return fmt.Errorf("Please provide a source image with `from` prior to run")
 		return fmt.Errorf("Please provide a source image with `from` prior to run")
 	}
 	}
 
 

+ 1 - 1
builder/evaluator.go

@@ -110,7 +110,7 @@ type Builder struct {
 	cmdSet      bool          // indicates is CMD was set in current Dockerfile
 	cmdSet      bool          // indicates is CMD was set in current Dockerfile
 	context     tarsum.TarSum // the context is a tarball that is uploaded by the client
 	context     tarsum.TarSum // the context is a tarball that is uploaded by the client
 	contextPath string        // the path of the temporary directory the local context is unpacked to (server side)
 	contextPath string        // the path of the temporary directory the local context is unpacked to (server side)
-
+	noBaseImage bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
 }
 }
 
 
 // Run the builder with the context. This is the lynchpin of this package. This
 // Run the builder with the context. This is the lynchpin of this package. This

+ 2 - 2
builder/internals.go

@@ -58,7 +58,7 @@ func (b *Builder) readContext(context io.Reader) error {
 }
 }
 
 
 func (b *Builder) commit(id string, autoCmd []string, comment string) error {
 func (b *Builder) commit(id string, autoCmd []string, comment string) error {
-	if b.image == "" {
+	if b.image == "" && !b.noBaseImage {
 		return fmt.Errorf("Please provide a source image with `from` prior to commit")
 		return fmt.Errorf("Please provide a source image with `from` prior to commit")
 	}
 	}
 	b.Config.Image = b.image
 	b.Config.Image = b.image
@@ -513,7 +513,7 @@ func (b *Builder) probeCache() (bool, error) {
 }
 }
 
 
 func (b *Builder) create() (*daemon.Container, error) {
 func (b *Builder) create() (*daemon.Container, error) {
-	if b.image == "" {
+	if b.image == "" && !b.noBaseImage {
 		return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
 		return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
 	}
 	}
 	b.Config.Image = b.image
 	b.Config.Image = b.image

+ 4 - 4
daemon/commit.go

@@ -59,17 +59,17 @@ func (daemon *Daemon) Commit(container *Container, repository, tag, comment, aut
 
 
 	// Create a new image from the container's base layers + a new layer from container changes
 	// Create a new image from the container's base layers + a new layer from container changes
 	var (
 	var (
-		containerID, containerImage string
-		containerConfig             *runconfig.Config
+		containerID, parentImageID string
+		containerConfig            *runconfig.Config
 	)
 	)
 
 
 	if container != nil {
 	if container != nil {
 		containerID = container.ID
 		containerID = container.ID
-		containerImage = container.Image
+		parentImageID = container.ImageID
 		containerConfig = container.Config
 		containerConfig = container.Config
 	}
 	}
 
 
-	img, err := daemon.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config)
+	img, err := daemon.graph.Create(rwTar, containerID, parentImageID, comment, author, containerConfig, config)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 4 - 4
daemon/container.go

@@ -62,8 +62,8 @@ type Container struct {
 	Path string
 	Path string
 	Args []string
 	Args []string
 
 
-	Config *runconfig.Config
-	Image  string
+	Config  *runconfig.Config
+	ImageID string `json:"Image"`
 
 
 	NetworkSettings *NetworkSettings
 	NetworkSettings *NetworkSettings
 
 
@@ -186,7 +186,7 @@ func (container *Container) WriteHostConfig() error {
 
 
 func (container *Container) LogEvent(action string) {
 func (container *Container) LogEvent(action string) {
 	d := container.daemon
 	d := container.daemon
-	if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.Image)).Run(); err != nil {
+	if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.ImageID)).Run(); err != nil {
 		log.Errorf("Error logging event %s for %s: %s", action, container.ID, err)
 		log.Errorf("Error logging event %s for %s: %s", action, container.ID, err)
 	}
 	}
 }
 }
@@ -786,7 +786,7 @@ func (container *Container) GetImage() (*image.Image, error) {
 	if container.daemon == nil {
 	if container.daemon == nil {
 		return nil, fmt.Errorf("Can't get image of unregistered container")
 		return nil, fmt.Errorf("Can't get image of unregistered container")
 	}
 	}
-	return container.daemon.graph.Get(container.Image)
+	return container.daemon.graph.Get(container.ImageID)
 }
 }
 
 
 func (container *Container) Unmount() error {
 func (container *Container) Unmount() error {

+ 16 - 8
daemon/create.go

@@ -5,6 +5,7 @@ import (
 
 
 	"github.com/docker/docker/engine"
 	"github.com/docker/docker/engine"
 	"github.com/docker/docker/graph"
 	"github.com/docker/docker/graph"
+	"github.com/docker/docker/image"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/libcontainer/label"
 	"github.com/docker/libcontainer/label"
@@ -68,15 +69,22 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 	var (
 	var (
 		container *Container
 		container *Container
 		warnings  []string
 		warnings  []string
+		img       *image.Image
+		imgID     string
+		err       error
 	)
 	)
 
 
-	img, err := daemon.repositories.LookupImage(config.Image)
-	if err != nil {
-		return nil, nil, err
-	}
-	if err := img.CheckDepth(); err != nil {
-		return nil, nil, err
+	if config.Image != "" {
+		img, err = daemon.repositories.LookupImage(config.Image)
+		if err != nil {
+			return nil, nil, err
+		}
+		if err = img.CheckDepth(); err != nil {
+			return nil, nil, err
+		}
+		imgID = img.ID
 	}
 	}
+
 	if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
 	if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
@@ -86,13 +94,13 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 			return nil, nil, err
 			return nil, nil, err
 		}
 		}
 	}
 	}
-	if container, err = daemon.newContainer(name, config, img); err != nil {
+	if container, err = daemon.newContainer(name, config, imgID); err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
 	if err := daemon.Register(container); err != nil {
 	if err := daemon.Register(container); err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
-	if err := daemon.createRootfs(container, img); err != nil {
+	if err := daemon.createRootfs(container); err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
 	if hostConfig != nil {
 	if hostConfig != nil {

+ 6 - 6
daemon/daemon.go

@@ -417,10 +417,10 @@ func (daemon *Daemon) checkDeprecatedExpose(config *runconfig.Config) bool {
 
 
 func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.Image) ([]string, error) {
 func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.Image) ([]string, error) {
 	warnings := []string{}
 	warnings := []string{}
-	if daemon.checkDeprecatedExpose(img.Config) || daemon.checkDeprecatedExpose(config) {
+	if (img != nil && daemon.checkDeprecatedExpose(img.Config)) || daemon.checkDeprecatedExpose(config) {
 		warnings = append(warnings, "The mapping to public ports on your host via Dockerfile EXPOSE (host:port:port) has been deprecated. Use -p to publish the ports.")
 		warnings = append(warnings, "The mapping to public ports on your host via Dockerfile EXPOSE (host:port:port) has been deprecated. Use -p to publish the ports.")
 	}
 	}
-	if img.Config != nil {
+	if img != nil && img.Config != nil {
 		if err := runconfig.Merge(config, img.Config); err != nil {
 		if err := runconfig.Merge(config, img.Config); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -557,7 +557,7 @@ func parseSecurityOpt(container *Container, config *runconfig.HostConfig) error
 	return err
 	return err
 }
 }
 
 
-func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) {
+func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID string) (*Container, error) {
 	var (
 	var (
 		id  string
 		id  string
 		err error
 		err error
@@ -578,7 +578,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
 		Args:            args, //FIXME: de-duplicate from config
 		Args:            args, //FIXME: de-duplicate from config
 		Config:          config,
 		Config:          config,
 		hostConfig:      &runconfig.HostConfig{},
 		hostConfig:      &runconfig.HostConfig{},
-		Image:           img.ID, // Always use the resolved image id
+		ImageID:         imgID,
 		NetworkSettings: &NetworkSettings{},
 		NetworkSettings: &NetworkSettings{},
 		Name:            name,
 		Name:            name,
 		Driver:          daemon.driver.String(),
 		Driver:          daemon.driver.String(),
@@ -590,14 +590,14 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
 	return container, err
 	return container, err
 }
 }
 
 
-func (daemon *Daemon) createRootfs(container *Container, img *image.Image) error {
+func (daemon *Daemon) createRootfs(container *Container) error {
 	// Step 1: create the container directory.
 	// Step 1: create the container directory.
 	// This doubles as a barrier to avoid race conditions.
 	// This doubles as a barrier to avoid race conditions.
 	if err := os.Mkdir(container.root, 0700); err != nil {
 	if err := os.Mkdir(container.root, 0700); err != nil {
 		return err
 		return err
 	}
 	}
 	initID := fmt.Sprintf("%s-init", container.ID)
 	initID := fmt.Sprintf("%s-init", container.ID)
-	if err := daemon.driver.Create(initID, img.ID); err != nil {
+	if err := daemon.driver.Create(initID, container.ImageID); err != nil {
 		return err
 		return err
 	}
 	}
 	initPath, err := daemon.driver.Get(initID, "")
 	initPath, err := daemon.driver.Get(initID, "")

+ 1 - 1
daemon/image_delete.go

@@ -131,7 +131,7 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.
 
 
 func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
 func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
 	for _, container := range daemon.List() {
 	for _, container := range daemon.List() {
-		parent, err := daemon.Repositories().LookupImage(container.Image)
+		parent, err := daemon.Repositories().LookupImage(container.ImageID)
 		if err != nil {
 		if err != nil {
 			if daemon.Graph().IsNotExist(err) {
 			if daemon.Graph().IsNotExist(err) {
 				return nil
 				return nil

+ 1 - 1
daemon/inspect.go

@@ -35,7 +35,7 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status {
 		out.SetList("Args", container.Args)
 		out.SetList("Args", container.Args)
 		out.SetJson("Config", container.Config)
 		out.SetJson("Config", container.Config)
 		out.SetJson("State", container.State)
 		out.SetJson("State", container.State)
-		out.SetJson("Image", container.Image)
+		out.Set("Image", container.ImageID)
 		out.SetJson("NetworkSettings", container.NetworkSettings)
 		out.SetJson("NetworkSettings", container.NetworkSettings)
 		out.Set("ResolvConfPath", container.ResolvConfPath)
 		out.Set("ResolvConfPath", container.ResolvConfPath)
 		out.Set("HostnamePath", container.HostnamePath)
 		out.Set("HostnamePath", container.HostnamePath)

+ 1 - 1
daemon/list.go

@@ -116,7 +116,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
 		out := &engine.Env{}
 		out := &engine.Env{}
 		out.SetJson("Id", container.ID)
 		out.SetJson("Id", container.ID)
 		out.SetList("Names", names[container.ID])
 		out.SetList("Names", names[container.ID])
-		out.SetJson("Image", daemon.Repositories().ImageName(container.Image))
+		out.SetJson("Image", daemon.Repositories().ImageName(container.ImageID))
 		if len(container.Args) > 0 {
 		if len(container.Args) > 0 {
 			args := []string{}
 			args := []string{}
 			for _, arg := range container.Args {
 			for _, arg := range container.Args {

+ 3 - 0
graph/tags.go

@@ -298,6 +298,9 @@ func validateRepoName(name string) error {
 	if name == "" {
 	if name == "" {
 		return fmt.Errorf("Repository name can't be empty")
 		return fmt.Errorf("Repository name can't be empty")
 	}
 	}
+	if name == "scratch" {
+		return fmt.Errorf("'scratch' is a reserved name")
+	}
 	return nil
 	return nil
 }
 }
 
 

+ 3 - 3
integration-cli/docker_cli_events_test.go

@@ -230,9 +230,9 @@ func TestEventsRedirectStdout(t *testing.T) {
 
 
 func TestEventsImagePull(t *testing.T) {
 func TestEventsImagePull(t *testing.T) {
 	since := time.Now().Unix()
 	since := time.Now().Unix()
-	pullCmd := exec.Command(dockerBinary, "pull", "scratch")
+	pullCmd := exec.Command(dockerBinary, "pull", "hello-world")
 	if out, _, err := runCommandWithOutput(pullCmd); err != nil {
 	if out, _, err := runCommandWithOutput(pullCmd); err != nil {
-		t.Fatalf("pulling the scratch image from has failed: %s, %v", out, err)
+		t.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err)
 	}
 	}
 
 
 	eventsCmd := exec.Command(dockerBinary, "events",
 	eventsCmd := exec.Command(dockerBinary, "events",
@@ -243,7 +243,7 @@ func TestEventsImagePull(t *testing.T) {
 	events := strings.Split(strings.TrimSpace(out), "\n")
 	events := strings.Split(strings.TrimSpace(out), "\n")
 	event := strings.TrimSpace(events[len(events)-1])
 	event := strings.TrimSpace(events[len(events)-1])
 
 
-	if !strings.HasSuffix(event, "scratch:latest: pull") {
+	if !strings.HasSuffix(event, "hello-world:latest: pull") {
 		t.Fatalf("Missing pull event - got:%q", event)
 		t.Fatalf("Missing pull event - got:%q", event)
 	}
 	}
 
 

+ 1 - 1
integration-cli/docker_cli_inspect_test.go

@@ -7,7 +7,7 @@ import (
 )
 )
 
 
 func TestInspectImage(t *testing.T) {
 func TestInspectImage(t *testing.T) {
-	imageTest := "scratch"
+	imageTest := "emptyfs"
 	imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"
 	imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"
 	imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Id}}'", imageTest)
 	imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Id}}'", imageTest)
 	out, exitCode, err := runCommandWithOutput(imagesCmd)
 	out, exitCode, err := runCommandWithOutput(imagesCmd)

+ 3 - 3
integration-cli/docker_cli_pull_test.go

@@ -9,11 +9,11 @@ import (
 
 
 // pulling an image from the central registry should work
 // pulling an image from the central registry should work
 func TestPullImageFromCentralRegistry(t *testing.T) {
 func TestPullImageFromCentralRegistry(t *testing.T) {
-	pullCmd := exec.Command(dockerBinary, "pull", "scratch")
+	pullCmd := exec.Command(dockerBinary, "pull", "hello-world")
 	if out, _, err := runCommandWithOutput(pullCmd); err != nil {
 	if out, _, err := runCommandWithOutput(pullCmd); err != nil {
-		t.Fatalf("pulling the scratch image from the registry has failed: %s, %v", out, err)
+		t.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err)
 	}
 	}
-	logDone("pull - pull scratch")
+	logDone("pull - pull hello-world")
 }
 }
 
 
 // pulling a non-existing image from the central registry should return a non-zero exit code
 // pulling a non-existing image from the central registry should return a non-zero exit code

+ 3 - 3
integration-cli/docker_cli_save_load_test.go

@@ -270,7 +270,7 @@ func TestSaveSingleTag(t *testing.T) {
 func TestSaveImageId(t *testing.T) {
 func TestSaveImageId(t *testing.T) {
 	repoName := "foobar-save-image-id-test"
 	repoName := "foobar-save-image-id-test"
 
 
-	tagCmdFinal := fmt.Sprintf("%v tag scratch:latest %v:latest", dockerBinary, repoName)
+	tagCmdFinal := fmt.Sprintf("%v tag emptyfs:latest %v:latest", dockerBinary, repoName)
 	tagCmd := exec.Command("bash", "-c", tagCmdFinal)
 	tagCmd := exec.Command("bash", "-c", tagCmdFinal)
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
@@ -370,7 +370,7 @@ func TestSaveMultipleNames(t *testing.T) {
 	repoName := "foobar-save-multi-name-test"
 	repoName := "foobar-save-multi-name-test"
 
 
 	// Make one image
 	// Make one image
-	tagCmdFinal := fmt.Sprintf("%v tag scratch:latest %v-one:latest", dockerBinary, repoName)
+	tagCmdFinal := fmt.Sprintf("%v tag emptyfs:latest %v-one:latest", dockerBinary, repoName)
 	tagCmd := exec.Command("bash", "-c", tagCmdFinal)
 	tagCmd := exec.Command("bash", "-c", tagCmdFinal)
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
@@ -378,7 +378,7 @@ func TestSaveMultipleNames(t *testing.T) {
 	defer deleteImages(repoName + "-one")
 	defer deleteImages(repoName + "-one")
 
 
 	// Make two images
 	// Make two images
-	tagCmdFinal = fmt.Sprintf("%v tag scratch:latest %v-two:latest", dockerBinary, repoName)
+	tagCmdFinal = fmt.Sprintf("%v tag emptyfs:latest %v-two:latest", dockerBinary, repoName)
 	tagCmd = exec.Command("bash", "-c", tagCmdFinal)
 	tagCmd = exec.Command("bash", "-c", tagCmdFinal)
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 		t.Fatalf("failed to tag repo: %s, %v", out, err)
 		t.Fatalf("failed to tag repo: %s, %v", out, err)

+ 2 - 2
project/make/.ensure-scratch

@@ -1,13 +1,13 @@
 #!/bin/bash
 #!/bin/bash
 
 
 if ! docker inspect scratch &> /dev/null; then
 if ! docker inspect scratch &> /dev/null; then
-	# let's build a "docker save" tarball for "scratch"
+	# let's build a "docker save" tarball for "emptyfs"
 	# see https://github.com/docker/docker/pull/5262
 	# see https://github.com/docker/docker/pull/5262
 	# and also https://github.com/docker/docker/issues/4242
 	# and also https://github.com/docker/docker/issues/4242
 	mkdir -p /docker-scratch
 	mkdir -p /docker-scratch
 	(
 	(
 		cd /docker-scratch
 		cd /docker-scratch
-		echo '{"scratch":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}' > repositories
+		echo '{"emptyfs":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}' > repositories
 		mkdir -p 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
 		mkdir -p 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
 		(
 		(
 			cd 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
 			cd 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158