[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