193 lines
7.0 KiB
Go
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"`
|
|
}
|