func (k *daemonConfigKey) Validate(d *Daemon, value string) error { // No need to validate when unsetting if value == "" { return nil } // Validate booleans if k.valueType == "bool" && !shared.StringInSlice(strings.ToLower(value), []string{"true", "false", "1", "0", "yes", "no", "on", "off"}) { return fmt.Errorf("Invalid value for a boolean: %s", value) } // Validate integers if k.valueType == "int" { _, err := strconv.ParseInt(value, 10, 64) if err != nil { return err } } // Check against valid values if k.validValues != nil && !shared.StringInSlice(value, k.validValues) { return fmt.Errorf("Invalid value, only the following values are allowed: %s", k.validValues) } // Run external validation function if k.validator != nil { err := k.validator(d, k.name(), value) if err != nil { return err } } return nil }
func (s *storageZfs) zfsGetPoolUsers() ([]string, error) { subvols, err := s.zfsListSubvolumes("") if err != nil { return []string{}, err } exceptions := []string{ "containers", "images", "snapshots", "deleted", "deleted/containers", "deleted/images"} users := []string{} for _, subvol := range subvols { path := strings.Split(subvol, "/") // Only care about plausible LXD paths if !shared.StringInSlice(path[0], exceptions) { continue } // Ignore empty paths if shared.StringInSlice(subvol, exceptions) { continue } users = append(users, subvol) } return users, nil }
func cmdGenerateDNS(c *lxd.Client, args []string) error { // A path must be provided if len(args) < 1 { return fmt.Errorf("A format must be provided (samba4 or bind9).") } format := args[0] if !shared.StringInSlice(format, []string{"samba4", "bind9"}) { return fmt.Errorf("Invalid format, supported values are samba4 or bind9") } // Load the simulation routers, err := importFromLXD(c) if err != nil { return err } for _, r := range routers { for _, d := range r.DNS { if format == "bind9" { fmt.Println(d) } else { fields := strings.Fields(d) domain := strings.Split(fields[0], ".") fmt.Printf("samba-tool dns add localhost %s %s %s %s\n", domain[len(domain)-1], fields[0], fields[2], fields[3]) } } } return nil }
func (s *storageZfs) zfsGetPoolUsers() ([]string, error) { subvols, err := s.zfsListSubvolumes("") if err != nil { return []string{}, err } exceptions := []string{ "containers", "images", "snapshots", "deleted", "deleted/containers", "deleted/images"} users := []string{} for _, subvol := range subvols { if shared.StringInSlice(subvol, exceptions) { continue } users = append(users, subvol) } return users, nil }
func imageValidSecret(fingerprint string, secret string) bool { for _, op := range operations { if op.resources == nil { continue } opImages, ok := op.resources["images"] if !ok { continue } if !shared.StringInSlice(fingerprint, opImages) { continue } opSecret, ok := op.metadata["secret"] if !ok { continue } if opSecret == secret { // Token is single-use, so cancel it now op.Cancel() return true } } return false }
// The handler for the post operation. func profilePost(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] req := profilesPostReq{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } // Sanity checks if req.Name == "" { return BadRequest(fmt.Errorf("No name provided")) } // Check that the name isn't already in use id, _, _ := dbProfileGet(d.db, req.Name) if id > 0 { return Conflict } if strings.Contains(req.Name, "/") { return BadRequest(fmt.Errorf("Profile names may not contain slashes")) } if shared.StringInSlice(req.Name, []string{".", ".."}) { return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name)) } err := dbProfileUpdate(d.db, name, req.Name) if err != nil { return InternalError(err) } return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name)) }
func run(args []string) error { // Parse command line gnuflag.Parse(true) if len(os.Args) == 1 || !shared.StringInSlice(os.Args[1], []string{"spawn", "delete"}) { fmt.Printf("Usage: %s spawn [--count=COUNT] [--image=IMAGE] [--privileged=BOOL] [--parallel=COUNT]\n", os.Args[0]) fmt.Printf(" %s delete [--parallel=COUNT]\n\n", os.Args[0]) gnuflag.Usage() fmt.Printf("\n") return fmt.Errorf("An action (spawn or delete) must be passed.") } // Connect to LXD c, err := lxd.NewClient(&lxd.DefaultConfig, "local") if err != nil { return err } switch os.Args[1] { case "spawn": return spawnContainers(c, *argCount, *argImage, *argPrivileged) case "delete": return deleteContainers(c) } return nil }
func compressFile(path string, compress string) (string, error) { reproducible := []string{"gzip"} args := []string{path, "-c"} if shared.StringInSlice(compress, reproducible) { args = append(args, "-n") } cmd := exec.Command(compress, args...) outfile, err := os.Create(path + ".compressed") if err != nil { return "", err } defer outfile.Close() cmd.Stdout = outfile err = cmd.Run() if err != nil { os.Remove(outfile.Name()) return "", err } return outfile.Name(), nil }
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 containerValidDevices(devices shared.Devices) error { // Empty device list if devices == nil { return nil } // Check each device individually for _, m := range devices { for k, _ := range m { if !containerValidDeviceConfigKey(m["type"], k) { return fmt.Errorf("Invalid device configuration key for %s: %s", m["type"], k) } } if m["type"] == "nic" { if m["nictype"] == "" { return fmt.Errorf("Missing nic type") } if !shared.StringInSlice(m["nictype"], []string{"bridged", "physical", "p2p", "macvlan"}) { return fmt.Errorf("Bad nic type: %s", m["nictype"]) } if shared.StringInSlice(m["nictype"], []string{"bridged", "physical", "macvlan"}) && m["parent"] == "" { return fmt.Errorf("Missing parent for %s type nic.", m["nictype"]) } } else if m["type"] == "disk" { if m["path"] == "" { return fmt.Errorf("Disk entry is missing the required \"path\" property.") } if m["source"] == "" && m["path"] != "/" { return fmt.Errorf("Disk entry is missing the required \"source\" property.") } } else if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) { if m["path"] == "" { return fmt.Errorf("Unix device entry is missing the required \"path\" property.") } } else if m["type"] == "none" { continue } else { return fmt.Errorf("Invalid device type: %s", m["type"]) } } return nil }
func (s *storageZfs) zfsClone(source string, name string, dest string, dotZfs bool) error { var mountpoint string mountpoint = shared.VarPath(dest) if dotZfs { mountpoint += ".zfs" } output, err := exec.Command( "zfs", "clone", "-p", "-o", fmt.Sprintf("mountpoint=%s", mountpoint), fmt.Sprintf("%s/%s@%s", s.zfsPool, source, name), fmt.Sprintf("%s/%s", s.zfsPool, dest)).CombinedOutput() if err != nil { s.log.Error("zfs clone failed", log.Ctx{"output": string(output)}) return fmt.Errorf("Failed to clone the filesystem: %s", output) } subvols, err := s.zfsListSubvolumes(source) if err != nil { return err } for _, sub := range subvols { snaps, err := s.zfsListSnapshots(sub) if err != nil { return err } if !shared.StringInSlice(name, snaps) { continue } destSubvol := dest + strings.TrimPrefix(sub, source) mountpoint = shared.VarPath(destSubvol) if dotZfs { mountpoint += ".zfs" } output, err := exec.Command( "zfs", "clone", "-p", "-o", fmt.Sprintf("mountpoint=%s", mountpoint), fmt.Sprintf("%s/%s@%s", s.zfsPool, sub, name), fmt.Sprintf("%s/%s", s.zfsPool, destSubvol)).CombinedOutput() if err != nil { s.log.Error("zfs clone failed", log.Ctx{"output": string(output)}) return fmt.Errorf("Failed to clone the sub-volume: %s", output) } } 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 containersPost(d *Daemon, r *http.Request) Response { shared.LogDebugf("Responding to container create") req := containerPostReq{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } if req.Name == "" { cs, err := dbContainersList(d.db, cTypeRegular) if err != nil { return InternalError(err) } i := 0 for { i++ req.Name = strings.ToLower(petname.Generate(2, "-")) if !shared.StringInSlice(req.Name, cs) { break } if i > 100 { return InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries")) } } shared.LogDebugf("No name provided, creating %s", req.Name) } if req.Devices == nil { req.Devices = shared.Devices{} } if req.Config == nil { req.Config = map[string]string{} } if strings.Contains(req.Name, shared.SnapshotDelimiter) { return BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter)) } switch req.Source.Type { case "image": return createFromImage(d, &req) case "none": return createFromNone(d, &req) case "migration": return createFromMigration(d, &req) case "copy": return createFromCopy(d, &req) default: return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type)) } }
func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*Response, error) { body := shared.Jmap{ "action": action, "timeout": timeout, "force": force} if shared.StringInSlice(string(action), []string{"start", "stop"}) { body["stateful"] = stateful } return c.put(fmt.Sprintf("containers/%s/state", name), body, Async) }
func (c *networkCmd) doNetworkList(config *lxd.Config, args []string) error { var remote string if len(args) > 1 { var name string remote, name = config.ParseRemoteAndContainer(args[1]) if name != "" { return fmt.Errorf(i18n.G("Cannot provide container name to list")) } } else { remote = config.DefaultRemote } client, err := lxd.NewClient(config, remote) if err != nil { return err } networks, err := client.ListNetworks() if err != nil { return err } data := [][]string{} for _, network := range networks { if shared.StringInSlice(network.Type, []string{"loopback", "unknown"}) { continue } strManaged := i18n.G("NO") if network.Managed { strManaged = i18n.G("YES") } strUsedBy := fmt.Sprintf("%d", len(network.UsedBy)) data = append(data, []string{network.Name, network.Type, strManaged, strUsedBy}) } table := tablewriter.NewWriter(os.Stdout) table.SetAutoWrapText(false) table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetRowLine(true) table.SetHeader([]string{ i18n.G("NAME"), i18n.G("TYPE"), i18n.G("MANAGED"), i18n.G("USED BY")}) sort.Sort(byName(data)) table.AppendBulk(data) table.Render() return nil }
func networkInterfaces(routers Routers) ([]string, []string, error) { vethInterfaces := []string{} bridgeInterfaces := []string{} for _, r := range routers { for _, p := range r.Peers { if strings.HasPrefix(p.Interface, "v") { veth := strings.TrimSuffix(strings.TrimSuffix(p.Interface, "-1"), "-2") if !shared.StringInSlice(veth, vethInterfaces) { vethInterfaces = append(vethInterfaces, veth) } } else if strings.HasPrefix(p.Interface, "br") { if !shared.StringInSlice(p.Interface, bridgeInterfaces) { bridgeInterfaces = append(bridgeInterfaces, p.Interface) } } else { return nil, nil, fmt.Errorf("Invalid interface name: %s", p.Interface) } } } return vethInterfaces, bridgeInterfaces, nil }
func networkGetTunnels(config map[string]string) []string { tunnels := []string{} for k, _ := range config { if !strings.HasPrefix(k, "tunnel.") { continue } fields := strings.Split(k, ".") if !shared.StringInSlice(fields[1], tunnels) { tunnels = append(tunnels, fields[1]) } } return tunnels }
// Connect opens an API connection to LXD and returns a high-level // Client wrapper around that connection. func Connect(cfg Config, verifyBridgeConfig bool) (*Client, error) { if err := cfg.Validate(); err != nil { return nil, errors.Trace(err) } remoteID := cfg.Remote.ID() raw, err := newRawClient(cfg.Remote) if err != nil { return nil, errors.Trace(err) } networkAPISupported := false if cfg.Remote.Protocol != SimplestreamsProtocol { status, err := raw.ServerStatus() if err != nil { return nil, errors.Trace(err) } if lxdshared.StringInSlice("network", status.APIExtensions) { networkAPISupported = true } } var bridgeName string if remoteID == remoteIDForLocal && verifyBridgeConfig { // If this is the LXD provider on the localhost, let's do an extra check to // make sure the default profile has a correctly configured bridge, and // which one is it. bridgeName, err = verifyDefaultProfileBridgeConfig(raw, networkAPISupported) if err != nil { return nil, errors.Trace(err) } } conn := &Client{ serverConfigClient: &serverConfigClient{raw}, certClient: &certClient{raw}, profileClient: &profileClient{raw}, instanceClient: &instanceClient{raw, remoteID}, imageClient: &imageClient{raw, connectToRaw}, networkClient: &networkClient{raw, networkAPISupported}, baseURL: raw.BaseURL, defaultProfileBridgeName: bridgeName, } return conn, nil }
func (c *deleteCmd) doDelete(d *lxd.Client, name string) error { if c.interactive { 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.")) } } resp, err := d.Delete(name) if err != nil { return err } return d.WaitForSuccess(resp.Operation) }
func patchInvalidProfileNames(name string, d *Daemon) error { profiles, err := dbProfiles(d.db) if err != nil { return err } for _, profile := range profiles { if strings.Contains(profile, "/") || shared.StringInSlice(profile, []string{".", ".."}) { shared.LogInfo("Removing unreachable profile (invalid name)", log.Ctx{"name": profile}) err := dbProfileDelete(d.db, profile) if err != nil { return err } } } return nil }
func networkPost(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] req := shared.NetworkConfig{} // Parse the request err := json.NewDecoder(r.Body).Decode(&req) if err != nil { return BadRequest(err) } // Get the existing network n, err := networkLoadByName(d, name) if err != nil { return NotFound } // Sanity checks if req.Name == "" { return BadRequest(fmt.Errorf("No name provided")) } err = networkValidName(req.Name) if err != nil { return BadRequest(err) } // Check that the name isn't already in use networks, err := networkGetInterfaces(d) if err != nil { return InternalError(err) } if shared.StringInSlice(req.Name, networks) { return Conflict } // Rename it err = n.Rename(req.Name) if err != nil { return SmartError(err) } return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, req.Name)) }
func profilesPost(d *Daemon, r *http.Request) Response { req := profilesPostReq{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } // Sanity checks if req.Name == "" { return BadRequest(fmt.Errorf("No name provided")) } _, profile, _ := dbProfileGet(d.db, req.Name) if profile != nil { return BadRequest(fmt.Errorf("The profile already exists")) } if strings.Contains(req.Name, "/") { return BadRequest(fmt.Errorf("Profile names may not contain slashes")) } if shared.StringInSlice(req.Name, []string{".", ".."}) { return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name)) } err := containerValidConfig(d, req.Config, true, false) if err != nil { return BadRequest(err) } err = containerValidDevices(req.Devices, true, false) if err != nil { return BadRequest(err) } // Update DB entry _, err = dbProfileCreate(d.db, req.Name, req.Description, req.Config, req.Devices) if err != nil { return InternalError( fmt.Errorf("Error inserting %s into database: %s", req.Name, err)) } return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name)) }
func networkGetInterfaces(d *Daemon) ([]string, error) { networks, err := dbNetworks(d.db) if err != nil { return nil, err } ifaces, err := net.Interfaces() if err != nil { return nil, err } for _, iface := range ifaces { if !shared.StringInSlice(iface.Name, networks) { networks = append(networks, iface.Name) } } return networks, nil }
func patchesApplyAll(d *Daemon) error { appliedPatches, err := dbPatches(d.db) if err != nil { return err } for _, patch := range patches { if shared.StringInSlice(patch.name, appliedPatches) { continue } err := patch.apply(d) if err != nil { return err } } return nil }
// Global functions func storageZFSGetPoolUsers(d *Daemon) ([]string, error) { zfs := storageZfs{} err := zfs.initShared() if err != nil { return []string{}, err } zfsPool, err := d.ConfigValueGet("storage.zfs_pool_name") if err != nil { return []string{}, err } if zfsPool == "" { return []string{}, nil } zfs.zfsPool = zfsPool subvols, err := zfs.zfsListSubvolumes("") if err != nil { return []string{}, err } exceptions := []string{ "containers", "images", "deleted", "deleted/containers", "deleted/images"} users := []string{} for _, subvol := range subvols { if shared.StringInSlice(subvol, exceptions) { continue } users = append(users, subvol) } return users, nil }
func isOnBridge(c container, bridge string) bool { for _, device := range c.ExpandedDevices() { if device["type"] != "nic" { continue } if !shared.StringInSlice(device["nictype"], []string{"bridged", "macvlan"}) { continue } if device["parent"] == "" { continue } if device["parent"] == bridge { return true } } return false }
func networkIsInUse(c container, name string) bool { for _, d := range c.ExpandedDevices() { if d["type"] != "nic" { continue } if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan"}) { continue } if d["parent"] == "" { continue } if d["parent"] == name { return true } } return false }
func eventSend(eventType string, eventMessage interface{}) error { event := shared.Jmap{} event["type"] = eventType event["timestamp"] = time.Now() event["metadata"] = eventMessage body, err := json.Marshal(event) if err != nil { return err } eventsLock.Lock() listeners := eventListeners eventsLock.Unlock() for _, listener := range listeners { if !shared.StringInSlice(eventType, listener.messageTypes) { continue } go func(listener *eventListener, body []byte) { listener.msgLock.Lock() err = listener.connection.WriteMessage(websocket.TextMessage, body) listener.msgLock.Unlock() if err != nil { listener.connection.Close() listener.active <- false eventsLock.Lock() delete(eventListeners, listener.id) eventsLock.Unlock() shared.Debugf("Disconnected events listener: %s", listener.id) } }(listener, body) } return nil }
func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { if cInfo.IsActive() && cState != nil && cState.Network != nil { ipv6s := []string{} for netName, net := range cState.Network { if net.Type == "loopback" { continue } for _, addr := range net.Addresses { if shared.StringInSlice(addr.Scope, []string{"link", "local"}) { continue } if addr.Family == "inet6" { ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", addr.Address, netName)) } } } return strings.Join(ipv6s, "\n") } else { return "" } }
func (s *storageZfs) zfsSnapshotRestore(path string, name string) error { output, err := tryExec( "zfs", "rollback", fmt.Sprintf("%s/%s@%s", s.zfsPool, path, name)) if err != nil { s.log.Error("zfs rollback failed", log.Ctx{"output": string(output)}) return fmt.Errorf("Failed to restore ZFS snapshot: %s", output) } subvols, err := s.zfsListSubvolumes(path) if err != nil { return err } for _, sub := range subvols { snaps, err := s.zfsListSnapshots(sub) if err != nil { return err } if !shared.StringInSlice(name, snaps) { continue } output, err := tryExec( "zfs", "rollback", fmt.Sprintf("%s/%s@%s", s.zfsPool, sub, name)) if err != nil { s.log.Error("zfs rollback failed", log.Ctx{"output": string(output)}) return fmt.Errorf("Failed to restore ZFS sub-volume snapshot: %s", output) } } return nil }