func main() { if err := run(); err != nil { // The action we take depends on the error we get. msg := fmt.Sprintf(i18n.G("error: %v"), err) switch t := err.(type) { case *url.Error: switch u := t.Err.(type) { case *net.OpError: if u.Op == "dial" && u.Net == "unix" { switch errno := u.Err.(type) { case syscall.Errno: switch errno { case syscall.ENOENT: msg = i18n.G("LXD socket not found; is LXD running?") case syscall.ECONNREFUSED: msg = i18n.G("Connection refused; is LXD running?") case syscall.EACCES: msg = i18n.G("Permisson denied, are you in the lxd group?") default: msg = fmt.Sprintf("%d %s", uintptr(errno), errno.Error()) } } } } } fmt.Fprintln(os.Stderr, fmt.Sprintf("%s", msg)) os.Exit(1) } }
func (c *publishCmd) flags() { gnuflag.BoolVar(&c.makePublic, "public", false, i18n.G("Make the image public")) gnuflag.Var(&c.pAliases, "alias", i18n.G("New alias to define at target")) gnuflag.BoolVar(&c.Force, "force", false, i18n.G("Stop the container if currently running")) gnuflag.BoolVar(&c.Force, "f", false, i18n.G("Stop the container if currently running")) gnuflag.StringVar(&c.compression_algorithm, "compression", "", i18n.G("Define a compression algorithm: for image or none")) }
func (c *imageCmd) flags() { gnuflag.BoolVar(&c.publicImage, "public", false, i18n.G("Make image public")) gnuflag.BoolVar(&c.copyAliases, "copy-aliases", false, i18n.G("Copy aliases from source")) gnuflag.BoolVar(&c.autoUpdate, "auto-update", false, i18n.G("Keep the image up to date after initial copy")) gnuflag.Var(&c.addAliases, "alias", i18n.G("New alias to define at target")) gnuflag.StringVar(&c.format, "format", "table", i18n.G("Format")) }
func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { if cInfo.Ephemeral { return i18n.G("EPHEMERAL") } else { return i18n.G("PERSISTENT") } }
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 *actionCmd) flags() { if c.hasTimeout { gnuflag.IntVar(&c.timeout, "timeout", -1, i18n.G("Time to wait for the container before killing it.")) gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the container to shutdown.")) gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Store the container state (only for stop).")) gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only forstart).")) } }
func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error { switch c.format { case listFormatTable: data := [][]string{} for _, image := range images { if !c.imageShouldShow(filters, &image) { continue } shortest := c.shortestAlias(image.Aliases) if len(image.Aliases) > 1 { shortest = fmt.Sprintf(i18n.G("%s (%d more)"), shortest, len(image.Aliases)-1) } fp := image.Fingerprint[0:12] public := i18n.G("no") description := c.findDescription(image.Properties) if image.Public { public = i18n.G("yes") } const layout = "Jan 2, 2006 at 3:04pm (MST)" uploaded := image.UploadDate.UTC().Format(layout) size := fmt.Sprintf("%.2fMB", float64(image.Size)/1024.0/1024.0) data = append(data, []string{shortest, fp, public, description, image.Architecture, size, uploaded}) } table := tablewriter.NewWriter(os.Stdout) table.SetAutoWrapText(false) table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetRowLine(true) table.SetHeader([]string{ i18n.G("ALIAS"), i18n.G("FINGERPRINT"), i18n.G("PUBLIC"), i18n.G("DESCRIPTION"), i18n.G("ARCH"), i18n.G("SIZE"), i18n.G("UPLOAD DATE")}) sort.Sort(SortImage(data)) table.AppendBulk(data) table.Render() case listFormatJSON: data := make([]*shared.ImageInfo, len(images)) for i := range images { data[i] = &images[i] } enc := json.NewEncoder(os.Stdout) err := enc.Encode(data) if err != nil { return err } default: return fmt.Errorf("invalid format %q", c.format) } return nil }
func (c *configCmd) deviceSet(config *lxd.Config, which string, args []string) error { if len(args) < 6 { return errArgs } remote, name := config.ParseRemoteAndContainer(args[2]) client, err := lxd.NewClient(config, remote) if err != nil { return err } devname := args[3] key := args[4] value := args[5] if which == "profile" { st, err := client.ProfileConfig(name) if err != nil { return err } dev, ok := st.Devices[devname] if !ok { return fmt.Errorf(i18n.G("The device doesn't exist")) } dev[key] = value st.Devices[devname] = dev err = client.PutProfile(name, *st) if err != nil { return err } } else { st, err := client.ContainerInfo(name) if err != nil { return err } dev, ok := st.Devices[devname] if !ok { return fmt.Errorf(i18n.G("The device doesn't exist")) } dev[key] = value st.Devices[devname] = dev err = client.UpdateContainerConfig(name, st.Brief()) if err != nil { return err } } return err }
func (c *actionCmd) run(config *lxd.Config, args []string) error { if len(args) == 0 { return errArgs } state := false // Only store state if asked to if c.action == "stop" && c.stateful { state = true } for _, nameArg := range args { remote, name := config.ParseRemoteAndContainer(nameArg) d, err := lxd.NewClient(config, remote) if err != nil { return err } if name == "" { return fmt.Errorf(i18n.G("Must supply container name for: ")+"\"%s\"", nameArg) } if c.action == shared.Start || c.action == shared.Stop { current, err := d.ContainerInfo(name) if err != nil { return err } // "start" for a frozen container means "unfreeze" if current.StatusCode == shared.Frozen { c.action = shared.Unfreeze } // Always restore state (if present) unless asked not to if c.action == shared.Start && current.Stateful && !c.stateless { state = true } } resp, err := d.Action(name, c.action, c.timeout, c.force, state) if err != nil { return err } if resp.Type != lxd.Async { return fmt.Errorf(i18n.G("bad result type from action")) } if err := d.WaitForSuccess(resp.Operation); err != nil { return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, name) } } return nil }
func (c *deleteCmd) promptDelete(name string) error { reader := bufio.NewReader(os.Stdin) fmt.Printf(i18n.G("Remove %s (yes/no): "), name) input, _ := reader.ReadString('\n') input = strings.TrimSuffix(input, "\n") if !shared.StringInSlice(strings.ToLower(input), []string{i18n.G("yes")}) { return fmt.Errorf(i18n.G("User aborted delete operation.")) } return nil }
func execIfAliases(config *lxd.Config, origArgs []string) { newArgs, expanded := expandAlias(config, origArgs) if !expanded { return } path, err := exec.LookPath(origArgs[0]) if err != nil { fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), err) os.Exit(5) } ret := syscall.Exec(path, newArgs, syscall.Environ()) fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), ret) os.Exit(5) }
func (c *networkCmd) doNetworkDetach(client *lxd.Client, name string, args []string) error { if len(args) < 1 || len(args) > 2 { return errArgs } containerName := args[0] devName := "" if len(args) > 1 { devName = args[1] } container, err := client.ContainerInfo(containerName) if err != nil { return err } if devName == "" { for n, d := range container.Devices { if d["type"] == "nic" && d["parent"] == name { if devName != "" { return fmt.Errorf(i18n.G("More than one device matches, specify the device name.")) } devName = n } } } if devName == "" { return fmt.Errorf(i18n.G("No device found for this network")) } device, ok := container.Devices[devName] if !ok { return fmt.Errorf(i18n.G("The specified device doesn't exist")) } if device["type"] != "nic" || device["parent"] != name { return fmt.Errorf(i18n.G("The specified device doesn't match the network")) } resp, err := client.ContainerDeviceDelete(containerName, devName) if err != nil { return err } return client.WaitForSuccess(resp.Operation) }
func (c *configCmd) deviceRm(config *lxd.Config, which string, args []string) error { if len(args) < 4 { return errArgs } remote, name := config.ParseRemoteAndContainer(args[2]) client, err := lxd.NewClient(config, remote) if err != nil { return err } devname := args[3] var resp *lxd.Response if which == "profile" { resp, err = client.ProfileDeviceDelete(name, devname) } else { resp, err = client.ContainerDeviceDelete(name, devname) } if err != nil { return err } if which != "profile" { err = client.WaitForSuccess(resp.Operation) } if err == nil { fmt.Printf(i18n.G("Device %s removed from %s")+"\n", devname, name) } return err }
func (c *configCmd) deviceAdd(config *lxd.Config, which string, args []string) error { if len(args) < 5 { return errArgs } remote, name := config.ParseRemoteAndContainer(args[2]) client, err := lxd.NewClient(config, remote) if err != nil { return err } devname := args[3] devtype := args[4] var props []string if len(args) > 5 { props = args[5:] } else { props = []string{} } var resp *lxd.Response if which == "profile" { resp, err = client.ProfileDeviceAdd(name, devname, devtype, props) } else { resp, err = client.ContainerDeviceAdd(name, devname, devtype, props) } if err != nil { return err } fmt.Printf(i18n.G("Device %s added to %s")+"\n", devname, name) if which == "profile" { return nil } return client.WaitForSuccess(resp.Operation) }
func getRemoteCertificate(address string) (*x509.Certificate, error) { // Setup a permissive TLS config tlsConfig, err := shared.GetTLSConfig("", "", nil) if err != nil { return nil, err } tlsConfig.InsecureSkipVerify = true tr := &http.Transport{ TLSClientConfig: tlsConfig, Dial: shared.RFC3493Dialer, Proxy: shared.ProxyFromEnvironment, } // Connect client := &http.Client{Transport: tr} resp, err := client.Get(address) if err != nil { return nil, err } // Retrieve the certificate if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 { return nil, fmt.Errorf(i18n.G("Unable to read remote TLS certificate")) } return resp.TLS.PeerCertificates[0], nil }
func (c *imageCmd) usage() string { return i18n.G( `Manipulate container images. In LXD containers are created from images. Those images were themselves either generated from an existing container or downloaded from an image server. When using remote images, LXD will automatically cache images for you and remove them upon expiration. The image unique identifier is the hash (sha-256) of its representation as a compressed tarball (or for split images, the concatenation of the metadata and rootfs tarballs). Images can be referenced by their full hash, shortest unique partial hash or alias name (if one is set). lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [prop=value] Import an image tarball (or tarballs) into the LXD image store. lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update] Copy an image from one LXD daemon to another over the network. The auto-update flag instructs the server to keep this image up to date. It requires the source to be an alias and for it to be public. lxc image delete [remote:]<image> Delete an image from the LXD image store. lxc image export [remote:]<image> Export an image from the LXD image store into a distributable tarball. lxc image info [remote:]<image> Print everything LXD knows about a given image. lxc image list [remote:] [filter] List images in the LXD image store. Filters may be of the <key>=<value> form for property based filtering, or part of the image hash or part of the image alias name. lxc image show [remote:]<image> Yaml output of the user modifiable properties of an image. lxc image edit [remote:]<image> Edit image, either by launching external editor or reading STDIN. Example: lxc image edit <image> # launch editor cat image.yml | lxc image edit <image> # read from image.yml lxc image alias create [remote:]<alias> <fingerprint> Create a new alias for an existing image. lxc image alias delete [remote:]<alias> Delete an alias. lxc image alias list [remote:] [filter] List the aliases. Filters may be part of the image hash or part of the image alias name. `) }
func (c *profileCmd) usage() string { return i18n.G( `Manage configuration profiles. lxc profile list [filters] List available profiles. lxc profile show <profile> Show details of a profile. lxc profile create <profile> Create a profile. lxc profile copy <profile> <remote> Copy the profile to the specified remote. lxc profile get <profile> <key> Get profile configuration. lxc profile set <profile> <key> <value> Set profile configuration. lxc profile delete <profile> Delete a profile. lxc profile edit <profile> Edit profile, either by launching external editor or reading STDIN. Example: lxc profile edit <profile> # launch editor cat profile.yml | lxc profile edit <profile> # read from profile.yml lxc profile apply <container> <profiles> Apply a comma-separated list of profiles to a container, in order. All profiles passed in this call (and only those) will be applied to the specified container. Example: lxc profile apply foo default,bar # Apply default and bar lxc profile apply foo default # Only default is active lxc profile apply '' # no profiles are applied anymore lxc profile apply bar,default # Apply default second now Devices: lxc profile device list <profile> List devices in the given profile. lxc profile device show <profile> Show full device details in the given profile. lxc profile device remove <profile> <name> Remove a device from a profile. lxc profile device add <profile name> <device name> <device type> [key=value]... Add a profile device, such as a disk or a nic, to the containers using the specified profile.`) }
func (c *profileCmd) doProfileDelete(client *lxd.Client, p string) error { err := client.ProfileDelete(p) if err == nil { fmt.Printf(i18n.G("Profile %s deleted")+"\n", p) } return err }
func (c *listCmd) usage() string { return i18n.G( `Lists the available resources. lxc list [resource] [filters] [-c columns] [--fast] The filters are: * A single keyword like "web" which will list any container with "web" in its name. * A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier: * "user.blah=abc" will list all containers with the "blah" user property set to "abc" * "u.blah=abc" will do the same * "security.privileged=1" will list all privileged containers * "s.privileged=1" will do the same The columns are: * 4 - IPv4 address * 6 - IPv6 address * a - architecture * c - creation date * n - name * p - pid of container init process * P - profiles * s - state * S - number of snapshots * t - type (persistent or ephemeral) Default column layout: ns46tS Fast column layout: nsacPt`) }
func (c *initCmd) checkNetwork(d *lxd.Client, name string) { ct, err := d.ContainerInfo(name) if err != nil { return } for _, d := range ct.ExpandedDevices { if d["type"] == "nic" { return } } fmt.Fprintf(os.Stderr, "\n"+i18n.G("The container you are starting doesn’t have any network attached to it.")+"\n") fmt.Fprintf(os.Stderr, " "+i18n.G("To create a new network, use: lxc network create")+"\n") fmt.Fprintf(os.Stderr, " "+i18n.G("To assign a network to a container, use: lxc network assign")+"\n\n") }
func (c *profileCmd) doProfileAssign(client *lxd.Client, d string, p string) error { resp, err := client.AssignProfile(d, p) if err != nil { return err } err = client.WaitForSuccess(resp.Operation) if err == nil { if p == "" { p = i18n.G("(none)") } fmt.Printf(i18n.G("Profiles %s applied to %s")+"\n", p, d) } return err }
func (c *profileCmd) doProfileRemove(client *lxd.Client, d string, p string) error { ct, err := client.ContainerInfo(d) if err != nil { return err } if !shared.StringInSlice(p, ct.Profiles) { return fmt.Errorf("Profile %s isn't currently applied to %s", p, d) } profiles := []string{} for _, profile := range ct.Profiles { if profile == p { continue } profiles = append(profiles, profile) } ct.Profiles = profiles err = client.UpdateContainerConfig(d, ct.Brief()) if err != nil { return err } fmt.Printf(i18n.G("Profile %s removed from %s")+"\n", p, d) return err }
func (c *listCmd) usage() string { return i18n.G( `Lists the available resources. lxc list [resource] [filters] [--format table|json] [-c columns] [--fast] The filters are: * A single keyword like "web" which will list any container with a name starting by "web". * A regular expression on the container name. (e.g. .*web.*01$) * A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier: * "user.blah=abc" will list all containers with the "blah" user property set to "abc". * "u.blah=abc" will do the same * "security.privileged=1" will list all privileged containers * "s.privileged=1" will do the same * A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*) Columns for table format are: * 4 - IPv4 address * 6 - IPv6 address * a - architecture * c - creation date * l - last used date * n - name * p - pid of container init process * P - profiles * s - state * S - number of snapshots * t - type (persistent or ephemeral) Default column layout: ns46tS Fast column layout: nsacPt`) }
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(i18n.G("'/' not allowed in snapshot name")) } resp, err := d.Snapshot(name, snapname, c.stateful) if err != nil { return err } return d.WaitForSuccess(resp.Operation) }
func (c *deleteCmd) usage() string { return i18n.G( `Delete containers or snapshots. lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/<snapshot>]...] Destroy containers or snapshots with any attached data (configuration, snapshots, ...).`) }
func (c *networkCmd) doNetworkDelete(client *lxd.Client, name string) error { err := client.NetworkDelete(name) if err == nil { fmt.Printf(i18n.G("Network %s deleted")+"\n", name) } return err }
func (c *infoCmd) usage() string { return i18n.G( `List information on containers. This will support remotes and images as well, but only containers for now. lxc info [<remote>:]container [--show-log]`) }
func (c *execCmd) usage() string { return i18n.G( `Execute the specified command in a container. lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... <command> Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored).`) }
func (c *imageCmd) imageEditHelp() string { return i18n.G( `### This is a yaml representation of the image properties. ### Any line starting with a '# will be ignored. ### ### Each property is represented by a single line: ### An example would be: ### description: My custom image`) }
func main() { if err := run(); err != nil { msg := fmt.Sprintf(i18n.G("error: %v"), err) lxdErr := lxd.GetLocalLXDErr(err) switch lxdErr { case syscall.ENOENT: msg = i18n.G("LXD socket not found; is LXD installed and running?") case syscall.ECONNREFUSED: msg = i18n.G("Connection refused; is LXD running?") case syscall.EACCES: msg = i18n.G("Permission denied, are you in the lxd group?") } fmt.Fprintln(os.Stderr, fmt.Sprintf("%s", msg)) os.Exit(1) } }