Files
2026-08-11 09:00:01 +02:00

193 lines
7.0 KiB
Go

package spec
import "time"
// ImageMode decides how the container image reaches the target host.
type ImageMode string
const (
// ImageAuto pulls from a registry when the reference looks pullable and
// falls back to streaming the image layers otherwise.
ImageAuto ImageMode = "auto"
// ImagePull always runs `docker pull` on the target.
ImagePull ImageMode = "pull"
// ImageStream always transfers `docker save` output.
ImageStream ImageMode = "stream"
// ImageSkip assumes the image is already present on the target.
ImageSkip ImageMode = "skip"
)
// ConflictPolicy decides what to do when the target already has a container,
// volume or network with the same name.
type ConflictPolicy string
const (
ConflictFail ConflictPolicy = "fail" // abort the item
ConflictSkip ConflictPolicy = "skip" // leave the target object untouched
ConflictReplace ConflictPolicy = "replace" // remove the target object first
ConflictRename ConflictPolicy = "rename" // create alongside with a suffix
)
// ItemSelection is the per-container answer to "what do you want to migrate?".
// Every data location is opted in or out individually.
type ItemSelection struct {
ContainerID string `json:"containerId"`
// Include is the master switch for this container.
Include bool `json:"include"`
// NameOverride renames the container on the target.
NameOverride string `json:"nameOverride,omitempty"`
// MigrateImage brings the image across; when false the container is
// created assuming the image already exists on the target.
MigrateImage bool `json:"migrateImage"`
ImageMode ImageMode `json:"imageMode"`
// MigrateNetworks recreates user-defined networks and reattaches them.
MigrateNetworks bool `json:"migrateNetworks"`
KeepStaticIPs bool `json:"keepStaticIps"`
MigratePorts bool `json:"migratePorts"`
// Mounts maps a container-side destination path to how it is handled.
Mounts map[string]MountSelection `json:"mounts"`
// StartAfter starts the container on the target once restored.
StartAfter bool `json:"startAfter"`
// StopSourceDuringCopy stops the source container for the duration of the
// data copy so the files are consistent, then restores its former state.
StopSourceDuringCopy bool `json:"stopSourceDuringCopy"`
// StopSourceAfter leaves the source container stopped once the migration
// succeeded, so the two hosts do not both serve the same workload.
StopSourceAfter bool `json:"stopSourceAfter"`
}
// MountAction is what to do with one data location.
type MountAction string
const (
// MountActionCopy recreates the mount and copies its contents.
MountActionCopy MountAction = "copy"
// MountActionStructure recreates the mount (volume or host directory) but
// leaves it empty.
MountActionStructure MountAction = "structure"
// MountActionSkip drops the mount from the target container entirely.
MountActionSkip MountAction = "skip"
)
// MountSelection is the per-mount answer, including an optional relocation of
// a bind mount to a different path on the target host.
type MountSelection struct {
Action MountAction `json:"action"`
// TargetSource relocates a bind mount on the target host. Empty keeps the
// source path. Ignored for volumes.
TargetSource string `json:"targetSource,omitempty"`
// TargetName renames a named volume on the target. Empty keeps the name.
TargetName string `json:"targetName,omitempty"`
}
// Options are the settings shared by every item in one migration run.
type Options struct {
Conflict ConflictPolicy `json:"conflict"`
RenameSuffix string `json:"renameSuffix,omitempty"` // used by ConflictRename, default "-migrated"
// Compress gzips data and image streams. Requires gzip on the target for
// SSH mode; always safe for package mode.
Compress bool `json:"compress"`
// CompressLevel is 1..9, defaulting to 1 (fast) because these transfers
// are usually bound by disk and network, not CPU.
CompressLevel int `json:"compressLevel"`
// DryRun performs every check and prints every command without changing
// anything on the target.
DryRun bool `json:"dryRun"`
// Parallelism is how many containers migrate at once.
Parallelism int `json:"parallelism"`
// VerifyAfter re-inspects each container on the target and compares the
// resulting spec against the source.
VerifyAfter bool `json:"verifyAfter"`
}
// DefaultOptions returns the options used when the UI has not overridden them.
func DefaultOptions() Options {
return Options{
Conflict: ConflictFail,
RenameSuffix: "-migrated",
Compress: true,
CompressLevel: 1,
Parallelism: 1,
VerifyAfter: true,
}
}
// DefaultSelection builds the "migrate everything" answer for a container,
// which is what the UI presents before the user changes anything.
func DefaultSelection(c *Container) ItemSelection {
sel := ItemSelection{
ContainerID: c.ID,
Include: false,
MigrateImage: true,
ImageMode: ImageAuto,
MigrateNetworks: true,
KeepStaticIPs: false,
MigratePorts: true,
Mounts: map[string]MountSelection{},
StartAfter: c.State == "running",
StopSourceDuringCopy: true,
StopSourceAfter: true,
}
for _, m := range c.Mounts {
action := MountActionCopy
if m.Kind == MountTmpfs {
action = MountActionStructure
}
sel.Mounts[m.Destination] = MountSelection{Action: action}
}
return sel
}
// Plan is a complete migration request: what to move, where, and how.
type Plan struct {
Items []ItemSelection `json:"items"`
Options Options `json:"options"`
// Target is the SSH connection id for host-to-host mode. Empty means the
// plan produces an offline package instead.
Target string `json:"target,omitempty"`
// PackageName is the base name of the produced package (package mode).
PackageName string `json:"packageName,omitempty"`
}
// Manifest is written into an offline migration package. It is descriptive:
// the generated install.sh is self-contained and does not parse it.
type Manifest struct {
FormatVersion int `json:"formatVersion"`
CreatedAt time.Time `json:"createdAt"`
CreatedBy string `json:"createdBy"`
SourceHost string `json:"sourceHost"`
DockerVersion string `json:"dockerVersion"`
Containers []Container `json:"containers"`
Volumes []Volume `json:"volumes"`
Networks []Network `json:"networks"`
Items []ItemSelection `json:"items"`
Options Options `json:"options"`
// Payloads lists every data file in the package with its checksum, so the
// installer can verify the archive survived the trip.
Payloads []Payload `json:"payloads"`
}
// Payload is one file inside a migration package.
type Payload struct {
Path string `json:"path"` // relative to the package root
Kind string `json:"kind"` // "image" | "mount"
Container string `json:"container,omitempty"`
Destination string `json:"destination,omitempty"` // mount destination it restores
Image string `json:"image,omitempty"`
Bytes int64 `json:"bytes"`
SHA256 string `json:"sha256"`
Compressed bool `json:"compressed"`
}