Updates the rmi code to treat canonical references as related to tagged references from the same repository during deletion.
Canonical references with a different repository name will be treated as separate references.
Updates the remove by ID logic to still remove an image if there is a single tag reference and only canonical references to the same repository remaining.
Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
| ... | ... |
@@ -104,27 +104,34 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I |
| 104 | 104 |
|
| 105 | 105 |
repoRefs = daemon.referenceStore.References(imgID) |
| 106 | 106 |
|
| 107 |
- // If this is a tag reference and all the remaining references |
|
| 108 |
- // to this image are digest references, delete the remaining |
|
| 109 |
- // references so that they don't prevent removal of the image. |
|
| 107 |
+ // If a tag reference was removed and the only remaining |
|
| 108 |
+ // references to the same repository are digest references, |
|
| 109 |
+ // then clean up those digest references. |
|
| 110 | 110 |
if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical {
|
| 111 |
- foundTagRef := false |
|
| 111 |
+ foundRepoTagRef := false |
|
| 112 | 112 |
for _, repoRef := range repoRefs {
|
| 113 |
- if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical {
|
|
| 114 |
- foundTagRef = true |
|
| 113 |
+ if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
|
|
| 114 |
+ foundRepoTagRef = true |
|
| 115 | 115 |
break |
| 116 | 116 |
} |
| 117 | 117 |
} |
| 118 |
- if !foundTagRef {
|
|
| 118 |
+ if !foundRepoTagRef {
|
|
| 119 |
+ // Remove canonical references from same repository |
|
| 120 |
+ remainingRefs := []reference.Named{}
|
|
| 119 | 121 |
for _, repoRef := range repoRefs {
|
| 120 |
- if _, err := daemon.removeImageRef(repoRef); err != nil {
|
|
| 121 |
- return records, err |
|
| 122 |
- } |
|
| 122 |
+ if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
|
|
| 123 |
+ if _, err := daemon.removeImageRef(repoRef); err != nil {
|
|
| 124 |
+ return records, err |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
|
|
| 128 |
+ records = append(records, untaggedRecord) |
|
| 129 |
+ } else {
|
|
| 130 |
+ remainingRefs = append(remainingRefs, repoRef) |
|
| 123 | 131 |
|
| 124 |
- untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
|
|
| 125 |
- records = append(records, untaggedRecord) |
|
| 132 |
+ } |
|
| 126 | 133 |
} |
| 127 |
- repoRefs = []reference.Named{}
|
|
| 134 |
+ repoRefs = remainingRefs |
|
| 128 | 135 |
} |
| 129 | 136 |
} |
| 130 | 137 |
|
| ... | ... |
@@ -135,11 +142,10 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I |
| 135 | 135 |
|
| 136 | 136 |
removedRepositoryRef = true |
| 137 | 137 |
} else {
|
| 138 |
- // If an ID reference was given AND there is exactly one |
|
| 139 |
- // repository reference to the image then we will want to |
|
| 140 |
- // remove that reference. |
|
| 141 |
- // FIXME: Is this the behavior we want? |
|
| 142 |
- if len(repoRefs) == 1 {
|
|
| 138 |
+ // If an ID reference was given AND there is at most one tag |
|
| 139 |
+ // reference to the image AND all references are within one |
|
| 140 |
+ // repository, then remove all references. |
|
| 141 |
+ if isSingleReference(repoRefs) {
|
|
| 143 | 142 |
c := conflictHard |
| 144 | 143 |
if !force {
|
| 145 | 144 |
c |= conflictSoft &^ conflictActiveReference |
| ... | ... |
@@ -148,21 +154,48 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I |
| 148 | 148 |
return nil, conflict |
| 149 | 149 |
} |
| 150 | 150 |
|
| 151 |
- parsedRef, err := daemon.removeImageRef(repoRefs[0]) |
|
| 152 |
- if err != nil {
|
|
| 153 |
- return nil, err |
|
| 154 |
- } |
|
| 151 |
+ for _, repoRef := range repoRefs {
|
|
| 152 |
+ parsedRef, err := daemon.removeImageRef(repoRef) |
|
| 153 |
+ if err != nil {
|
|
| 154 |
+ return nil, err |
|
| 155 |
+ } |
|
| 155 | 156 |
|
| 156 |
- untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
|
| 157 |
+ untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
|
| 157 | 158 |
|
| 158 |
- daemon.LogImageEvent(imgID.String(), imgID.String(), "untag") |
|
| 159 |
- records = append(records, untaggedRecord) |
|
| 159 |
+ daemon.LogImageEvent(imgID.String(), imgID.String(), "untag") |
|
| 160 |
+ records = append(records, untaggedRecord) |
|
| 161 |
+ } |
|
| 160 | 162 |
} |
| 161 | 163 |
} |
| 162 | 164 |
|
| 163 | 165 |
return records, daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef) |
| 164 | 166 |
} |
| 165 | 167 |
|
| 168 |
+// isSingleReference returns true when all references are from one repository |
|
| 169 |
+// and there is at most one tag. Returns false for empty input. |
|
| 170 |
+func isSingleReference(repoRefs []reference.Named) bool {
|
|
| 171 |
+ if len(repoRefs) <= 1 {
|
|
| 172 |
+ return len(repoRefs) == 1 |
|
| 173 |
+ } |
|
| 174 |
+ var singleRef reference.Named |
|
| 175 |
+ canonicalRefs := map[string]struct{}{}
|
|
| 176 |
+ for _, repoRef := range repoRefs {
|
|
| 177 |
+ if _, isCanonical := repoRef.(reference.Canonical); isCanonical {
|
|
| 178 |
+ canonicalRefs[repoRef.Name()] = struct{}{}
|
|
| 179 |
+ } else if singleRef == nil {
|
|
| 180 |
+ singleRef = repoRef |
|
| 181 |
+ } else {
|
|
| 182 |
+ return false |
|
| 183 |
+ } |
|
| 184 |
+ } |
|
| 185 |
+ if singleRef == nil {
|
|
| 186 |
+ // Just use first canonical ref |
|
| 187 |
+ singleRef = repoRefs[0] |
|
| 188 |
+ } |
|
| 189 |
+ _, ok := canonicalRefs[singleRef.Name()] |
|
| 190 |
+ return len(canonicalRefs) == 1 && ok |
|
| 191 |
+} |
|
| 192 |
+ |
|
| 166 | 193 |
// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the |
| 167 | 194 |
// given imageID. |
| 168 | 195 |
func isImageIDPrefix(imageID, possiblePrefix string) bool {
|