Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing additional configuration during bootstrap #72

Merged
merged 10 commits into from
Nov 23, 2023
6 changes: 3 additions & 3 deletions config/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 15 additions & 4 deletions example/cmd/microctl/main_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"strings"
"time"

"github.com/canonical/microcluster/microcluster"
Expand All @@ -14,6 +15,7 @@ type cmdInit struct {

flagBootstrap bool
flagToken string
flagConfig []string
}

func (c *cmdInit) Command() *cobra.Command {
Expand All @@ -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
}

Expand All @@ -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")
Expand Down
24 changes: 21 additions & 3 deletions example/cmd/microd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand All @@ -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
},
Expand Down
19 changes: 10 additions & 9 deletions internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand All @@ -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 {
Expand Down Expand Up @@ -236,7 +237,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
}
Expand Down Expand Up @@ -306,7 +307,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 {
Expand Down Expand Up @@ -378,7 +379,7 @@ func (d *Daemon) StartAPI(bootstrap bool, newConfig *trust.Location, joinAddress
return err
}

return d.hooks.OnBootstrap(d.State())
return d.hooks.OnBootstrap(d.State(), initConfig)
}

if len(joinAddresses) != 0 {
Expand Down Expand Up @@ -420,7 +421,7 @@ func (d *Daemon) StartAPI(bootstrap bool, newConfig *trust.Location, joinAddress
}

if len(joinAddresses) > 0 {
err = d.hooks.PreJoin(d.State())
err = d.hooks.PreJoin(d.State(), initConfig)
if err != nil {
return err
}
Expand Down Expand Up @@ -471,7 +472,7 @@ func (d *Daemon) StartAPI(bootstrap bool, newConfig *trust.Location, joinAddress
}

if len(joinAddresses) > 0 {
return d.hooks.PostJoin(d.State())
return d.hooks.PostJoin(d.State(), initConfig)
}

return nil
Expand Down
4 changes: 2 additions & 2 deletions internal/rest/resources/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
9 changes: 5 additions & 4 deletions internal/rest/types/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
2 changes: 1 addition & 1 deletion internal/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions microcluster/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down
Loading