package migrate import ( "strings" "testing" "github.com/arescom/dockmv/internal/spec" ) func sample() *spec.Container { return &spec.Container{ ID: "abc123", Name: "app", State: "running", Image: "app:1.0", Mounts: []spec.Mount{ {Kind: spec.MountVolume, Name: "appdata", Destination: "/data", SizeBytes: 4096}, {Kind: spec.MountBind, Source: "/srv/app/conf", Destination: "/etc/app", ReadOnly: true}, {Kind: spec.MountAnonymous, Name: strings.Repeat("f", 64), Destination: "/tmp/cache"}, {Kind: spec.MountTmpfs, Destination: "/run"}, }, Endpoints: []spec.Endpoint{{Network: "appnet"}}, } } func TestPrepareDefaultsCopyEverything(t *testing.T) { c := sample() sel := spec.DefaultSelection(c) sel.Include = true p, err := Prepare(c, sel, []spec.Volume{{Name: "appdata", Driver: "local"}}, []spec.Network{{Name: "appnet", Driver: "bridge"}}) if err != nil { t.Fatal(err) } // tmpfs carries no data, so exactly the three real locations transfer. if len(p.Transfers) != 3 { t.Fatalf("expected 3 transfers, got %d: %+v", len(p.Transfers), p.Transfers) } if len(p.Volumes) != 1 || p.Volumes[0].Name != "appdata" { t.Errorf("named volume not scheduled for creation: %+v", p.Volumes) } if len(p.Networks) != 1 { t.Errorf("network not scheduled for creation: %+v", p.Networks) } byDest := map[string]Transfer{} for _, tr := range p.Transfers { byDest[tr.Destination] = tr } if got := byDest["/data"].RestoreInto; got != "/" { t.Errorf("/data must be restored into /, got %q", got) } if got := byDest["/etc/app"].RestoreInto; got != "/etc" { t.Errorf("/etc/app must be restored into /etc, got %q", got) } if !byDest["/etc/app"].ReadOnly { t.Error("read-only bind must be flagged so it is seeded through a staging container") } } func TestPrepareSkipAndRelocate(t *testing.T) { c := sample() sel := spec.DefaultSelection(c) sel.Include = true sel.Mounts["/tmp/cache"] = spec.MountSelection{Action: spec.MountActionSkip} sel.Mounts["/etc/app"] = spec.MountSelection{Action: spec.MountActionCopy, TargetSource: "/opt/app/conf"} sel.Mounts["/data"] = spec.MountSelection{Action: spec.MountActionStructure, TargetName: "appdata2"} p, err := Prepare(c, sel, []spec.Volume{{Name: "appdata", Driver: "local"}}, nil) if err != nil { t.Fatal(err) } if !p.Render.DropMounts["/tmp/cache"] { t.Error("skipped mount must be dropped from the create command") } // structure-only means the volume is created but no data is copied. for _, tr := range p.Transfers { if tr.Destination == "/data" { t.Error("a structure-only mount must not be transferred") } } if len(p.Volumes) != 1 || p.Volumes[0].Name != "appdata2" { t.Errorf("renamed volume not applied: %+v", p.Volumes) } args := strings.Join(p.Target.CreateArgs(p.Render), " ") if !strings.Contains(args, "--volume /opt/app/conf:/etc/app:ro") { t.Errorf("relocated bind not applied: %s", args) } if !strings.Contains(args, "--volume appdata2:/data") { t.Errorf("renamed volume not applied to create args: %s", args) } if strings.Contains(args, "/tmp/cache") { t.Errorf("skipped mount still present: %s", args) } } func TestPrepareAnonymousVolumeIsRecreatedFresh(t *testing.T) { c := sample() sel := spec.DefaultSelection(c) sel.Include = true p, err := Prepare(c, sel, nil, nil) if err != nil { t.Fatal(err) } args := strings.Join(p.Target.CreateArgs(p.Render), " ") if strings.Contains(args, strings.Repeat("f", 64)) { t.Errorf("the generated volume name must not be pinned on the target: %s", args) } if !strings.Contains(args, "--volume /tmp/cache") { t.Errorf("anonymous volume must still be declared: %s", args) } } func TestStagingPathsLandArchiveOnTheMountPoint(t *testing.T) { // A tar produced from /var/lib/postgresql/data has entries rooted at // "data/", so the staging container must mount the volume at // /data and extract into . mountAt, into := StagingPaths("/var/lib/postgresql/data") if mountAt != into+"/data" { t.Fatalf("mount point %q is not directly under the extraction dir %q", mountAt, into) } } func TestPrepareRefusesRootMount(t *testing.T) { c := &spec.Container{ ID: "x", Name: "weird", Image: "img", Mounts: []spec.Mount{{Kind: spec.MountBind, Source: "/", Destination: "/"}}, } sel := spec.DefaultSelection(c) sel.Include = true p, err := Prepare(c, sel, nil, nil) if err != nil { t.Fatal(err) } if len(p.Transfers) != 0 { t.Errorf("a mount at / must not be copied: %+v", p.Transfers) } if len(p.Notes) == 0 { t.Error("refusing to copy / should be reported to the operator") } }