func containerSnapRestore(d *Daemon, name string, snap string) error { // normalize snapshot name if !shared.IsSnapshot(snap) { snap = name + shared.SnapshotDelimiter + snap } shared.Log.Info( "RESTORE => Restoring snapshot", log.Ctx{ "snapshot": snap, "container": name}) c, err := containerLXDLoad(d, name) if err != nil { shared.Log.Error( "RESTORE => loadcontainerLXD() failed", log.Ctx{ "container": name, "err": err}) return err } source, err := containerLXDLoad(d, snap) if err != nil { return err } if err := c.Restore(source); err != nil { return err } return nil }
func (c *snapshotCmd) run(config *lxd.Config, args []string) error { if len(args) < 1 { return errArgs } var snapname string if len(args) < 2 { snapname = "" } else { snapname = args[1] } remote, name := config.ParseRemoteAndContainer(args[0]) d, err := lxd.NewClient(config, remote) if err != nil { return err } // we don't allow '/' in snapshot names if shared.IsSnapshot(snapname) { return fmt.Errorf(gettext.Gettext("'/' not allowed in snapshot name\n")) } resp, err := d.Snapshot(name, snapname, c.stateful) if err != nil { return err } return d.WaitForSuccess(resp.Operation) }
func (c *Client) ImageFromContainer(cname string, public bool, aliases []string, properties map[string]string) (string, error) { source := shared.Jmap{"type": "container", "name": cname} if shared.IsSnapshot(cname) { source["type"] = "snapshot" } body := shared.Jmap{"public": public, "source": source, "properties": properties} resp, err := c.post("images", body, Async) if err != nil { return "", err } jmap, err := c.AsyncWaitMeta(resp) if err != nil { return "", err } fingerprint, err := jmap.GetString("fingerprint") if err != nil { return "", err } /* add new aliases */ for _, alias := range aliases { c.DeleteAlias(alias) err = c.PostAlias(alias, alias, fingerprint) if err != nil { fmt.Printf(i18n.G("Error adding alias %s")+"\n", alias) } } return fingerprint, nil }
func (c *deleteCmd) run(config *lxd.Config, args []string) error { if len(args) == 0 { return errArgs } for _, nameArg := range args { remote, name := config.ParseRemoteAndContainer(nameArg) d, err := lxd.NewClient(config, remote) if err != nil { return err } if c.interactive { err := c.promptDelete(name) if err != nil { return err } } if shared.IsSnapshot(name) { return c.doDelete(d, name) } ct, err := d.ContainerInfo(name) if err != nil { return err } if ct.StatusCode != 0 && ct.StatusCode != shared.Stopped { if !c.force { return fmt.Errorf(i18n.G("The container is currently running, stop it first or pass --force.")) } resp, err := d.Action(name, shared.Stop, -1, true, false) if err != nil { return err } op, err := d.WaitFor(resp.Operation) if err != nil { return err } if op.StatusCode == shared.Failure { return fmt.Errorf(i18n.G("Stopping container failed!")) } if ct.Ephemeral == true { return nil } } if err := c.doDelete(d, name); err != nil { return err } } return nil }
func (c *Client) GetMigrationSourceWS(container string) (*Response, error) { body := shared.Jmap{"migration": true} url := fmt.Sprintf("containers/%s", container) if shared.IsSnapshot(container) { pieces := strings.SplitN(container, shared.SnapshotDelimiter, 2) if len(pieces) != 2 { return nil, fmt.Errorf("invalid snapshot name %s", container) } url = fmt.Sprintf("containers/%s/snapshots/%s", pieces[0], pieces[1]) } return c.post(url, body, Async) }
func (c *moveCmd) run(config *lxd.Config, args []string) error { if len(args) != 2 { return errArgs } sourceRemote, sourceName := config.ParseRemoteAndContainer(args[0]) destRemote, destName := config.ParseRemoteAndContainer(args[1]) // As an optimization, if the source an destination are the same, do // this via a simple rename. This only works for containers that aren't // running, containers that are running should be live migrated (of // course, this changing of hostname isn't supported right now, so this // simply won't work). if sourceRemote == destRemote { source, err := lxd.NewClient(config, sourceRemote) if err != nil { return err } canRename := false if shared.IsSnapshot(sourceName) { canRename = true } else { status, err := source.ContainerStatus(sourceName) if err != nil { return err } canRename = status.Status.StatusCode != shared.Running } if canRename { rename, err := source.Rename(sourceName, destName) if err != nil { return err } return source.WaitForSuccess(rename.Operation) } } // A move is just a copy followed by a delete; however, we want to // keep the volatile entries around since we are moving the container. if err := copyContainer(config, args[0], args[1], true); err != nil { return err } return commands["delete"].run(config, args[:1]) }
func containerSnapRestore(d *Daemon, name string, snap string) error { // normalize snapshot name if !shared.IsSnapshot(snap) { snap = name + shared.SnapshotDelimiter + snap } shared.Log.Info( "RESTORE => Restoring snapshot", log.Ctx{ "snapshot": snap, "container": name}) c, err := containerLoadByName(d, name) if err != nil { shared.Log.Error( "RESTORE => loadcontainerLXD() failed", log.Ctx{ "container": name, "err": err}) return err } source, err := containerLoadByName(d, snap) if err != nil { switch err { case sql.ErrNoRows: return fmt.Errorf("snapshot %s does not exist", snap) default: return err } } if err := c.Restore(source); err != nil { return err } return nil }
func (c *restoreCmd) run(config *lxd.Config, args []string) error { if len(args) < 2 { return errArgs } var snapname = args[1] remote, name := config.ParseRemoteAndContainer(args[0]) d, err := lxd.NewClient(config, remote) if err != nil { return err } if !shared.IsSnapshot(snapname) { snapname = fmt.Sprintf("%s/%s", name, snapname) } resp, err := d.RestoreSnapshot(name, snapname, c.stateful) if err != nil { return err } return d.WaitForSuccess(resp.Operation) }
func createFromCopy(d *Daemon, req *containerPostReq) Response { if req.Source.Source == "" { return BadRequest(fmt.Errorf("must specify a source container")) } // Make sure the source exists. source, err := newLxdContainer(req.Source.Source, d) if err != nil { return SmartError(err) } if req.Config == nil { config := make(map[string]string) for key, value := range source.config { if key[0:8] == "volatile" { shared.Debugf("skipping: %s\n", key) continue } req.Config[key] = value } req.Config = config } if req.Profiles == nil { req.Profiles = source.profiles } args := DbCreateContainerArgs{ d: d, name: req.Name, ctype: cTypeRegular, config: req.Config, profiles: req.Profiles, ephem: req.Ephemeral, baseImage: req.Source.BaseImage, } _, err = dbCreateContainer(args) if err != nil { return SmartError(err) } var oldPath string if shared.IsSnapshot(req.Source.Source) { snappieces := strings.SplitN(req.Source.Source, "/", 2) oldPath = migration.AddSlash(shared.VarPath("lxc", snappieces[0], "snapshots", snappieces[1], "rootfs")) } else { oldPath = migration.AddSlash(shared.VarPath("lxc", req.Source.Source, "rootfs")) } subvol := strings.TrimSuffix(oldPath, "rootfs/") dpath := shared.VarPath("lxc", req.Name) // Destination path if !btrfsIsSubvolume(subvol) { if err := os.MkdirAll(dpath, 0700); err != nil { removeContainer(d, req.Name) return InternalError(err) } if err := extractShiftIfExists(d, source, req.Source.BaseImage, req.Name); err != nil { removeContainer(d, req.Name) return InternalError(err) } } newPath := fmt.Sprintf("%s/%s", dpath, "rootfs") run := func() shared.OperationResult { if btrfsIsSubvolume(subvol) { /* * Copy by using btrfs snapshot */ output, err := btrfsSnapshot(subvol, dpath, false) if err != nil { shared.Debugf("Failed to create a BTRFS Snapshot of '%s' to '%s'.", subvol, dpath) shared.Debugf(string(output)) return shared.OperationError(err) } } else { /* * Copy by using rsync */ output, err := exec.Command("rsync", "-a", "--devices", oldPath, newPath).CombinedOutput() if err != nil { shared.Debugf("rsync failed:\n%s", output) return shared.OperationError(err) } } if !source.isPrivileged() { err = setUnprivUserAcl(source, dpath) if err != nil { shared.Debugf("Error adding acl for container root: falling back to chmod\n") output, err := exec.Command("chmod", "+x", dpath).CombinedOutput() if err != nil { shared.Debugf("Error chmoding the container root\n") shared.Debugf(string(output)) return shared.OperationError(err) } } } c, err := newLxdContainer(req.Name, d) if err != nil { return shared.OperationError(err) } err = templateApply(c, "copy") if err != nil { return shared.OperationError(err) } return shared.OperationError(nil) } resources := make(map[string][]string) resources["containers"] = []string{req.Name, req.Source.Source} return &asyncResponse{run: run, resources: resources} }
/* * This function takes a container or snapshot from the local image server and * exports it as an image. */ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq, builddir string) (info shared.ImageInfo, err error) { info.Properties = map[string]string{} name := req.Source["name"] ctype := req.Source["type"] if ctype == "" || name == "" { return info, fmt.Errorf("No source provided") } switch ctype { case "snapshot": if !shared.IsSnapshot(name) { return info, fmt.Errorf("Not a snapshot") } case "container": if shared.IsSnapshot(name) { return info, fmt.Errorf("This is a snapshot") } default: return info, fmt.Errorf("Bad type") } info.Filename = req.Filename switch req.Public { case true: info.Public = 1 case false: info.Public = 0 } snap := "" if ctype == "snapshot" { fields := strings.SplitN(name, "/", 2) if len(fields) != 2 { return info, fmt.Errorf("Not a snapshot") } name = fields[0] snap = fields[1] } c, err := newLxdContainer(name, d) if err != nil { return info, err } // Build the actual image file tarfile, err := ioutil.TempFile(builddir, "lxd_build_tar_") if err != nil { return info, err } if err := c.exportToTar(snap, tarfile); err != nil { tarfile.Close() return info, fmt.Errorf("imgPostContInfo: exportToTar failed: %s\n", err) } tarfile.Close() _, err = exec.Command("gzip", tarfile.Name()).CombinedOutput() if err != nil { shared.Debugf("image compression\n") return info, err } gztarpath := fmt.Sprintf("%s.gz", tarfile.Name()) sha256 := sha256.New() tarf, err := os.Open(gztarpath) if err != nil { return info, err } info.Size, err = io.Copy(sha256, tarf) tarf.Close() if err != nil { return info, err } info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) /* rename the the file to the expected name so our caller can use it */ finalName := shared.VarPath("images", info.Fingerprint) err = shared.FileMove(gztarpath, finalName) if err != nil { return info, err } info.Architecture = c.architecture info.Properties = req.Properties return info, nil }
func containerCreateInternal(d *Daemon, args containerArgs) (container, error) { // Set default values if args.Profiles == nil { args.Profiles = []string{"default"} } if args.Config == nil { args.Config = map[string]string{} } if args.BaseImage != "" { args.Config["volatile.base_image"] = args.BaseImage } if args.Devices == nil { args.Devices = shared.Devices{} } if args.Architecture == 0 { args.Architecture = d.architectures[0] } // Validate container name if args.Ctype == cTypeRegular { err := containerValidName(args.Name) if err != nil { return nil, err } } // Validate container config err := containerValidConfig(d, args.Config, false, false) if err != nil { return nil, err } // Validate container devices err = containerValidDevices(args.Devices, false, false) if err != nil { return nil, err } // Validate architecture _, err = shared.ArchitectureName(args.Architecture) if err != nil { return nil, err } // Validate profiles profiles, err := dbProfiles(d.db) if err != nil { return nil, err } for _, profile := range args.Profiles { if !shared.StringInSlice(profile, profiles) { return nil, fmt.Errorf("Requested profile '%s' doesn't exist", profile) } } path := containerPath(args.Name, args.Ctype == cTypeSnapshot) if shared.PathExists(path) { if shared.IsSnapshot(args.Name) { return nil, fmt.Errorf("Snapshot '%s' already exists", args.Name) } return nil, fmt.Errorf("The container already exists") } // Wipe any existing log for this container name os.RemoveAll(shared.LogPath(args.Name)) // Create the container entry id, err := dbContainerCreate(d.db, args) if err != nil { return nil, err } args.Id = id // Read the timestamp from the database dbArgs, err := dbContainerGet(d.db, args.Name) if err != nil { return nil, err } args.CreationDate = dbArgs.CreationDate args.LastUsedDate = dbArgs.LastUsedDate return containerLXCCreate(d, args) }
func copyContainer(config *lxd.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int) error { sourceRemote, sourceName := config.ParseRemoteAndContainer(sourceResource) destRemote, destName := config.ParseRemoteAndContainer(destResource) if sourceName == "" { return fmt.Errorf(gettext.Gettext("you must specify a source container name")) } if destName == "" { destName = sourceName } source, err := lxd.NewClient(config, sourceRemote) if err != nil { return err } status := &shared.ContainerState{} // TODO: presumably we want to do this for copying snapshots too? We // need to think a bit more about how we track the baseImage in the // face of LVM and snapshots in general; this will probably make more // sense once that work is done. baseImage := "" if !shared.IsSnapshot(sourceName) { status, err = source.ContainerStatus(sourceName) if err != nil { return err } baseImage = status.Config["volatile.base_image"] if !keepVolatile { for k := range status.Config { if strings.HasPrefix(k, "volatile") { delete(status.Config, k) } } } } // Do a local copy if the remotes are the same, otherwise do a migration if sourceRemote == destRemote { if sourceName == destName { return fmt.Errorf(gettext.Gettext("can't copy to the same container name")) } cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles, ephemeral == 1) if err != nil { return err } return source.WaitForSuccess(cp.Operation) } else { dest, err := lxd.NewClient(config, destRemote) if err != nil { return err } sourceProfs := shared.NewStringSet(status.Profiles) destProfs, err := dest.ListProfiles() if err != nil { return err } if !sourceProfs.IsSubset(shared.NewStringSet(destProfs)) { return fmt.Errorf(gettext.Gettext("not all the profiles from the source exist on the target")) } if ephemeral == -1 { ct, err := source.ContainerStatus(sourceName) if err != nil { return err } if ct.Ephemeral { ephemeral = 1 } else { ephemeral = 0 } } sourceWSResponse, err := source.GetMigrationSourceWS(sourceName) if err != nil { return err } secrets := map[string]string{} op, err := sourceWSResponse.MetadataAsOperation() if err == nil && op.Metadata != nil { for k, v := range *op.Metadata { secrets[k] = v.(string) } } else { // FIXME: This is a backward compatibility codepath if err := json.Unmarshal(sourceWSResponse.Metadata, &secrets); err != nil { return err } } addresses, err := source.Addresses() if err != nil { return err } for _, addr := range addresses { sourceWSUrl := "wss://" + addr + path.Join(sourceWSResponse.Operation, "websocket") var migration *lxd.Response migration, err = dest.MigrateFrom(destName, sourceWSUrl, secrets, status.Config, status.Profiles, baseImage, ephemeral == 1) if err != nil { continue } if err = dest.WaitForSuccess(migration.Operation); err != nil { continue } return nil } return err } }
/* * This function takes a container or snapshot from the local image server and * exports it as an image. */ func imgPostContInfo(d *Daemon, r *http.Request, req imageFromContainerPostReq, builddir string) (public int, fingerprint string, arch int, filename string, size int64, properties map[string]string, err error) { properties = map[string]string{} name := req.Source["name"] ctype := req.Source["type"] if ctype == "" || name == "" { return 0, "", 0, "", 0, properties, fmt.Errorf("No source provided") } switch ctype { case "snapshot": if !shared.IsSnapshot(name) { return 0, "", 0, "", 0, properties, fmt.Errorf("Not a snapshot") } case "container": if shared.IsSnapshot(name) { return 0, "", 0, "", 0, properties, fmt.Errorf("This is a snapshot") } default: return 0, "", 0, "", 0, properties, fmt.Errorf("Bad type") } filename = req.Filename switch req.Public { case true: public = 1 case false: public = 0 } snap := "" if ctype == "snapshot" { fields := strings.SplitN(name, "/", 2) if len(fields) != 2 { return 0, "", 0, "", 0, properties, fmt.Errorf("Not a snapshot") } name = fields[0] snap = fields[1] } c, err := newLxdContainer(name, d) if err != nil { return 0, "", 0, "", 0, properties, err } if err := c.exportToDir(snap, builddir); err != nil { return 0, "", 0, "", 0, properties, err } // Build the actual image file tarfname := fmt.Sprintf("%s.tar.xz", name) tarpath := filepath.Join(builddir, tarfname) args := []string{"-C", builddir, "--numeric-owner", "-Jcf", tarpath} if shared.PathExists(filepath.Join(builddir, "metadata.yaml")) { args = append(args, "metadata.yaml") } args = append(args, "rootfs") output, err := exec.Command("tar", args...).CombinedOutput() if err != nil { shared.Debugf("image packing failed\n") shared.Debugf("command was: tar %q\n", args) shared.Debugf(string(output)) return 0, "", 0, "", 0, properties, err } // get the size and fingerprint sha256 := sha256.New() tarf, err := os.Open(tarpath) if err != nil { return 0, "", 0, "", 0, properties, err } size, err = io.Copy(sha256, tarf) tarf.Close() if err != nil { return 0, "", 0, "", 0, properties, err } fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) /* rename the the file to the expected name so our caller can use it */ imagefname := filepath.Join(builddir, fingerprint) err = os.Rename(tarpath, imagefname) if err != nil { return 0, "", 0, "", 0, properties, err } arch = c.architecture properties = req.Properties return }
func (c *publishCmd) run(config *lxd.Config, args []string) error { var cRemote string var cName string iName := "" iRemote := "" properties := map[string]string{} firstprop := 1 // first property is arg[2] if arg[1] is image remote, else arg[1] if len(args) < 1 { return errArgs } cRemote, cName = config.ParseRemoteAndContainer(args[0]) if len(args) >= 2 && !strings.Contains(args[1], "=") { firstprop = 2 iRemote, iName = config.ParseRemoteAndContainer(args[1]) } else { iRemote, iName = config.ParseRemoteAndContainer("") } if cName == "" { return fmt.Errorf(i18n.G("Container name is mandatory")) } if iName != "" { return fmt.Errorf(i18n.G("There is no \"image name\". Did you want an alias?")) } d, err := lxd.NewClient(config, iRemote) if err != nil { return err } s := d if cRemote != iRemote { s, err = lxd.NewClient(config, cRemote) if err != nil { return err } } if !shared.IsSnapshot(cName) { ct, err := s.ContainerInfo(cName) if err != nil { return err } wasRunning := ct.StatusCode != 0 && ct.StatusCode != shared.Stopped wasEphemeral := ct.Ephemeral if wasRunning { if !c.Force { return fmt.Errorf(i18n.G("The container is currently running. Use --force to have it stopped and restarted.")) } if ct.Ephemeral { ct.Ephemeral = false err := s.UpdateContainerConfig(cName, ct.Brief()) if err != nil { return err } } resp, err := s.Action(cName, shared.Stop, -1, true, false) if err != nil { return err } op, err := s.WaitFor(resp.Operation) if err != nil { return err } if op.StatusCode == shared.Failure { return fmt.Errorf(i18n.G("Stopping container failed!")) } defer s.Action(cName, shared.Start, -1, true, false) if wasEphemeral { ct.Ephemeral = true err := s.UpdateContainerConfig(cName, ct.Brief()) if err != nil { return err } } } } for i := firstprop; i < len(args); i++ { entry := strings.SplitN(args[i], "=", 2) if len(entry) < 2 { return errArgs } properties[entry[0]] = entry[1] } var fp string // We should only set the properties field if there actually are any. // Otherwise we will only delete any existing properties on publish. // This is something which only direct callers of the API are allowed to // do. if len(properties) == 0 { properties = nil } // Optimized local publish if cRemote == iRemote { fp, err = d.ImageFromContainer(cName, c.makePublic, c.pAliases, properties, c.compression_algorithm) if err != nil { return err } fmt.Printf(i18n.G("Container published with fingerprint: %s")+"\n", fp) return nil } fp, err = s.ImageFromContainer(cName, false, nil, properties, c.compression_algorithm) if err != nil { return err } defer s.DeleteImage(fp) err = s.CopyImage(fp, d, false, c.pAliases, c.makePublic, false, nil) if err != nil { return err } fmt.Printf(i18n.G("Container published with fingerprint: %s")+"\n", fp) return nil }
func copyContainer(config *lxd.Config, sourceResource string, destResource string, keepVolatile bool) error { sourceRemote, sourceName := config.ParseRemoteAndContainer(sourceResource) destRemote, destName := config.ParseRemoteAndContainer(destResource) if sourceName == "" { return fmt.Errorf(gettext.Gettext("you must specify a source container name")) } if destName == "" { destName = sourceName } source, err := lxd.NewClient(config, sourceRemote) if err != nil { return err } status := &shared.ContainerState{} // TODO: presumably we want to do this for copying snapshots too? We // need to think a bit more about how we track the baseImage in the // face of LVM and snapshots in general; this will probably make more // sense once that work is done. baseImage := "" if !shared.IsSnapshot(sourceName) { status, err = source.ContainerStatus(sourceName, false) if err != nil { return err } baseImage = status.Config["volatile.baseImage"] if status.State() == shared.RUNNING && sourceName != destName { return fmt.Errorf(gettext.Gettext("Changing the name of a running container during copy isn't supported.")) } if !keepVolatile { for k := range status.Config { if strings.HasPrefix(k, "volatile") { delete(status.Config, k) } } } } // Do a local copy if the remotes are the same, otherwise do a migration if sourceRemote == destRemote { if sourceName == destName { return fmt.Errorf(gettext.Gettext("can't copy to the same container name")) } cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles) if err != nil { return err } return source.WaitForSuccess(cp.Operation) } else { if sourceRemote == "" || destRemote == "" { return fmt.Errorf(gettext.Gettext("non-http remotes are not supported for migration right now")) } dest, err := lxd.NewClient(config, destRemote) if err != nil { return err } sourceProfs := shared.NewStringSet(status.Profiles) destProfs, err := dest.ListProfiles() if err != nil { return err } if !sourceProfs.IsSubset(shared.NewStringSet(destProfs)) { return fmt.Errorf(gettext.Gettext("not all the profiles from the source exist on the target")) } to, err := source.MigrateTo(sourceName) if err != nil { return err } secrets := map[string]string{} if err := json.Unmarshal(to.Metadata, &secrets); err != nil { return err } url := source.BaseWSURL + path.Join(to.Operation, "websocket") migration, err := dest.MigrateFrom(sourceName, url, secrets, status.Config, status.Profiles, baseImage) if err != nil { return err } if err := dest.WaitForSuccess(migration.Operation); err != nil { return err } if sourceName != destName { rename, err := dest.Rename(sourceName, destName) if err != nil { return err } return dest.WaitForSuccess(rename.Operation) } return nil } }
func copyContainer(config *lxd.Config, sourceResource string, destResource string, keepVolatile bool) error { sourceRemote, sourceName := config.ParseRemoteAndContainer(sourceResource) destRemote, destName := config.ParseRemoteAndContainer(destResource) if sourceName == "" { return fmt.Errorf(gettext.Gettext("you must specify a source container name")) } if destName == "" { destName = sourceName } source, err := lxd.NewClient(config, sourceRemote) if err != nil { return err } status := &shared.ContainerState{} // TODO: presumably we want to do this for copying snapshots too? We // need to think a bit more about how we track the baseImage in the // face of LVM and snapshots in general; this will probably make more // sense once that work is done. baseImage := "" if !shared.IsSnapshot(sourceName) { status, err = source.ContainerStatus(sourceName, false) if err != nil { return err } baseImage = status.Config["volatile.base_image"] if status.State() == shared.RUNNING && sourceName != destName { return fmt.Errorf(gettext.Gettext("Changing the name of a running container during copy isn't supported.")) } if !keepVolatile { for k := range status.Config { if strings.HasPrefix(k, "volatile") { delete(status.Config, k) } } } } // Do a local copy if the remotes are the same, otherwise do a migration if sourceRemote == destRemote { if sourceName == destName { return fmt.Errorf(gettext.Gettext("can't copy to the same container name")) } cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles) if err != nil { return err } return source.WaitForSuccess(cp.Operation) } else { dest, err := lxd.NewClient(config, destRemote) if err != nil { return err } sourceProfs := shared.NewStringSet(status.Profiles) destProfs, err := dest.ListProfiles() if err != nil { return err } if !sourceProfs.IsSubset(shared.NewStringSet(destProfs)) { return fmt.Errorf(gettext.Gettext("not all the profiles from the source exist on the target")) } sourceWSResponse, err := source.GetMigrationSourceWS(sourceName) if err != nil { return err } secrets := map[string]string{} if err := json.Unmarshal(sourceWSResponse.Metadata, &secrets); err != nil { return err } addresses := make([]string, 0) if source.Transport == "unix" { serverStatus, err := source.ServerStatus() if err != nil { return err } addresses = serverStatus.Environment.Addresses } else if source.Transport == "https" { addresses = append(addresses, source.BaseURL[8:]) } else { return fmt.Errorf(gettext.Gettext("unknown transport type: %s"), source.Transport) } if len(addresses) == 0 { return fmt.Errorf(gettext.Gettext("The source remote isn't available over the network")) } for _, addr := range addresses { sourceWSUrl := "wss://" + addr + path.Join(sourceWSResponse.Operation, "websocket") var migration *lxd.Response migration, err = dest.MigrateFrom(destName, sourceWSUrl, secrets, status.Config, status.Profiles, baseImage) if err != nil { continue } if err = dest.WaitForSuccess(migration.Operation); err != nil { continue } return nil } return err } }
func (c *configCmd) run(config *lxd.Config, args []string) error { if len(args) < 1 { return errArgs } switch args[0] { case "unset": if len(args) < 2 { return errArgs } // Deal with local server if len(args) == 2 { c, err := lxd.NewClient(config, config.DefaultRemote) if err != nil { return err } ss, err := c.ServerStatus() if err != nil { return err } _, ok := ss.Config[args[1]] if !ok { return fmt.Errorf(i18n.G("Can't unset key '%s', it's not currently set."), args[1]) } _, err = c.SetServerConfig(args[1], "") return err } // Deal with remote server remote, container := config.ParseRemoteAndContainer(args[1]) if container == "" { c, err := lxd.NewClient(config, remote) if err != nil { return err } ss, err := c.ServerStatus() if err != nil { return err } _, ok := ss.Config[args[1]] if !ok { return fmt.Errorf(i18n.G("Can't unset key '%s', it's not currently set."), args[1]) } _, err = c.SetServerConfig(args[2], "") return err } // Deal with container args = append(args, "") return c.doSet(config, args, true) case "set": if len(args) < 3 { return errArgs } // Deal with local server if len(args) == 3 { c, err := lxd.NewClient(config, config.DefaultRemote) if err != nil { return err } _, err = c.SetServerConfig(args[1], args[2]) return err } // Deal with remote server remote, container := config.ParseRemoteAndContainer(args[1]) if container == "" { c, err := lxd.NewClient(config, remote) if err != nil { return err } _, err = c.SetServerConfig(args[2], args[3]) return err } // Deal with container return c.doSet(config, args, false) case "trust": if len(args) < 2 { return errArgs } switch args[1] { case "list": var remote string if len(args) == 3 { remote = config.ParseRemote(args[2]) } else { remote = config.DefaultRemote } d, err := lxd.NewClient(config, remote) if err != nil { return err } trust, err := d.CertificateList() if err != nil { return err } data := [][]string{} for _, cert := range trust { fp := cert.Fingerprint[0:12] certBlock, _ := pem.Decode([]byte(cert.Certificate)) if certBlock == nil { return fmt.Errorf(i18n.G("Invalid certificate")) } cert, err := x509.ParseCertificate(certBlock.Bytes) if err != nil { return err } const layout = "Jan 2, 2006 at 3:04pm (MST)" issue := cert.NotBefore.Format(layout) expiry := cert.NotAfter.Format(layout) data = append(data, []string{fp, cert.Subject.CommonName, issue, expiry}) } table := tablewriter.NewWriter(os.Stdout) table.SetAutoWrapText(false) table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetRowLine(true) table.SetHeader([]string{ i18n.G("FINGERPRINT"), i18n.G("COMMON NAME"), i18n.G("ISSUE DATE"), i18n.G("EXPIRY DATE")}) sort.Sort(SortImage(data)) table.AppendBulk(data) table.Render() return nil case "add": var remote string if len(args) < 3 { return fmt.Errorf(i18n.G("No certificate provided to add")) } else if len(args) == 4 { remote = config.ParseRemote(args[2]) } else { remote = config.DefaultRemote } d, err := lxd.NewClient(config, remote) if err != nil { return err } fname := args[len(args)-1] cert, err := shared.ReadCert(fname) if err != nil { return err } name, _ := shared.SplitExt(fname) return d.CertificateAdd(cert, name) case "remove": var remote string if len(args) < 3 { return fmt.Errorf(i18n.G("No fingerprint specified.")) } else if len(args) == 4 { remote = config.ParseRemote(args[2]) } else { remote = config.DefaultRemote } d, err := lxd.NewClient(config, remote) if err != nil { return err } return d.CertificateRemove(args[len(args)-1]) default: return errArgs } case "show": remote := config.DefaultRemote container := "" if len(args) > 1 { remote, container = config.ParseRemoteAndContainer(args[1]) } d, err := lxd.NewClient(config, remote) if err != nil { return err } var data []byte if len(args) == 1 || container == "" { config, err := d.ServerStatus() if err != nil { return err } brief := config.Brief() data, err = yaml.Marshal(&brief) } else { var brief shared.BriefContainerInfo if shared.IsSnapshot(container) { config, err := d.SnapshotInfo(container) if err != nil { return err } brief = shared.BriefContainerInfo{ Profiles: config.Profiles, Config: config.Config, Devices: config.Devices, Ephemeral: config.Ephemeral, } if c.expanded { brief = shared.BriefContainerInfo{ Profiles: config.Profiles, Config: config.ExpandedConfig, Devices: config.ExpandedDevices, Ephemeral: config.Ephemeral, } } } else { config, err := d.ContainerInfo(container) if err != nil { return err } brief = config.Brief() if c.expanded { brief = config.BriefExpanded() } } data, err = yaml.Marshal(&brief) if err != nil { return err } } fmt.Printf("%s", data) return nil case "get": if len(args) > 3 || len(args) < 2 { return errArgs } remote := config.DefaultRemote container := "" key := args[1] if len(args) > 2 { remote, container = config.ParseRemoteAndContainer(args[1]) key = args[2] } d, err := lxd.NewClient(config, remote) if err != nil { return err } if container != "" { resp, err := d.ContainerInfo(container) if err != nil { return err } fmt.Println(resp.Config[key]) } else { resp, err := d.ServerStatus() if err != nil { return err } value := resp.Config[key] if value == nil { value = "" } else if value == true { value = "true" } else if value == false { value = "false" } fmt.Println(value) } return nil case "profile": case "device": if len(args) < 2 { return errArgs } switch args[1] { case "list": return c.deviceList(config, "container", args) case "add": return c.deviceAdd(config, "container", args) case "remove": return c.deviceRm(config, "container", args) case "get": return c.deviceGet(config, "container", args) case "set": return c.deviceSet(config, "container", args) case "unset": return c.deviceUnset(config, "container", args) case "show": return c.deviceShow(config, "container", args) default: return errArgs } case "edit": if len(args) < 1 { return errArgs } remote := config.DefaultRemote container := "" if len(args) > 1 { remote, container = config.ParseRemoteAndContainer(args[1]) } d, err := lxd.NewClient(config, remote) if err != nil { return err } if len(args) == 1 || container == "" { return c.doDaemonConfigEdit(d) } return c.doContainerConfigEdit(d, container) default: return errArgs } return errArgs }
/* * This function takes a container or snapshot from the local image server and * exports it as an image. */ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq, builddir string) (info shared.ImageInfo, err error) { info.Properties = map[string]string{} name := req.Source["name"] ctype := req.Source["type"] if ctype == "" || name == "" { return info, fmt.Errorf("No source provided") } switch ctype { case "snapshot": if !shared.IsSnapshot(name) { return info, fmt.Errorf("Not a snapshot") } case "container": if shared.IsSnapshot(name) { return info, fmt.Errorf("This is a snapshot") } default: return info, fmt.Errorf("Bad type") } info.Filename = req.Filename switch req.Public { case true: info.Public = true case false: info.Public = false } c, err := containerLoadByName(d, name) if err != nil { return info, err } // Build the actual image file tarfile, err := ioutil.TempFile(builddir, "lxd_build_tar_") if err != nil { return info, err } defer os.Remove(tarfile.Name()) if err := c.Export(tarfile); err != nil { tarfile.Close() return info, fmt.Errorf("imgPostContInfo: export failed: %s", err) } tarfile.Close() compress, err := d.ConfigValueGet("images.compression_algorithm") if err != nil { return info, err } // Default to gzip for this if compress == "" { compress = "gzip" } var compressedPath string if compress != "none" { compressedPath, err = compressFile(tarfile.Name(), compress) if err != nil { return info, err } } else { compressedPath = tarfile.Name() } defer os.Remove(compressedPath) sha256 := sha256.New() tarf, err := os.Open(compressedPath) if err != nil { return info, err } info.Size, err = io.Copy(sha256, tarf) tarf.Close() if err != nil { return info, err } info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil)) _, err = dbImageGet(d.db, info.Fingerprint, false, true) if err == nil { return info, fmt.Errorf("The image already exists: %s", info.Fingerprint) } /* rename the the file to the expected name so our caller can use it */ finalName := shared.VarPath("images", info.Fingerprint) err = shared.FileMove(compressedPath, finalName) if err != nil { return info, err } info.Architecture = c.Architecture() info.Properties = req.Properties return info, nil }
func containerSnapRestore(d *Daemon, name string, snap string) error { // normalize snapshot name if !shared.IsSnapshot(snap) { snap = fmt.Sprintf("%s/%s", name, snap) } shared.Debugf("RESTORE => Restoring snapshot [%s] on container [%s]", snap, name) /* * restore steps: * 1. stop container if already running * 2. overwrite existing config with snapshot config * 3. copy snapshot rootfs to container */ wasRunning := false c, err := newLxdContainer(name, d) if err != nil { shared.Debugf("RESTORE => Error: newLxdContainer() failed for container", err) return err } // 1. stop container // TODO: stateful restore ? if c.c.Running() { wasRunning = true if err = c.Stop(); err != nil { shared.Debugf("RESTORE => Error: could not stop container", err) return err } shared.Debugf("RESTORE => Stopped container %s", name) } // 2, replace config // Make sure the source exists. source, err := newLxdContainer(snap, d) if err != nil { shared.Debugf("RESTORE => Error: newLxdContainer() failed for snapshot", err) return err } newConfig := containerConfigReq{} newConfig.Config = source.config newConfig.Profiles = source.profiles newConfig.Devices = source.devices err = containerReplaceConfig(d, c, name, newConfig) if err != nil { shared.Debugf("RESTORE => err #4", err) return err } // 3. copy rootfs // TODO: btrfs optimizations containerRootPath := shared.VarPath("lxc", name) if !shared.IsDir(path.Dir(containerRootPath)) { shared.Debugf("RESTORE => containerRoot [%s] directory does not exist", containerRootPath) return os.ErrNotExist } var snapshotRootFSPath string snapshotRootFSPath = migration.AddSlash(snapshotRootfsDir(c, strings.SplitN(snap, "/", 2)[1])) containerRootFSPath := migration.AddSlash(fmt.Sprintf("%s/%s", containerRootPath, "rootfs")) shared.Debugf("RESTORE => Copying %s to %s", snapshotRootFSPath, containerRootFSPath) rsyncVerbosity := "-q" if *debug { rsyncVerbosity = "-vi" } output, err := exec.Command("rsync", "-a", "-c", "-HAX", "--devices", "--delete", rsyncVerbosity, snapshotRootFSPath, containerRootFSPath).CombinedOutput() shared.Debugf("RESTORE => rsync output\n%s", output) if err == nil && !source.isPrivileged() { err = setUnprivUserAcl(c, containerRootPath) if err != nil { shared.Debugf("Error adding acl for container root: falling back to chmod\n") output, err := exec.Command("chmod", "+x", containerRootPath).CombinedOutput() if err != nil { shared.Debugf("Error chmoding the container root\n") shared.Debugf(string(output)) return err } } } else { shared.Debugf("rsync failed:\n%s", output) return err } if wasRunning { c.Start() } return nil }
func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int) error { sourceRemote, sourceName := config.ParseRemoteAndContainer(sourceResource) destRemote, destName := config.ParseRemoteAndContainer(destResource) if sourceName == "" { return fmt.Errorf(i18n.G("you must specify a source container name")) } if destName == "" && destResource != "" { destName = sourceName } source, err := lxd.NewClient(config, sourceRemote) if err != nil { return err } var status struct { Architecture string Devices shared.Devices Config map[string]string Profiles []string } // TODO: presumably we want to do this for copying snapshots too? We // need to think a bit more about how we track the baseImage in the // face of LVM and snapshots in general; this will probably make more // sense once that work is done. baseImage := "" if !shared.IsSnapshot(sourceName) { result, err := source.ContainerInfo(sourceName) if err != nil { return err } status.Architecture = result.Architecture status.Devices = result.Devices status.Config = result.Config status.Profiles = result.Profiles } else { result, err := source.SnapshotInfo(sourceName) if err != nil { return err } status.Architecture = result.Architecture status.Devices = result.Devices status.Config = result.Config status.Profiles = result.Profiles } if c.profArgs != nil { status.Profiles = append(status.Profiles, c.profArgs...) } if configMap != nil { for key, value := range configMap { status.Config[key] = value } } baseImage = status.Config["volatile.base_image"] if !keepVolatile { for k := range status.Config { if strings.HasPrefix(k, "volatile") { delete(status.Config, k) } } } // Do a local copy if the remotes are the same, otherwise do a migration if sourceRemote == destRemote { if sourceName == destName { return fmt.Errorf(i18n.G("can't copy to the same container name")) } cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles, ephemeral == 1) if err != nil { return err } err = source.WaitForSuccess(cp.Operation) if err != nil { return err } if destResource == "" { op, err := cp.MetadataAsOperation() if err != nil { return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server")) } containers, ok := op.Resources["containers"] if !ok || len(containers) == 0 { return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server")) } fields := strings.Split(containers[0], "/") fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1]) } return nil } dest, err := lxd.NewClient(config, destRemote) if err != nil { return err } sourceProfs := shared.NewStringSet(status.Profiles) destProfs := []string{} profiles, err := dest.ListProfiles() if err != nil { return err } for _, profile := range profiles { destProfs = append(destProfs, profile.Name) } if !sourceProfs.IsSubset(shared.NewStringSet(destProfs)) { return fmt.Errorf(i18n.G("not all the profiles from the source exist on the target")) } if ephemeral == -1 { ct, err := source.ContainerInfo(sourceName) if err != nil { return err } if ct.Ephemeral { ephemeral = 1 } else { ephemeral = 0 } } sourceWSResponse, err := source.GetMigrationSourceWS(sourceName) if err != nil { return err } secrets := map[string]string{} op, err := sourceWSResponse.MetadataAsOperation() if err != nil { return err } for k, v := range *op.Metadata { secrets[k] = v.(string) } addresses, err := source.Addresses() if err != nil { return err } /* Since we're trying a bunch of different network ports that * may be invalid, we can get "bad handshake" errors when the * websocket code tries to connect. If the first error is a * real error, but the subsequent errors are only network * errors, we should try to report the first real error. Of * course, if all the errors are websocket errors, let's just * report that. */ for _, addr := range addresses { var migration *lxd.Response sourceWSUrl := "https://" + addr + sourceWSResponse.Operation migration, err = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1, false, source, sourceWSResponse.Operation) if err != nil { continue } if err := source.WaitForSuccess(sourceWSResponse.Operation); err != nil { return err } // If push mode is implemented then MigrateFrom will return a // non-waitable operation. So this needs to be conditionalized // on pull mode. if err = dest.WaitForSuccess(migration.Operation); err != nil { return err } if destResource == "" { op, err := migration.MetadataAsOperation() if err != nil { return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server")) } containers, ok := op.Resources["containers"] if !ok || len(containers) == 0 { return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server")) } fields := strings.Split(containers[0], "/") fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1]) } return nil } return err }
func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int) error { sourceRemote, sourceName := config.ParseRemoteAndContainer(sourceResource) destRemote, destName := config.ParseRemoteAndContainer(destResource) if sourceName == "" { return fmt.Errorf(i18n.G("you must specify a source container name")) } if destName == "" { destName = sourceName } source, err := lxd.NewClient(config, sourceRemote) if err != nil { return err } status := &shared.ContainerInfo{} // TODO: presumably we want to do this for copying snapshots too? We // need to think a bit more about how we track the baseImage in the // face of LVM and snapshots in general; this will probably make more // sense once that work is done. baseImage := "" if !shared.IsSnapshot(sourceName) { status, err = source.ContainerInfo(sourceName) if err != nil { return err } baseImage = status.Config["volatile.base_image"] if !keepVolatile { for k := range status.Config { if strings.HasPrefix(k, "volatile") { delete(status.Config, k) } } } } // Do a local copy if the remotes are the same, otherwise do a migration if sourceRemote == destRemote { if sourceName == destName { return fmt.Errorf(i18n.G("can't copy to the same container name")) } cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles, ephemeral == 1) if err != nil { return err } return source.WaitForSuccess(cp.Operation) } dest, err := lxd.NewClient(config, destRemote) if err != nil { return err } sourceProfs := shared.NewStringSet(status.Profiles) destProfs, err := dest.ListProfiles() if err != nil { return err } if !sourceProfs.IsSubset(shared.NewStringSet(destProfs)) { return fmt.Errorf(i18n.G("not all the profiles from the source exist on the target")) } if ephemeral == -1 { ct, err := source.ContainerInfo(sourceName) if err != nil { return err } if ct.Ephemeral { ephemeral = 1 } else { ephemeral = 0 } } sourceWSResponse, err := source.GetMigrationSourceWS(sourceName) if err != nil { return err } secrets := map[string]string{} op, err := sourceWSResponse.MetadataAsOperation() if err != nil { return err } for k, v := range *op.Metadata { secrets[k] = v.(string) } addresses, err := source.Addresses() if err != nil { return err } /* Since we're trying a bunch of different network ports that * may be invalid, we can get "bad handshake" errors when the * websocket code tries to connect. If the first error is a * real error, but the subsequent errors are only network * errors, we should try to report the first real error. Of * course, if all the errors are websocket errors, let's just * report that. */ for _, addr := range addresses { var migration *lxd.Response sourceWSUrl := "https://" + addr + sourceWSResponse.Operation migration, err = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1) if err != nil { continue } if err = dest.WaitForSuccess(migration.Operation); err != nil { return err } return nil } return err }