2014-08-05 02:41:30 -04:00
package daemon
import (
"fmt"
"strings"
2015-05-27 21:54:54 -04:00
"github.com/Sirupsen/logrus"
2015-04-03 11:31:30 -04:00
"github.com/docker/docker/api/types"
2014-08-05 02:41:30 -04:00
"github.com/docker/docker/graph"
2015-07-20 13:57:15 -04:00
"github.com/docker/docker/image"
2014-08-05 02:41:30 -04:00
"github.com/docker/docker/pkg/parsers"
2015-03-24 07:25:26 -04:00
"github.com/docker/docker/pkg/stringid"
2015-02-26 21:23:50 -05:00
"github.com/docker/docker/utils"
2014-08-05 02:41:30 -04:00
)
2015-04-09 07:59:50 -04:00
// FIXME: remove ImageDelete's dependency on Daemon, then move to graph/
func ( daemon * Daemon ) ImageDelete ( name string , force , noprune bool ) ( [ ] types . ImageDelete , error ) {
2015-04-03 11:31:30 -04:00
list := [ ] types . ImageDelete { }
2015-04-09 07:59:50 -04:00
if err := daemon . imgDeleteHelper ( name , & list , true , force , noprune ) ; err != nil {
return nil , err
2014-08-05 02:41:30 -04:00
}
2015-04-03 11:31:30 -04:00
if len ( list ) == 0 {
2015-04-09 07:59:50 -04:00
return nil , fmt . Errorf ( "Conflict, %s wasn't deleted" , name )
2014-08-05 02:41:30 -04:00
}
2015-04-09 07:59:50 -04:00
return list , nil
2014-08-05 02:41:30 -04:00
}
2015-04-09 07:59:50 -04:00
func ( daemon * Daemon ) imgDeleteHelper ( name string , list * [ ] types . ImageDelete , first , force , noprune bool ) error {
2014-08-05 02:41:30 -04:00
var (
repoName , tag string
tags = [ ] string { }
)
2015-04-10 21:24:21 -04:00
repoAndTags := make ( map [ string ] [ ] string )
2014-08-05 02:41:30 -04:00
// 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
}
2015-03-04 16:18:45 -05:00
if name == "" {
return fmt . Errorf ( "Image name can not be blank" )
}
2014-08-05 02:41:30 -04:00
img , err := daemon . Repositories ( ) . LookupImage ( name )
if err != nil {
if r , _ := daemon . Repositories ( ) . Get ( repoName ) ; r != nil {
2015-02-26 21:23:50 -05:00
return fmt . Errorf ( "No such image: %s" , utils . ImageReference ( repoName , tag ) )
2014-08-05 02:41:30 -04:00
}
return fmt . Errorf ( "No such image: %s" , name )
}
if strings . Contains ( img . ID , name ) {
repoName = ""
tag = ""
}
2015-06-19 11:01:39 -04:00
byParents := daemon . Graph ( ) . ByParent ( )
2014-08-05 02:41:30 -04:00
2014-09-09 20:32:14 -04:00
repos := daemon . Repositories ( ) . ByID ( ) [ img . ID ]
2014-08-05 02:41:30 -04:00
//If delete by id, see if the id belong only to one repository
2015-05-29 14:12:58 -04:00
deleteByID := repoName == ""
if deleteByID {
2014-09-09 20:32:14 -04:00
for _ , repoAndTag := range repos {
2014-08-05 02:41:30 -04:00
parsedRepo , parsedTag := parsers . ParseRepositoryTag ( repoAndTag )
if repoName == "" || repoName == parsedRepo {
repoName = parsedRepo
if parsedTag != "" {
2015-04-10 21:24:21 -04:00
repoAndTags [ repoName ] = append ( repoAndTags [ repoName ] , parsedTag )
2014-08-05 02:41:30 -04:00
}
2015-02-25 23:01:35 -05:00
} else if repoName != parsedRepo && ! force && first {
2014-08-05 02:41:30 -04:00
// 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 )
2015-04-10 21:24:21 -04:00
} else {
//the id belongs to multiple repos, with -f just delete all
repoName = parsedRepo
if parsedTag != "" {
repoAndTags [ repoName ] = append ( repoAndTags [ repoName ] , parsedTag )
}
2014-08-05 02:41:30 -04:00
}
}
} else {
2015-04-10 21:24:21 -04:00
repoAndTags [ repoName ] = append ( repoAndTags [ repoName ] , tag )
2014-08-05 02:41:30 -04:00
}
2015-04-10 21:24:21 -04:00
if ! first && len ( repoAndTags ) > 0 {
2014-08-05 02:41:30 -04:00
return nil
}
2015-05-29 14:12:58 -04:00
if len ( repos ) <= 1 || ( len ( repoAndTags ) <= 1 && deleteByID ) {
2014-09-09 20:32:14 -04:00
if err := daemon . canDeleteImage ( img . ID , force ) ; err != nil {
return err
}
}
// Untag the current image
2015-04-10 21:24:21 -04:00
for repoName , tags := range repoAndTags {
for _ , tag := range tags {
tagDeleted , err := daemon . Repositories ( ) . Delete ( repoName , tag )
if err != nil {
return err
}
if tagDeleted {
* list = append ( * list , types . ImageDelete {
Untagged : utils . ImageReference ( repoName , tag ) ,
} )
daemon . EventsService . Log ( "untag" , img . ID , "" )
}
2014-08-05 02:41:30 -04:00
}
}
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
}
2015-04-03 11:31:30 -04:00
* list = append ( * list , types . ImageDelete {
Deleted : img . ID ,
} )
2015-04-03 18:17:49 -04:00
daemon . EventsService . Log ( "delete" , img . ID , "" )
2014-08-05 02:41:30 -04:00
if img . Parent != "" && ! noprune {
2015-04-09 07:59:50 -04:00
err := daemon . imgDeleteHelper ( img . Parent , list , false , force , noprune )
2014-08-05 02:41:30 -04:00
if first {
return err
}
}
}
}
return nil
}
2014-09-09 20:32:14 -04:00
func ( daemon * Daemon ) canDeleteImage ( imgID string , force bool ) error {
2015-04-07 22:29:29 -04:00
if daemon . Graph ( ) . IsHeld ( imgID ) {
return fmt . Errorf ( "Conflict, cannot delete because %s is held by an ongoing pull or build" , stringid . TruncateID ( imgID ) )
}
2014-08-05 02:41:30 -04:00
for _ , container := range daemon . List ( ) {
2015-05-27 21:54:54 -04:00
if container . ImageID == "" {
// This technically should never happen, but if the container
// has no ImageID then log the situation and move on.
// If we allowed processing to continue then the code later
// on would fail with a "Prefix can't be empty" error even
// though the bad container has nothing to do with the image
// we're trying to delete.
logrus . Errorf ( "Container %q has no image associated with it!" , container . ID )
continue
}
2014-10-28 17:06:23 -04:00
parent , err := daemon . Repositories ( ) . LookupImage ( container . ImageID )
2014-08-05 02:41:30 -04:00
if err != nil {
2015-03-27 21:07:20 -04:00
if daemon . Graph ( ) . IsNotExist ( err , container . ImageID ) {
2015-07-10 09:35:44 -04:00
continue
2014-11-17 16:16:33 -05:00
}
2014-08-05 02:41:30 -04:00
return err
}
2015-07-20 13:57:15 -04:00
if err := daemon . graph . WalkHistory ( parent , func ( p image . Image ) error {
2014-08-05 02:41:30 -04:00
if imgID == p . ID {
2014-08-31 11:20:35 -04:00
if container . IsRunning ( ) {
2014-08-05 02:41:30 -04:00
if force {
2015-03-24 07:25:26 -04:00
return fmt . Errorf ( "Conflict, cannot force delete %s because the running container %s is using it, stop it and retry" , stringid . TruncateID ( imgID ) , stringid . TruncateID ( container . ID ) )
2014-08-05 02:41:30 -04:00
}
2015-03-24 07:25:26 -04:00
return fmt . Errorf ( "Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force" , stringid . TruncateID ( imgID ) , stringid . TruncateID ( container . ID ) )
2014-08-05 02:41:30 -04:00
} else if ! force {
2015-03-24 07:25:26 -04:00
return fmt . Errorf ( "Conflict, cannot delete %s because the container %s is using it, use -f to force" , stringid . TruncateID ( imgID ) , stringid . TruncateID ( container . ID ) )
2014-08-05 02:41:30 -04:00
}
}
return nil
} ) ; err != nil {
return err
}
}
return nil
}