[lxc-devel] [lxd/master] [TESTING] New join api

freeekanayaka on Github lxc-bot at linuxcontainers.org
Thu Jun 14 10:00:54 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180614/e4e15443/attachment.bin>
-------------- next part --------------
From 5ee9d0a532641a4f87aeee4d8263413845bc9738 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Thu, 14 Jun 2018 08:31:37 +0000
Subject: [PATCH 1/3] Extract cmdInit.ApplyConfig into a separete
 initApplyConfig function

No logic change, just moving around code.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/init.go      | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/main_init.go | 372 +----------------------------------------------------
 2 files changed, 384 insertions(+), 371 deletions(-)
 create mode 100644 lxd/init.go

diff --git a/lxd/init.go b/lxd/init.go
new file mode 100644
index 000000000..873faa00d
--- /dev/null
+++ b/lxd/init.go
@@ -0,0 +1,383 @@
+package main
+
+import (
+	"fmt"
+
+	lxd "github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/cluster"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/pkg/errors"
+)
+
+type initData struct {
+	api.ServerPut `yaml:",inline"`
+	Cluster       *initDataCluster       `json:"cluster" yaml:"cluster"`
+	Networks      []api.NetworksPost     `json:"networks" yaml:"networks"`
+	StoragePools  []api.StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"`
+	Profiles      []api.ProfilesPost     `json:"profiles" yaml:"profiles"`
+}
+
+type initDataCluster struct {
+	api.ClusterPut  `yaml:",inline"`
+	ClusterPassword string `json:"cluster_password" yaml:"cluster_password"`
+}
+
+// Helper to initialize a LXD instance using the definitions from an initData
+// object.
+//
+// It's used both by the 'lxd init' command and by the PUT /1.0/cluster API.
+func initApplyConfig(d lxd.ContainerServer, config initData) error {
+	// Handle reverts
+	revert := true
+	reverts := []func(){}
+	defer func() {
+		if !revert {
+			return
+		}
+
+		// Lets undo things in reverse order
+		for i := len(reverts) - 1; i >= 0; i-- {
+			reverts[i]()
+		}
+	}()
+
+	// Apply server configuration
+	if config.Config != nil && len(config.Config) > 0 {
+		// Get current config
+		currentServer, etag, err := d.GetServer()
+		if err != nil {
+			return errors.Wrap(err, "Failed to retrieve current server configuration")
+		}
+
+		// Setup reverter
+		reverts = append(reverts, func() {
+			d.UpdateServer(currentServer.Writable(), "")
+		})
+
+		// Prepare the update
+		newServer := api.ServerPut{}
+		err = shared.DeepCopy(currentServer.Writable(), &newServer)
+		if err != nil {
+			return errors.Wrap(err, "Failed to copy server configuration")
+		}
+
+		for k, v := range config.Config {
+			newServer.Config[k] = fmt.Sprintf("%v", v)
+		}
+
+		// Apply it
+		err = d.UpdateServer(newServer, etag)
+		if err != nil {
+			return errors.Wrap(err, "Failed to update server configuration")
+		}
+	}
+
+	// Apply network configuration
+	if config.Networks != nil && len(config.Networks) > 0 {
+		// Get the list of networks
+		networkNames, err := d.GetNetworkNames()
+		if err != nil {
+			return errors.Wrap(err, "Failed to retrieve list of networks")
+		}
+
+		// Network creator
+		createNetwork := func(network api.NetworksPost) error {
+			// Create the network if doesn't exist
+			err := d.CreateNetwork(network)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to create network '%s'", network.Name)
+			}
+
+			// Setup reverter
+			reverts = append(reverts, func() {
+				d.DeleteNetwork(network.Name)
+			})
+
+			return nil
+		}
+
+		// Network updater
+		updateNetwork := func(network api.NetworksPost) error {
+			// Get the current network
+			currentNetwork, etag, err := d.GetNetwork(network.Name)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to retrieve current network '%s'", network.Name)
+			}
+
+			// Setup reverter
+			reverts = append(reverts, func() {
+				d.UpdateNetwork(currentNetwork.Name, currentNetwork.Writable(), "")
+			})
+
+			// Prepare the update
+			newNetwork := api.NetworkPut{}
+			err = shared.DeepCopy(currentNetwork.Writable(), &newNetwork)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to copy configuration of network '%s'", network.Name)
+			}
+
+			// Description override
+			if network.Description != "" {
+				newNetwork.Description = network.Description
+			}
+
+			// Config overrides
+			for k, v := range network.Config {
+				newNetwork.Config[k] = fmt.Sprintf("%v", v)
+			}
+
+			// Apply it
+			err = d.UpdateNetwork(currentNetwork.Name, newNetwork, etag)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to update network '%s'", network.Name)
+			}
+
+			return nil
+		}
+
+		for _, network := range config.Networks {
+			// New network
+			if !shared.StringInSlice(network.Name, networkNames) {
+				err := createNetwork(network)
+				if err != nil {
+					return err
+				}
+
+				continue
+			}
+
+			// Existing network
+			err := updateNetwork(network)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Apply storage configuration
+	if config.StoragePools != nil && len(config.StoragePools) > 0 {
+		// Get the list of storagePools
+		storagePoolNames, err := d.GetStoragePoolNames()
+		if err != nil {
+			return errors.Wrap(err, "Failed to retrieve list of storage pools")
+		}
+
+		// StoragePool creator
+		createStoragePool := func(storagePool api.StoragePoolsPost) error {
+			// Create the storagePool if doesn't exist
+			err := d.CreateStoragePool(storagePool)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to create storage pool '%s'", storagePool.Name)
+			}
+
+			// Setup reverter
+			reverts = append(reverts, func() {
+				d.DeleteStoragePool(storagePool.Name)
+			})
+
+			return nil
+		}
+
+		// StoragePool updater
+		updateStoragePool := func(storagePool api.StoragePoolsPost) error {
+			// Get the current storagePool
+			currentStoragePool, etag, err := d.GetStoragePool(storagePool.Name)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to retrieve current storage pool '%s'", storagePool.Name)
+			}
+
+			// Sanity check
+			if currentStoragePool.Driver != storagePool.Driver {
+				return fmt.Errorf("Storage pool '%s' is of type '%s' instead of '%s'", currentStoragePool.Name, currentStoragePool.Driver, storagePool.Driver)
+			}
+
+			// Setup reverter
+			reverts = append(reverts, func() {
+				d.UpdateStoragePool(currentStoragePool.Name, currentStoragePool.Writable(), "")
+			})
+
+			// Prepare the update
+			newStoragePool := api.StoragePoolPut{}
+			err = shared.DeepCopy(currentStoragePool.Writable(), &newStoragePool)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to copy configuration of storage pool '%s'", storagePool.Name)
+			}
+
+			// Description override
+			if storagePool.Description != "" {
+				newStoragePool.Description = storagePool.Description
+			}
+
+			// Config overrides
+			for k, v := range storagePool.Config {
+				newStoragePool.Config[k] = fmt.Sprintf("%v", v)
+			}
+
+			// Apply it
+			err = d.UpdateStoragePool(currentStoragePool.Name, newStoragePool, etag)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to update storage pool '%s'", storagePool.Name)
+			}
+
+			return nil
+		}
+
+		for _, storagePool := range config.StoragePools {
+			// New storagePool
+			if !shared.StringInSlice(storagePool.Name, storagePoolNames) {
+				err := createStoragePool(storagePool)
+				if err != nil {
+					return err
+				}
+
+				continue
+			}
+
+			// Existing storagePool
+			err := updateStoragePool(storagePool)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Apply profile configuration
+	if config.Profiles != nil && len(config.Profiles) > 0 {
+		// Get the list of profiles
+		profileNames, err := d.GetProfileNames()
+		if err != nil {
+			return errors.Wrap(err, "Failed to retrieve list of profiles")
+		}
+
+		// Profile creator
+		createProfile := func(profile api.ProfilesPost) error {
+			// Create the profile if doesn't exist
+			err := d.CreateProfile(profile)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to create profile '%s'", profile.Name)
+			}
+
+			// Setup reverter
+			reverts = append(reverts, func() {
+				d.DeleteProfile(profile.Name)
+			})
+
+			return nil
+		}
+
+		// Profile updater
+		updateProfile := func(profile api.ProfilesPost) error {
+			// Get the current profile
+			currentProfile, etag, err := d.GetProfile(profile.Name)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to retrieve current profile '%s'", profile.Name)
+			}
+
+			// Setup reverter
+			reverts = append(reverts, func() {
+				d.UpdateProfile(currentProfile.Name, currentProfile.Writable(), "")
+			})
+
+			// Prepare the update
+			newProfile := api.ProfilePut{}
+			err = shared.DeepCopy(currentProfile.Writable(), &newProfile)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to copy configuration of profile '%s'", profile.Name)
+			}
+
+			// Description override
+			if profile.Description != "" {
+				newProfile.Description = profile.Description
+			}
+
+			// Config overrides
+			for k, v := range profile.Config {
+				newProfile.Config[k] = fmt.Sprintf("%v", v)
+			}
+
+			// Device overrides
+			for k, v := range profile.Devices {
+				// New device
+				_, ok := newProfile.Devices[k]
+				if !ok {
+					newProfile.Devices[k] = v
+					continue
+				}
+
+				// Existing device
+				for configKey, configValue := range v {
+					newProfile.Devices[k][configKey] = fmt.Sprintf("%v", configValue)
+				}
+			}
+
+			// Apply it
+			err = d.UpdateProfile(currentProfile.Name, newProfile, etag)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to update profile '%s'", profile.Name)
+			}
+
+			return nil
+		}
+
+		for _, profile := range config.Profiles {
+			// New profile
+			if !shared.StringInSlice(profile.Name, profileNames) {
+				err := createProfile(profile)
+				if err != nil {
+					return err
+				}
+
+				continue
+			}
+
+			// Existing profile
+			err := updateProfile(profile)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Apply clustering configuration
+	if config.Cluster != nil && config.Cluster.Enabled {
+		// Get the current cluster configuration
+		currentCluster, etag, err := d.GetCluster()
+		if err != nil {
+			return errors.Wrap(err, "Failed to retrieve current cluster config")
+		}
+
+		// Check if already enabled
+		if !currentCluster.Enabled {
+			// Setup trust relationship
+			if config.Cluster.ClusterAddress != "" && config.Cluster.ClusterPassword != "" {
+				// Get our certificate
+				serverConfig, _, err := d.GetServer()
+				if err != nil {
+					return errors.Wrap(err, "Failed to retrieve server configuration")
+				}
+
+				// Try to setup trust
+				err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.Cluster.ClusterAddress,
+					config.Cluster.ClusterCertificate, config.Cluster.ClusterPassword)
+				if err != nil {
+					return errors.Wrap(err, "Failed to setup cluster trust")
+				}
+			}
+
+			// Configure the cluster
+			op, err := d.UpdateCluster(config.Cluster.ClusterPut, etag)
+			if err != nil {
+				return errors.Wrap(err, "Failed to configure cluster")
+			}
+
+			err = op.Wait()
+			if err != nil {
+				return errors.Wrap(err, "Failed to configure cluster")
+			}
+		}
+	}
+
+	revert = false
+	return nil
+}
diff --git a/lxd/main_init.go b/lxd/main_init.go
index 6ed65867a..ee1994bf2 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -7,25 +7,10 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/lxc/lxd/client"
-	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/api"
 )
 
