fix missing layers when exporting a full repository
| ... | ... |
@@ -30,24 +30,21 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
|
| 30 | 30 |
defer os.RemoveAll(tempdir) |
| 31 | 31 |
|
| 32 | 32 |
rootRepoMap := map[string]Repository{}
|
| 33 |
+ addKey := func(name string, tag string, id string) {
|
|
| 34 |
+ log.Debugf("add key [%s:%s]", name, tag)
|
|
| 35 |
+ if repo, ok := rootRepoMap[name]; !ok {
|
|
| 36 |
+ rootRepoMap[name] = Repository{tag: id}
|
|
| 37 |
+ } else {
|
|
| 38 |
+ repo[tag] = id |
|
| 39 |
+ } |
|
| 40 |
+ } |
|
| 33 | 41 |
for _, name := range job.Args {
|
| 34 | 42 |
log.Debugf("Serializing %s", name)
|
| 35 | 43 |
rootRepo := s.Repositories[name] |
| 36 | 44 |
if rootRepo != nil {
|
| 37 | 45 |
// this is a base repo name, like 'busybox' |
| 38 |
- for _, id := range rootRepo {
|
|
| 39 |
- if _, ok := rootRepoMap[name]; !ok {
|
|
| 40 |
- rootRepoMap[name] = rootRepo |
|
| 41 |
- } else {
|
|
| 42 |
- log.Debugf("Duplicate key [%s]", name)
|
|
| 43 |
- if rootRepoMap[name].Contains(rootRepo) {
|
|
| 44 |
- log.Debugf("skipping, because it is present [%s:%q]", name, rootRepo)
|
|
| 45 |
- continue |
|
| 46 |
- } |
|
| 47 |
- log.Debugf("updating [%s]: [%q] with [%q]", name, rootRepoMap[name], rootRepo)
|
|
| 48 |
- rootRepoMap[name].Update(rootRepo) |
|
| 49 |
- } |
|
| 50 |
- |
|
| 46 |
+ for tag, id := range rootRepo {
|
|
| 47 |
+ addKey(name, tag, id) |
|
| 51 | 48 |
if err := s.exportImage(job.Eng, id, tempdir); err != nil {
|
| 52 | 49 |
return job.Error(err) |
| 53 | 50 |
} |
| ... | ... |
@@ -65,18 +62,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
|
| 65 | 65 |
// check this length, because a lookup of a truncated has will not have a tag |
| 66 | 66 |
// and will not need to be added to this map |
| 67 | 67 |
if len(repoTag) > 0 {
|
| 68 |
- if _, ok := rootRepoMap[repoName]; !ok {
|
|
| 69 |
- rootRepoMap[repoName] = Repository{repoTag: img.ID}
|
|
| 70 |
- } else {
|
|
| 71 |
- log.Debugf("Duplicate key [%s]", repoName)
|
|
| 72 |
- newRepo := Repository{repoTag: img.ID}
|
|
| 73 |
- if rootRepoMap[repoName].Contains(newRepo) {
|
|
| 74 |
- log.Debugf("skipping, because it is present [%s:%q]", repoName, newRepo)
|
|
| 75 |
- continue |
|
| 76 |
- } |
|
| 77 |
- log.Debugf("updating [%s]: [%q] with [%q]", repoName, rootRepoMap[repoName], newRepo)
|
|
| 78 |
- rootRepoMap[repoName].Update(newRepo) |
|
| 79 |
- } |
|
| 68 |
+ addKey(repoName, repoTag, img.ID) |
|
| 80 | 69 |
} |
| 81 | 70 |
if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil {
|
| 82 | 71 |
return job.Error(err) |
| ... | ... |
@@ -8,6 +8,8 @@ import ( |
| 8 | 8 |
"os/exec" |
| 9 | 9 |
"path/filepath" |
| 10 | 10 |
"reflect" |
| 11 |
+ "sort" |
|
| 12 |
+ "strings" |
|
| 11 | 13 |
"testing" |
| 12 | 14 |
|
| 13 | 15 |
"github.com/docker/docker/vendor/src/github.com/kr/pty" |
| ... | ... |
@@ -260,6 +262,66 @@ func TestSaveMultipleNames(t *testing.T) {
|
| 260 | 260 |
logDone("save - save by multiple names")
|
| 261 | 261 |
} |
| 262 | 262 |
|
| 263 |
+func TestSaveRepoWithMultipleImages(t *testing.T) {
|
|
| 264 |
+ |
|
| 265 |
+ makeImage := func(from string, tag string) string {
|
|
| 266 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", from, "true") |
|
| 267 |
+ var ( |
|
| 268 |
+ out string |
|
| 269 |
+ err error |
|
| 270 |
+ ) |
|
| 271 |
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
|
| 272 |
+ t.Fatalf("failed to create a container: %v %v", out, err)
|
|
| 273 |
+ } |
|
| 274 |
+ cleanedContainerID := stripTrailingCharacters(out) |
|
| 275 |
+ |
|
| 276 |
+ commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, tag) |
|
| 277 |
+ if out, _, err = runCommandWithOutput(commitCmd); err != nil {
|
|
| 278 |
+ t.Fatalf("failed to commit container: %v %v", out, err)
|
|
| 279 |
+ } |
|
| 280 |
+ imageID := stripTrailingCharacters(out) |
|
| 281 |
+ |
|
| 282 |
+ deleteContainer(cleanedContainerID) |
|
| 283 |
+ return imageID |
|
| 284 |
+ } |
|
| 285 |
+ |
|
| 286 |
+ repoName := "foobar-save-multi-images-test" |
|
| 287 |
+ tagFoo := repoName + ":foo" |
|
| 288 |
+ tagBar := repoName + ":bar" |
|
| 289 |
+ |
|
| 290 |
+ idFoo := makeImage("busybox:latest", tagFoo)
|
|
| 291 |
+ idBar := makeImage("busybox:latest", tagBar)
|
|
| 292 |
+ |
|
| 293 |
+ deleteImages(repoName) |
|
| 294 |
+ |
|
| 295 |
+ // create the archive |
|
| 296 |
+ saveCmdFinal := fmt.Sprintf("%v save %v | tar t | grep 'VERSION' |cut -d / -f1", dockerBinary, repoName)
|
|
| 297 |
+ saveCmd := exec.Command("bash", "-c", saveCmdFinal)
|
|
| 298 |
+ out, _, err := runCommandWithOutput(saveCmd) |
|
| 299 |
+ if err != nil {
|
|
| 300 |
+ t.Fatalf("failed to save multiple images: %s, %v", out, err)
|
|
| 301 |
+ } |
|
| 302 |
+ actual := strings.Split(stripTrailingCharacters(out), "\n") |
|
| 303 |
+ |
|
| 304 |
+ // make the list of expected layers |
|
| 305 |
+ historyCmdFinal := fmt.Sprintf("%v history -q --no-trunc %v", dockerBinary, "busybox:latest")
|
|
| 306 |
+ historyCmd := exec.Command("bash", "-c", historyCmdFinal)
|
|
| 307 |
+ out, _, err = runCommandWithOutput(historyCmd) |
|
| 308 |
+ if err != nil {
|
|
| 309 |
+ t.Fatalf("failed to get history: %s, %v", out, err)
|
|
| 310 |
+ } |
|
| 311 |
+ |
|
| 312 |
+ expected := append(strings.Split(stripTrailingCharacters(out), "\n"), idFoo, idBar) |
|
| 313 |
+ |
|
| 314 |
+ sort.Strings(actual) |
|
| 315 |
+ sort.Strings(expected) |
|
| 316 |
+ if !reflect.DeepEqual(expected, actual) {
|
|
| 317 |
+ t.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected)
|
|
| 318 |
+ } |
|
| 319 |
+ |
|
| 320 |
+ logDone("save - save repository with multiple images")
|
|
| 321 |
+} |
|
| 322 |
+ |
|
| 263 | 323 |
// Issue #6722 #5892 ensure directories are included in changes |
| 264 | 324 |
func TestSaveDirectoryPermissions(t *testing.T) {
|
| 265 | 325 |
layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
|