From cf640aca6d688c0f035261e6f69d6ef7970dc09d Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:47:46 +0000 Subject: [PATCH 01/10] internal/rest/types: Adds InitConfig to Control type. Signed-off-by: Mark Laing --- internal/rest/types/control.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/rest/types/control.go b/internal/rest/types/control.go index 7151388a..ba443896 100644 --- a/internal/rest/types/control.go +++ b/internal/rest/types/control.go @@ -6,8 +6,9 @@ import ( // Control represents the arguments that can be used to initialize/shutdown the daemon. type Control struct { - Bootstrap bool `json:"bootstrap" yaml:"bootstrap"` - JoinToken string `json:"join_token" yaml:"join_token"` - Address types.AddrPort `json:"address" yaml:"address"` - Name string `json:"name" yaml:"name"` + Bootstrap bool `json:"bootstrap" yaml:"bootstrap"` + InitConfig map[string]string `json:"config" yaml:"config"` + JoinToken string `json:"join_token" yaml:"join_token"` + Address types.AddrPort `json:"address" yaml:"address"` + Name string `json:"name" yaml:"name"` } From 4547d8af8e0efaa0617b9807125dcd72302fbc27 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:48:31 +0000 Subject: [PATCH 02/10] microcluster: Pass initConfig into NewCluster and JoinCluster methods. Signed-off-by: Mark Laing --- microcluster/app.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microcluster/app.go b/microcluster/app.go index a631b3a9..f2a7cb18 100644 --- a/microcluster/app.go +++ b/microcluster/app.go @@ -198,7 +198,7 @@ func (m *MicroCluster) Ready(timeoutSeconds int) error { } // NewCluster bootstrapps a brand new cluster with this daemon as its only member. -func (m *MicroCluster) NewCluster(name string, address string, timeout time.Duration) error { +func (m *MicroCluster) NewCluster(name string, address string, config map[string]string, timeout time.Duration) error { c, err := m.LocalClient() if err != nil { return err @@ -209,11 +209,11 @@ func (m *MicroCluster) NewCluster(name string, address string, timeout time.Dura return fmt.Errorf("Received invalid address %q: %w", address, err) } - return c.ControlDaemon(m.ctx, internalTypes.Control{Bootstrap: true, Address: addr, Name: name}, timeout) + return c.ControlDaemon(m.ctx, internalTypes.Control{Bootstrap: true, Address: addr, Name: name, InitConfig: config}, timeout) } // JoinCluster joins an existing cluster with a join token supplied by an existing cluster member. -func (m *MicroCluster) JoinCluster(name string, address string, token string, timeout time.Duration) error { +func (m *MicroCluster) JoinCluster(name string, address string, token string, initConfig map[string]string, timeout time.Duration) error { c, err := m.LocalClient() if err != nil { return err @@ -224,7 +224,7 @@ func (m *MicroCluster) JoinCluster(name string, address string, token string, ti return fmt.Errorf("Received invalid address %q: %w", address, err) } - return c.ControlDaemon(m.ctx, internalTypes.Control{JoinToken: token, Address: addr, Name: name}, timeout) + return c.ControlDaemon(m.ctx, internalTypes.Control{JoinToken: token, Address: addr, Name: name, InitConfig: initConfig}, timeout) } // NewJoinToken creates and records a new join token containing all the necessary credentials for joining a cluster. From 8ba449d3a81853f9629528e2d2aa24dabe192581 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:49:41 +0000 Subject: [PATCH 03/10] internal/daemon: Add initConfig argument to daemon's StartAPI method. Signed-off-by: Mark Laing --- internal/daemon/daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index d43ff0cd..e778dcb2 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -306,7 +306,7 @@ func (d *Daemon) initServer(resources ...*resources.Resources) *http.Server { // StartAPI starts up the admin and consumer APIs, and generates a cluster cert // if we are bootstrapping the first node. -func (d *Daemon) StartAPI(bootstrap bool, newConfig *trust.Location, joinAddresses ...string) error { +func (d *Daemon) StartAPI(bootstrap bool, initConfig map[string]string, newConfig *trust.Location, joinAddresses ...string) error { if newConfig != nil { err := d.setDaemonConfig(newConfig) if err != nil { From bc16f4fa2d70cbf802b15c3f31952addf8e10e1a Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:50:44 +0000 Subject: [PATCH 04/10] internal/state: Add initConfig argument to StartAPI on State. Signed-off-by: Mark Laing --- internal/state/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/state/state.go b/internal/state/state.go index 9a1ac99a..d7acb1ba 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -53,7 +53,7 @@ type State struct { Remotes func() *trust.Remotes // Initialize APIs and bootstrap/join database. - StartAPI func(bootstrap bool, newConfig *trust.Location, joinAddresses ...string) error + StartAPI func(bootstrap bool, initConfig map[string]string, newConfig *trust.Location, joinAddresses ...string) error // Stop fully stops the daemon, its database, and all listeners. Stop func() error From 9b03ef3f549ccf20d2e9063b1e4d0925a1eb1f65 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:51:31 +0000 Subject: [PATCH 05/10] internal/rest/resources: Update calls to StartAPI. Signed-off-by: Mark Laing --- internal/rest/resources/control.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/rest/resources/control.go b/internal/rest/resources/control.go index 325800c2..f3216060 100644 --- a/internal/rest/resources/control.go +++ b/internal/rest/resources/control.go @@ -45,7 +45,7 @@ func controlPost(state *state.State, r *http.Request) response.Response { } daemonConfig := &trust.Location{Address: req.Address, Name: req.Name} - err = state.StartAPI(req.Bootstrap, daemonConfig) + err = state.StartAPI(req.Bootstrap, req.InitConfig, daemonConfig) if err != nil { return response.SmartError(err) } @@ -138,7 +138,7 @@ func joinWithToken(state *state.State, req *internalTypes.Control) response.Resp } // Start the HTTPS listeners and join Dqlite. - err = state.StartAPI(false, daemonConfig, joinAddrs.Strings()...) + err = state.StartAPI(false, req.InitConfig, daemonConfig, joinAddrs.Strings()...) if err != nil { return response.SmartError(err) } From ebb736baa6f29afef809a87c66c4f0caa1070eff Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:52:06 +0000 Subject: [PATCH 06/10] internal/daemon: Update calls to StartAPI. Signed-off-by: Mark Laing --- internal/daemon/daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index e778dcb2..45c5790e 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -236,7 +236,7 @@ func (d *Daemon) reloadIfBootstrapped() error { return fmt.Errorf("Failed to retrieve daemon configuration yaml: %w", err) } - err = d.StartAPI(false, nil) + err = d.StartAPI(false, nil, nil) if err != nil { return err } From ccde6235a4e5f4a74edc825fa4c450db76972122 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:52:28 +0000 Subject: [PATCH 07/10] config: Add initConfig to bootstrap and join hooks. Signed-off-by: Mark Laing --- config/hooks.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/hooks.go b/config/hooks.go index 409f3b5c..7350b656 100644 --- a/config/hooks.go +++ b/config/hooks.go @@ -8,18 +8,18 @@ import ( // integrate with other tools. type Hooks struct { // OnBootstrap is run after the daemon is initialized and bootstrapped. - OnBootstrap func(s *state.State) error + OnBootstrap func(s *state.State, initConfig map[string]string) error // OnStart is run after the daemon is started. OnStart func(s *state.State) error // PostJoin is run after the daemon is initialized, joined the cluster and existing members triggered // their 'OnNewMember' hooks. - PostJoin func(s *state.State) error + PostJoin func(s *state.State, initConfig map[string]string) error // PreJoin is run after the daemon is initialized and joined the cluster but before existing members triggered // their 'OnNewMember' hooks. - PreJoin func(s *state.State) error + PreJoin func(s *state.State, initConfig map[string]string) error // PreRemove is run on a cluster member just before it is removed from the cluster. PreRemove func(s *state.State) error From 9e9308970b83f3decc5bac98503167b27d19477c Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:53:41 +0000 Subject: [PATCH 08/10] internal/daemon: Update calls to hooks. Signed-off-by: Mark Laing --- internal/daemon/daemon.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 45c5790e..f92f5934 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -168,8 +168,9 @@ func (d *Daemon) init(listenPort string, extendedEndpoints []rest.Endpoint, sche } func (d *Daemon) applyHooks(hooks *config.Hooks) { - // Apply a no-op hook for any missing hooks. + // Apply a no-op hooks for any missing hooks. noOpHook := func(s *state.State) error { return nil } + noOpInitHook := func(s *state.State, initConfig map[string]string) error { return nil } if hooks == nil { d.hooks = config.Hooks{} @@ -178,15 +179,15 @@ func (d *Daemon) applyHooks(hooks *config.Hooks) { } if d.hooks.OnBootstrap == nil { - d.hooks.OnBootstrap = noOpHook + d.hooks.OnBootstrap = noOpInitHook } if d.hooks.PostJoin == nil { - d.hooks.PostJoin = noOpHook + d.hooks.PostJoin = noOpInitHook } if d.hooks.PreJoin == nil { - d.hooks.PreJoin = noOpHook + d.hooks.PreJoin = noOpInitHook } if d.hooks.OnStart == nil { @@ -378,7 +379,7 @@ func (d *Daemon) StartAPI(bootstrap bool, initConfig map[string]string, newConfi return err } - return d.hooks.OnBootstrap(d.State()) + return d.hooks.OnBootstrap(d.State(), initConfig) } if len(joinAddresses) != 0 { @@ -420,7 +421,7 @@ func (d *Daemon) StartAPI(bootstrap bool, initConfig map[string]string, newConfi } if len(joinAddresses) > 0 { - err = d.hooks.PreJoin(d.State()) + err = d.hooks.PreJoin(d.State(), initConfig) if err != nil { return err } @@ -471,7 +472,7 @@ func (d *Daemon) StartAPI(bootstrap bool, initConfig map[string]string, newConfi } if len(joinAddresses) > 0 { - return d.hooks.PostJoin(d.State()) + return d.hooks.PostJoin(d.State(), initConfig) } return nil From 81c116022126f882637a6124adc5a6fd37a5b7ca Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:55:19 +0000 Subject: [PATCH 09/10] microctl: Update example to pass in config on init. Signed-off-by: Mark Laing --- example/cmd/microctl/main_init.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/example/cmd/microctl/main_init.go b/example/cmd/microctl/main_init.go index 6ce1f20c..df08d5f2 100644 --- a/example/cmd/microctl/main_init.go +++ b/example/cmd/microctl/main_init.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "strings" "time" "github.com/canonical/microcluster/microcluster" @@ -14,6 +15,7 @@ type cmdInit struct { flagBootstrap bool flagToken string + flagConfig []string } func (c *cmdInit) Command() *cobra.Command { @@ -27,6 +29,9 @@ func (c *cmdInit) Command() *cobra.Command { cmd.Flags().BoolVar(&c.flagBootstrap, "bootstrap", false, "Configure a new cluster with this daemon") cmd.Flags().StringVar(&c.flagToken, "token", "", "Join a cluster with a join token") + cmd.Flags().StringSliceVar(&c.flagConfig, "config", nil, "Extra configuration to be applied during bootstrap") + cmd.MarkFlagsMutuallyExclusive("bootstrap", "token") + return cmd } @@ -40,16 +45,22 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error { return fmt.Errorf("Unable to configure MicroCluster: %w", err) } - if c.flagBootstrap && c.flagToken != "" { - return fmt.Errorf("Option must be one of bootstrap or token") + conf := make(map[string]string, len(c.flagConfig)) + for _, setting := range c.flagConfig { + key, value, ok := strings.Cut(setting, "=") + if !ok { + return fmt.Errorf("Malformed additional configuration value %s", setting) + } + + conf[key] = value } if c.flagBootstrap { - return m.NewCluster(args[0], args[1], time.Second*30) + return m.NewCluster(args[0], args[1], conf, time.Second*30) } if c.flagToken != "" { - return m.JoinCluster(args[0], args[1], c.flagToken, time.Second*30) + return m.JoinCluster(args[0], args[1], c.flagToken, conf, time.Second*30) } return fmt.Errorf("Option must be one of bootstrap or token") From 51dffd8eefb3a3bad633cafbb7840640c1aa33d5 Mon Sep 17 00:00:00 2001 From: Mark Laing Date: Thu, 23 Nov 2023 18:55:44 +0000 Subject: [PATCH 10/10] microd: Update example to log config passed in on init. Signed-off-by: Mark Laing --- example/cmd/microd/main.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/example/cmd/microd/main.go b/example/cmd/microd/main.go index 3823df80..5ce4fb7a 100644 --- a/example/cmd/microd/main.go +++ b/example/cmd/microd/main.go @@ -69,8 +69,14 @@ func (c *cmdDaemon) Run(cmd *cobra.Command, args []string) error { // exampleHooks are some example post-action hooks that can be run by MicroCluster. exampleHooks := &config.Hooks{ // OnBootstrap is run after the daemon is initialized and bootstrapped. - OnBootstrap: func(s *state.State) error { + OnBootstrap: func(s *state.State, initConfig map[string]string) error { + logCtx := logger.Ctx{} + for k, v := range initConfig { + logCtx[k] = v + } + logger.Info("This is a hook that runs after the daemon is initialized and bootstrapped") + logger.Info("Here are the extra configuration keys that were passed into the init --bootstrap command", logCtx) return nil }, @@ -83,15 +89,27 @@ func (c *cmdDaemon) Run(cmd *cobra.Command, args []string) error { }, // PostJoin is run after the daemon is initialized and joins a cluster. - PostJoin: func(s *state.State) error { + PostJoin: func(s *state.State, initConfig map[string]string) error { + logCtx := logger.Ctx{} + for k, v := range initConfig { + logCtx[k] = v + } + logger.Info("This is a hook that runs after the daemon is initialized and joins an existing cluster, after OnNewMember runs on all peers") + logger.Info("Here are the extra configuration keys that were passed into the init --join command", logCtx) return nil }, // PreJoin is run after the daemon is initialized and joins a cluster. - PreJoin: func(s *state.State) error { + PreJoin: func(s *state.State, initConfig map[string]string) error { + logCtx := logger.Ctx{} + for k, v := range initConfig { + logCtx[k] = v + } + logger.Info("This is a hook that runs after the daemon is initialized and joins an existing cluster, before OnNewMember runs on all peers") + logger.Info("Here are the extra configuration keys that were passed into the init --join command", logCtx) return nil },