-type initData struct {
-	api.ServerPut `yaml:",inline"`
-	Cluster       *initDataCluster       `json:"cluster" yaml:"cluster"`
-	Networks      []api.NetworksPost     `json:"networks" yaml:"networks"`
-	StoragePools  []api.StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"`
-	Profiles      []api.ProfilesPost     `json:"profiles" yaml:"profiles"`
-}
-
-type initDataCluster struct {
-	api.ClusterPut  `yaml:",inline"`
-	ClusterPassword string `json:"cluster_password" yaml:"cluster_password"`
-}
-
 type cmdInit struct {
 	global *cmdGlobal
 
@@ -114,7 +99,7 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error {
 		}
 	}
 
-	return c.ApplyConfig(cmd, args, d, *config)
+	return initApplyConfig(d, *config)
 }
 
 func (c *cmdInit) availableStorageDrivers(poolType string) []string {
@@ -157,358 +142,3 @@ func (c *cmdInit) availableStorageDrivers(poolType string) []string {
 
 	return drivers
 }
-
-func (c *cmdInit) ApplyConfig(cmd *cobra.Command, args []string, d lxd.ContainerServer, config initData) error {
-	// Handle reverts
-	revert := true
-	reverts := []func(){}
-	defer func() {
-		if !revert {
-			return
-		}
-
-		// Lets undo things in reverse order
-		for i := len(reverts) - 1; i >= 0; i-- {
-			reverts[i]()
-		}
-	}()
-
-	// Apply server configuration
-	if config.Config != nil && len(config.Config) > 0 {
-		// Get current config
-		currentServer, etag, err := d.GetServer()
-		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve current server configuration")
-		}
-
-		// Setup reverter
-		reverts = append(reverts, func() {
-			d.UpdateServer(currentServer.Writable(), "")
-		})
-
-		// Prepare the update
-		newServer := api.ServerPut{}
-		err = shared.DeepCopy(currentServer.Writable(), &newServer)
-		if err != nil {
-			return errors.Wrap(err, "Failed to copy server configuration")
-		}
-
-		for k, v := range config.Config {
-			newServer.Config[k] = fmt.Sprintf("%v", v)
-		}
-
-		// Apply it
-		err = d.UpdateServer(newServer, etag)
-		if err != nil {
-			return errors.Wrap(err, "Failed to update server configuration")
-		}
-	}
-
-	// Apply network configuration
-	if config.Networks != nil && len(config.Networks) > 0 {
-		// Get the list of networks
-		networkNames, err := d.GetNetworkNames()
-		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve list of networks")
-		}
-
-		// Network creator
-		createNetwork := func(network api.NetworksPost) error {
-			// Create the network if doesn't exist
-			err := d.CreateNetwork(network)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to create network '%s'", network.Name)
-			}
-
-			// Setup reverter
-			reverts = append(reverts, func() {
-				d.DeleteNetwork(network.Name)
-			})
-
-			return nil
-		}
-
-		// Network updater
-		updateNetwork := func(network api.NetworksPost) error {
-			// Get the current network
-			currentNetwork, etag, err := d.GetNetwork(network.Name)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to retrieve current network '%s'", network.Name)
-			}
-
-			// Setup reverter
-			reverts = append(reverts, func() {
-				d.UpdateNetwork(currentNetwork.Name, currentNetwork.Writable(), "")
-			})
-
-			// Prepare the update
-			newNetwork := api.NetworkPut{}
-			err = shared.DeepCopy(currentNetwork.Writable(), &newNetwork)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to copy configuration of network '%s'", network.Name)
-			}
-
-			// Description override
-			if network.Description != "" {
-				newNetwork.Description = network.Description
-			}
-
-			// Config overrides
-			for k, v := range network.Config {
-				newNetwork.Config[k] = fmt.Sprintf("%v", v)
-			}
-
-			// Apply it
-			err = d.UpdateNetwork(currentNetwork.Name, newNetwork, etag)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to update network '%s'", network.Name)
-			}
-
-			return nil
-		}
-
-		for _, network := range config.Networks {
-			// New network
-			if !shared.StringInSlice(network.Name, networkNames) {
-				err := createNetwork(network)
-				if err != nil {
-					return err
-				}
-
-				continue
-			}
-
-			// Existing network
-			err := updateNetwork(network)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	// Apply storage configuration
-	if config.StoragePools != nil && len(config.StoragePools) > 0 {
-		// Get the list of storagePools
-		storagePoolNames, err := d.GetStoragePoolNames()
-		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve list of storage pools")
-		}
-
-		// StoragePool creator
-		createStoragePool := func(storagePool api.StoragePoolsPost) error {
-			// Create the storagePool if doesn't exist
-			err := d.CreateStoragePool(storagePool)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to create storage pool '%s'", storagePool.Name)
-			}
-
-			// Setup reverter
-			reverts = append(reverts, func() {
-				d.DeleteStoragePool(storagePool.Name)
-			})
-
-			return nil
-		}
-
-		// StoragePool updater
-		updateStoragePool := func(storagePool api.StoragePoolsPost) error {
-			// Get the current storagePool
-			currentStoragePool, etag, err := d.GetStoragePool(storagePool.Name)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to retrieve current storage pool '%s'", storagePool.Name)
-			}
-
-			// Sanity check
-			if currentStoragePool.Driver != storagePool.Driver {
-				return fmt.Errorf("Storage pool '%s' is of type '%s' instead of '%s'", currentStoragePool.Name, currentStoragePool.Driver, storagePool.Driver)
-			}
-
-			// Setup reverter
-			reverts = append(reverts, func() {
-				d.UpdateStoragePool(currentStoragePool.Name, currentStoragePool.Writable(), "")
-			})
-
-			// Prepare the update
-			newStoragePool := api.StoragePoolPut{}
-			err = shared.DeepCopy(currentStoragePool.Writable(), &newStoragePool)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to copy configuration of storage pool '%s'", storagePool.Name)
-			}
-
-			// Description override
-			if storagePool.Description != "" {
-				newStoragePool.Description = storagePool.Description
-			}
-
-			// Config overrides
-			for k, v := range storagePool.Config {
-				newStoragePool.Config[k] = fmt.Sprintf("%v", v)
-			}
-
-			// Apply it
-			err = d.UpdateStoragePool(currentStoragePool.Name, newStoragePool, etag)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to update storage pool '%s'", storagePool.Name)
-			}
-
-			return nil
-		}
-
-		for _, storagePool := range config.StoragePools {
-			// New storagePool
-			if !shared.StringInSlice(storagePool.Name, storagePoolNames) {
-				err := createStoragePool(storagePool)
-				if err != nil {
-					return err
-				}
-
-				continue
-			}
-
-			// Existing storagePool
-			err := updateStoragePool(storagePool)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	// Apply profile configuration
-	if config.Profiles != nil && len(config.Profiles) > 0 {
-		// Get the list of profiles
-		profileNames, err := d.GetProfileNames()
-		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve list of profiles")
-		}
-
-		// Profile creator
-		createProfile := func(profile api.ProfilesPost) error {
-			// Create the profile if doesn't exist
-			err := d.CreateProfile(profile)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to create profile '%s'", profile.Name)
-			}
-
-			// Setup reverter
-			reverts = append(reverts, func() {
-				d.DeleteProfile(profile.Name)
-			})
-
-			return nil
-		}
-
-		// Profile updater
-		updateProfile := func(profile api.ProfilesPost) error {
-			// Get the current profile
-			currentProfile, etag, err := d.GetProfile(profile.Name)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to retrieve current profile '%s'", profile.Name)
-			}
-
-			// Setup reverter
-			reverts = append(reverts, func() {
-				d.UpdateProfile(currentProfile.Name, currentProfile.Writable(), "")
-			})
-
-			// Prepare the update
-			newProfile := api.ProfilePut{}
-			err = shared.DeepCopy(currentProfile.Writable(), &newProfile)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to copy configuration of profile '%s'", profile.Name)
-			}
-
-			// Description override
-			if profile.Description != "" {
-				newProfile.Description = profile.Description
-			}
-
-			// Config overrides
-			for k, v := range profile.Config {
-				newProfile.Config[k] = fmt.Sprintf("%v", v)
-			}
-
-			// Device overrides
-			for k, v := range profile.Devices {
-				// New device
-				_, ok := newProfile.Devices[k]
-				if !ok {
-					newProfile.Devices[k] = v
-					continue
-				}
-
-				// Existing device
-				for configKey, configValue := range v {
-					newProfile.Devices[k][configKey] = fmt.Sprintf("%v", configValue)
-				}
-			}
-
-			// Apply it
-			err = d.UpdateProfile(currentProfile.Name, newProfile, etag)
-			if err != nil {
-				return errors.Wrapf(err, "Failed to update profile '%s'", profile.Name)
-			}
-
-			return nil
-		}
-
-		for _, profile := range config.Profiles {
-			// New profile
-			if !shared.StringInSlice(profile.Name, profileNames) {
-				err := createProfile(profile)
-				if err != nil {
-					return err
-				}
-
-				continue
-			}
-
-			// Existing profile
-			err := updateProfile(profile)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	// Apply clustering configuration
-	if config.Cluster != nil && config.Cluster.Enabled {
-		// Get the current cluster configuration
-		currentCluster, etag, err := d.GetCluster()
-		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve current cluster config")
-		}
-
-		// Check if already enabled
-		if !currentCluster.Enabled {
-			// Setup trust relationship
-			if config.Cluster.ClusterAddress != "" && config.Cluster.ClusterPassword != "" {
-				// Get our certificate
-				serverConfig, _, err := d.GetServer()
-				if err != nil {
-					return errors.Wrap(err, "Failed to retrieve server configuration")
-				}
-
-				// Try to setup trust
-				err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.Cluster.ClusterAddress,
-					config.Cluster.ClusterCertificate, config.Cluster.ClusterPassword)
-				if err != nil {
-					return errors.Wrap(err, "Failed to setup cluster trust")
-				}
-			}
-
-			// Configure the cluster
-			op, err := d.UpdateCluster(config.Cluster.ClusterPut, etag)
-			if err != nil {
-				return errors.Wrap(err, "Failed to configure cluster")
-			}
-
-			err = op.Wait()
-			if err != nil {
-				return errors.Wrap(err, "Failed to configure cluster")
-			}
-		}
-	}
-
-	revert = false
-	return nil
-}

