Initial push

This commit is contained in:
2026-08-11 09:00:01 +02:00
commit fe8b354adc
54 changed files with 12640 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
package migrate
import (
"strings"
"testing"
"github.com/arescom/docker-migrate/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
// <root>/data and extract into <root>.
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")
}
}