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"` }