The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3263
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === This branch consists of a couple of separate commits: 1) make cmdInit testable by extracting its global variables and "boundaries", so most of its logic can be run in isolation in a unit test. A sample unit test has been added as exemplification, I plan to add more as I add the logic for the new "pre-seed" feature as per #2834. 2) extract a few inline functions into methods, for easier reading of the core logic. The commit messages of the individual commits provide a bit of extra narrative. I'm bundling these two changes together for convenience, but I'm happy to split them in separate branches if more appropriate.
From ffa4a6ccc5cfdeb138cea154178806422d2fc048 Mon Sep 17 00:00:00 2001 From: Free Ekanayaka <[email protected]> Date: Fri, 28 Apr 2017 14:48:02 +0200 Subject: [PATCH 1/2] Make the log cmdInit unit-testable This branch essentially wires into cmdInit the logic previously introduced in the shared/cmd package. In order to be able to run the underlying logic in isolation a few globals (such as command line arguments) have been replaced with explicit parameters, that get passed to the CmdInit structure. An initial unit test has been added, mostly for exemplification, and I plan to keep working on the logic and on the tests in order to implement #2834. Signed-off-by: Free Ekanayaka <[email protected]> --- lxd/main_init.go | 235 +++++++++++++++++++------------------------------- lxd/main_init_test.go | 42 +++++++++ lxd/main_test.go | 4 +- 3 files changed, 135 insertions(+), 146 deletions(-) create mode 100644 lxd/main_init_test.go diff --git a/lxd/main_init.go b/lxd/main_init.go index 72fedd9..bc4dc31 100644 --- a/lxd/main_init.go +++ b/lxd/main_init.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "fmt" "net" "os" @@ -14,10 +13,33 @@ import ( "github.com/lxc/lxd/client" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" + "github.com/lxc/lxd/shared/cmd" "github.com/lxc/lxd/shared/logger" ) -func cmdInit() error { +// CmdInitArgs holds command line arguments for the "lxd init" command. +type CmdInitArgs struct { + Auto bool + StorageBackend string + StorageCreateDevice string + StorageCreateLoop int64 + StorageDataset string + NetworkPort int64 + NetworkAddress string + TrustPassword string +} + +// CmdInit implements the "lxd init" command line. +type CmdInit struct { + Context *cmd.Context + Args *CmdInitArgs + RunningInUserns bool + SocketPath string + PasswordReader func(int) ([]byte, error) +} + +// Run triggers the execution of the init command. +func (cmd *CmdInit) Run() error { var defaultPrivileged int // controls whether we set security.privileged=true var storageSetup bool // == supportedStoragePoolDrivers var storageBackend string // == supportedStoragePoolDrivers @@ -37,7 +59,7 @@ func cmdInit() error { // Detect userns defaultPrivileged = -1 - runningInUserns = shared.RunningInUserNS() + runningInUserns = cmd.RunningInUserns imagesAutoUpdate = true backendsAvailable := []string{"dir"} @@ -63,107 +85,8 @@ func cmdInit() error { backendsAvailable = append(backendsAvailable, driver) } - reader := bufio.NewReader(os.Stdin) - - askBool := func(question string, default_ string) bool { - for { - fmt.Printf(question) - input, _ := reader.ReadString('\n') - input = strings.TrimSuffix(input, "\n") - if input == "" { - input = default_ - } - if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) { - return true - } else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) { - return false - } - - fmt.Printf("Invalid input, try again.\n\n") - } - } - - askChoice := func(question string, choices []string, default_ string) string { - for { - fmt.Printf(question) - input, _ := reader.ReadString('\n') - input = strings.TrimSuffix(input, "\n") - if input == "" { - input = default_ - } - if shared.StringInSlice(input, choices) { - return input - } - - fmt.Printf("Invalid input, try again.\n\n") - } - } - - askInt := func(question string, min int64, max int64, default_ string) int64 { - for { - fmt.Printf(question) - input, _ := reader.ReadString('\n') - input = strings.TrimSuffix(input, "\n") - if input == "" { - input = default_ - } - intInput, err := strconv.ParseInt(input, 10, 64) - - if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) { - return intInput - } - - fmt.Printf("Invalid input, try again.\n\n") - } - } - - askString := func(question string, default_ string, validate func(string) error) string { - for { - fmt.Printf(question) - input, _ := reader.ReadString('\n') - input = strings.TrimSuffix(input, "\n") - if input == "" { - input = default_ - } - if validate != nil { - result := validate(input) - if result != nil { - fmt.Printf("Invalid input: %s\n\n", result) - continue - } - } - if len(input) != 0 { - return input - } - - fmt.Printf("Invalid input, try again.\n\n") - } - } - - askPassword := func(question string) string { - for { - fmt.Printf(question) - pwd, _ := terminal.ReadPassword(0) - fmt.Printf("\n") - inFirst := string(pwd) - inFirst = strings.TrimSuffix(inFirst, "\n") - - fmt.Printf("Again: ") - pwd, _ = terminal.ReadPassword(0) - fmt.Printf("\n") - inSecond := string(pwd) - inSecond = strings.TrimSuffix(inSecond, "\n") - - if inFirst == inSecond { - return inFirst - } - - fmt.Printf("Invalid input, try again.\n\n") - } - } - // Connect to LXD - c, err := lxd.ConnectLXDUnix("", nil) + c, err := lxd.ConnectLXDUnix(cmd.SocketPath, nil) if err != nil { return fmt.Errorf("Unable to talk to LXD: %s", err) } @@ -240,46 +163,46 @@ func cmdInit() error { return err } - if *argAuto { - if *argStorageBackend == "" { - *argStorageBackend = "dir" + if cmd.Args.Auto { + if cmd.Args.StorageBackend == "" { + cmd.Args.StorageBackend = "dir" } // Do a bunch of sanity checks - if !shared.StringInSlice(*argStorageBackend, supportedStoragePoolDrivers) { - return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", *argStorageBackend) + if !shared.StringInSlice(cmd.Args.StorageBackend, supportedStoragePoolDrivers) { + return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", cmd.Args.StorageBackend) } - if !shared.StringInSlice(*argStorageBackend, backendsAvailable) { - return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", *argStorageBackend) + if !shared.StringInSlice(cmd.Args.StorageBackend, backendsAvailable) { + return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", cmd.Args.StorageBackend) } - if *argStorageBackend == "dir" { - if *argStorageCreateLoop != -1 || *argStorageCreateDevice != "" || *argStorageDataset != "" { + if cmd.Args.StorageBackend == "dir" { + if cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageDataset != "" { return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.") } } else { - if *argStorageCreateLoop != -1 && *argStorageCreateDevice != "" { + if cmd.Args.StorageCreateLoop != -1 && cmd.Args.StorageCreateDevice != "" { return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified.") } } - if *argNetworkAddress == "" { - if *argNetworkPort != -1 { + if cmd.Args.NetworkAddress == "" { + if cmd.Args.NetworkPort != -1 { return fmt.Errorf("--network-port cannot be used without --network-address.") } - if *argTrustPassword != "" { + if cmd.Args.TrustPassword != "" { return fmt.Errorf("--trust-password cannot be used without --network-address.") } } - storageBackend = *argStorageBackend - storageLoopSize = *argStorageCreateLoop - storageDevice = *argStorageCreateDevice - storageDataset = *argStorageDataset - networkAddress = *argNetworkAddress - networkPort = *argNetworkPort - trustPassword = *argTrustPassword + storageBackend = cmd.Args.StorageBackend + storageLoopSize = cmd.Args.StorageCreateLoop + storageDevice = cmd.Args.StorageCreateDevice + storageDataset = cmd.Args.StorageDataset + networkAddress = cmd.Args.NetworkAddress + networkPort = cmd.Args.NetworkPort + trustPassword = cmd.Args.TrustPassword storagePool = "default" // FIXME: Allow to configure multiple storage pools on auto init @@ -288,7 +211,7 @@ func cmdInit() error { storageSetup = true } } else { - if *argStorageBackend != "" || *argStorageCreateDevice != "" || *argStorageCreateLoop != -1 || *argStorageDataset != "" || *argNetworkAddress != "" || *argNetworkPort != -1 || *argTrustPassword != "" { + if cmd.Args.StorageBackend != "" || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageDataset != "" || cmd.Args.NetworkAddress != "" || cmd.Args.NetworkPort != -1 || cmd.Args.TrustPassword != "" { return fmt.Errorf("Init configuration is only valid with --auto") } @@ -300,9 +223,9 @@ func cmdInit() error { // User chose an already existing storage pool name. Ask him // again if he still wants to create one. askForStorageAgain: - storageSetup = askBool("Do you want to configure a new storage pool (yes/no) [default=yes]? ", "yes") + storageSetup = cmd.Context.AskBool("Do you want to configure a new storage pool (yes/no) [default=yes]? ", "yes") if storageSetup { - storagePool = askString("Name of the new storage pool [default=default]: ", "default", nil) + storagePool = cmd.Context.AskString("Name of the new storage pool [default=default]: ", "default", nil) if shared.StringInSlice(storagePool, pools) { fmt.Printf("The requested storage pool \"%s\" already exists. Please choose another name.\n", storagePool) // Ask the user again if hew wants to create a @@ -310,7 +233,7 @@ func cmdInit() error { goto askForStorageAgain } - storageBackend = askChoice(fmt.Sprintf("Name of the storage backend to use (%s) [default=%s]: ", strings.Join(backendsAvailable, ", "), defaultStorage), supportedStoragePoolDrivers, defaultStorage) + storageBackend = cmd.Context.AskChoice(fmt.Sprintf("Name of the storage backend to use (%s) [default=%s]: ", strings.Join(backendsAvailable, ", "), defaultStorage), supportedStoragePoolDrivers, defaultStorage) if !shared.StringInSlice(storageBackend, supportedStoragePoolDrivers) { return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend) @@ -324,19 +247,19 @@ func cmdInit() error { if storageSetup && storageBackend != "dir" { storageLoopSize = -1 q := fmt.Sprintf("Create a new %s pool (yes/no) [default=yes]? ", strings.ToUpper(storageBackend)) - if askBool(q, "yes") { - if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") { + if cmd.Context.AskBool(q, "yes") { + if cmd.Context.AskBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") { deviceExists := func(path string) error { if !shared.IsBlockdevPath(path) { return fmt.Errorf("'%s' is not a block device", path) } return nil } - storageDevice = askString("Path to the existing block device: ", "", deviceExists) + storageDevice = cmd.Context.AskString("Path to the existing block device: ", "", deviceExists) } else { backingFs, err := filesystemDetect(shared.VarPath()) if err == nil && storageBackend == "btrfs" && backingFs == "btrfs" { - if askBool("Would you like to create a new subvolume for the BTRFS storage pool (yes/no) [default=yes]: ", "yes") { + if cmd.Context.AskBool("Would you like to create a new subvolume for the BTRFS storage pool (yes/no) [default=yes]: ", "yes") { storageDataset = shared.VarPath("storage-pools", storagePool) } } else { @@ -357,12 +280,12 @@ func cmdInit() error { } q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%dGB]: ", def) - storageLoopSize = askInt(q, 1, -1, fmt.Sprintf("%d", def)) + storageLoopSize = cmd.Context.AskInt(q, 1, -1, fmt.Sprintf("%d", def)) } } } else { q := fmt.Sprintf("Name of the existing %s pool or dataset: ", strings.ToUpper(storageBackend)) - storageDataset = askString(q, "", nil) + storageDataset = cmd.Context.AskString(q, "", nil) } } @@ -385,14 +308,14 @@ in theory attack their parent container and gain more privileges than they otherwise would. `) - if askBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") { + if cmd.Context.AskBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") { defaultPrivileged = 1 } else { defaultPrivileged = 0 } } - if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") { + if cmd.Context.AskBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") { isIPAddress := func(s string) error { if s != "all" && net.ParseIP(s) == nil { return fmt.Errorf("'%s' is not an IP address", s) @@ -400,7 +323,7 @@ they otherwise would. return nil } - networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress) + networkAddress = cmd.Context.AskString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress) if networkAddress == "all" { networkAddress = "::" } @@ -408,18 +331,18 @@ they otherwise would. if net.ParseIP(networkAddress).To4() == nil { networkAddress = fmt.Sprintf("[%s]", networkAddress) } - networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443") - trustPassword = askPassword("Trust password for new clients: ") + networkPort = cmd.Context.AskInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443") + trustPassword = cmd.Context.AskPassword("Trust password for new clients: ", cmd.PasswordReader) } - if !askBool("Would you like stale cached images to be updated automatically (yes/no) [default=yes]? ", "yes") { + if !cmd.Context.AskBool("Would you like stale cached images to be updated automatically (yes/no) [default=yes]? ", "yes") { imagesAutoUpdate = false } askForNetworkAgain: bridgeName = "" - if askBool("Would you like to create a new network bridge (yes/no) [default=yes]? ", "yes") { - bridgeName = askString("What should the new bridge be called [default=lxdbr0]? ", "lxdbr0", networkValidName) + if cmd.Context.AskBool("Would you like to create a new network bridge (yes/no) [default=yes]? ", "yes") { + bridgeName = cmd.Context.AskString("What should the new bridge be called [default=lxdbr0]? ", "lxdbr0", networkValidName) _, _, err := c.GetNetwork(bridgeName) if err == nil { fmt.Printf("The requested network bridge \"%s\" already exists. Please choose another name.\n", bridgeName) @@ -428,7 +351,7 @@ they otherwise would. goto askForNetworkAgain } - bridgeIPv4 = askString("What IPv4 address should be used (CIDR subnet notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { + bridgeIPv4 = cmd.Context.AskString("What IPv4 address should be used (CIDR subnet notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { if shared.StringInSlice(value, []string{"auto", "none"}) { return nil } @@ -436,10 +359,10 @@ they otherwise would. }) if !shared.StringInSlice(bridgeIPv4, []string{"auto", "none"}) { - bridgeIPv4Nat = askBool("Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]? ", "yes") + bridgeIPv4Nat = cmd.Context.AskBool("Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]? ", "yes") } - bridgeIPv6 = askString("What IPv6 address should be used (CIDR subnet notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { + bridgeIPv6 = cmd.Context.AskString("What IPv6 address should be used (CIDR subnet notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { if shared.StringInSlice(value, []string{"auto", "none"}) { return nil } @@ -447,7 +370,7 @@ they otherwise would. }) if !shared.StringInSlice(bridgeIPv6, []string{"auto", "none"}) { - bridgeIPv6Nat = askBool("Would you like LXD to NAT IPv6 traffic on your bridge? [default=yes]? ", "yes") + bridgeIPv6Nat = cmd.Context.AskBool("Would you like LXD to NAT IPv6 traffic on your bridge? [default=yes]? ", "yes") } } } @@ -640,3 +563,25 @@ they otherwise would. fmt.Printf("LXD has been successfully configured.\n") return nil } + +func cmdInit() error { + context := cmd.NewContext(os.Stdin, os.Stdout, os.Stderr) + args := &CmdInitArgs{ + Auto: *argAuto, + StorageBackend: *argStorageBackend, + StorageCreateDevice: *argStorageCreateDevice, + StorageCreateLoop: *argStorageCreateLoop, + StorageDataset: *argStorageDataset, + NetworkPort: *argNetworkPort, + NetworkAddress: *argNetworkAddress, + TrustPassword: *argTrustPassword, + } + command := &CmdInit{ + Context: context, + Args: args, + RunningInUserns: shared.RunningInUserNS(), + SocketPath: "", + PasswordReader: terminal.ReadPassword, + } + return command.Run() +} diff --git a/lxd/main_init_test.go b/lxd/main_init_test.go new file mode 100644 index 0000000..0c4b0bd --- /dev/null +++ b/lxd/main_init_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "testing" + + "github.com/lxc/lxd/shared/cmd" + "github.com/stretchr/testify/suite" +) + +type cmdInitTestSuite struct { + lxdTestSuite + context *cmd.Context + args *CmdInitArgs + command *CmdInit +} + +func (suite *cmdInitTestSuite) SetupSuite() { + suite.lxdTestSuite.SetupSuite() + suite.context = cmd.NewMemoryContext(cmd.NewMemoryStreams("")) + suite.args = &CmdInitArgs{ + NetworkPort: -1, + StorageCreateLoop: -1, + } + suite.command = &CmdInit{ + Context: suite.context, + Args: suite.args, + RunningInUserns: false, + SocketPath: suite.d.UnixSocket.Socket.Addr().String(), + } +} + +// If any argument intended for --auto is passed in interactive mode, an +// error is returned. +func (suite *cmdInitTestSuite) TestCmdInit_InteractiveWithAutoArgs() { + suite.args.NetworkPort = 9999 + err := suite.command.Run() + suite.Req.Equal(err.Error(), "Init configuration is only valid with --auto") +} + +func TestCmdInitTestSuite(t *testing.T) { + suite.Run(t, new(cmdInitTestSuite)) +} diff --git a/lxd/main_test.go b/lxd/main_test.go index 732d369..a6828c8 100644 --- a/lxd/main_test.go +++ b/lxd/main_test.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "io/ioutil" "os" "testing" @@ -13,7 +14,8 @@ import ( func mockStartDaemon() (*Daemon, error) { d := &Daemon{ - MockMode: true, + MockMode: true, + tlsConfig: &tls.Config{}, } if err := d.Init(); err != nil { From eecbdcb3ac98e07b30d0014ef562b5ca711e1074 Mon Sep 17 00:00:00 2001 From: Free Ekanayaka <[email protected]> Date: Tue, 2 May 2017 07:40:32 +0200 Subject: [PATCH 2/2] Move state-changing inline functions to own methods This is a mechanical commit just moving the various inline helpers functions of CmdInit.Run to separate methods, in order to make Run() itself a bit slimmer and easier to read with a brief eye look. There's no logic change. Signed-off-by: Free Ekanayaka <[email protected]> --- lxd/main_init.go | 148 +++++++++++++++++++++++++++---------------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/lxd/main_init.go b/lxd/main_init.go index bc4dc31..89f25ab 100644 --- a/lxd/main_init.go +++ b/lxd/main_init.go @@ -91,71 +91,6 @@ func (cmd *CmdInit) Run() error { return fmt.Errorf("Unable to talk to LXD: %s", err) } - setServerConfig := func(key string, value string) error { - server, etag, err := c.GetServer() - if err != nil { - return err - } - - if server.Config == nil { - server.Config = map[string]interface{}{} - } - - server.Config[key] = value - - err = c.UpdateServer(server.Writable(), etag) - if err != nil { - return err - } - - return nil - } - - profileDeviceAdd := func(profileName string, deviceName string, deviceConfig map[string]string) error { - profile, etag, err := c.GetProfile(profileName) - if err != nil { - return err - } - - if profile.Devices == nil { - profile.Devices = map[string]map[string]string{} - } - - _, ok := profile.Devices[deviceName] - if ok { - return fmt.Errorf("Device already exists: %s", deviceName) - } - - profile.Devices[deviceName] = deviceConfig - - err = c.UpdateProfile(profileName, profile.Writable(), etag) - if err != nil { - return err - } - - return nil - } - - setProfileConfigItem := func(profileName string, key string, value string) error { - profile, etag, err := c.GetProfile(profileName) - if err != nil { - return err - } - - if profile.Config == nil { - profile.Config = map[string]string{} - } - - profile.Config[key] = value - - err = c.UpdateProfile(profileName, profile.Writable(), etag) - if err != nil { - return err - } - - return nil - } - pools, err := c.GetStoragePoolNames() if err != nil { // We should consider this fatal since this means @@ -378,7 +313,7 @@ they otherwise would. if storageSetup { // Unset core.https_address and core.trust_password for _, key := range []string{"core.https_address", "core.trust_password"} { - err = setServerConfig(key, "") + err = cmd.setServerConfig(c, key, "") if err != nil { return err } @@ -468,7 +403,7 @@ they otherwise would. "pool": storagePool, } - err = profileDeviceAdd("default", "root", props) + err = cmd.profileDeviceAdd(c, "default", "root", props) if err != nil { return err } @@ -483,12 +418,12 @@ they otherwise would. } if defaultPrivileged == 0 { - err = setProfileConfigItem("default", "security.privileged", "") + err = cmd.setProfileConfigItem(c, "default", "security.privileged", "") if err != nil { return err } } else if defaultPrivileged == 1 { - err = setProfileConfigItem("default", "security.privileged", "true") + err = cmd.setProfileConfigItem(c, "default", "security.privileged", "true") if err != nil { } } @@ -500,26 +435,26 @@ they otherwise would. } if val, ok := ss.Config["images.auto_update_interval"]; ok && val == "0" { - err = setServerConfig("images.auto_update_interval", "") + err = cmd.setServerConfig(c, "images.auto_update_interval", "") if err != nil { return err } } } else { - err = setServerConfig("images.auto_update_interval", "0") + err = cmd.setServerConfig(c, "images.auto_update_interval", "0") if err != nil { return err } } if networkAddress != "" { - err = setServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort)) + err = cmd.setServerConfig(c, "core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort)) if err != nil { return err } if trustPassword != "" { - err = setServerConfig("core.trust_password", trustPassword) + err = cmd.setServerConfig(c, "core.trust_password", trustPassword) if err != nil { return err } @@ -554,7 +489,7 @@ they otherwise would. "parent": bridgeName, } - err = profileDeviceAdd("default", "eth0", props) + err = cmd.profileDeviceAdd(c, "default", "eth0", props) if err != nil { return err } @@ -564,6 +499,71 @@ they otherwise would. return nil } +func (cmd *CmdInit) setServerConfig(c lxd.ContainerServer, key string, value string) error { + server, etag, err := c.GetServer() + if err != nil { + return err + } + + if server.Config == nil { + server.Config = map[string]interface{}{} + } + + server.Config[key] = value + + err = c.UpdateServer(server.Writable(), etag) + if err != nil { + return err + } + + return nil +} + +func (cmd *CmdInit) profileDeviceAdd(c lxd.ContainerServer, profileName string, deviceName string, deviceConfig map[string]string) error { + profile, etag, err := c.GetProfile(profileName) + if err != nil { + return err + } + + if profile.Devices == nil { + profile.Devices = map[string]map[string]string{} + } + + _, ok := profile.Devices[deviceName] + if ok { + return fmt.Errorf("Device already exists: %s", deviceName) + } + + profile.Devices[deviceName] = deviceConfig + + err = c.UpdateProfile(profileName, profile.Writable(), etag) + if err != nil { + return err + } + + return nil +} + +func (cmd *CmdInit) setProfileConfigItem(c lxd.ContainerServer, profileName string, key string, value string) error { + profile, etag, err := c.GetProfile(profileName) + if err != nil { + return err + } + + if profile.Config == nil { + profile.Config = map[string]string{} + } + + profile.Config[key] = value + + err = c.UpdateProfile(profileName, profile.Writable(), etag) + if err != nil { + return err + } + + return nil +} + func cmdInit() error { context := cmd.NewContext(os.Stdin, os.Stdout, os.Stderr) args := &CmdInitArgs{
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
