8936789919
There has been a lot of discussion (issues 4242 and 5262) about making `FROM scratch` either a special case or making `FROM` optional, implying starting from an empty file system. This patch makes the build command `FROM scratch` special cased from now on and if used does not pull/set the the initial layer of the build to the ancient image ID (511136ea..) but instead marks the build as having no base image. The next command in the dockerfile will create an image with a parent image ID of "". This means every image ever can now use one fewer layer! This also makes the image name `scratch` a reserved name by the TagStore. You will not be able to tag an image with this name from now on. If any users currently have an image tagged as `scratch`, they will still be able to use that image, but will not be able to tag a new image with that name. Goodbye '511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158', it was nice knowing you. Fixes #4242 Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"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/utils"
|
|
)
|
|
|
|
func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status {
|
|
if n := len(job.Args); n != 1 {
|
|
return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
}
|
|
imgs := engine.NewTable("", 0)
|
|
if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil {
|
|
return job.Error(err)
|
|
}
|
|
if len(imgs.Data) == 0 {
|
|
return job.Errorf("Conflict, %s wasn't deleted", job.Args[0])
|
|
}
|
|
if _, err := imgs.WriteListTo(job.Stdout); err != nil {
|
|
return job.Error(err)
|
|
}
|
|
return engine.StatusOK
|
|
}
|
|
|
|
// FIXME: make this private and use the job instead
|
|
func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.Table, first, force, noprune bool) error {
|
|
var (
|
|
repoName, tag string
|
|
tags = []string{}
|
|
)
|
|
|
|
// FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes
|
|
repoName, tag = parsers.ParseRepositoryTag(name)
|
|
if tag == "" {
|
|
tag = graph.DEFAULTTAG
|
|
}
|
|
|
|
img, err := daemon.Repositories().LookupImage(name)
|
|
if err != nil {
|
|
if r, _ := daemon.Repositories().Get(repoName); r != nil {
|
|
return fmt.Errorf("No such image: %s:%s", repoName, tag)
|
|
}
|
|
return fmt.Errorf("No such image: %s", name)
|
|
}
|
|
|
|
if strings.Contains(img.ID, name) {
|
|
repoName = ""
|
|
tag = ""
|
|
}
|
|
|
|
byParents, err := daemon.Graph().ByParent()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repos := daemon.Repositories().ByID()[img.ID]
|
|
|
|
//If delete by id, see if the id belong only to one repository
|
|
if repoName == "" {
|
|
for _, repoAndTag := range repos {
|
|
parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
|
|
if repoName == "" || repoName == parsedRepo {
|
|
repoName = parsedRepo
|
|
if parsedTag != "" {
|
|
tags = append(tags, parsedTag)
|
|
}
|
|
} else if repoName != parsedRepo && !force {
|
|
// the id belongs to multiple repos, like base:latest and user:test,
|
|
// in that case return conflict
|
|
return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
|
|
}
|
|
}
|
|
} else {
|
|
tags = append(tags, tag)
|
|
}
|
|
|
|
if !first && len(tags) > 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(repos) <= 1 {
|
|
if err := daemon.canDeleteImage(img.ID, force); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Untag the current image
|
|
for _, tag := range tags {
|
|
tagDeleted, err := daemon.Repositories().Delete(repoName, tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tagDeleted {
|
|
out := &engine.Env{}
|
|
out.Set("Untagged", repoName+":"+tag)
|
|
imgs.Add(out)
|
|
eng.Job("log", "untag", img.ID, "").Run()
|
|
}
|
|
}
|
|
tags = daemon.Repositories().ByID()[img.ID]
|
|
if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
|
|
if len(byParents[img.ID]) == 0 {
|
|
if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
|
|
return err
|
|
}
|
|
if err := daemon.Graph().Delete(img.ID); err != nil {
|
|
return err
|
|
}
|
|
out := &engine.Env{}
|
|
out.SetJson("Deleted", img.ID)
|
|
imgs.Add(out)
|
|
eng.Job("log", "delete", img.ID, "").Run()
|
|
if img.Parent != "" && !noprune {
|
|
err := daemon.DeleteImage(eng, img.Parent, imgs, false, force, noprune)
|
|
if first {
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
|
|
for _, container := range daemon.List() {
|
|
parent, err := daemon.Repositories().LookupImage(container.ImageID)
|
|
if err != nil {
|
|
if daemon.Graph().IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := parent.WalkHistory(func(p *image.Image) error {
|
|
if imgID == p.ID {
|
|
if container.IsRunning() {
|
|
if force {
|
|
return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
}
|
|
return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
} else if !force {
|
|
return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
}
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|