|
@@ -104,27 +104,34 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
|
|
|
|
|
|
repoRefs = daemon.referenceStore.References(imgID)
|
|
repoRefs = daemon.referenceStore.References(imgID)
|
|
|
|
|
|
- // If this is a tag reference and all the remaining references
|
|
|
|
- // to this image are digest references, delete the remaining
|
|
|
|
- // references so that they don't prevent removal of the image.
|
|
|
|
|
|
+ // If a tag reference was removed and the only remaining
|
|
|
|
+ // references to the same repository are digest references,
|
|
|
|
+ // then clean up those digest references.
|
|
if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical {
|
|
if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical {
|
|
- foundTagRef := false
|
|
|
|
|
|
+ foundRepoTagRef := false
|
|
for _, repoRef := range repoRefs {
|
|
for _, repoRef := range repoRefs {
|
|
- if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical {
|
|
|
|
- foundTagRef = true
|
|
|
|
|
|
+ if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
|
|
|
|
+ foundRepoTagRef = true
|
|
break
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if !foundTagRef {
|
|
|
|
|
|
+ if !foundRepoTagRef {
|
|
|
|
+ // Remove canonical references from same repository
|
|
|
|
+ remainingRefs := []reference.Named{}
|
|
for _, repoRef := range repoRefs {
|
|
for _, repoRef := range repoRefs {
|
|
- if _, err := daemon.removeImageRef(repoRef); err != nil {
|
|
|
|
- return records, err
|
|
|
|
- }
|
|
|
|
|
|
+ if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
|
|
|
|
+ if _, err := daemon.removeImageRef(repoRef); err != nil {
|
|
|
|
+ return records, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
|
|
|
|
+ records = append(records, untaggedRecord)
|
|
|
|
+ } else {
|
|
|
|
+ remainingRefs = append(remainingRefs, repoRef)
|
|
|
|
|
|
- untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
|
|
|
|
- records = append(records, untaggedRecord)
|
|
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- repoRefs = []reference.Named{}
|
|
|
|
|
|
+ repoRefs = remainingRefs
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -135,11 +142,10 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
|
|
|
|
|
|
removedRepositoryRef = true
|
|
removedRepositoryRef = true
|
|
} else {
|
|
} else {
|
|
- // If an ID reference was given AND there is exactly one
|
|
|
|
- // repository reference to the image then we will want to
|
|
|
|
- // remove that reference.
|
|
|
|
- // FIXME: Is this the behavior we want?
|
|
|
|
- if len(repoRefs) == 1 {
|
|
|
|
|
|
+ // If an ID reference was given AND there is at most one tag
|
|
|
|
+ // reference to the image AND all references are within one
|
|
|
|
+ // repository, then remove all references.
|
|
|
|
+ if isSingleReference(repoRefs) {
|
|
c := conflictHard
|
|
c := conflictHard
|
|
if !force {
|
|
if !force {
|
|
c |= conflictSoft &^ conflictActiveReference
|
|
c |= conflictSoft &^ conflictActiveReference
|
|
@@ -148,21 +154,48 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
|
|
return nil, conflict
|
|
return nil, conflict
|
|
}
|
|
}
|
|
|
|
|
|
- parsedRef, err := daemon.removeImageRef(repoRefs[0])
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
|
|
+ for _, repoRef := range repoRefs {
|
|
|
|
+ parsedRef, err := daemon.removeImageRef(repoRef)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
|
|
- untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
|
|
|
|
|
+ untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
|
|
|
|
|
- daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
|
|
|
|
- records = append(records, untaggedRecord)
|
|
|
|
|
|
+ daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
|
|
|
|
+ records = append(records, untaggedRecord)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return records, daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef)
|
|
return records, daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// isSingleReference returns true when all references are from one repository
|
|
|
|
+// and there is at most one tag. Returns false for empty input.
|
|
|
|
+func isSingleReference(repoRefs []reference.Named) bool {
|
|
|
|
+ if len(repoRefs) <= 1 {
|
|
|
|
+ return len(repoRefs) == 1
|
|
|
|
+ }
|
|
|
|
+ var singleRef reference.Named
|
|
|
|
+ canonicalRefs := map[string]struct{}{}
|
|
|
|
+ for _, repoRef := range repoRefs {
|
|
|
|
+ if _, isCanonical := repoRef.(reference.Canonical); isCanonical {
|
|
|
|
+ canonicalRefs[repoRef.Name()] = struct{}{}
|
|
|
|
+ } else if singleRef == nil {
|
|
|
|
+ singleRef = repoRef
|
|
|
|
+ } else {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if singleRef == nil {
|
|
|
|
+ // Just use first canonical ref
|
|
|
|
+ singleRef = repoRefs[0]
|
|
|
|
+ }
|
|
|
|
+ _, ok := canonicalRefs[singleRef.Name()]
|
|
|
|
+ return len(canonicalRefs) == 1 && ok
|
|
|
|
+}
|
|
|
|
+
|
|
// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the
|
|
// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the
|
|
// given imageID.
|
|
// given imageID.
|
|
func isImageIDPrefix(imageID, possiblePrefix string) bool {
|
|
func isImageIDPrefix(imageID, possiblePrefix string) bool {
|