The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/4729

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From cb3f1097cdefda8842d734d4efd4e6042393fb4f Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Wed, 27 Jun 2018 20:52:57 +0200
Subject: [PATCH 1/3] lxc-to-lxd: Rewrite in Go

Signed-off-by: Thomas Hipp <[email protected]>
---
 lxc-to-lxd/config.go       | 214 ++++++++++++++++
 lxc-to-lxd/main.go         |  44 ++++
 lxc-to-lxd/main_migrate.go | 602 +++++++++++++++++++++++++++++++++++++++++++++
 lxc-to-lxd/main_netcat.go  |  72 ++++++
 lxc-to-lxd/network.go      |  31 +++
 lxc-to-lxd/transfer.go     | 141 +++++++++++
 lxc-to-lxd/utils.go        | 182 ++++++++++++++
 7 files changed, 1286 insertions(+)
 create mode 100644 lxc-to-lxd/config.go
 create mode 100644 lxc-to-lxd/main.go
 create mode 100644 lxc-to-lxd/main_migrate.go
 create mode 100644 lxc-to-lxd/main_netcat.go
 create mode 100644 lxc-to-lxd/network.go
 create mode 100644 lxc-to-lxd/transfer.go
 create mode 100644 lxc-to-lxd/utils.go

diff --git a/lxc-to-lxd/config.go b/lxc-to-lxd/config.go
new file mode 100644
index 000000000..615e7eb11
--- /dev/null
+++ b/lxc-to-lxd/config.go
@@ -0,0 +1,214 @@
+package main
+
+import (
+       "bufio"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "strings"
+
+       "github.com/lxc/lxd/shared"
+)
+
+var checkedKeys = []string{
+       "lxc.aa_allow_incomplete",
+       "lxc.aa_profile",
+       "lxc.apparmor.allow_incomplete",
+       "lxc.apparmor.profile",
+       "lxc.arch",
+       "lxc.autodev",
+       "lxc.cap.drop",
+       "lxc.environment",
+       "lxc.haltsignal",
+       "lxc.id_map",
+       "lxc.idmap",
+       "lxc.include",
+       "lxc.loglevel",
+       "lxc.mount",
+       "lxc.mount.auto",
+       "lxc.mount.entry",
+       "lxc.pts",
+       "lxc.pty.max",
+       "lxc.rebootsignal",
+       "lxc.rootfs",
+       "lxc.rootfs.backend",
+       "lxc.rootfs.mount",
+       "lxc.rootfs.path",
+       "lxc.seccomp",
+       "lxc.signal.halt",
+       "lxc.signal.reboot",
+       "lxc.signal.stop",
+       "lxc.start.auto",
+       "lxc.start.delay",
+       "lxc.start.order",
+       "lxc.stopsignal",
+       "lxc.tty",
+       "lxc.tty.max",
+       "lxc.uts.name",
+       "lxc.utsname",
+       "lxd.migrated",
+}
+
+func getUnsupportedKeys(config []string) []string {
+       var out []string
+
+       for _, a := range config {
+               supported := false
+
+               for _, b := range checkedKeys {
+                       if a == b {
+                               supported = true
+                               break
+                       }
+               }
+
+               if !supported {
+                       out = append(out, a)
+               }
+       }
+
+       return out
+}
+
+func getConfig(config []string, key string) []string {
+       // Return an array since keys can be specified more than once
+       var out []string
+
+       for _, c := range config {
+               text := strings.TrimSpace(c)
+
+               // Ignore empty lines and comments
+               if len(text) == 0 || strings.HasPrefix(text, "#") {
+                       continue
+               }
+
+               line := strings.Split(text, "=")
+               k := strings.TrimSpace(line[0])
+               v := strings.Trim(strings.TrimSpace(line[1]), "\"")
+
+               if k == key && len(v) > 0 {
+                       out = append(out, v)
+               }
+       }
+
+       if len(out) == 0 {
+               return nil
+       }
+
+       return out
+}
+
+func getConfigKeys(config []string) []string {
+       // Make sure we don't have duplicate keys
+       m := make(map[string]bool, 0)
+       for _, c := range config {
+               text := strings.TrimSpace(c)
+
+               // Ignore empty lines and comments
+               if len(text) == 0 || strings.HasPrefix(text, "#") {
+                       continue
+               }
+
+               line := strings.Split(text, "=")
+               key := strings.TrimSpace(line[0])
+               if strings.HasPrefix(key, "lxc.") {
+                       m[key] = true
+               }
+       }
+
+       var out []string
+       for k := range m {
+               out = append(out, k)
+       }
+
+       return out
+}
+
+func parseConfig(path string) ([]string, error) {
+       file, err := os.Open(path)
+       if err != nil {
+               return nil, err
+       }
+       defer file.Close()
+
+       var config []string
+
+       // Parse config
+       sc := bufio.NewScanner(file)
+       for sc.Scan() {
+               text := strings.TrimSpace(sc.Text())
+
+               // Ignore empty lines and comments
+               if len(text) == 0 || strings.HasPrefix(text, "#") {
+                       continue
+               }
+
+               line := strings.Split(text, "=")
+               key := strings.TrimSpace(line[0])
+               value := strings.TrimSpace(line[1])
+
+               switch key {
+               // Parse user-added includes
+               case "lxc.include":
+                       // Ignore our own default configs
+                       if strings.HasPrefix(value, "/usr/share/lxc/config/") {
+                               continue
+                       }
+
+                       if shared.PathExists(value) {
+                               if shared.IsDir(value) {
+                                       files, err := ioutil.ReadDir(value)
+                                       if err != nil {
+                                               return nil, err
+                                       }
+
+                                       for _, file := range files {
+                                               path := filepath.Join(value, 
file.Name())
+                                               if !strings.HasSuffix(path, 
".conf") {
+                                                       continue
+                                               }
+
+                                               config = append(config, path)
+                                       }
+                               } else {
+                                       c, err := parseConfig(value)
+                                       if err != nil {
+                                               return nil, err
+                                       }
+
+                                       config = append(config, c...)
+                               }
+                               continue
+                       }
+               // Expand any fstab
+               case "lxc.mount":
+                       if !shared.PathExists(value) {
+                               fmt.Println("Container fstab file doesn't 
exist, skipping...")
+                               continue
+                       }
+
+                       file, err := os.Open(value)
+                       if err != nil {
+                               return nil, err
+                       }
+                       defer file.Close()
+
+                       sc := bufio.NewScanner(file)
+                       for sc.Scan() {
+                               text := strings.TrimSpace(sc.Text())
+
+                               if len(text) > 0 && !strings.HasPrefix(text, 
"#") {
+                                       config = append(config, 
fmt.Sprintf("lxc.mount.entry = %s", text))
+                               }
+                       }
+
+                       continue
+
+               default:
+                       config = append(config, text)
+               }
+       }
+
+       return config, nil
+}
diff --git a/lxc-to-lxd/main.go b/lxc-to-lxd/main.go
new file mode 100644
index 000000000..a350214ed
--- /dev/null
+++ b/lxc-to-lxd/main.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+       "os"
+
+       "github.com/spf13/cobra"
+
+       "github.com/lxc/lxd/shared/version"
+)
+
+type cmdGlobal struct {
+       flagVersion bool
+       flagHelp    bool
+}
+
+func main() {
+       // migrate command (main)
+       migrateCmd := cmdMigrate{}
+       app := migrateCmd.Command()
+       app.SilenceUsage = true
+
+       // Workaround for main command
+       app.Args = cobra.ArbitraryArgs
+
+       // Global flags
+       globalCmd := cmdGlobal{}
+       migrateCmd.global = &globalCmd
+       app.PersistentFlags().BoolVar(&globalCmd.flagVersion, "version", false, 
"Print version number")
+       app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, 
"Print help")
+
+       // Version handling
+       app.SetVersionTemplate("{{.Version}}\n")
+       app.Version = version.Version
+
+       // netcat sub-command
+       netcatCmd := cmdNetcat{global: &globalCmd}
+       app.AddCommand(netcatCmd.Command())
+
+       // Run the main command and handle errors
+       err := app.Execute()
+       if err != nil {
+               os.Exit(1)
+       }
+}
diff --git a/lxc-to-lxd/main_migrate.go b/lxc-to-lxd/main_migrate.go
new file mode 100644
index 000000000..f973e3b21
--- /dev/null
+++ b/lxc-to-lxd/main_migrate.go
@@ -0,0 +1,602 @@
+package main
+
+import (
+       "encoding/json"
+       "fmt"
+       "os"
+       "runtime"
+       "strconv"
+       "strings"
+
+       "github.com/spf13/cobra"
+       lxc "gopkg.in/lxc/go-lxc.v2"
+
+       lxd "github.com/lxc/lxd/client"
+       "github.com/lxc/lxd/lxc/config"
+       "github.com/lxc/lxd/lxc/utils"
+       "github.com/lxc/lxd/lxd/types"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/api"
+       "github.com/lxc/lxd/shared/i18n"
+       "github.com/lxc/lxd/shared/osarch"
+)
+
+type cmdMigrate struct {
+       global *cmdGlobal
+
+       conf     *config.Config
+       confPath string
+       cmd      *cobra.Command
+
+       // Flags
+       flagDryRun     bool
+       flagDebug      bool
+       flagAll        bool
+       flagDelete     bool
+       flagStorage    string
+       flagLXCPath    string
+       flagRsyncArgs  string
+       flagContainers []string
+}
+
+func (c *cmdMigrate) Command() *cobra.Command {
+       cmd := &cobra.Command{
+               Use:   "lxc-to-lxd",
+               Short: i18n.G("Command line client for container migration"),
+       }
+
+       // Wrappers
+       cmd.RunE = c.RunE
+
+       // Flags
+       cmd.Flags().BoolVar(&c.flagDryRun, "dry-run", false, i18n.G("Dry run 
mode"))
+       cmd.Flags().BoolVar(&c.flagDebug, "debug", false, i18n.G("Print 
debugging output"))
+       cmd.Flags().BoolVar(&c.flagAll, "all", false, i18n.G("Import all 
containers"))
+       cmd.Flags().BoolVar(&c.flagDelete, "delete", false, i18n.G("Delete the 
source container"))
+       cmd.Flags().StringVar(&c.flagStorage, "storage", "",
+               i18n.G("Storage pool to use for the container")+"``")
+       cmd.Flags().StringVar(&c.flagLXCPath, "lxcpath", 
lxc.DefaultConfigPath(),
+               i18n.G("Alternate LXC path")+"``")
+       cmd.Flags().StringVar(&c.flagRsyncArgs, "rsync-args", "",
+               "Extra arguments to pass to rsync"+"``")
+       cmd.Flags().StringSliceVar(&c.flagContainers, "containers", nil,
+               i18n.G("Container(s) to import")+"``")
+
+       return cmd
+}
+
+func (c *cmdMigrate) RunE(cmd *cobra.Command, args []string) error {
+       if (len(c.flagContainers) == 0 && !c.flagAll) || (len(c.flagContainers) 
> 0 && c.flagAll) {
+               fmt.Fprintln(os.Stderr, "You must either pass container names 
or --all")
+               os.Exit(1)
+       }
+       // Connect to LXD
+       d, err := lxd.ConnectLXDUnix("", nil)
+       if err != nil {
+               return err
+       }
+
+       // Retrieve LXC containers
+       for _, container := range lxc.Containers(c.flagLXCPath) {
+               if !c.flagAll && !shared.StringInSlice(container.Name(), 
c.flagContainers) {
+                       continue
+               }
+
+               err := convertContainer(d, container, c.flagStorage,
+                       c.flagDryRun, c.flagRsyncArgs, c.flagDebug)
+               if err != nil {
+                       fmt.Printf("Skipping container '%s': %v\n", 
container.Name(), err)
+                       continue
+               }
+
+               // Delete container
+               if c.flagDelete {
+                       if c.flagDryRun {
+                               fmt.Println("Would destroy container now")
+                       } else {
+                               err := container.Destroy()
+                               if err != nil {
+                                       fmt.Printf("Failed to destroy container 
'%s': %v\n", container.Name(), err)
+                               }
+                       }
+               }
+       }
+
+       return nil
+}
+
+func validateConfig(conf []string, container *lxc.Container) error {
+       // Checking whether container has already been migrated
+       fmt.Println("Checking whether container has already been migrated")
+       if len(getConfig(conf, "lxd.migrated")) > 0 {
+               return fmt.Errorf("Container has already been migrated")
+       }
+
+       // Validate lxc.utsname / lxc.uts.name
+       value := getConfig(conf, "lxc.uts.name")
+       if value == nil {
+               value = getConfig(conf, "lxc.utsname")
+       }
+       if value == nil || value[0] != container.Name() {
+               return fmt.Errorf("Container name doesn't match lxc.uts.name / 
lxc.utsname")
+       }
+
+       // Validate lxc.aa_allow_incomplete: must be set to 0 or unset.
+       fmt.Println("Validating whether incomplete AppArmor support is enabled")
+       value = getConfig(conf, "lxc.apparmor.allow_incomplete")
+       if value == nil {
+               value = getConfig(conf, "lxc.aa_allow_incomplete")
+       }
+       if value != nil {
+               v, err := strconv.Atoi(value[0])
+               if err != nil {
+                       return err
+               }
+
+               if v != 0 {
+                       return fmt.Errorf("Container allows incomplete AppArmor 
support")
+               }
+       }
+
+       // Validate lxc.autodev: must be set to 1 or unset.
+       fmt.Println("Validating whether mounting a minimal /dev is enabled")
+       value = getConfig(conf, "lxc.autodev")
+       if value != nil {
+               v, err := strconv.Atoi(value[0])
+               if err != nil {
+                       return err
+               }
+
+               if v != 1 {
+                       return fmt.Errorf("Container doesn't mount a minimal 
/dev filesystem")
+               }
+       }
+
+       // Extract and valid rootfs key
+       fmt.Println("Validating container rootfs")
+       rootfs, err := getRootfs(conf)
+       if err != nil {
+               return err
+       }
+
+       if !shared.PathExists(rootfs) {
+               return fmt.Errorf("Couldn't find the container rootfs '%s'", 
rootfs)
+       }
+
+       return nil
+}
+
+func convertContainer(d lxd.ContainerServer, container *lxc.Container, storage 
string,
+       dryRun bool, rsyncArgs string, debug bool) error {
+       // Don't migrate running containers
+       if container.Running() {
+               return fmt.Errorf("Only stopped containers can be migrated")
+       }
+
+       fmt.Println("Parsing LXC configuration")
+       conf, err := parseConfig(container.ConfigFileName())
+       if err != nil {
+               return err
+       }
+
+       if debug {
+               fmt.Printf("Container configuration:\n  %v\n", 
strings.Join(conf, "\n  "))
+       }
+
+       // Check whether there are unsupported keys in the config
+       fmt.Println("Checking for unsupported LXC configuration keys")
+       keys := getUnsupportedKeys(getConfigKeys(conf))
+       for _, k := range keys {
+               if !strings.HasPrefix(k, "lxc.net.") &&
+                       !strings.HasPrefix(k, "lxc.network.") &&
+                       !strings.HasPrefix(k, "lxc.cgroup.") &&
+                       !strings.HasPrefix(k, "lxc.cgroup2.") {
+                       return fmt.Errorf("Found unsupported config key '%s'", 
k)
+               }
+       }
+
+       // Make sure we don't have a conflict
+       fmt.Println("Checking for existing containers")
+       containers, err := d.GetContainerNames()
+       if err != nil {
+               return err
+       }
+
+       found := false
+       for _, name := range containers {
+               if name == container.Name() {
+                       found = true
+               }
+       }
+       if found {
+               return fmt.Errorf("Container already exists")
+       }
+
+       // Validate config
+       err = validateConfig(conf, container)
+       if err != nil {
+               return err
+       }
+
+       newConfig := make(map[string]string, 0)
+
+       value := getConfig(conf, "lxd.idmap")
+       if value == nil {
+               value = getConfig(conf, "lxd.id_map")
+       }
+       if value == nil {
+               // Privileged container
+               newConfig["security.privileged"] = "true"
+       } else {
+               // Unprivileged container
+               newConfig["security.privileged"] = "false"
+       }
+
+       newDevices := make(types.Devices, 0)
+
+       // Convert network configuration
+       err = convertNetworkConfig(container, newDevices)
+       if err != nil {
+               return err
+       }
+
+       // Convert storage configuration
+       err = convertStorageConfig(conf, newDevices)
+       if err != nil {
+               return err
+       }
+
+       // Convert environment
+       fmt.Println("Processing environment configuration")
+       value = getConfig(conf, "lxc.environment")
+       for _, env := range value {
+               entry := strings.Split(env, "=")
+               key := strings.TrimSpace(entry[0])
+               val := strings.TrimSpace(entry[len(entry)-1])
+               newConfig[fmt.Sprintf("environment.%s", key)] = val
+       }
+
+       // Convert auto-start
+       fmt.Println("Processing container boot configuration")
+       value = getConfig(conf, "lxc.start.auto")
+       if value != nil {
+               val, err := strconv.Atoi(value[0])
+               if err != nil {
+                       return err
+               }
+
+               if val > 0 {
+                       newConfig["boot.autostart"] = "true"
+               }
+       }
+
+       value = getConfig(conf, "lxc.start.delay")
+       if value != nil {
+               val, err := strconv.Atoi(value[0])
+               if err != nil {
+                       return err
+               }
+
+               if val > 0 {
+                       newConfig["boot.autostart.delay"] = value[0]
+               }
+       }
+
+       value = getConfig(conf, "lxc.start.order")
+       if value != nil {
+               val, err := strconv.Atoi(value[0])
+               if err != nil {
+                       return err
+               }
+
+               if val > 0 {
+                       newConfig["boot.autostart.priority"] = value[0]
+               }
+       }
+
+       // Convert apparmor
+       fmt.Println("Processing container apparmor configuration")
+       value = getConfig(conf, "lxc.apparmor.profile")
+       if value == nil {
+               value = getConfig(conf, "lxc.aa_profile")
+       }
+       if value != nil {
+               if value[0] == "lxc-container-default-with-nesting" {
+                       newConfig["security.nesting"] = "true"
+               } else if value[0] != "lxc-container-default" {
+                       newConfig["raw.lxc"] = 
fmt.Sprintf("lxc.aa_profile=%s\n", value[0])
+               }
+       }
+
+       // Convert seccomp
+       fmt.Println("Processing container seccomp configuration")
+       value = getConfig(conf, "lxc.seccomp.profile")
+       if value == nil {
+               value = getConfig(conf, "lxc.seccomp")
+       }
+       if value != nil && value[0] != "/usr/share/lxc/config/common.seccomp" {
+               return fmt.Errorf("Custom seccomp profiles aren't supported")
+       }
+
+       // Convert SELinux
+       fmt.Println("Processing container SELinux configuration")
+       value = getConfig(conf, "lxc.selinux.context")
+       if value == nil {
+               value = getConfig(conf, "lxc.se_context")
+       }
+       if value != nil {
+               return fmt.Errorf("Custom SELinux policies aren't supported")
+       }
+
+       // Convert capabilities
+       fmt.Println("Processing container capabilities configuration")
+       value = getConfig(conf, "lxc.cap.drop")
+       if value != nil {
+               for _, cap := range strings.Split(value[0], " ") {
+                       // Ignore capabilities that are dropped in LXD 
containers by default.
+                       if shared.StringInSlice(cap, []string{"mac_admin", 
"mac_override", "sys_module",
+                               "sys_time"}) {
+                               continue
+                       }
+                       return fmt.Errorf("Custom capabilities aren't 
supported")
+               }
+       }
+
+       value = getConfig(conf, "lxc.cap.keep")
+       if value != nil {
+               return fmt.Errorf("Custom capabilities aren't supported")
+       }
+
+       // Add rest of the keys to lxc.raw
+       for _, c := range conf {
+               parts := strings.SplitN(c, "=", 2)
+               if len(parts) != 2 {
+                       continue
+               }
+
+               key := strings.TrimSpace(parts[0])
+               val := strings.TrimSpace(parts[1])
+
+               switch key {
+               case "lxc.signal.halt", "lxc.haltsignal":
+                       newConfig["raw.lxc"] += 
fmt.Sprintf("lxc.signal.halt=%s\n", val)
+               case "lxc.signal.reboot", "lxc.rebootsignal":
+                       newConfig["raw.lxc"] += 
fmt.Sprintf("lxc.signal.reboot=%s\n", val)
+               case "lxc.signal.stop", "lxc.stopsignal":
+                       newConfig["raw.lxc"] += 
fmt.Sprintf("lxc.signal.stop=%s\n", val)
+               case "lxc.apparmor.allow_incomplete", "lxc.aa_allow_incomplete":
+                       newConfig["raw.lxc"] += 
fmt.Sprintf("lxc.apparmor.allow_incomplete=%s\n", val)
+               case "lxc.pty.max", "lxc.pts":
+                       newConfig["raw.lxc"] += fmt.Sprintf("lxc.pty.max=%s\n", 
val)
+               case "lxc.tty.max", "lxc.tty":
+                       newConfig["raw.lxc"] += fmt.Sprintf("lxc.tty.max=%s\n", 
val)
+               }
+       }
+
+       // Setup the container creation request
+       req := api.ContainersPost{
+               Name: container.Name(),
+               Source: api.ContainerSource{
+                       Type: "migration",
+                       Mode: "push",
+               },
+       }
+       req.Config = newConfig
+       req.Devices = newDevices
+       req.Profiles = []string{"default"}
+
+       // Set the container architecture if set in LXC
+       fmt.Println("Processing container architecture configuration")
+       var arch string
+       value = getConfig(conf, "lxc.arch")
+       if value == nil {
+               fmt.Println("Couldn't find container architecture, assuming 
native")
+               arch = runtime.GOARCH
+       } else {
+               arch = value[0]
+       }
+
+       archId, err := osarch.ArchitectureId(arch)
+       if err != nil {
+               return err
+       }
+
+       req.Architecture, err = osarch.ArchitectureName(archId)
+       if err != nil {
+               return err
+       }
+
+       if storage != "" {
+               req.Devices["root"] = map[string]string{
+                       "type": "disk",
+                       "pool": storage,
+                       "path": "/",
+               }
+       }
+
+       if debug {
+               out, _ := json.MarshalIndent(req, "", "  ")
+               fmt.Printf("LXD container config:\n%v\n", string(out))
+       }
+
+       // Create container
+       fmt.Println("Creating container")
+       if dryRun {
+               fmt.Println("Would create container now")
+       } else {
+               op, err := d.CreateContainer(req)
+               if err != nil {
+                       return err
+               }
+
+               progress := utils.ProgressRenderer{Format: "Transferring 
container: %s"}
+               _, err = op.AddHandler(progress.UpdateOp)
+               if err != nil {
+                       progress.Done("")
+                       return err
+               }
+
+               rootfs, _ := getRootfs(conf)
+
+               err = transferRootfs(d, op, rootfs, rsyncArgs)
+               if err != nil {
+                       return err
+               }
+
+               progress.Done(fmt.Sprintf("Container '%s' successfully 
created", container.Name()))
+       }
+
+       return nil
+}
+
+func convertNetworkConfig(container *lxc.Container, devices types.Devices) 
error {
+       networkDevice := func(network map[string]string) map[string]string {
+               if network == nil {
+                       return nil
+               }
+
+               device := make(map[string]string, 0)
+               device["type"] = "nic"
+
+               // Get the device type
+               device["nictype"] = network["type"]
+
+               // Convert the configuration
+               for k, v := range network {
+                       switch k {
+                       case "hwaddr", "mtu", "name":
+                               device[k] = v
+                       case "link":
+                               device["parent"] = v
+                       case "veth_pair":
+                               device["host_name"] = v
+                       case "":
+                               // empty key
+                               return nil
+                       }
+               }
+
+               switch device["nictype"] {
+               case "veth":
+                       _, ok := device["parent"]
+                       if ok {
+                               device["nictype"] = "bridged"
+                       } else {
+                               device["nictype"] = "p2p"
+                       }
+               case "phys":
+                       device["nictype"] = "physical"
+               case "empty":
+                       return nil
+               }
+
+               return device
+       }
+
+       fmt.Println("Processing network configuration")
+
+       devices["eth0"] = make(map[string]string, 0)
+       devices["eth0"]["type"] = "none"
+
+       // New config key
+       for i, _ := range container.ConfigItem("lxc.net") {
+               network := networkGet(container, i, "lxc.net")
+
+               dev := networkDevice(network)
+               if dev == nil {
+                       continue
+               }
+
+               devices[fmt.Sprintf("convert_net%d", i)] = dev
+       }
+
+       // Old config key
+       for i, _ := range container.ConfigItem("lxc.network") {
+               network := networkGet(container, i, "lxc.network")
+
+               dev := networkDevice(network)
+               if dev == nil {
+                       continue
+               }
+
+               devices[fmt.Sprintf("convert_net%d", len(devices))] = dev
+       }
+
+       return nil
+}
+
+func convertStorageConfig(conf []string, devices types.Devices) error {
+       fmt.Println("Processing storage configuration")
+
+       i := 0
+       for _, mount := range getConfig(conf, "lxc.mount.entry") {
+               parts := strings.Split(mount, " ")
+               if len(parts) < 4 {
+                       return fmt.Errorf("Invalid mount configuration: %s", 
mount)
+               }
+
+               // Ignore mounts that are present in LXD containers by default.
+               if shared.StringInSlice(parts[0], []string{"proc", "sysfs"}) {
+                       continue
+               }
+
+               device := make(map[string]string, 0)
+               device["type"] = "disk"
+
+               // Deal with read-only mounts
+               if shared.StringInSlice("ro", strings.Split(parts[3], ",")) {
+                       device["readonly"] = "true"
+               }
+
+               // Deal with optional mounts
+               if shared.StringInSlice("optional", strings.Split(parts[3], 
",")) {
+                       device["optional"] = "true"
+               } else {
+                       if strings.HasPrefix(parts[0], "/") {
+                               if !shared.PathExists(parts[0]) {
+                                       return fmt.Errorf("Invalid path: %s", 
parts[0])
+                               }
+                       } else {
+                               continue
+                       }
+               }
+
+               // Set the source
+               device["source"] = parts[0]
+
+               // Figure out the target
+               if !strings.HasPrefix(parts[1], "/") {
+                       device["path"] = fmt.Sprintf("/%s", parts[1])
+               } else {
+                       rootfs, err := getRootfs(conf)
+                       if err != nil {
+                               return err
+                       }
+                       device["path"] = strings.TrimPrefix(parts[1], rootfs)
+               }
+
+               devices[fmt.Sprintf("convert_mount%d", i)] = device
+               i++
+       }
+
+       return nil
+}
+
+func getRootfs(conf []string) (string, error) {
+       value := getConfig(conf, "lxc.rootfs.path")
+       if value == nil {
+               value = getConfig(conf, "lxc.rootfs")
+               if value == nil {
+                       return "", fmt.Errorf("Invalid container, missing 
lxc.rootfs key")
+               }
+       }
+
+       // XXX: ignore the first part (storage) for now
+       parts := strings.Split(value[0], ":")
+
+       if len(parts) != 2 {
+               return "", fmt.Errorf("Invalid container, invalid lxc.rootfs 
key")
+       }
+
+       return parts[1], nil
+}
diff --git a/lxc-to-lxd/main_netcat.go b/lxc-to-lxd/main_netcat.go
new file mode 100644
index 000000000..db21bed71
--- /dev/null
+++ b/lxc-to-lxd/main_netcat.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+       "fmt"
+       "io"
+       "net"
+       "os"
+       "sync"
+
+       "github.com/spf13/cobra"
+
+       "github.com/lxc/lxd/shared/eagain"
+)
+
+type cmdNetcat struct {
+       global *cmdGlobal
+}
+
+func (c *cmdNetcat) Command() *cobra.Command {
+       cmd := &cobra.Command{}
+
+       cmd.Use = "netcat <address>"
+       cmd.Short = "Sends stdin data to a unix socket"
+       cmd.RunE = c.Run
+       cmd.Hidden = true
+
+       return cmd
+}
+
+func (c *cmdNetcat) Run(cmd *cobra.Command, args []string) error {
+       // Help and usage
+       if len(args) == 0 {
+               cmd.Help()
+               return nil
+       }
+
+       // Handle mandatory arguments
+       if len(args) != 1 {
+               cmd.Help()
+               return fmt.Errorf("Missing required argument")
+       }
+
+       // Connect to the provided address
+       uAddr, err := net.ResolveUnixAddr("unix", args[0])
+       if err != nil {
+               return err
+       }
+
+       conn, err := net.DialUnix("unix", nil, uAddr)
+       if err != nil {
+               return err
+       }
+
+       // We'll wait until we're done reading from the socket
+       wg := sync.WaitGroup{}
+       wg.Add(1)
+
+       go func() {
+               io.Copy(eagain.Writer{Writer: os.Stdout}, eagain.Reader{Reader: 
conn})
+               conn.Close()
+               wg.Done()
+       }()
+
+       go func() {
+               io.Copy(eagain.Writer{Writer: conn}, eagain.Reader{Reader: 
os.Stdin})
+       }()
+
+       // Wait
+       wg.Wait()
+
+       return nil
+}
diff --git a/lxc-to-lxd/network.go b/lxc-to-lxd/network.go
new file mode 100644
index 000000000..97570064e
--- /dev/null
+++ b/lxc-to-lxd/network.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+       "fmt"
+       "strings"
+
+       lxc "gopkg.in/lxc/go-lxc.v2"
+)
+
+func networkGet(container *lxc.Container, index int, configKey string) 
map[string]string {
+       keys := container.ConfigKeys(fmt.Sprintf("%s.%d", configKey, index))
+       if len(keys) == 0 {
+               return nil
+       }
+
+       dev := make(map[string]string, 0)
+       for _, k := range keys {
+               value := container.ConfigItem(fmt.Sprintf("%s.%d.%s", 
configKey, index, k))
+               if len(value) == 0 || strings.TrimSpace(value[0]) == "" {
+                       continue
+               }
+
+               dev[k] = value[0]
+       }
+
+       if len(dev) == 0 {
+               return nil
+       }
+
+       return dev
+}
diff --git a/lxc-to-lxd/transfer.go b/lxc-to-lxd/transfer.go
new file mode 100644
index 000000000..c190d4d05
--- /dev/null
+++ b/lxc-to-lxd/transfer.go
@@ -0,0 +1,141 @@
+package main
+
+import (
+       "fmt"
+       "io"
+       "io/ioutil"
+       "net"
+       "os"
+       "os/exec"
+       "strings"
+
+       "github.com/gorilla/websocket"
+       "github.com/pborman/uuid"
+
+       "github.com/lxc/lxd/lxd/migration"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/version"
+)
+
+// Send an rsync stream of a path over a websocket
+func rsyncSend(conn *websocket.Conn, path string, rsyncArgs string) error {
+       cmd, dataSocket, stderr, err := rsyncSendSetup(path, rsyncArgs)
+       if err != nil {
+               return err
+       }
+
+       if dataSocket != nil {
+               defer dataSocket.Close()
+       }
+
+       readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, 
io.ReadCloser(dataSocket), nil, nil)
+
+       output, err := ioutil.ReadAll(stderr)
+       if err != nil {
+               cmd.Process.Kill()
+               cmd.Wait()
+               return fmt.Errorf("Failed to rsync: %v\n%s", err, output)
+       }
+
+       err = cmd.Wait()
+       <-readDone
+       <-writeDone
+
+       if err != nil {
+               return fmt.Errorf("Failed to rsync: %v\n%s", err, output)
+       }
+
+       return nil
+}
+
+// Spawn the rsync process
+func rsyncSendSetup(path string, rsyncArgs string) (*exec.Cmd, net.Conn, 
io.ReadCloser, error) {
+       auds := fmt.Sprintf("@lxc-to-lxd/%s", uuid.NewRandom().String())
+       if len(auds) > shared.ABSTRACT_UNIX_SOCK_LEN-1 {
+               auds = auds[:shared.ABSTRACT_UNIX_SOCK_LEN-1]
+       }
+
+       l, err := net.Listen("unix", auds)
+       if err != nil {
+               return nil, nil, nil, err
+       }
+
+       execPath, err := os.Readlink("/proc/self/exe")
+       if err != nil {
+               return nil, nil, nil, err
+       }
+
+       rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, auds)
+
+       args := []string{
+               "-ar",
+               "--devices",
+               "--numeric-ids",
+               "--partial",
+               "--sparse",
+       }
+
+       // Ignore deletions (requires 3.1 or higher)
+       rsyncCheckVersion := func(min string) bool {
+               out, err := shared.RunCommand("rsync", "--version")
+               if err != nil {
+                       return false
+               }
+
+               fields := strings.Split(out, " ")
+               curVer, err := version.Parse(fields[3])
+               if err != nil {
+                       return false
+               }
+
+               minVer, err := version.Parse(min)
+               if err != nil {
+                       return false
+               }
+
+               return curVer.Compare(minVer) >= 0
+       }
+
+       if rsyncCheckVersion("3.1.0") {
+               args = append(args, "--ignore-missing-args")
+       }
+
+       if rsyncArgs != "" {
+               args = append(args, strings.Split(rsyncArgs, " ")...)
+       }
+
+       args = append(args, []string{path, "localhost:/tmp/foo"}...)
+       args = append(args, []string{"-e", rsyncCmd}...)
+
+       cmd := exec.Command("rsync", args...)
+       cmd.Stdout = os.Stderr
+
+       stderr, err := cmd.StderrPipe()
+       if err != nil {
+               return nil, nil, nil, err
+       }
+
+       if err := cmd.Start(); err != nil {
+               return nil, nil, nil, err
+       }
+
+       conn, err := l.Accept()
+       if err != nil {
+               cmd.Process.Kill()
+               cmd.Wait()
+               return nil, nil, nil, err
+       }
+       l.Close()
+
+       return cmd, conn, stderr, nil
+}
+
+func protoSendError(ws *websocket.Conn, err error) {
+       migration.ProtoSendControl(ws, err)
+
+       if err != nil {
+               closeMsg := 
websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
+               ws.WriteMessage(websocket.CloseMessage, closeMsg)
+               ws.Close()
+       }
+}
diff --git a/lxc-to-lxd/utils.go b/lxc-to-lxd/utils.go
new file mode 100644
index 000000000..bcad65d8b
--- /dev/null
+++ b/lxc-to-lxd/utils.go
@@ -0,0 +1,182 @@
+package main
+
+import (
+       "crypto/x509"
+       "encoding/pem"
+       "fmt"
+       "strings"
+       "syscall"
+
+       "golang.org/x/crypto/ssh/terminal"
+
+       "github.com/lxc/lxd/client"
+       "github.com/lxc/lxd/lxd/migration"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/api"
+)
+
+func transferRootfs(dst lxd.ContainerServer, op lxd.Operation, rootfs string, 
rsyncArgs string) error {
+       opAPI := op.Get()
+
+       // Connect to the websockets
+       wsControl, err := op.GetWebsocket(opAPI.Metadata["control"].(string))
+       if err != nil {
+               return err
+       }
+
+       wsFs, err := op.GetWebsocket(opAPI.Metadata["fs"].(string))
+       if err != nil {
+               return err
+       }
+
+       // Setup control struct
+       fs := migration.MigrationFSType_RSYNC
+       header := migration.MigrationHeader{
+               Fs: &fs,
+       }
+
+       err = migration.ProtoSend(wsControl, &header)
+       if err != nil {
+               protoSendError(wsControl, err)
+               return err
+       }
+
+       err = migration.ProtoRecv(wsControl, &header)
+       if err != nil {
+               protoSendError(wsControl, err)
+               return err
+       }
+
+       // Send the filesystem
+       abort := func(err error) error {
+               protoSendError(wsControl, err)
+               return err
+       }
+
+       err = rsyncSend(wsFs, rootfs, rsyncArgs)
+       if err != nil {
+               return abort(err)
+       }
+
+       // Check the result
+       msg := migration.MigrationControl{}
+       err = migration.ProtoRecv(wsControl, &msg)
+       if err != nil {
+               wsControl.Close()
+               return err
+       }
+
+       if !*msg.Success {
+               return fmt.Errorf(*msg.Message)
+       }
+
+       return nil
+}
+
+func connectTarget(url string) (lxd.ContainerServer, error) {
+       // Generate a new client certificate for this
+       fmt.Println("Generating a temporary client certificate. This may take a 
minute...")
+       clientCrt, clientKey, err := shared.GenerateMemCert(true)
+       if err != nil {
+               return nil, err
+       }
+
+       // Attempt to connect using the system CA
+       args := lxd.ConnectionArgs{}
+       args.TLSClientCert = string(clientCrt)
+       args.TLSClientKey = string(clientKey)
+       args.UserAgent = "LXC-TO-LXD"
+       c, err := lxd.ConnectLXD(url, &args)
+
+       var certificate *x509.Certificate
+       if err != nil {
+               // Failed to connect using the system CA, so retrieve the 
remote certificate
+               certificate, err = shared.GetRemoteCertificate(url)
+               if err != nil {
+                       return nil, err
+               }
+       }
+
+       // Handle certificate prompt
+       if certificate != nil {
+               digest := shared.CertFingerprint(certificate)
+
+               fmt.Printf("Certificate fingerprint: %s\n", digest)
+               fmt.Printf("ok (y/n)? ")
+               line, err := shared.ReadStdin()
+               if err != nil {
+                       return nil, err
+               }
+
+               if len(line) < 1 || line[0] != 'y' && line[0] != 'Y' {
+                       return nil, fmt.Errorf("Server certificate rejected by 
user")
+               }
+
+               serverCrt := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", 
Bytes: certificate.Raw})
+               args.TLSServerCert = string(serverCrt)
+
+               // Setup a new connection, this time with the remote certificate
+               c, err = lxd.ConnectLXD(url, &args)
+               if err != nil {
+                       return nil, err
+               }
+       }
+
+       // Get server information
+       srv, _, err := c.GetServer()
+       if err != nil {
+               return nil, err
+       }
+
+       // Check if our cert is already trusted
+       if srv.Auth == "trusted" {
+               return c, nil
+       }
+
+       // Prompt for trust password
+       fmt.Printf("Admin password for %s: ", url)
+       pwd, err := terminal.ReadPassword(0)
+       if err != nil {
+               return nil, err
+       }
+       fmt.Println("")
+
+       // Add client certificate to trust store
+       req := api.CertificatesPost{
+               Password: string(pwd),
+       }
+       req.Type = "client"
+
+       err = c.CreateCertificate(req)
+       if err != nil {
+               return nil, err
+       }
+
+       return c, nil
+}
+
+func setupSource(path string, mounts []string) error {
+       prefix := "/"
+       if len(mounts) > 0 {
+               prefix = mounts[0]
+       }
+
+       // Mount everything
+       for _, mount := range mounts {
+               target := fmt.Sprintf("%s/%s", path, strings.TrimPrefix(mount, 
prefix))
+
+               // Mount the path
+               err := syscall.Mount(mount, target, "none", syscall.MS_BIND, "")
+               if err != nil {
+                       return fmt.Errorf("Failed to mount %s: %v", mount, err)
+               }
+
+               // Make it read-only
+               err = syscall.Mount("", target, "none", 
syscall.MS_BIND|syscall.MS_RDONLY|syscall.MS_REMOUNT, "")
+               if err != nil {
+                       return fmt.Errorf("Failed to make %s read-only: %v", 
mount, err)
+               }
+       }
+
+       return nil
+}

From 9db8e09fd107c1b5e5433fb0a09522fc22bff7d3 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Wed, 27 Jun 2018 20:54:00 +0200
Subject: [PATCH 2/3] tests: Add lxc-to-lxd tests

Signed-off-by: Thomas Hipp <[email protected]>
---
 lxc-to-lxd/main_migrate_test.go | 374 ++++++++++++++++++++++++++++++++++++++++
 test/suites/lxc-to-lxd.sh       |  60 +++++++
 2 files changed, 434 insertions(+)
 create mode 100644 lxc-to-lxd/main_migrate_test.go
 create mode 100644 test/suites/lxc-to-lxd.sh

diff --git a/lxc-to-lxd/main_migrate_test.go b/lxc-to-lxd/main_migrate_test.go
new file mode 100644
index 000000000..a0f5fbb02
--- /dev/null
+++ b/lxc-to-lxd/main_migrate_test.go
@@ -0,0 +1,374 @@
+package main
+
+import (
+       "io/ioutil"
+       "log"
+       "os"
+       "strings"
+       "testing"
+
+       "github.com/lxc/lxd/lxd/types"
+       "github.com/stretchr/testify/require"
+       lxc "gopkg.in/lxc/go-lxc.v2"
+)
+
+func TestValidateConfig(t *testing.T) {
+       tests := []struct {
+               name       string
+               config     []string
+               err        string
+               shouldFail bool
+       }{
+               {
+                       "container migrated",
+                       []string{
+                               "lxd.migrated = 1",
+                       },
+                       "Container has already been migrated",
+                       true,
+               },
+               {
+                       "container name missmatch (1)",
+                       []string{
+                               "lxc.uts.name = c2",
+                       },
+                       "Container name doesn't match lxc.uts.name / 
lxc.utsname",
+                       true,
+               },
+               {
+                       "container name missmatch (2)",
+                       []string{
+                               "lxc.utsname = c2",
+                       },
+                       "Container name doesn't match lxc.uts.name / 
lxc.utsname",
+                       true,
+               },
+               {
+                       "incomplete AppArmor support (1)",
+                       []string{
+                               "lxc.uts.name = c1",
+                               "lxc.apparmor.allow_incomplete = 1",
+                       },
+                       "Container allows incomplete AppArmor support",
+                       true,
+               },
+               {
+                       "incomplete AppArmor support (2)",
+                       []string{
+                               "lxc.uts.name = c1",
+                               "lxc.aa_allow_incomplete = 1",
+                       },
+                       "Container allows incomplete AppArmor support",
+                       true,
+               },
+               {
+                       "missing minimal /dev filesystem",
+                       []string{
+                               "lxc.uts.name = c1",
+                               "lxc.apparmor.allow_incomplete = 0",
+                               "lxc.autodev = 0",
+                       },
+                       "Container doesn't mount a minimal /dev filesystem",
+                       true,
+               },
+               {
+                       "missing lxc.rootfs key",
+                       []string{
+                               "lxc.uts.name = c1",
+                               "lxc.apparmor.allow_incomplete = 0",
+                               "lxc.autodev = 1",
+                       },
+                       "Invalid container, missing lxc.rootfs key",
+                       true,
+               },
+               {
+                       "invalid lxc.rootfs key",
+                       []string{
+                               "lxc.uts.name = c1",
+                               "lxc.apparmor.allow_incomplete = 0",
+                               "lxc.autodev = 1",
+                               "lxc.rootfs = /invalid/path",
+                       },
+                       "Invalid container, invalid lxc.rootfs key",
+                       true,
+               },
+               {
+                       "non-existent rootfs path",
+                       []string{
+                               "lxc.uts.name = c1",
+                               "lxc.apparmor.allow_incomplete = 0",
+                               "lxc.autodev = 1",
+                               "lxc.rootfs = dir:/invalid/path",
+                       },
+                       "Couldn't find the container rootfs '/invalid/path'",
+                       true,
+               },
+       }
+
+       lxcPath, err := ioutil.TempDir("", "lxc-to-lxd-test-")
+       require.NoError(t, err)
+       defer os.RemoveAll(lxcPath)
+
+       c, err := lxc.NewContainer("c1", lxcPath)
+       require.NoError(t, err)
+
+       for i, tt := range tests {
+               log.Printf("Running test #%d: %s", i, tt.name)
+               err := validateConfig(tt.config, c)
+               if tt.shouldFail {
+                       require.EqualError(t, err, tt.err)
+               } else {
+                       require.NoError(t, err)
+               }
+       }
+}
+
+func TestConvertNetworkConfig(t *testing.T) {
+       tests := []struct {
+               name            string
+               config          []string
+               expectedDevices types.Devices
+               expectedError   string
+               shouldFail      bool
+       }{
+               {
+                       "loopback only",
+                       []string{},
+                       types.Devices{
+                               "eth0": map[string]string{
+                                       "type": "none",
+                               },
+                       },
+                       "",
+                       false,
+               },
+               {
+                       "multiple network devices",
+                       []string{
+                               "lxc.net.1.type = macvlan",
+                               "lxc.net.1.macvlan.mode = bridge",
+                               "lxc.net.1.link = mvlan0",
+                               "lxc.net.1.hwaddr = 00:16:3e:8d:4f:51",
+                               "lxc.net.1.name = eth1",
+                               "lxc.net.2.type = veth",
+                               "lxc.net.2.link = lxcbr0",
+                               "lxc.net.2.hwaddr = 00:16:3e:a2:7d:54",
+                               "lxc.net.2.name = eth2",
+                       },
+                       types.Devices{
+                               "convert_net2": map[string]string{
+                                       "type":    "nic",
+                                       "nictype": "bridged",
+                                       "parent":  "lxcbr0",
+                                       "name":    "eth2",
+                                       "hwaddr":  "00:16:3e:a2:7d:54",
+                               },
+                               "eth0": map[string]string{
+                                       "type": "none",
+                               },
+                               "convert_net1": map[string]string{
+                                       "name":    "eth1",
+                                       "hwaddr":  "00:16:3e:8d:4f:51",
+                                       "type":    "nic",
+                                       "nictype": "macvlan",
+                                       "parent":  "mvlan0",
+                               },
+                       },
+                       "",
+                       false,
+               },
+       }
+
+       lxcPath, err := ioutil.TempDir("", "lxc-to-lxd-test-")
+       require.NoError(t, err)
+       defer os.RemoveAll(lxcPath)
+
+       for i, tt := range tests {
+               log.Printf("Running test #%d: %s", i, tt.name)
+
+               c, err := lxc.NewContainer("c1", lxcPath)
+               require.NoError(t, err)
+
+               err = c.Create(lxc.TemplateOptions{Template: "busybox"})
+               require.NoError(t, err)
+
+               for _, conf := range tt.config {
+                       parts := strings.SplitN(conf, "=", 2)
+                       require.Equal(t, 2, len(parts))
+                       err := c.SetConfigItem(strings.TrimSpace(parts[0]), 
strings.TrimSpace(parts[1]))
+                       require.NoError(t, err)
+               }
+
+               devices := make(types.Devices, 0)
+               err = convertNetworkConfig(c, devices)
+               if tt.shouldFail {
+                       require.EqualError(t, err, tt.expectedError)
+               } else {
+                       require.NoError(t, err)
+                       require.Equal(t, tt.expectedDevices, devices)
+               }
+
+               err = c.Destroy()
+               require.NoError(t, err)
+       }
+}
+
+func TestConvertStorageConfig(t *testing.T) {
+       tests := []struct {
+               name            string
+               config          []string
+               expectedDevices types.Devices
+               expectedError   string
+               shouldFail      bool
+       }{
+               {
+                       "invalid path",
+                       []string{
+                               "lxc.mount.entry = /foo lib none ro,bind 0 0",
+                       },
+                       types.Devices{},
+                       "Invalid path: /foo",
+                       true,
+               },
+               {
+                       "invalid rootfs",
+                       []string{
+                               "lxc.rootfs.path = /invalid",
+                               "lxc.mount.entry = /lib /lib none ro,bind 0 0",
+                       },
+                       types.Devices{},
+                       "Invalid container, invalid lxc.rootfs key",
+                       true,
+               },
+               {
+                       "ignored default mounts",
+                       []string{
+                               "lxc.mount.entry = proc /proc proc defaults 0 
0",
+                       },
+                       types.Devices{},
+                       "",
+                       false,
+               },
+               {
+                       "ignored mounts",
+                       []string{
+                               "lxc.mount.entry = shm /dev/shm tmpfs defaults 
0 0",
+                       },
+                       types.Devices{},
+                       "",
+                       false,
+               },
+               {
+                       "valid mount configuration",
+                       []string{
+                               "lxc.rootfs.path = dir:/tmp",
+                               "lxc.mount.entry = /lib lib none ro,bind 0 0",
+                               "lxc.mount.entry = /usr/lib usr/lib none 
ro,bind 1 0",
+                               "lxc.mount.entry = /lib64 lib64 none ro,bind 0 
0",
+                               "lxc.mount.entry = /usr/lib64 usr/lib64 none 
ro,bind 1 0",
+                               "lxc.mount.entry = /sys/kernel/security 
/sys/kernel/security none ro,bind,optional 1 0",
+                               "lxc.mount.entry = /mnt /tmp/mnt none ro,bind 0 
0",
+                       },
+                       types.Devices{
+                               "convert_mount0": map[string]string{
+                                       "type":     "disk",
+                                       "readonly": "true",
+                                       "source":   "/lib",
+                                       "path":     "/lib",
+                               },
+                               "convert_mount1": map[string]string{
+                                       "type":     "disk",
+                                       "readonly": "true",
+                                       "source":   "/usr/lib",
+                                       "path":     "/usr/lib",
+                               },
+                               "convert_mount2": map[string]string{
+                                       "type":     "disk",
+                                       "readonly": "true",
+                                       "source":   "/lib64",
+                                       "path":     "/lib64",
+                               },
+                               "convert_mount3": map[string]string{
+                                       "type":     "disk",
+                                       "readonly": "true",
+                                       "source":   "/usr/lib64",
+                                       "path":     "/usr/lib64",
+                               },
+                               "convert_mount4": map[string]string{
+                                       "type":     "disk",
+                                       "readonly": "true",
+                                       "optional": "true",
+                                       "source":   "/sys/kernel/security",
+                                       "path":     "/sys/kernel/security",
+                               },
+                               "convert_mount5": map[string]string{
+                                       "type":     "disk",
+                                       "readonly": "true",
+                                       "source":   "/mnt",
+                                       "path":     "/mnt",
+                               },
+                       },
+                       "",
+                       false,
+               },
+       }
+
+       for i, tt := range tests {
+               log.Printf("Running test #%d: %s", i, tt.name)
+               devices := make(types.Devices, 0)
+               err := convertStorageConfig(tt.config, devices)
+               if tt.shouldFail {
+                       require.EqualError(t, err, tt.expectedError)
+               } else {
+                       require.NoError(t, err)
+                       require.Equal(t, tt.expectedDevices, devices)
+               }
+       }
+}
+
+func TestGetRootfs(t *testing.T) {
+       tests := []struct {
+               name           string
+               config         []string
+               expectedOutput string
+               expectedError  string
+               shouldFail     bool
+       }{
+               {
+                       "missing lxc.rootfs key",
+                       []string{},
+                       "",
+                       "Invalid container, missing lxc.rootfs key",
+                       true,
+               },
+               {
+                       "invalid lxc.rootfs key",
+                       []string{
+                               "lxc.rootfs = foobar",
+                       },
+                       "",
+                       "Invalid container, invalid lxc.rootfs key",
+                       true,
+               },
+               {
+                       "valid lxc.rootfs key",
+                       []string{
+                               "lxc.rootfs = dir:foobar",
+                       },
+                       "foobar",
+                       "",
+                       false,
+               },
+       }
+
+       for i, tt := range tests {
+               log.Printf("Running test #%d: %s", i, tt.name)
+               rootfs, err := getRootfs(tt.config)
+               require.Equal(t, tt.expectedOutput, rootfs)
+               if tt.shouldFail {
+                       require.EqualError(t, err, tt.expectedError)
+               } else {
+                       require.NoError(t, err)
+               }
+       }
+}
diff --git a/test/suites/lxc-to-lxd.sh b/test/suites/lxc-to-lxd.sh
new file mode 100644
index 000000000..238c7e7ef
--- /dev/null
+++ b/test/suites/lxc-to-lxd.sh
@@ -0,0 +1,60 @@
+test_lxc_to_lxd() {
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  LXC_DIR="${TEST_DIR}/lxc"
+
+  mkdir -p "${LXC_DIR}"
+
+  # Create LXC containers
+  lxc-create -P "${LXC_DIR}" -n c1 -B dir -t busybox
+  lxc-create -P "${LXC_DIR}" -n c2 -B dir -t busybox
+  lxc-create -P "${LXC_DIR}" -n c3 -B dir -t busybox
+
+  # Convert single LXC container (dry run)
+  lxc-to-lxd --lxcpath "${LXC_DIR}" --dry-run --delete --containers c1
+
+  # Ensure the LXC containers have not been deleted
+  [[ $(lxc-ls -P "${LXC_DIR}" -1 | wc -l) -eq 3 ]]
+
+  # Ensure no containers have been converted
+  ! lxc info c1
+  ! lxc info c2
+  ! lxc info c3
+
+  # Convert single LXC container
+  lxc-to-lxd --lxcpath "${LXC_DIR}" --containers c1
+
+  # Ensure the LXC containers have not been deleted
+  [[ $(lxc-ls -P "${LXC_DIR}" -1 | wc -l) -eq 3 ]]
+
+  # Ensure only c1 has been converted
+  lxc info c1
+  ! lxc info c2
+  ! lxc info c3
+
+  # Ensure the converted container is startable
+  lxc start c1
+  lxc delete -f c1
+
+  # Convert some LXC containers
+  lxc-to-lxd --lxcpath "${LXC_DIR}" --delete --containers c1,c2
+
+  # Ensure the LXC containers c1 and c2 have been deleted
+  [[ $(lxc-ls -P "${LXC_DIR}" -1 | wc -l) -eq 1 ]]
+
+  # Ensure all containers have been converted
+  lxc info c1
+  lxc info c2
+  ! lxc info c3
+
+  # Convert all LXC containers
+  lxc-to-lxd --lxcpath "${LXC_DIR}" --delete --all
+
+  # Ensure the remaining LXC containers have been deleted
+  [[ $(lxc-ls -P "${LXC_DIR}" -1 | wc -l) -eq 0 ]]
+
+  # Ensure all containers have been converted
+  lxc info c1
+  lxc info c2
+  lxc info c3
+}

From 85e45554ccc180a55c13b213ec3c9e0019344722 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Tue, 3 Jul 2018 15:16:44 +0200
Subject: [PATCH 3/3] scripts: Remove lxc-to-lxd

Signed-off-by: Thomas Hipp <[email protected]>
---
 scripts/lxc-to-lxd | 641 -----------------------------------------------------
 1 file changed, 641 deletions(-)
 delete mode 100755 scripts/lxc-to-lxd

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
deleted file mode 100755
index 4a93a4e16..000000000
--- a/scripts/lxc-to-lxd
+++ /dev/null
@@ -1,641 +0,0 @@
-#!/usr/bin/env python3
-import argparse
-import http.client
-import json
-import os
-import socket
-import subprocess
-import sys
-
-try:
-    import lxc
-except ImportError:
-    print("You must have python3-lxc installed for this script to work.")
-    sys.exit(1)
-
-
-# Whitelist of keys we either need to check or allow setting in LXD. The latter
-# is strictly only true for 'lxc.aa_profile'.
-keys_to_check = [
-    'lxc.pts',
-    # 'lxc.tty',
-    # 'lxc.devttydir',
-    # 'lxc.kmsg',
-    'lxc.aa_profile',
-    # 'lxc.cgroup.',
-    'lxc.loglevel',
-    # 'lxc.logfile',
-    'lxc.mount.auto',
-    'lxc.mount',
-    # 'lxc.rootfs.mount',
-    # 'lxc.rootfs.options',
-    # 'lxc.pivotdir',
-    # 'lxc.hook.pre-start',
-    # 'lxc.hook.pre-mount',
-    # 'lxc.hook.mount',
-    # 'lxc.hook.autodev',
-    # 'lxc.hook.start',
-    # 'lxc.hook.stop',
-    # 'lxc.hook.post-stop',
-    # 'lxc.hook.clone',
-    # 'lxc.hook.destroy',
-    # 'lxc.hook',
-    'lxc.network.type',
-    'lxc.network.flags',
-    'lxc.network.link',
-    'lxc.network.name',
-    'lxc.network.macvlan.mode',
-    'lxc.network.veth.pair',
-    # 'lxc.network.script.up',
-    # 'lxc.network.script.down',
-    'lxc.network.hwaddr',
-    'lxc.network.mtu',
-    # 'lxc.network.vlan.id',
-    # 'lxc.network.ipv4.gateway',
-    # 'lxc.network.ipv4',
-    # 'lxc.network.ipv6.gateway',
-    # 'lxc.network.ipv6',
-    # 'lxc.network.',
-    # 'lxc.network',
-    # 'lxc.console.logfile',
-    # 'lxc.console',
-    'lxc.include',
-    'lxc.start.auto',
-    'lxc.start.delay',
-    'lxc.start.order',
-    # 'lxc.monitor.unshare',
-    # 'lxc.group',
-    'lxc.environment',
-    # 'lxc.init_cmd',
-    # 'lxc.init_uid',
-    # 'lxc.init_gid',
-    # 'lxc.ephemeral',
-    # 'lxc.syslog',
-    # 'lxc.no_new_privs',
-
-    # Additional keys that are either set by this script or are used to report
-    # helpful errors to users.
-    'lxc.arch',
-    'lxc.id_map',
-    'lxd.migrated',
-    'lxc.rootfs.backend',
-    'lxc.rootfs',
-    'lxc.utsname',
-    'lxc.aa_allow_incomplete',
-    'lxc.autodev',
-    'lxc.haltsignal',
-    'lxc.rebootsignal',
-    'lxc.stopsignal',
-    'lxc.mount.entry',
-    'lxc.cap.drop',
-    # 'lxc.cap.keep',
-    'lxc.seccomp',
-    # 'lxc.se_context',
-    ]
-
-
-# Unix connection to LXD
-class UnixHTTPConnection(http.client.HTTPConnection):
-    def __init__(self, path):
-        http.client.HTTPConnection.__init__(self, 'localhost')
-        self.path = path
-
-    def connect(self):
-        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-        sock.connect(self.path)
-        self.sock = sock
-
-
-# Fetch a config key as a list
-def config_get(config, key, default=None):
-    result = []
-    for line in config:
-        fields = line.split("=", 1)
-        if fields[0].strip() == key:
-            result.append(fields[-1].strip())
-
-    if len(result) == 0:
-        return default
-    else:
-        return result
-
-
-def config_keys(config):
-    keys = []
-    for line in config:
-        fields = line.split("=", 1)
-        cur = fields[0].strip()
-        if cur and not cur.startswith("#") and cur.startswith("lxc."):
-            keys.append(cur)
-
-    return keys
-
-
-# Parse a LXC configuration file, called recursively for includes
-def config_parse(path):
-    config = []
-    with open(path, "r") as fd:
-        for line in fd:
-            line = line.strip()
-            key = line.split("=", 1)[0].strip()
-            value = line.split("=", 1)[-1].strip()
-
-            # Parse user-added includes
-            if key == "lxc.include":
-                # Ignore our own default configs
-                if value.startswith("/usr/share/lxc/config/"):
-                    continue
-
-                if os.path.isfile(value):
-                    config += config_parse(value)
-                    continue
-                elif os.path.isdir(value):
-                    for entry in os.listdir(value):
-                        if not entry.endswith(".conf"):
-                            continue
-
-                        config += config_parse(os.path.join(value, entry))
-                    continue
-                else:
-                    print("Invalid include: %s", line)
-
-            # Expand any fstab
-            if key == "lxc.mount":
-                if not os.path.exists(value):
-                    print("Container fstab file doesn't exist, skipping...")
-                    continue
-
-                with open(value, "r") as fd:
-                    for line in fd:
-                        line = line.strip()
-                        if (line and not line.startswith("#") and
-                                line.startswith("lxc.")):
-                            config.append("lxc.mount.entry = %s" % line)
-                continue
-
-            # Process normal configuration keys
-            if line and not line.strip().startswith("#"):
-                config.append(line)
-
-    return config
-
-
-def container_exists(lxd_socket, container_name):
-    lxd = UnixHTTPConnection(lxd_socket)
-    lxd.request("GET", "/1.0/containers/%s" % container_name)
-    if lxd.getresponse().status == 404:
-        return False
-
-    return True
-
-
-def container_create(lxd_socket, args):
-    # Define the container
-    lxd = UnixHTTPConnection(lxd_socket)
-    lxd.request("POST", "/1.0/containers", json.dumps(args))
-    r = lxd.getresponse()
-
-    # Decode the response
-    resp = json.loads(r.read().decode())
-    if resp["type"] == "error":
-        raise Exception("Failed to define container: %s" % resp["error"])
-
-    # Wait for result
-    lxd = UnixHTTPConnection(lxd_socket)
-    lxd.request("GET", "%s/wait" % resp["operation"])
-    r = lxd.getresponse()
-
-    # Decode the response
-    resp = json.loads(r.read().decode())
-    if resp["type"] == "error":
-        raise Exception("Failed to define container: %s" % resp["error"])
-
-
-# Convert a LXC container to a LXD one
-def convert_container(lxd_socket, container_name, args):
-    print("==> Processing container: %s" % container_name)
-
-    # Load the container
-    try:
-        container = lxc.Container(container_name, args.lxcpath)
-    except Exception:
-        print("Invalid container configuration, skipping...")
-        return False
-
-    # As some keys can't be queried over the API, parse the config ourselves
-    print("Parsing LXC configuration")
-    lxc_config = config_parse(container.config_file_name)
-    found_keys = config_keys(lxc_config)
-
-    # Generic check for any invalid LXC configuration keys.
-    print("Checking for unsupported LXC configuration keys")
-    diff = list(set(found_keys) - set(keys_to_check))
-    for d in diff:
-        if (not d.startswith('lxc.network.') and not
-                d.startswith('lxc.cgroup.')):
-            print("Found at least one unsupported config key %s: " % d)
-            print("Not importing this container, skipping...")
-            return False
-
-    if args.debug:
-        print("Container configuration:")
-        print(" ", end="")
-        print("\n ".join(lxc_config))
-        print("")
-
-    # Check for keys that have values differing from the LXD defaults.
-    print("Checking whether container has already been migrated")
-    if config_get(lxc_config, "lxd.migrated"):
-        print("Container has already been migrated, skipping...")
-        return False
-
-    # Make sure we don't have a conflict
-    print("Checking for existing containers")
-    if container_exists(lxd_socket, container_name):
-        print("Container already exists, skipping...")
-        return False
-
-    # Validating lxc.id_map: must be unset.
-    print("Validating container mode")
-    if config_get(lxc_config, "lxc.id_map"):
-        print("Unprivileged containers aren't supported, skipping...")
-        return False
-
-    # Validate lxc.utsname
-    print("Validating container name")
-    value = config_get(lxc_config, "lxc.utsname")
-    if value and value[0] != container_name:
-        print("Container name doesn't match lxc.utsname, skipping...")
-        return False
-
-    # Validate lxc.aa_allow_incomplete: must be set to 0 or unset.
-    print("Validating whether incomplete AppArmor support is enabled")
-    value = config_get(lxc_config, "lxc.aa_allow_incomplete")
-    if value and int(value[0]) != 0:
-        print("Container allows incomplete AppArmor support, skipping...")
-        return False
-
-    # Validate lxc.autodev: must be set to 1 or unset.
-    print("Validating whether mounting a minimal /dev is enabled")
-    value = config_get(lxc_config, "lxc.autodev")
-    if value and int(value[0]) != 1:
-        print("Container doesn't mount a minimal /dev filesystem, skipping...")
-        return False
-
-    # Validate lxc.haltsignal: must be unset.
-    print("Validating that no custom haltsignal is set")
-    value = config_get(lxc_config, "lxc.haltsignal")
-    if value:
-        print("Container sets custom halt signal, skipping...")
-        return False
-
-    # Validate lxc.rebootsignal: must be unset.
-    print("Validating that no custom rebootsignal is set")
-    value = config_get(lxc_config, "lxc.rebootsignal")
-    if value:
-        print("Container sets custom reboot signal, skipping...")
-        return False
-
-    # Validate lxc.stopsignal: must be unset.
-    print("Validating that no custom stopsignal is set")
-    value = config_get(lxc_config, "lxc.stopsignal")
-    if value:
-        print("Container sets custom stop signal, skipping...")
-        return False
-
-    # Extract and valid rootfs key
-    print("Validating container rootfs")
-    value = config_get(lxc_config, "lxc.rootfs")
-    if not value:
-        print("Invalid container, missing lxc.rootfs key, skipping...")
-        return False
-
-    rootfs = value[0]
-
-    if not os.path.exists(rootfs):
-        print("Couldn't find the container rootfs '%s', skipping..." % rootfs)
-        return False
-
-    # Base config
-    config = {}
-    config['security.privileged'] = "true"
-    devices = {}
-    devices['eth0'] = {'type': "none"}
-
-    # Convert network configuration
-    print("Processing network configuration")
-    try:
-        count = len(container.get_config_item("lxc.network"))
-    except Exception:
-        count = 0
-
-    for i in range(count):
-        device = {"type": "nic"}
-
-        # Get the device type
-        device["nictype"] = container.get_config_item("lxc.network")[i]
-
-        # Get everything else
-        dev = container.network[i]
-
-        # Validate configuration
-        if dev.ipv4 or dev.ipv4_gateway:
-            print("IPv4 network configuration isn't supported, skipping...")
-            return False
-
-        if dev.ipv6 or dev.ipv6_gateway:
-            print("IPv6 network configuration isn't supported, skipping...")
-            return False
-
-        if dev.script_up or dev.script_down:
-            print("Network config scripts aren't supported, skipping...")
-            return False
-
-        if device["nictype"] == "none":
-            print("\"none\" network mode isn't supported, skipping...")
-            return False
-
-        if device["nictype"] == "vlan":
-            print("\"vlan\" network mode isn't supported, skipping...")
-            return False
-
-        # Convert the configuration
-        if dev.hwaddr:
-            device['hwaddr'] = dev.hwaddr
-
-        if dev.link:
-            device['parent'] = dev.link
-
-        if dev.mtu:
-            device['mtu'] = dev.mtu
-
-        if dev.name:
-            device['name'] = dev.name
-
-        if dev.veth_pair:
-            device['host_name'] = dev.veth_pair
-
-        if device["nictype"] == "veth":
-            if "parent" in device:
-                device["nictype"] = "bridged"
-            else:
-                device["nictype"] = "p2p"
-
-        if device["nictype"] == "phys":
-            device["nictype"] = "physical"
-
-        if device["nictype"] == "empty":
-            continue
-
-        devices['convert_net%d' % i] = device
-        count += 1
-
-    # Convert storage configuration
-    value = config_get(lxc_config, "lxc.mount.entry", [])
-    i = 0
-    for entry in value:
-        mount = entry.split(" ")
-        if len(mount) < 4:
-            print("Invalid mount configuration, skipping...")
-            return False
-
-        # Ignore mounts that are present in LXD containers by default.
-        if mount[0] in ("proc", "sysfs"):
-            continue
-
-        device = {'type': "disk"}
-
-        # Deal with read-only mounts
-        if "ro" in mount[3].split(","):
-            device['readonly'] = "true"
-
-        # Deal with optional mounts
-        if "optional" in mount[3].split(","):
-            device['optional'] = "true"
-        elif not os.path.exists(mount[0]):
-            print("Invalid mount configuration, source path doesn't exist.")
-            return False
-
-        # Set the source
-        device['source'] = mount[0]
-
-        # Figure out the target
-        if mount[1][0] != "/":
-            device['path'] = "/%s" % mount[1]
-        else:
-            device['path'] = mount[1].split(rootfs, 1)[-1]
-
-        devices['convert_mount%d' % i] = device
-        i += 1
-
-    # Convert environment
-    print("Processing environment configuration")
-    value = config_get(lxc_config, "lxc.environment", [])
-    for env in value:
-        entry = env.split("=", 1)
-        config['environment.%s' % entry[0].strip()] = entry[-1].strip()
-
-    # Convert auto-start
-    print("Processing container boot configuration")
-    value = config_get(lxc_config, "lxc.start.auto")
-    if value and int(value[0]) > 0:
-        config['boot.autostart'] = "true"
-
-    value = config_get(lxc_config, "lxc.start.delay")
-    if value and int(value[0]) > 0:
-        config['boot.autostart.delay'] = value[0]
-
-    value = config_get(lxc_config, "lxc.start.order")
-    if value and int(value[0]) > 0:
-        config['boot.autostart.priority'] = value[0]
-
-    # Convert apparmor
-    print("Processing container apparmor configuration")
-    value = config_get(lxc_config, "lxc.aa_profile")
-    if value:
-        if value[0] == "lxc-container-default-with-nesting":
-            config['security.nesting'] = "true"
-        elif value[0] != "lxc-container-default":
-            config["raw.lxc"] = "lxc.aa_profile=%s" % value[0]
-
-    # Convert seccomp
-    print("Processing container seccomp configuration")
-    value = config_get(lxc_config, "lxc.seccomp")
-    if value and value[0] != "/usr/share/lxc/config/common.seccomp":
-        print("Custom seccomp profiles aren't supported, skipping...")
-        return False
-
-    # Convert SELinux
-    print("Processing container SELinux configuration")
-    value = config_get(lxc_config, "lxc.se_context")
-    if value:
-        print("Custom SELinux policies aren't supported, skipping...")
-        return False
-
-    # Convert capabilities
-    print("Processing container capabilities configuration")
-    value = config_get(lxc_config, "lxc.cap.drop")
-    if value:
-        for cap in value:
-            # Ignore capabilities that are dropped in LXD containers by 
default.
-            if cap in ("mac_admin", "mac_override", "sys_module", "sys_time"):
-                continue
-            print("Custom capabilities aren't supported, skipping...")
-            return False
-
-    value = config_get(lxc_config, "lxc.cap.keep")
-    if value:
-        print("Custom capabilities aren't supported, skipping...")
-        return False
-
-    # Setup the container creation request
-    new = {'name': container_name,
-           'source': {'type': 'none'},
-           'config': config,
-           'devices': devices,
-           'profiles': ["default"]}
-
-    # Set the container architecture if set in LXC
-    print("Processing container architecture configuration")
-    arches = {'i686': "i686",
-              'x86_64': "x86_64",
-              'armhf': "armv7l",
-              'arm64': "aarch64",
-              'powerpc': "ppc",
-              'powerpc64': "ppc64",
-              'ppc64el': "ppc64le",
-              's390x': "s390x"}
-
-    arch = None
-    try:
-        arch = config_get(lxc_config, "lxc.arch", None)
-
-        if arch and arch[0] in arches:
-            new['architecture'] = arches[arch[0]]
-        else:
-            print("Unknown architecture, assuming native.")
-    except Exception:
-        print("Couldn't find container architecture, assuming native.")
-
-    # Define the container in LXD
-    if args.debug:
-        print("LXD container config:")
-        print(json.dumps(new, indent=True, sort_keys=True))
-
-    if args.dry_run:
-        return True
-
-    if container.running:
-        print("Only stopped containers can be migrated, skipping...")
-        return False
-
-    try:
-        print("Creating the container")
-        container_create(lxd_socket, new)
-    except Exception as e:
-        raise
-        print("Failed to create the container: %s" % e)
-        return False
-
-    # Transfer the filesystem
-    lxd_rootfs = os.path.join(args.lxdpath, "containers",
-                              container_name, "rootfs")
-
-    if args.move_rootfs:
-        if os.path.exists(lxd_rootfs):
-            os.rmdir(lxd_rootfs)
-
-        if subprocess.call(["mv", rootfs, lxd_rootfs]) != 0:
-            print("Failed to move the container rootfs, skipping...")
-            return False
-
-        os.mkdir(rootfs)
-    else:
-        print("Copying container rootfs")
-        if not os.path.exists(lxd_rootfs):
-            os.mkdir(lxd_rootfs)
-
-        if subprocess.call(["rsync", "-Aa", "--sparse",
-                            "--acls", "--numeric-ids", "--hard-links",
-                            "%s/" % rootfs, "%s/" % lxd_rootfs]) != 0:
-            print("Failed to transfer the container rootfs, skipping...")
-            return False
-
-    # Delete the source
-    if args.delete:
-        print("Deleting source container")
-        container.delete()
-
-    # Mark the container as migrated
-    with open(container.config_file_name, "a") as fd:
-        fd.write("lxd.migrated=true\n")
-    print("Container is ready to use")
-    return True
-
-
-# Argument parsing
-parser = argparse.ArgumentParser()
-parser.add_argument("--dry-run", action="store_true", default=False,
-                    help="Dry run mode")
-parser.add_argument("--debug", action="store_true", default=False,
-                    help="Print debugging output")
-parser.add_argument("--all", action="store_true", default=False,
-                    help="Import all containers")
-parser.add_argument("--delete", action="store_true", default=False,
-                    help="Delete the source container")
-parser.add_argument("--move-rootfs", action="store_true", default=False,
-                    help="Move the container rootfs rather than copying it")
-parser.add_argument("--lxcpath", type=str, default=False,
-                    help="Alternate LXC path")
-parser.add_argument("--lxdpath", type=str, default="/var/lib/lxd",
-                    help="Alternate LXD path")
-parser.add_argument(dest='containers', metavar="CONTAINER", type=str,
-                    help="Container to import", nargs="*")
-args = parser.parse_args()
-
-# Sanity checks
-if not os.geteuid() == 0:
-    parser.error("You must be root to run this tool")
-
-if (not args.containers and not args.all) or (args.containers and args.all):
-    parser.error("You must either pass container names or --all")
-
-# Connect to LXD
-if 'LXD_SOCKET' in os.environ:
-    lxd_socket = os.environ['LXD_SOCKET']
-else:
-    lxd_socket = os.path.join(args.lxdpath, "unix.socket")
-
-if not os.path.exists(lxd_socket):
-    print("LXD isn't running.")
-    sys.exit(1)
-
-# Run migration
-results = {}
-count = 0
-for container_name in lxc.list_containers(config_path=args.lxcpath):
-    if args.containers and container_name not in args.containers:
-        continue
-
-    if count > 0:
-        print("")
-
-    results[container_name] = convert_container(lxd_socket,
-                                                container_name, args)
-    count += 1
-
-# Print summary
-if not results:
-    print("No container to migrate")
-    sys.exit(0)
-
-print("")
-print("==> Migration summary")
-for name, result in results.items():
-    if result:
-        print("%s: SUCCESS" % name)
-    else:
-        print("%s: FAILURE" % name)
-
-if False in results.values():
-    sys.exit(1)
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to