From e469bee487d9704efc152374d3de38928f30b8fb Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Thu, 14 Jun 2018 09:31:41 +0000
Subject: [PATCH 2/3] Split initApplyConfig into initDataNodeApply and
 initDataClusterApply

The logic initDataNodeApply will be shared with the PUT /1.0/cluster API.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/init.go                  | 109 ++++++++++++++++++++++---------------------
 lxd/main_init.go             |  15 +++++-
 lxd/main_init_auto.go        |  10 ++--
 lxd/main_init_interactive.go |  78 +++++++++++++++----------------
 lxd/main_init_preseed.go     |   4 +-
 5 files changed, 115 insertions(+), 101 deletions(-)

diff --git a/lxd/init.go b/lxd/init.go
index 873faa00d..49d441649 100644
--- a/lxd/init.go
+++ b/lxd/init.go
@@ -10,9 +10,8 @@ import (
 	"github.com/pkg/errors"
 )
 
-type initData struct {
+type initDataNode struct {
 	api.ServerPut `yaml:",inline"`
-	Cluster       *initDataCluster       `json:"cluster" yaml:"cluster"`
 	Networks      []api.NetworksPost     `json:"networks" yaml:"networks"`
 	StoragePools  []api.StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"`
 	Profiles      []api.ProfilesPost     `json:"profiles" yaml:"profiles"`
@@ -23,31 +22,28 @@ type initDataCluster struct {
 	ClusterPassword string `json:"cluster_password" yaml:"cluster_password"`
 }
 
-// Helper to initialize a LXD instance using the definitions from an initData
-// object.
+// Helper to initialize node-specific entities on a LXD instance using the
+// definitions from the given initDataNode object.
 //
 // It's used both by the 'lxd init' command and by the PUT /1.0/cluster API.
-func initApplyConfig(d lxd.ContainerServer, config initData) error {
+//
+// In case of error, the returned function can be used to revert the changes.
+func initDataNodeApply(d lxd.ContainerServer, config initDataNode) (func(), error) {
 	// Handle reverts
-	revert := true
 	reverts := []func(){}
-	defer func() {
-		if !revert {
-			return
-		}
-
+	revert := func() {
 		// Lets undo things in reverse order
 		for i := len(reverts) - 1; i >= 0; i-- {
 			reverts[i]()
 		}
-	}()
+	}
 
 	// Apply server configuration
 	if config.Config != nil && len(config.Config) > 0 {
 		// Get current config
 		currentServer, etag, err := d.GetServer()
 		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve current server configuration")
+			return revert, errors.Wrap(err, "Failed to retrieve current server configuration")
 		}
 
 		// Setup reverter
@@ -59,7 +55,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 		newServer := api.ServerPut{}
 		err = shared.DeepCopy(currentServer.Writable(), &newServer)
 		if err != nil {
-			return errors.Wrap(err, "Failed to copy server configuration")
+			return revert, errors.Wrap(err, "Failed to copy server configuration")
 		}
 
 		for k, v := range config.Config {
@@ -69,7 +65,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 		// Apply it
 		err = d.UpdateServer(newServer, etag)
 		if err != nil {
-			return errors.Wrap(err, "Failed to update server configuration")
+			return revert, errors.Wrap(err, "Failed to update server configuration")
 		}
 	}
 
@@ -78,7 +74,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 		// Get the list of networks
 		networkNames, err := d.GetNetworkNames()
 		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve list of networks")
+			return revert, errors.Wrap(err, "Failed to retrieve list of networks")
 		}
 
 		// Network creator
@@ -141,7 +137,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 			if !shared.StringInSlice(network.Name, networkNames) {
 				err := createNetwork(network)
 				if err != nil {
-					return err
+					return revert, err
 				}
 
 				continue
@@ -150,7 +146,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 			// Existing network
 			err := updateNetwork(network)
 			if err != nil {
-				return err
+				return revert, err
 			}
 		}
 	}
@@ -160,7 +156,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 		// Get the list of storagePools
 		storagePoolNames, err := d.GetStoragePoolNames()
 		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve list of storage pools")
+			return revert, errors.Wrap(err, "Failed to retrieve list of storage pools")
 		}
 
 		// StoragePool creator
@@ -228,7 +224,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 			if !shared.StringInSlice(storagePool.Name, storagePoolNames) {
 				err := createStoragePool(storagePool)
 				if err != nil {
-					return err
+					return revert, err
 				}
 
 				continue
@@ -237,7 +233,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 			// Existing storagePool
 			err := updateStoragePool(storagePool)
 			if err != nil {
-				return err
+				return revert, err
 			}
 		}
 	}
