diff --git a/example/api/extended.go b/example/api/extended.go index 1cfd3aead..39fbd28e7 100644 --- a/example/api/extended.go +++ b/example/api/extended.go @@ -15,7 +15,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) // This is an example extended endpoint reachable at /1.0/extended/simple. @@ -40,7 +39,7 @@ var extendedWebsocketCmd = rest.Endpoint{ // This is the POST handler for the /1.0/extended/simple endpoint. // This example shows how to forward a request to other cluster members. -func cmdSimple(state state.State, r *http.Request) response.Response { +func cmdSimple(state types.State, r *http.Request) response.Response { // Check the user agent header to check if we are the notifying cluster member. if !types.IsNotification(r) { // Get a collection of clients every other cluster member, with the notification user-agent set. @@ -101,7 +100,7 @@ func cmdSimple(state state.State, r *http.Request) response.Response { // This is the GET handler for the /1.0/extended/websocket endpoint. // This example shows how to use websockets. -func cmdWebsocket(state state.State, r *http.Request) response.Response { +func cmdWebsocket(state types.State, r *http.Request) response.Response { if r.Header.Get("Upgrade") == "websocket" { var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, diff --git a/example/cmd/microd/main.go b/example/cmd/microd/main.go index cb22eb04b..dea8b295b 100644 --- a/example/cmd/microd/main.go +++ b/example/cmd/microd/main.go @@ -16,7 +16,6 @@ import ( "github.com/canonical/microcluster/v3/example/version" "github.com/canonical/microcluster/v3/microcluster" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) type cmdGlobal struct { @@ -89,9 +88,9 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { } // exampleHooks are some example post-action hooks that can be run by MicroCluster. - dargs.Hooks = &state.Hooks{ + dargs.Hooks = &types.Hooks{ // PostBootstrap is run after the daemon is initialized and bootstrapped. - PostBootstrap: func(ctx context.Context, s state.State, initConfig map[string]string) error { + PostBootstrap: func(ctx context.Context, s types.State, initConfig map[string]string) error { // We can derive the logger using our custom handler from the app. logger := m.LoggerFromContext(ctx) @@ -115,7 +114,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { return nil }, - PreInit: func(ctx context.Context, s state.State, bootstrap bool, initConfig map[string]string) error { + PreInit: func(ctx context.Context, s types.State, bootstrap bool, initConfig map[string]string) error { logger := m.LoggerFromContext(ctx) logger.Info("This is a hook that runs before the daemon is initialized") @@ -125,7 +124,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // OnStart is run after the daemon is started. - OnStart: func(ctx context.Context, s state.State) error { + OnStart: func(ctx context.Context, s types.State) error { logger := m.LoggerFromContext(ctx) logger.Info("This is a hook that runs after the daemon first starts") @@ -134,7 +133,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // PostJoin is run after the daemon is initialized and joins a cluster. - PostJoin: func(ctx context.Context, s state.State, initConfig map[string]string) error { + PostJoin: func(ctx context.Context, s types.State, initConfig map[string]string) error { logger := m.LoggerFromContext(ctx) logger.Info("This is a hook that runs after the daemon is initialized and joins an existing cluster, after OnNewMember runs on all peers") @@ -144,7 +143,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // PreJoin is run after the daemon is initialized and joins a cluster. - PreJoin: func(ctx context.Context, s state.State, initConfig map[string]string) error { + PreJoin: func(ctx context.Context, s types.State, initConfig map[string]string) error { logger := m.LoggerFromContext(ctx) logger.Info("This is a hook that runs after the daemon is initialized and joins an existing cluster, before OnNewMember runs on all peers") @@ -154,7 +153,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // PostRemove is run after the daemon is removed from a cluster. - PostRemove: func(ctx context.Context, s state.State, force bool) error { + PostRemove: func(ctx context.Context, s types.State, force bool) error { logger := m.LoggerFromContext(ctx) logger.Info(fmt.Sprintf("This is a hook that is run on peer %q after a cluster member is removed, with the force flag set to %v", s.Name(), force)) @@ -163,7 +162,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // PreRemove is run before the daemon is removed from the cluster. - PreRemove: func(ctx context.Context, s state.State, force bool) error { + PreRemove: func(ctx context.Context, s types.State, force bool) error { logger := m.LoggerFromContext(ctx) logger.Info(fmt.Sprintf("This is a hook that is run on peer %q just before it is removed, with the force flag set to %v", s.Name(), force)) @@ -172,7 +171,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // OnHeartbeat is run after a successful heartbeat round. - OnHeartbeat: func(ctx context.Context, s state.State, roleStatus map[string]types.RoleStatus) error { + OnHeartbeat: func(ctx context.Context, s types.State, roleStatus map[string]types.RoleStatus) error { logger := m.LoggerFromContext(ctx) logger.Info("This is a hook that is run on the dqlite leader after a successful heartbeat; role information for cluster members is available") @@ -190,7 +189,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // OnNewMember is run after a new member has joined. - OnNewMember: func(ctx context.Context, s state.State, newMember types.ClusterMemberLocal) error { + OnNewMember: func(ctx context.Context, s types.State, newMember types.ClusterMemberLocal) error { logger := m.LoggerFromContext(ctx) logger.Info(fmt.Sprintf("This is a hook that is run on peer %q when the new cluster member %q has joined", s.Name(), newMember.Name)) @@ -199,7 +198,7 @@ func (c *cmdDaemon) run(cmd *cobra.Command, args []string) error { }, // OnDaemonConfigUpdate is run after the local daemon config of a cluster member got modified. - OnDaemonConfigUpdate: func(ctx context.Context, s state.State, config types.DaemonConfig) error { + OnDaemonConfigUpdate: func(ctx context.Context, s types.State, config types.DaemonConfig) error { logger := m.LoggerFromContext(ctx) logger.Info(fmt.Sprintf("Running OnDaemonConfigUpdate triggered by %q", config.Name)) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index b28848754..68beaab3c 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -40,7 +40,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) // Args are the data needed to start a MicroCluster daemon. @@ -64,7 +63,7 @@ type Args struct { APIExtensions []string // Functions that trigger at various lifecycle events - Hooks *state.Hooks + Hooks *types.Hooks // Each rest.Server will be initialized and managed by microcluster. ExtensionServers map[string]rest.Server @@ -97,7 +96,7 @@ type Daemon struct { fsWatcher *sys.Watcher trustStore *trust.Store - hooks state.Hooks // Hooks to be called upon various daemon actions. + hooks types.Hooks // Hooks to be called upon various daemon actions. ReadyChan chan struct{} // Closed when the daemon is fully ready. shutdownCtx context.Context // Cancelled when shutdown starts. @@ -250,7 +249,7 @@ func (d *Daemon) Run(ctx context.Context, stateDir string, args Args) error { } } -func (d *Daemon) init(listenAddress string, socketGroup string, heartbeatInterval time.Duration, schemaExtensions []clusterDB.Update, apiExtensions []string, hooks *state.Hooks) error { +func (d *Daemon) init(listenAddress string, socketGroup string, heartbeatInterval time.Duration, schemaExtensions []clusterDB.Update, apiExtensions []string, hooks *types.Hooks) error { d.applyHooks(hooks) // Register smart error mappings. @@ -374,21 +373,21 @@ func (d *Daemon) init(listenAddress string, socketGroup string, heartbeatInterva return nil } -func (d *Daemon) applyHooks(hooks *state.Hooks) { +func (d *Daemon) applyHooks(hooks *types.Hooks) { // Apply a no-op hooks for any missing hooks. - noOpHook := func(ctx context.Context, s state.State) error { return nil } - noOpRemoveHook := func(ctx context.Context, s state.State, force bool) error { return nil } - noOpInitHook := func(ctx context.Context, s state.State, initConfig map[string]string) error { return nil } - noOpGenericInitHook := func(ctx context.Context, s state.State, bootstrap bool, initConfig map[string]string) error { + noOpHook := func(ctx context.Context, s types.State) error { return nil } + noOpRemoveHook := func(ctx context.Context, s types.State, force bool) error { return nil } + noOpInitHook := func(ctx context.Context, s types.State, initConfig map[string]string) error { return nil } + noOpGenericInitHook := func(ctx context.Context, s types.State, bootstrap bool, initConfig map[string]string) error { return nil } - noOpConfigHook := func(ctx context.Context, s state.State, config types.DaemonConfig) error { return nil } - noOpNewMemberHook := func(ctx context.Context, s state.State, newMember types.ClusterMemberLocal) error { return nil } - noOpHeartbeatHook := func(ctx context.Context, s state.State, roleStatus map[string]types.RoleStatus) error { return nil } + noOpConfigHook := func(ctx context.Context, s types.State, config types.DaemonConfig) error { return nil } + noOpNewMemberHook := func(ctx context.Context, s types.State, newMember types.ClusterMemberLocal) error { return nil } + noOpHeartbeatHook := func(ctx context.Context, s types.State, roleStatus map[string]types.RoleStatus) error { return nil } if hooks == nil { - d.hooks = state.Hooks{} + d.hooks = types.Hooks{} } else { d.hooks = *hooks } @@ -1109,7 +1108,7 @@ func (d *Daemon) FileSystem() types.OS { } // State creates a State instance with the daemon's stateful components. -func (d *Daemon) State() state.State { +func (d *Daemon) State() types.State { state := &internalState.InternalState{ Hooks: &d.hooks, Context: d.shutdownCtx, diff --git a/internal/rest/access/authentication.go b/internal/rest/access/authentication.go index 86584b76d..b2a6e585a 100644 --- a/internal/rest/access/authentication.go +++ b/internal/rest/access/authentication.go @@ -36,7 +36,7 @@ func (e ErrInvalidHost) Unwrap() error { // AllowAuthenticated checks if the request is trusted by extracting access.TrustedRequest from the request context. // This handler is used as an access handler by default if AllowUntrusted is false on a rest.EndpointAction. -func AllowAuthenticated(state state.State, r *http.Request) (bool, response.Response) { +func AllowAuthenticated(state types.State, r *http.Request) (bool, response.Response) { trusted := r.Context().Value(client.CtxAccess) if trusted == nil { return false, response.Forbidden(nil) @@ -97,7 +97,7 @@ func checkMutualTLS(ctx context.Context, cert x509.Certificate, trustedCerts map // Authenticate ensures the request certificates are trusted against the given set of trusted certificates. // - Requests over the unix socket are always allowed. // - HTTP requests require the TLS Peer certificate to match an entry in the supplied map of certificates. -func Authenticate(s state.State, r *http.Request, hostAddress string, trustedCerts map[string]x509.Certificate) (bool, error) { +func Authenticate(s types.State, r *http.Request, hostAddress string, trustedCerts map[string]x509.Certificate) (bool, error) { if r.RemoteAddr == "@" { return true, nil } diff --git a/internal/rest/resources/api_1.0.go b/internal/rest/resources/api_1.0.go index 462b68423..ba9ee0fdb 100644 --- a/internal/rest/resources/api_1.0.go +++ b/internal/rest/resources/api_1.0.go @@ -7,7 +7,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var api10Cmd = rest.Endpoint{ @@ -16,7 +15,7 @@ var api10Cmd = rest.Endpoint{ Get: rest.EndpointAction{Handler: api10Get, AllowUntrusted: true}, } -func api10Get(s state.State, r *http.Request) response.Response { +func api10Get(s types.State, r *http.Request) response.Response { addrPort, err := types.ParseAddrPort(s.Address().Host) if err != nil { return response.SmartError(err) diff --git a/internal/rest/resources/certificates.go b/internal/rest/resources/certificates.go index b9e732582..a3bfd054a 100644 --- a/internal/rest/resources/certificates.go +++ b/internal/rest/resources/certificates.go @@ -21,7 +21,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var clusterCertificatesCmd = rest.Endpoint{ @@ -31,7 +30,7 @@ var clusterCertificatesCmd = rest.Endpoint{ Put: rest.EndpointAction{Handler: clusterCertificatesPut, AccessHandler: access.AllowAuthenticated}, } -func clusterCertificatesPut(s state.State, r *http.Request) response.Response { +func clusterCertificatesPut(s types.State, r *http.Request) response.Response { certificateName, err := url.PathUnescape(mux.Vars(r)["name"]) if err != nil { return response.SmartError(err) diff --git a/internal/rest/resources/cluster.go b/internal/rest/resources/cluster.go index 8551a8970..12f669383 100644 --- a/internal/rest/resources/cluster.go +++ b/internal/rest/resources/cluster.go @@ -32,7 +32,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var clusterCmd = rest.Endpoint{ @@ -61,7 +60,7 @@ var clusterMemberInternalCmd = rest.Endpoint{ Put: rest.EndpointAction{Handler: clusterMemberPut, AccessHandler: access.AllowAuthenticated}, } -func clusterPost(s state.State, r *http.Request) response.Response { +func clusterPost(s types.State, r *http.Request) response.Response { err := s.Database().IsOpen(r.Context()) if err != nil { return response.SmartError(err) @@ -243,7 +242,7 @@ func clusterPost(s state.State, r *http.Request) response.Response { return response.SyncResponse(true, tokenResponse) } -func clusterGet(s state.State, r *http.Request) response.Response { +func clusterGet(s types.State, r *http.Request) response.Response { status := s.Database().Status() // If the database is not in a ready or waiting state, we can't be sure it's available for use. @@ -328,7 +327,7 @@ func clusterGet(s state.State, r *http.Request) response.Response { // from the cluster when not the leader. var clusterDisableMu sync.Mutex -func clusterMemberPut(s state.State, r *http.Request) response.Response { +func clusterMemberPut(s types.State, r *http.Request) response.Response { force := r.URL.Query().Get("force") == "1" reExec, err := resetClusterMember(r.Context(), s, force) if err != nil { @@ -356,7 +355,7 @@ func clusterMemberPut(s state.State, r *http.Request) response.Response { // resetClusterMember clears the daemon state, closing the database and stopping all listeners. // Returns a function that can be used to re-exec the daemon, forcibly reloading its state. -func resetClusterMember(ctx context.Context, s state.State, force bool) (reExec func(), err error) { +func resetClusterMember(ctx context.Context, s types.State, force bool) (reExec func(), err error) { intState, err := internalState.ToInternal(s) if err != nil { return nil, err @@ -415,7 +414,7 @@ func resetClusterMember(ctx context.Context, s state.State, force bool) (reExec } // clusterMemberDelete Removes a cluster member from dqlite and re-execs its daemon. -func clusterMemberDelete(s state.State, r *http.Request) response.Response { +func clusterMemberDelete(s types.State, r *http.Request) response.Response { force := r.URL.Query().Get("force") == "1" addr := r.URL.Query().Get("address") name, err := url.PathUnescape(mux.Vars(r)["name"]) diff --git a/internal/rest/resources/control.go b/internal/rest/resources/control.go index 083e41f15..b1eb98ab7 100644 --- a/internal/rest/resources/control.go +++ b/internal/rest/resources/control.go @@ -22,7 +22,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var controlCmd = rest.Endpoint{ @@ -31,7 +30,7 @@ var controlCmd = rest.Endpoint{ Post: rest.EndpointAction{Handler: controlPost, AccessHandler: access.AllowAuthenticated}, } -func controlPost(state state.State, r *http.Request) response.Response { +func controlPost(state types.State, r *http.Request) response.Response { status := state.Database().Status() if status != types.DatabaseNotReady { return response.SmartError(fmt.Errorf("Unable to initialize cluster: %s", status)) @@ -186,7 +185,7 @@ func controlPost(state state.State, r *http.Request) response.Response { return response.EmptySyncResponse } -func joinWithToken(state state.State, r *http.Request, req *types.Control) (*types.TokenResponse, *types.Remote, error) { +func joinWithToken(state types.State, r *http.Request, req *types.Control) (*types.TokenResponse, *types.Remote, error) { token, err := types.DecodeToken(req.JoinToken) if err != nil { return nil, nil, err @@ -289,7 +288,7 @@ func writeCert(dir, prefix string, cert, key, ca []byte) error { return nil } -func setupLocalMember(state state.State, localClusterMember *types.Remote, joinInfo *types.TokenResponse) ([]string, error) { +func setupLocalMember(state types.State, localClusterMember *types.Remote, joinInfo *types.TokenResponse) ([]string, error) { // Set up cluster certificate. err := writeCert(state.FileSystem().StateDir(), string(types.ClusterCertificateName), []byte(joinInfo.ClusterCert.String()), []byte(joinInfo.ClusterKey), nil) if err != nil { diff --git a/internal/rest/resources/daemon.go b/internal/rest/resources/daemon.go index c6226ee0b..b6a6de957 100644 --- a/internal/rest/resources/daemon.go +++ b/internal/rest/resources/daemon.go @@ -13,7 +13,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var daemonCmd = rest.Endpoint{ @@ -23,7 +22,7 @@ var daemonCmd = rest.Endpoint{ Put: rest.EndpointAction{Handler: daemonServersPut, AccessHandler: access.AllowAuthenticated}, } -func daemonServersGet(s state.State, r *http.Request) response.Response { +func daemonServersGet(s types.State, r *http.Request) response.Response { intState, err := internalState.ToInternal(s) if err != nil { return response.SmartError(err) @@ -32,7 +31,7 @@ func daemonServersGet(s state.State, r *http.Request) response.Response { return response.SyncResponse(true, intState.LocalConfig().GetServers()) } -func daemonServersPut(s state.State, r *http.Request) response.Response { +func daemonServersPut(s types.State, r *http.Request) response.Response { req := make(map[string]types.ServerConfig) // Parse the request. diff --git a/internal/rest/resources/database.go b/internal/rest/resources/database.go index 79716d4f0..6833f9b71 100644 --- a/internal/rest/resources/database.go +++ b/internal/rest/resources/database.go @@ -8,6 +8,7 @@ import ( "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" + "github.com/canonical/microcluster/v3/microcluster/types" ) var databaseCmd = rest.Endpoint{ @@ -18,7 +19,7 @@ var databaseCmd = rest.Endpoint{ Patch: rest.EndpointAction{Handler: databasePatch}, } -func databasePost(state state.State, r *http.Request) response.Response { +func databasePost(state types.State, r *http.Request) response.Response { // Compare the dqlite version of the connecting client with our own. versionHeader := r.Header.Get("X-Dqlite-Version") if versionHeader == "" { @@ -39,7 +40,7 @@ func databasePost(state state.State, r *http.Request) response.Response { return response.EmptySyncResponse } -func databasePatch(s state.State, r *http.Request) response.Response { +func databasePatch(s types.State, r *http.Request) response.Response { // Compare the dqlite version of the connecting client with our own. versionHeader := r.Header.Get("X-Dqlite-Version") if versionHeader == "" { diff --git a/internal/rest/resources/heartbeat.go b/internal/rest/resources/heartbeat.go index 8d9972259..3755a5870 100644 --- a/internal/rest/resources/heartbeat.go +++ b/internal/rest/resources/heartbeat.go @@ -16,7 +16,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var heartbeatCmd = rest.Endpoint{ @@ -25,7 +24,7 @@ var heartbeatCmd = rest.Endpoint{ Post: rest.EndpointAction{Handler: heartbeatPost, AllowUntrusted: true}, } -func heartbeatPost(s state.State, r *http.Request) response.Response { +func heartbeatPost(s types.State, r *http.Request) response.Response { var hbInfo types.HeartbeatInfo err := json.NewDecoder(r.Body).Decode(&hbInfo) if err != nil { @@ -89,7 +88,7 @@ func heartbeatPost(s state.State, r *http.Request) response.Response { // beginHeartbeat initiates a heartbeat from the leader node to all other cluster members, if we haven't sent one out // recently. -func beginHeartbeat(ctx context.Context, s state.State, hbReq types.HeartbeatInfo) response.Response { +func beginHeartbeat(ctx context.Context, s types.State, hbReq types.HeartbeatInfo) response.Response { if s.Address().Host != hbReq.LeaderAddress { return response.SmartError(fmt.Errorf("Attempt to initiate heartbeat from non-leader")) } diff --git a/internal/rest/resources/hooks.go b/internal/rest/resources/hooks.go index 99b0634c2..c796c8968 100644 --- a/internal/rest/resources/hooks.go +++ b/internal/rest/resources/hooks.go @@ -14,7 +14,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var hooksCmd = rest.Endpoint{ @@ -23,7 +22,7 @@ var hooksCmd = rest.Endpoint{ Post: rest.EndpointAction{Handler: hooksPost, AccessHandler: access.AllowAuthenticated, ProxyTarget: true}, } -func hooksPost(s state.State, r *http.Request) response.Response { +func hooksPost(s types.State, r *http.Request) response.Response { hookTypeStr, err := url.PathUnescape(mux.Vars(r)["hookType"]) if err != nil { return response.SmartError(err) diff --git a/internal/rest/resources/hooks_test.go b/internal/rest/resources/hooks_test.go index 9f1a93da2..cfb007c3c 100644 --- a/internal/rest/resources/hooks_test.go +++ b/internal/rest/resources/hooks_test.go @@ -34,20 +34,20 @@ func (t *hooksSuite) Test_hooks() { s := &state.InternalState{ Context: context.TODO(), InternalName: func() string { return "n0" }, - Hooks: &state.Hooks{ - PostRemove: func(ctx context.Context, state state.State, force bool) error { + Hooks: &types.Hooks{ + PostRemove: func(ctx context.Context, state types.State, force bool) error { ranHook = types.PostRemove isForce = force return nil }, - PreRemove: func(ctx context.Context, state state.State, force bool) error { + PreRemove: func(ctx context.Context, state types.State, force bool) error { ranHook = types.PreRemove isForce = force return nil }, - OnNewMember: func(ctx context.Context, state state.State, newMember types.ClusterMemberLocal) error { + OnNewMember: func(ctx context.Context, state types.State, newMember types.ClusterMemberLocal) error { ranHook = types.OnNewMember return nil }, diff --git a/internal/rest/resources/ready.go b/internal/rest/resources/ready.go index dcce6fddd..cdd3e0e53 100644 --- a/internal/rest/resources/ready.go +++ b/internal/rest/resources/ready.go @@ -8,7 +8,7 @@ import ( internalState "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" - "github.com/canonical/microcluster/v3/state" + "github.com/canonical/microcluster/v3/microcluster/types" ) var readyCmd = rest.Endpoint{ @@ -18,7 +18,7 @@ var readyCmd = rest.Endpoint{ Get: rest.EndpointAction{Handler: getWaitReady, AccessHandler: access.AllowAuthenticated}, } -func getWaitReady(state state.State, r *http.Request) response.Response { +func getWaitReady(state types.State, r *http.Request) response.Response { intState, err := internalState.ToInternal(state) if err != nil { return response.SmartError(err) diff --git a/internal/rest/resources/shutdown.go b/internal/rest/resources/shutdown.go index 7e2799b1d..eaa18a4ad 100644 --- a/internal/rest/resources/shutdown.go +++ b/internal/rest/resources/shutdown.go @@ -9,7 +9,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var shutdownCmd = rest.Endpoint{ @@ -19,7 +18,7 @@ var shutdownCmd = rest.Endpoint{ Post: rest.EndpointAction{Handler: shutdownPost, AccessHandler: access.AllowAuthenticated}, } -func shutdownPost(state state.State, r *http.Request) response.Response { +func shutdownPost(state types.State, r *http.Request) response.Response { intState, err := internalState.ToInternal(state) if err != nil { return response.SmartError(err) diff --git a/internal/rest/resources/sql.go b/internal/rest/resources/sql.go index 7c0645de3..2d36beb44 100644 --- a/internal/rest/resources/sql.go +++ b/internal/rest/resources/sql.go @@ -14,7 +14,6 @@ import ( "github.com/canonical/microcluster/v3/internal/db/query" "github.com/canonical/microcluster/v3/internal/log" "github.com/canonical/microcluster/v3/internal/rest/access" - "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" @@ -28,7 +27,7 @@ var sqlCmd = rest.Endpoint{ } // Perform a database dump. -func sqlGet(state state.State, r *http.Request) response.Response { +func sqlGet(state types.State, r *http.Request) response.Response { parentCtx, cancel := context.WithTimeout(r.Context(), 30*time.Second) defer cancel() @@ -54,7 +53,7 @@ func sqlGet(state state.State, r *http.Request) response.Response { } // Execute queries. -func sqlPost(state state.State, r *http.Request) response.Response { +func sqlPost(state types.State, r *http.Request) response.Response { parentCtx, cancel := context.WithTimeout(r.Context(), 30*time.Second) defer cancel() req := &types.SQLQuery{} diff --git a/internal/rest/resources/tokens.go b/internal/rest/resources/tokens.go index 6f427aac5..cdac0fa76 100644 --- a/internal/rest/resources/tokens.go +++ b/internal/rest/resources/tokens.go @@ -20,7 +20,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var tokensCmd = rest.Endpoint{ @@ -36,7 +35,7 @@ var tokenCmd = rest.Endpoint{ Delete: rest.EndpointAction{Handler: tokenDelete, AccessHandler: access.AllowAuthenticated}, } -func tokensPost(state state.State, r *http.Request) response.Response { +func tokensPost(state types.State, r *http.Request) response.Response { req := types.TokenRequest{} // Parse the request. @@ -131,7 +130,7 @@ func tokensPost(state state.State, r *http.Request) response.Response { return response.SyncResponse(true, tokenString) } -func tokensGet(state state.State, r *http.Request) response.Response { +func tokensGet(state types.State, r *http.Request) response.Response { clusterCert, err := state.ClusterCert().PublicKeyX509() if err != nil { return response.InternalError(err) @@ -173,7 +172,7 @@ func tokensGet(state state.State, r *http.Request) response.Response { return response.SyncResponse(true, records) } -func tokenDelete(state state.State, r *http.Request) response.Response { +func tokenDelete(state types.State, r *http.Request) response.Response { name, err := url.PathUnescape(mux.Vars(r)["name"]) if err != nil { return response.SmartError(err) diff --git a/internal/rest/resources/truststore.go b/internal/rest/resources/truststore.go index 71a987578..aeb86269c 100644 --- a/internal/rest/resources/truststore.go +++ b/internal/rest/resources/truststore.go @@ -18,7 +18,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) var trustCmd = rest.Endpoint{ @@ -35,7 +34,7 @@ var trustEntryCmd = rest.Endpoint{ Delete: rest.EndpointAction{Handler: trustDelete, AccessHandler: access.AllowAuthenticated}, } -func trustPost(s state.State, r *http.Request) response.Response { +func trustPost(s types.State, r *http.Request) response.Response { req := types.ClusterMemberLocal{} // Parse the request. @@ -114,7 +113,7 @@ func trustPost(s state.State, r *http.Request) response.Response { return response.EmptySyncResponse } -func trustDelete(s state.State, r *http.Request) response.Response { +func trustDelete(s types.State, r *http.Request) response.Response { name, err := url.PathUnescape(mux.Vars(r)["name"]) if err != nil { return response.SmartError(err) diff --git a/internal/rest/rest.go b/internal/rest/rest.go index f260d692a..7bde4e56d 100644 --- a/internal/rest/rest.go +++ b/internal/rest/rest.go @@ -21,10 +21,10 @@ import ( internalState "github.com/canonical/microcluster/v3/internal/state" "github.com/canonical/microcluster/v3/microcluster/rest" "github.com/canonical/microcluster/v3/microcluster/rest/response" - "github.com/canonical/microcluster/v3/state" + "github.com/canonical/microcluster/v3/microcluster/types" ) -func handleAPIRequest(action rest.EndpointAction, state state.State, w http.ResponseWriter, r *http.Request) response.Response { +func handleAPIRequest(action rest.EndpointAction, state types.State, w http.ResponseWriter, r *http.Request) response.Response { if action.Handler == nil { return response.NotImplemented(nil) } @@ -67,7 +67,7 @@ func handleAPIRequest(action rest.EndpointAction, state state.State, w http.Resp return action.Handler(state, r) } -func proxyTarget(action rest.EndpointAction, s state.State, r *http.Request) response.Response { +func proxyTarget(action rest.EndpointAction, s types.State, r *http.Request) response.Response { if r.URL == nil { return action.Handler(s, r) } @@ -162,7 +162,7 @@ func proxyTarget(action rest.EndpointAction, s state.State, r *http.Request) res return response.SyncResponse(true, resp.Metadata) } -func handleDatabaseRequest(action rest.EndpointAction, state state.State, w http.ResponseWriter, r *http.Request) response.Response { +func handleDatabaseRequest(action rest.EndpointAction, state types.State, w http.ResponseWriter, r *http.Request) response.Response { trusted := r.Context().Value(client.CtxAccess) if trusted == nil { return response.Forbidden(nil) @@ -216,7 +216,7 @@ func handleDatabaseRequest(action rest.EndpointAction, state state.State, w http // HandleEndpoint adds the endpoint to the mux router. A function variable is used to implement common logic // before calling the endpoint action handler associated with the request method, if it exists. -func HandleEndpoint(state state.State, mux *mux.Router, version string, e rest.Endpoint) { +func HandleEndpoint(state types.State, mux *mux.Router, version string, e rest.Endpoint) { url := "/" + version if e.Path != "" { url = filepath.Join(url, e.Path) diff --git a/internal/state/hooks.go b/internal/state/hooks.go deleted file mode 100644 index ddd124b3f..000000000 --- a/internal/state/hooks.go +++ /dev/null @@ -1,43 +0,0 @@ -package state - -import ( - "context" - - "github.com/canonical/microcluster/v3/microcluster/types" -) - -// Hooks holds customizable functions that can be called at varying points by the daemon to. -// integrate with other tools. -type Hooks struct { - // PreInit is run before the daemon is initialized. - PreInit func(ctx context.Context, s State, bootstrap bool, initConfig map[string]string) error - - // PostBootstrap is run after the daemon is initialized and bootstrapped. - PostBootstrap func(ctx context.Context, s State, initConfig map[string]string) error - - // OnStart is run after the daemon is started. Its context will not be cancelled until the daemon is shutting down. - OnStart func(ctx context.Context, s State) error - - // PostJoin is run after the daemon is initialized, joined the cluster and existing members triggered - // their 'OnNewMember' hooks. - PostJoin func(ctx context.Context, s 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(ctx context.Context, s State, initConfig map[string]string) error - - // PreRemove is run on a cluster member just before it is removed from the cluster. - PreRemove func(ctx context.Context, s State, force bool) error - - // PostRemove is run on all other peers after one is removed from the cluster. - PostRemove func(ctx context.Context, s State, force bool) error - - // OnHeartbeat is run after a successful heartbeat round. - OnHeartbeat func(ctx context.Context, s State, roleStatus map[string]types.RoleStatus) error - - // OnNewMember is run on each peer after a new cluster member has joined and executed their 'PreJoin' hook. - OnNewMember func(ctx context.Context, s State, newMember types.ClusterMemberLocal) error - - // OnDaemonConfigUpdate is a post-action hook that is run on all cluster members when any cluster member receives a local configuration update. - OnDaemonConfigUpdate func(ctx context.Context, s State, config types.DaemonConfig) error -} diff --git a/internal/state/state.go b/internal/state/state.go index c253c8f85..57a554451 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -24,42 +24,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/types" ) -// State exposes the internal daemon state for use with extended API handlers. -type State interface { - // FileSystem structure. - FileSystem() types.OS - - // Listen Address. - Address() *url.URL - - // Name of the cluster member. - Name() string - - // Version is provided by the MicroCluster consumer. - Version() string - - // Server certificate is used for server-to-server connection. - ServerCert() *shared.CertInfo - - // Cluster certificate is used for downstream connections within a cluster. - ClusterCert() *shared.CertInfo - - // Database. - Database() types.DB - - // Local truststore access. - Truststore() types.Store - - // Returns a connector for interconnection with the cluster. - Connect() types.Connector - - // HasExtension returns whether the given API extension is supported. - HasExtension(ext string) bool - - // ExtensionServers returns an immutable list of the daemon's additional listeners. - ExtensionServers() []string -} - // InternalState is a gateway to the stateful components of the microcluster daemon. type InternalState struct { // Context. @@ -99,7 +63,7 @@ type InternalState struct { Extensions types.Extensions // Hooks contain external implementations that are triggered by specific cluster actions. - Hooks *Hooks + Hooks *types.Hooks InternalFileSystem func() types.OS InternalAddress func() *url.URL @@ -271,7 +235,7 @@ func (s *InternalState) RandomMember(isNotification bool) (types.Client, error) } // ToInternal returns the underlying InternalState from the exposed State interface. -func ToInternal(s State) (*InternalState, error) { +func ToInternal(s types.State) (*InternalState, error) { internal, ok := s.(*InternalState) if ok { return internal, nil diff --git a/microcluster/rest/rest.go b/microcluster/rest/rest.go index 6eb142011..ff7338861 100644 --- a/microcluster/rest/rest.go +++ b/microcluster/rest/rest.go @@ -6,7 +6,6 @@ import ( "github.com/canonical/microcluster/v3/microcluster/rest/response" "github.com/canonical/microcluster/v3/microcluster/types" - "github.com/canonical/microcluster/v3/state" ) // EndpointAlias represents an alias URL of and Endpoint in our API. @@ -17,8 +16,8 @@ type EndpointAlias struct { // EndpointAction represents an action on an API endpoint. type EndpointAction struct { - Handler func(state state.State, r *http.Request) response.Response - AccessHandler func(state state.State, r *http.Request) (trusted bool, resp response.Response) + Handler func(state types.State, r *http.Request) response.Response + AccessHandler func(state types.State, r *http.Request) (trusted bool, resp response.Response) AllowUntrusted bool ProxyTarget bool // Allow forwarding of the request to a target if ?target=name is specified. } diff --git a/microcluster/types/hooks.go b/microcluster/types/hooks.go index 681cd9858..72515c2c2 100644 --- a/microcluster/types/hooks.go +++ b/microcluster/types/hooks.go @@ -1,5 +1,7 @@ package types +import "context" + // HookType represents the various types of hooks available to microcluster. type HookType string @@ -48,3 +50,39 @@ type HookNewMemberOptions struct { // Name is the name of the new cluster member that joined the cluster, triggering this hook. NewMember ClusterMemberLocal `json:"new_member" yaml:"new_member"` } + +// Hooks holds customizable functions that can be called at varying points by the daemon to +// integrate with other tools. +type Hooks struct { + // PreInit is run before the daemon is initialized. + PreInit func(ctx context.Context, s State, bootstrap bool, initConfig map[string]string) error + + // PostBootstrap is run after the daemon is initialized and bootstrapped. + PostBootstrap func(ctx context.Context, s State, initConfig map[string]string) error + + // OnStart is run after the daemon is started. Its context will not be cancelled until the daemon is shutting down. + OnStart func(ctx context.Context, s State) error + + // PostJoin is run after the daemon is initialized, joined the cluster and existing members triggered + // their 'OnNewMember' hooks. + PostJoin func(ctx context.Context, s 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(ctx context.Context, s State, initConfig map[string]string) error + + // PreRemove is run on a cluster member just before it is removed from the cluster. + PreRemove func(ctx context.Context, s State, force bool) error + + // PostRemove is run on all other peers after one is removed from the cluster. + PostRemove func(ctx context.Context, s State, force bool) error + + // OnHeartbeat is run after a successful heartbeat round. + OnHeartbeat func(ctx context.Context, s State, roleStatus map[string]RoleStatus) error + + // OnNewMember is run on each peer after a new cluster member has joined and executed their 'PreJoin' hook. + OnNewMember func(ctx context.Context, s State, newMember ClusterMemberLocal) error + + // OnDaemonConfigUpdate is a post-action hook that is run on all cluster members when any cluster member receives a local configuration update. + OnDaemonConfigUpdate func(ctx context.Context, s State, config DaemonConfig) error +} diff --git a/microcluster/types/state.go b/microcluster/types/state.go new file mode 100644 index 000000000..a29222a87 --- /dev/null +++ b/microcluster/types/state.go @@ -0,0 +1,43 @@ +package types + +import ( + "net/url" + + "github.com/canonical/lxd/shared" +) + +// State exposes the internal daemon state for use with extended API handlers. +type State interface { + // Listen Address. + Address() *url.URL + + // Name of the cluster member. + Name() string + + // Version is provided by the MicroCluster consumer. + Version() string + + // Server certificate is used for server-to-server connection. + ServerCert() *shared.CertInfo + + // Cluster certificate is used for downstream connections within a cluster. + ClusterCert() *shared.CertInfo + + // HasExtension returns whether the given API extension is supported. + HasExtension(ext string) bool + + // ExtensionServers returns an immutable list of the daemon's additional listeners. + ExtensionServers() []string + + // FileSystem structure. + FileSystem() OS + + // Database. + Database() DB + + // Local truststore access. + Truststore() Store + + // Returns a connector for interconnection with the cluster. + Connect() Connector +} diff --git a/state/state.go b/state/state.go deleted file mode 100644 index 90db23768..000000000 --- a/state/state.go +++ /dev/null @@ -1,9 +0,0 @@ -package state - -import "github.com/canonical/microcluster/v3/internal/state" - -// State exposes the internal daemon state for use with extended API handlers. -type State = state.State - -// Hooks exposes the Hooks struct to be imported by the upstream project. -type Hooks = state.Hooks