Beispiel #1
0
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
}
Beispiel #2
0
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)
}
Beispiel #3
0
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
}
Beispiel #4
0
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
}
Beispiel #5
0
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)
}
Beispiel #6
0
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])
}
Beispiel #7
0
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
}
Beispiel #8
0
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)
}
Beispiel #9
0
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}
}
Beispiel #10
0
/*
 * 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
}
Beispiel #11
0
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)
}
Beispiel #12
0
Datei: copy.go Projekt: ralic/lxd
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
	}
}
Beispiel #13
0
/*
 * 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
}
Beispiel #14
0
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
}
Beispiel #15
0
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
	}
}
Beispiel #16
0
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
	}
}
Beispiel #17
0
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
}
Beispiel #18
0
/*
 * 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
}
Beispiel #19
0
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
}
Beispiel #20
0
Datei: copy.go Projekt: vahe/lxd
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
}
Beispiel #21
0
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
}