@@ -247,7 +243,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 		// Get the list of profiles
 		profileNames, err := d.GetProfileNames()
 		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve list of profiles")
+			return revert, errors.Wrap(err, "Failed to retrieve list of profiles")
 		}
 
 		// Profile creator
@@ -325,7 +321,7 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 			if !shared.StringInSlice(profile.Name, profileNames) {
 				err := createProfile(profile)
 				if err != nil {
-					return err
+					return revert, err
 				}
 
 				continue
@@ -334,50 +330,57 @@ func initApplyConfig(d lxd.ContainerServer, config initData) error {
 			// Existing profile
 			err := updateProfile(profile)
 			if err != nil {
-				return err
+				return revert, err
 			}
 		}
 	}
 
-	// Apply clustering configuration
-	if config.Cluster != nil && config.Cluster.Enabled {
-		// Get the current cluster configuration
-		currentCluster, etag, err := d.GetCluster()
-		if err != nil {
-			return errors.Wrap(err, "Failed to retrieve current cluster config")
-		}
+	return nil, nil
+}
 
-		// Check if already enabled
-		if !currentCluster.Enabled {
-			// Setup trust relationship
-			if config.Cluster.ClusterAddress != "" && config.Cluster.ClusterPassword != "" {
-				// Get our certificate
-				serverConfig, _, err := d.GetServer()
-				if err != nil {
-					return errors.Wrap(err, "Failed to retrieve server configuration")
-				}
+// Helper to initialize LXD clustering.
+//
+// Used by the 'lxd init' command.
+func initDataClusterApply(d lxd.ContainerServer, config *initDataCluster) error {
+	if config == nil || !config.Enabled {
+		return nil
+	}
 
-				// Try to setup trust
-				err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.Cluster.ClusterAddress,
-					config.Cluster.ClusterCertificate, config.Cluster.ClusterPassword)
-				if err != nil {
-					return errors.Wrap(err, "Failed to setup cluster trust")
-				}
-			}
+	// Get the current cluster configuration
+	currentCluster, etag, err := d.GetCluster()
+	if err != nil {
+		return errors.Wrap(err, "Failed to retrieve current cluster config")
+	}
 
-			// Configure the cluster
-			op, err := d.UpdateCluster(config.Cluster.ClusterPut, etag)
+	// Check if already enabled
+	if !currentCluster.Enabled {
+		// Setup trust relationship
+		if config.ClusterAddress != "" && config.ClusterPassword != "" {
+			// Get our certificate
+			serverConfig, _, err := d.GetServer()
 			if err != nil {
-				return errors.Wrap(err, "Failed to configure cluster")
+				return errors.Wrap(err, "Failed to retrieve server configuration")
 			}
 
-			err = op.Wait()
+			// Try to setup trust
+			err = cluster.SetupTrust(serverConfig.Environment.Certificate, config.ClusterAddress,
+				config.ClusterCertificate, config.ClusterPassword)
 			if err != nil {
-				return errors.Wrap(err, "Failed to configure cluster")
+				return errors.Wrap(err, "Failed to setup cluster trust")
 			}
 		}
+
+		// Configure the cluster
+		op, err := d.UpdateCluster(config.ClusterPut, etag)
+		if err != nil {
+			return errors.Wrap(err, "Failed to configure cluster")
+		}
+
+		err = op.Wait()
+		if err != nil {
+			return errors.Wrap(err, "Failed to configure cluster")
+		}
 	}
 
-	revert = false
 	return nil
 }
