diff --git a/api/client/commands.go b/api/client/commands.go index 89e5796bbb..6290268b46 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1696,7 +1696,12 @@ func (cli *DockerCli) CmdPs(args ...string) error { 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 = "" + } + + 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))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 9fb44fc452..6108967c3b 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -21,6 +21,12 @@ import ( "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. func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error { return nil @@ -115,6 +121,12 @@ func from(b *Builder, args []string, attributes map[string]bool, original string name := args[0] + if name == NoBaseImageSpecifier { + b.image = "" + b.noBaseImage = true + return nil + } + image, err := b.Daemon.Repositories().LookupImage(name) if b.Pull { 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 // 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") } diff --git a/builder/evaluator.go b/builder/evaluator.go index 4ed66c0054..eef222b943 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -110,7 +110,7 @@ type Builder struct { cmdSet bool // indicates is CMD was set in current Dockerfile 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) - + 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 diff --git a/builder/internals.go b/builder/internals.go index d30a7da810..1caa33141c 100644 --- a/builder/internals.go +++ b/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 { - if b.image == "" { + if b.image == "" && !b.noBaseImage { return fmt.Errorf("Please provide a source image with `from` prior to commit") } b.Config.Image = b.image @@ -513,7 +513,7 @@ func (b *Builder) probeCache() (bool, 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") } b.Config.Image = b.image diff --git a/daemon/commit.go b/daemon/commit.go index 950925ade3..06d0465adc 100644 --- a/daemon/commit.go +++ b/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 var ( - containerID, containerImage string - containerConfig *runconfig.Config + containerID, parentImageID string + containerConfig *runconfig.Config ) if container != nil { containerID = container.ID - containerImage = container.Image + parentImageID = container.ImageID 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 { return nil, err } diff --git a/daemon/container.go b/daemon/container.go index 3c05c645a7..75cd133fec 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -62,8 +62,8 @@ type Container struct { Path string Args []string - Config *runconfig.Config - Image string + Config *runconfig.Config + ImageID string `json:"Image"` NetworkSettings *NetworkSettings @@ -186,7 +186,7 @@ func (container *Container) WriteHostConfig() error { func (container *Container) LogEvent(action string) { 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) } } @@ -786,7 +786,7 @@ func (container *Container) GetImage() (*image.Image, error) { if container.daemon == nil { 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 { diff --git a/daemon/create.go b/daemon/create.go index f9d986491f..958a42ea73 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -5,6 +5,7 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/graph" + "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/runconfig" "github.com/docker/libcontainer/label" @@ -68,15 +69,22 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos var ( container *Container 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 { return nil, nil, err } @@ -86,13 +94,13 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos 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 } if err := daemon.Register(container); err != nil { return nil, nil, err } - if err := daemon.createRootfs(container, img); err != nil { + if err := daemon.createRootfs(container); err != nil { return nil, nil, err } if hostConfig != nil { diff --git a/daemon/daemon.go b/daemon/daemon.go index 40b56ea1cf..1553f198d1 100644 --- a/daemon/daemon.go +++ b/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) { 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.") } - if img.Config != nil { + if img != nil && img.Config != nil { if err := runconfig.Merge(config, img.Config); err != nil { return nil, err } @@ -557,7 +557,7 @@ func parseSecurityOpt(container *Container, config *runconfig.HostConfig) error 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 ( id string err error @@ -578,7 +578,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i Args: args, //FIXME: de-duplicate from config Config: config, hostConfig: &runconfig.HostConfig{}, - Image: img.ID, // Always use the resolved image id + ImageID: imgID, NetworkSettings: &NetworkSettings{}, Name: name, Driver: daemon.driver.String(), @@ -590,14 +590,14 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i 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. // This doubles as a barrier to avoid race conditions. if err := os.Mkdir(container.root, 0700); err != nil { return err } 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 } initPath, err := daemon.driver.Get(initID, "") diff --git a/daemon/image_delete.go b/daemon/image_delete.go index f39b0dd617..19f81f11a5 100644 --- a/daemon/image_delete.go +++ b/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 { for _, container := range daemon.List() { - parent, err := daemon.Repositories().LookupImage(container.Image) + parent, err := daemon.Repositories().LookupImage(container.ImageID) if err != nil { if daemon.Graph().IsNotExist(err) { return nil diff --git a/daemon/inspect.go b/daemon/inspect.go index c930cdd7fc..2bf1773d3a 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -35,7 +35,7 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status { out.SetList("Args", container.Args) out.SetJson("Config", container.Config) out.SetJson("State", container.State) - out.SetJson("Image", container.Image) + out.Set("Image", container.ImageID) out.SetJson("NetworkSettings", container.NetworkSettings) out.Set("ResolvConfPath", container.ResolvConfPath) out.Set("HostnamePath", container.HostnamePath) diff --git a/daemon/list.go b/daemon/list.go index 188a9861ec..937cdd2123 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -116,7 +116,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status { out := &engine.Env{} out.SetJson("Id", 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 { args := []string{} for _, arg := range container.Args { diff --git a/graph/tags.go b/graph/tags.go index 5c3e533b2a..826ab0bf75 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -298,6 +298,9 @@ func validateRepoName(name string) error { if name == "" { return fmt.Errorf("Repository name can't be empty") } + if name == "scratch" { + return fmt.Errorf("'scratch' is a reserved name") + } return nil } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index a56788e219..8f7263c0be 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -230,9 +230,9 @@ func TestEventsRedirectStdout(t *testing.T) { func TestEventsImagePull(t *testing.T) { since := time.Now().Unix() - pullCmd := exec.Command(dockerBinary, "pull", "scratch") + pullCmd := exec.Command(dockerBinary, "pull", "hello-world") 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", @@ -243,7 +243,7 @@ func TestEventsImagePull(t *testing.T) { events := strings.Split(strings.TrimSpace(out), "\n") 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) } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index bb99818bf9..cf42217ac8 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -7,7 +7,7 @@ import ( ) func TestInspectImage(t *testing.T) { - imageTest := "scratch" + imageTest := "emptyfs" imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158" imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Id}}'", imageTest) out, exitCode, err := runCommandWithOutput(imagesCmd) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index b67b1caca5..5b3324c771 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -9,11 +9,11 @@ import ( // pulling an image from the central registry should work 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 { - 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 diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index 94bfe3d6a8..c745df69f3 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -270,7 +270,7 @@ func TestSaveSingleTag(t *testing.T) { func TestSaveImageId(t *testing.T) { 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) if out, _, err := runCommandWithOutput(tagCmd); err != nil { 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" // 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) if out, _, err := runCommandWithOutput(tagCmd); err != nil { t.Fatalf("failed to tag repo: %s, %v", out, err) @@ -378,7 +378,7 @@ func TestSaveMultipleNames(t *testing.T) { defer deleteImages(repoName + "-one") // 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) if out, _, err := runCommandWithOutput(tagCmd); err != nil { t.Fatalf("failed to tag repo: %s, %v", out, err) diff --git a/project/make/.ensure-scratch b/project/make/.ensure-scratch index 9a9a43a0f8..8c421ed29e 100644 --- a/project/make/.ensure-scratch +++ b/project/make/.ensure-scratch @@ -1,13 +1,13 @@ #!/bin/bash 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 # and also https://github.com/docker/docker/issues/4242 mkdir -p /docker-scratch ( cd /docker-scratch - echo '{"scratch":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}' > repositories + echo '{"emptyfs":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}' > repositories mkdir -p 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158 ( cd 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158