diff --git a/lxd/main_init.go b/lxd/main_init.go
index ee1994bf2..7b819eea5 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -11,6 +11,11 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
+type cmdInitData struct {
+	Node    initDataNode     `yaml:",inline"`
+	Cluster *initDataCluster `json:"cluster" yaml:"cluster"`
+}
+
 type cmdInit struct {
 	global *cmdGlobal
 
@@ -73,7 +78,7 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error {
 	}
 
 	// Prepare the input data
-	var config *initData
+	var config *cmdInitData
 
 	// Preseed mode
 	if c.flagPreseed {
@@ -99,7 +104,13 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error {
 		}
 	}
 
-	return initApplyConfig(d, *config)
+	revert, err := initDataNodeApply(d, config.Node)
+	if err != nil {
+		revert()
+		return err
+	}
+
+	return initDataClusterApply(d, config.Cluster)
 }
 
 func (c *cmdInit) availableStorageDrivers(poolType string) []string {
diff --git a/lxd/main_init_auto.go b/lxd/main_init_auto.go
index 656b8d02f..a819bca3e 100644
--- a/lxd/main_init_auto.go
+++ b/lxd/main_init_auto.go
@@ -11,7 +11,7 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
-func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*initData, error) {
+func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
 	// Sanity checks
 	if c.flagStorageBackend != "" && !shared.StringInSlice(c.flagStorageBackend, supportedStoragePoolDrivers) {
 		return nil, fmt.Errorf("The requested backend '%s' isn't supported by lxd init", c.flagStorageBackend)
@@ -59,8 +59,8 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ
 		c.flagNetworkPort = 8443
 	}
 
-	// Fill in the configuration
-	config := initData{}
+	// Fill in the node configuration
+	config := initDataNode{}
 	config.Config = map[string]interface{}{}
 
 	// Network listening
@@ -143,7 +143,7 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ
 		idx := 0
 		for {
 			if shared.PathExists(fmt.Sprintf("/sys/class/net/lxdbr%d", idx)) {
-				idx += 1
+				idx++
 				continue
 			}
 
@@ -180,5 +180,5 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ
 		}
 	}
 
-	return &config, nil
+	return &cmdInitData{Node: config}, nil
 }
diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go
index 73ae50d59..fef37b1ec 100644
--- a/lxd/main_init_interactive.go
+++ b/lxd/main_init_interactive.go
@@ -23,13 +23,13 @@ import (
 	"github.com/lxc/lxd/shared/idmap"
 )
 
-func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*initData, error) {
+func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
 	// Initialize config
-	config := initData{}
-	config.Config = map[string]interface{}{}
-	config.Networks = []api.NetworksPost{}
-	config.StoragePools = []api.StoragePoolsPost{}
-	config.Profiles = []api.ProfilesPost{
+	config := cmdInitData{}
+	config.Node.Config = map[string]interface{}{}
+	config.Node.Networks = []api.NetworksPost{}
+	config.Node.StoragePools = []api.StoragePoolsPost{}
+	config.Node.Profiles = []api.ProfilesPost{
 		{
 			Name: "default",
 			ProfilePut: api.ProfilePut{
@@ -85,7 +85,7 @@ func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.Contai
 	return &config, nil
 }
 
-func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error {
+func (c *cmdInit) askClustering(config *cmdInitData, d lxd.ContainerServer) error {
 	if cli.AskBool("Would you like to use LXD clustering? (yes/no) [default=no]: ", "no") {
 		config.Cluster = &initDataCluster{}
 		config.Cluster.Enabled = true
@@ -103,7 +103,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error {
 		address := util.NetworkInterfaceAddress()
 		serverAddress := util.CanonicalNetworkAddress(cli.AskString(
 			fmt.Sprintf("What IP address or DNS name should be used to reach this node? [default=%s]: ", address), address, nil))
-		config.Config["core.https_address"] = serverAddress
+		config.Node.Config["core.https_address"] = serverAddress
 
 		if cli.AskBool("Are you joining an existing cluster? (yes/no) [default=no]: ", "no") {
 			// Existing cluster
@@ -177,7 +177,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error {
 				return errors.Wrap(err, "Failed to retrieve storage pools from the cluster")
 			}
 
-			config.StoragePools = []api.StoragePoolsPost{}
+			config.Node.StoragePools = []api.StoragePoolsPost{}
 			for _, pool := range targetPools {
 				// Skip pending pools
 				if pool.Status == "PENDING" {
@@ -208,7 +208,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error {
 						fmt.Sprintf(`Choose the local disk or dataset for storage pool "%s" (empty for loop disk): `, pool.Name), "", validator)
 				}
 
-				config.StoragePools = append(config.StoragePools, newPool)
+				config.Node.StoragePools = append(config.Node.StoragePools, newPool)
 			}
 
 			// Prompt for network config
@@ -217,7 +217,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error {
 				return errors.Wrap(err, "Failed to retrieve networks from the cluster")
 			}
 
-			config.Networks = []api.NetworksPost{}
+			config.Node.Networks = []api.NetworksPost{}
 			for _, network := range targetNetworks {
 				// Skip not-managed or pending networks
 				if !network.Managed || network.Status == "PENDING" {
@@ -240,12 +240,12 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error {
 						fmt.Sprintf(`Choose the local network interface to connect to network "%s" (empty for none): `, network.Name), "", validator)
 				}
 
-				config.Networks = append(config.Networks, newNetwork)
+				config.Node.Networks = append(config.Node.Networks, newNetwork)
 			}
 		} else {
 			// Password authentication
 			if cli.AskBool("Setup password authentication on the cluster? (yes/no) [default=yes]: ", "yes") {
-				config.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ")
+				config.Node.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ")
 			}
 		}
 	}
@@ -253,7 +253,7 @@ func (c *cmdInit) askClustering(config *initData, d lxd.ContainerServer) error {
 	return nil
 }
 
-func (c *cmdInit) askMAAS(config *initData, d lxd.ContainerServer) error {
+func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.ContainerServer) error {
 	if !cli.AskBool("Would you like to connect to a MAAS server? (yes/no) [default=no]: ", "no") {
 		return nil
 	}
@@ -265,16 +265,16 @@ func (c *cmdInit) askMAAS(config *initData, d lxd.ContainerServer) error {
 
 	maasHostname := cli.AskString(fmt.Sprintf("What's the name of this host in MAAS? [default=%s]: ", serverName), serverName, nil)
 	if maasHostname != serverName {
-		config.Config["maas.machine"] = maasHostname
+		config.Node.Config["maas.machine"] = maasHostname
 	}
 
-	config.Config["maas.api.url"] = cli.AskString("URL of your MAAS server (e.g. http://1.2.3.4:5240/MAAS): ", "", nil)
-	config.Config["maas.api.key"] = cli.AskString("API key for your MAAS server: ", "", nil)
+	config.Node.Config["maas.api.url"] = cli.AskString("URL of your MAAS server (e.g. http://1.2.3.4:5240/MAAS): ", "", nil)
+	config.Node.Config["maas.api.key"] = cli.AskString("API key for your MAAS server: ", "", nil)
 
 	return nil
 }
 
-func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error {
+func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.ContainerServer) error {
 	if config.Cluster != nil || !cli.AskBool("Would you like to create a new local network bridge? (yes/no) [default=yes]: ", "yes") {
 		// At this time, only the Ubuntu kernel supports the Fan, detect it
 		fanKernel := false
@@ -295,7 +295,7 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error {
 				}
 
 				// Add to the default profile
-				config.Profiles[0].Devices["eth0"] = map[string]string{
+				config.Node.Profiles[0].Devices["eth0"] = map[string]string{
 					"type":    "nic",
 					"nictype": "macvlan",
 					"name":    "eth0",
@@ -303,22 +303,22 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error {
 				}
 
 				if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", name)) {
-					config.Profiles[0].Devices["eth0"]["nictype"] = "bridged"
+					config.Node.Profiles[0].Devices["eth0"]["nictype"] = "bridged"
 				}
 
-				if config.Config["maas.api.url"] != nil && cli.AskBool("Is this interface connected to your MAAS server? (yes/no) [default=yes]: ", "yes") {
+				if config.Node.Config["maas.api.url"] != nil && cli.AskBool("Is this interface connected to your MAAS server? (yes/no) [default=yes]: ", "yes") {
 					maasSubnetV4 := cli.AskString("MAAS IPv4 subnet name for this interface (empty for no subnet): ", "",
 						func(input string) error { return nil })
 
 					if maasSubnetV4 != "" {
-						config.Profiles[0].Devices["eth0"]["maas.subnet.ipv4"] = maasSubnetV4
+						config.Node.Profiles[0].Devices["eth0"]["maas.subnet.ipv4"] = maasSubnetV4
 					}
 
 					maasSubnetV6 := cli.AskString("MAAS IPv6 subnet name for this interface (empty for no subnet): ", "",
 						func(input string) error { return nil })
 
 					if maasSubnetV6 != "" {
-						config.Profiles[0].Devices["eth0"]["maas.subnet.ipv6"] = maasSubnetV6
+						config.Node.Profiles[0].Devices["eth0"]["maas.subnet.ipv6"] = maasSubnetV6
 					}
 				}
 
@@ -333,10 +333,10 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error {
 			}
 
 			// Add the new network
-			config.Networks = append(config.Networks, network)
+			config.Node.Networks = append(config.Node.Networks, network)
 
 			// Add to the default profile
-			config.Profiles[0].Devices["eth0"] = map[string]string{
+			config.Node.Profiles[0].Devices["eth0"] = map[string]string{
 				"type":    "nic",
 				"nictype": "bridged",
 				"name":    "eth0",
@@ -361,7 +361,7 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error {
 		}
 
 		// Add to the default profile
-		config.Profiles[0].Devices["eth0"] = map[string]string{
+		config.Node.Profiles[0].Devices["eth0"] = map[string]string{
 			"type":    "nic",
 			"nictype": "bridged",
 			"name":    "eth0",
@@ -397,14 +397,14 @@ func (c *cmdInit) askNetworking(config *initData, d lxd.ContainerServer) error {
 		}
 
 		// Add the new network
-		config.Networks = append(config.Networks, network)
+		config.Node.Networks = append(config.Node.Networks, network)
 		break
 	}
 
 	return nil
 }
 
-func (c *cmdInit) askStorage(config *initData, d lxd.ContainerServer) error {
+func (c *cmdInit) askStorage(config *cmdInitData, d lxd.ContainerServer) error {
 	if config.Cluster != nil {
 		if cli.AskBool("Do you want to configure a new local storage pool? (yes/no) [default=yes]: ", "yes") {
 			err := c.askStoragePool(config, d, "local")
@@ -430,7 +430,7 @@ func (c *cmdInit) askStorage(config *initData, d lxd.ContainerServer) error {
 	return c.askStoragePool(config, d, "all")
 }
 
-func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolType string) error {
+func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.ContainerServer, poolType string) error {
 	// Figure out the preferred storage driver
 	availableBackends := c.availableStorageDrivers(poolType)
 
@@ -474,7 +474,7 @@ func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolTy
 		}
 
 		// Add to the default profile
-		config.Profiles[0].Devices["root"] = map[string]string{
+		config.Node.Profiles[0].Devices["root"] = map[string]string{
 			"type": "disk",
 			"path": "/",
 			"pool": pool.Name,
@@ -490,7 +490,7 @@ func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolTy
 
 		// Optimization for dir
 		if pool.Driver == "dir" {
-			config.StoragePools = append(config.StoragePools, pool)
+			config.Node.StoragePools = append(config.Node.StoragePools, pool)
 			break
 		}
 
@@ -498,7 +498,7 @@ func (c *cmdInit) askStoragePool(config *initData, d lxd.ContainerServer, poolTy
 		if pool.Driver == "btrfs" && backingFs == "btrfs" {
 			if cli.AskBool(fmt.Sprintf("Would you like to create a new btrfs subvolume under %s? (yes/no) [default=yes]: ", shared.VarPath("")), "yes") {
 				pool.Config["source"] = shared.VarPath("storage-pools", pool.Name)
-				config.StoragePools = append(config.StoragePools, pool)
+				config.Node.StoragePools = append(config.Node.StoragePools, pool)
 				break
 			}
 		}
@@ -577,14 +577,14 @@ your Linux distribution and run "lxd init" again afterwards.
 			}
 		}
 
-		config.StoragePools = append(config.StoragePools, pool)
+		config.Node.StoragePools = append(config.Node.StoragePools, pool)
 		break
 	}
 
 	return nil
 }
 
-func (c *cmdInit) askDaemon(config *initData, d lxd.ContainerServer) error {
+func (c *cmdInit) askDaemon(config *cmdInitData, d lxd.ContainerServer) error {
 	// Detect lack of uid/gid
 	idmapset, err := idmap.DefaultIdmapSet("")
 	if (err != nil || len(idmapset.Idmap) == 0 || idmapset.Usable() != nil) && shared.RunningInUserNS() {
@@ -601,7 +601,7 @@ they otherwise would.
 `)
 
 		if cli.AskBool("Would you like to have your containers share their parent's allocation? (yes/no) [default=yes]: ", "yes") {
-			config.Profiles[0].Config["security.privileged"] = "true"
+			config.Node.Profiles[0].Config["security.privileged"] = "true"
 		}
 	}
 
@@ -625,16 +625,16 @@ they otherwise would.
 		}
 
 		netPort := cli.AskInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
-		config.Config["core.https_address"] = fmt.Sprintf("%s:%d", netAddr, netPort)
-		config.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ")
-		if config.Config["core.trust_password"] == "" {
+		config.Node.Config["core.https_address"] = fmt.Sprintf("%s:%d", netAddr, netPort)
+		config.Node.Config["core.trust_password"] = cli.AskPassword("Trust password for new clients: ")
+		if config.Node.Config["core.trust_password"] == "" {
 			fmt.Printf("No password set, client certificates will have to be manually trusted.")
 		}
 	}
 
 	// Ask if the user wants images to be automatically refreshed
 	if !cli.AskBool("Would you like stale cached images to be updated automatically? (yes/no) [default=yes] ", "yes") {
-		config.Config["images.auto_update_interval"] = "0"
+		config.Node.Config["images.auto_update_interval"] = "0"
 	}
 
 	return nil
diff --git a/lxd/main_init_preseed.go b/lxd/main_init_preseed.go
index e21cc2034..0f75481d6 100644
--- a/lxd/main_init_preseed.go
+++ b/lxd/main_init_preseed.go
@@ -11,7 +11,7 @@ import (
 	"github.com/lxc/lxd/client"
 )
 
-func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*initData, error) {
+func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
 	// Read the YAML
 	bytes, err := ioutil.ReadAll(os.Stdin)
 	if err != nil {
@@ -19,7 +19,7 @@ func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerS
 	}
 
 	// Parse the YAML
-	config := initData{}
+	config := cmdInitData{}
 	err = yaml.Unmarshal(bytes, &config)
 	if err != nil {
 		return nil, errors.Wrap(err, "Failed to parse the preseed")

From 8b3a766c7c51f4897c4261479d42e24288b5590b Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Thu, 14 Jun 2018 10:00:01 +0000
Subject: [PATCH 3/3] Extend api.Cluster schema

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 shared/api/cluster.go | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/shared/api/cluster.go b/shared/api/cluster.go
index 3ae4db490..0d9d7d550 100644
--- a/shared/api/cluster.go
+++ b/shared/api/cluster.go
@@ -4,8 +4,16 @@ package api
 //
 // API extension: clustering
 type Cluster struct {
-	ServerName string `json:"server_name" yaml:"server_name"`
-	Enabled    bool   `json:"enabled" yaml:"enabled"`
+	ServerName   string                   `json:"server_name" yaml:"server_name"`
+	Enabled      bool                     `json:"enabled" yaml:"enabled"`
+	MemberConfig []ClusterMemberConfigKey `json:"member_config" yaml:"member_config"`
+}
+
+// ClusterMemberConfigKey represents a single config key that a new member of
+// the cluster is required to provide when joining.
+type ClusterMemberConfigKey struct {
+	Name        string `json:"name" yaml:"name"`
+	Description string `json:"description" yaml:"description"`
 }
 
 // ClusterPut represents the fields required to bootstrap or join a LXD


More information about the lxc-devel mailing list