From ef86c1bbd1ab12bbbf4cc443ff70d46af8b1e328 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sat, 7 Sep 2024 13:52:32 +0200 Subject: [PATCH] Make Daemon into a concrete type which implements RPC directly --- go/cmd/entrypoint/client.go | 6 +- go/cmd/entrypoint/daemon.go | 2 +- go/cmd/entrypoint/daemon_util.go | 13 +- go/cmd/entrypoint/host.go | 12 +- go/cmd/entrypoint/nebula.go | 4 +- go/daemon/client.go | 23 +-- go/daemon/daemon.go | 231 +++++++++++++++--------------- go/daemon/garage_client_params.go | 2 +- go/daemon/global_bucket.go | 4 +- go/daemon/rpc.go | 173 ++++------------------ 10 files changed, 173 insertions(+), 297 deletions(-) diff --git a/go/cmd/entrypoint/client.go b/go/cmd/entrypoint/client.go index cba6ba2..6787ac3 100644 --- a/go/cmd/entrypoint/client.go +++ b/go/cmd/entrypoint/client.go @@ -2,13 +2,13 @@ package main import ( "fmt" - "isle/daemon" + "isle/bootstrap" ) -func (ctx subCmdCtx) getHosts() (daemon.GetHostsResult, error) { +func (ctx subCmdCtx) getHosts() ([]bootstrap.Host, error) { res, err := ctx.daemonRPC.GetHosts(ctx) if err != nil { - return daemon.GetHostsResult{}, fmt.Errorf("calling GetHosts: %w", err) + return nil, fmt.Errorf("calling GetHosts: %w", err) } return res, nil } diff --git a/go/cmd/entrypoint/daemon.go b/go/cmd/entrypoint/daemon.go index a21525b..654aff4 100644 --- a/go/cmd/entrypoint/daemon.go +++ b/go/cmd/entrypoint/daemon.go @@ -77,7 +77,7 @@ var subCmdDaemon = subCmd{ { logger := logger.WithNamespace("http") httpSrv, err := newHTTPServer( - ctx, logger, daemon.NewRPC(daemonInst), + ctx, logger, daemonInst, ) if err != nil { return fmt.Errorf("starting HTTP server: %w", err) diff --git a/go/cmd/entrypoint/daemon_util.go b/go/cmd/entrypoint/daemon_util.go index 2ef2f93..3f415b5 100644 --- a/go/cmd/entrypoint/daemon_util.go +++ b/go/cmd/entrypoint/daemon_util.go @@ -6,7 +6,6 @@ import ( "fmt" "io/fs" "isle/daemon" - "isle/daemon/jsonrpc2" "net" "net/http" "os" @@ -18,7 +17,7 @@ import ( const daemonHTTPRPCPath = "/rpc/v0.json" func newHTTPServer( - ctx context.Context, logger *mlog.Logger, rpc daemon.RPC, + ctx context.Context, logger *mlog.Logger, daemonInst *daemon.Daemon, ) ( *http.Server, error, ) { @@ -51,16 +50,8 @@ func newHTTPServer( } logger.Info(ctx, "HTTP server socket created") - - rpcHandler := jsonrpc2.Chain( - jsonrpc2.NewMLogMiddleware(logger.WithNamespace("rpc")), - jsonrpc2.ExposeServerSideErrorsMiddleware, - )( - jsonrpc2.NewDispatchHandler(&rpc), - ) - httpMux := http.NewServeMux() - httpMux.Handle(daemonHTTPRPCPath, jsonrpc2.NewHTTPHandler(rpcHandler)) + httpMux.Handle(daemonHTTPRPCPath, daemonInst.HTTPRPCHandler()) srv := &http.Server{Handler: httpMux} diff --git a/go/cmd/entrypoint/host.go b/go/cmd/entrypoint/host.go index 70ab281..b3aa681 100644 --- a/go/cmd/entrypoint/host.go +++ b/go/cmd/entrypoint/host.go @@ -43,7 +43,11 @@ var subCmdHostCreate = subCmd{ return errors.New("--hostname is required") } - res, err := ctx.daemonRPC.CreateHost( + var ( + res daemon.JoiningBootstrap + err error + ) + res, err = ctx.daemonRPC.CreateHost( ctx, hostName.V, daemon.CreateHostOpts{ IP: ip.V, CanCreateHosts: *canCreateHosts, @@ -53,7 +57,7 @@ var subCmdHostCreate = subCmd{ return fmt.Errorf("calling CreateHost: %w", err) } - return json.NewEncoder(os.Stdout).Encode(res.JoiningBootstrap) + return json.NewEncoder(os.Stdout).Encode(res) }, } @@ -74,8 +78,8 @@ var subCmdHostList = subCmd{ Storage bootstrap.GarageHost `json:",omitempty"` } - hosts := make([]host, 0, len(hostsRes.Hosts)) - for _, h := range hostsRes.Hosts { + hosts := make([]host, 0, len(hostsRes)) + for _, h := range hostsRes { host := host{ Name: string(h.Name), diff --git a/go/cmd/entrypoint/nebula.go b/go/cmd/entrypoint/nebula.go index 01bd67e..30df076 100644 --- a/go/cmd/entrypoint/nebula.go +++ b/go/cmd/entrypoint/nebula.go @@ -53,7 +53,7 @@ var subCmdNebulaCreateCert = subCmd{ return fmt.Errorf("calling CreateNebulaCertificate: %w", err) } - nebulaHostCertPEM, err := res.HostNebulaCertificate.Unwrap().MarshalToPEM() + nebulaHostCertPEM, err := res.Unwrap().MarshalToPEM() if err != nil { return fmt.Errorf("marshaling cert to PEM: %w", err) } @@ -112,7 +112,7 @@ var subCmdNebulaShow = subCmd{ SubnetCIDR: subnet.String(), } - for _, h := range hosts.Hosts { + for _, h := range hosts { if h.Nebula.PublicAddr == "" { continue } diff --git a/go/daemon/client.go b/go/daemon/client.go index 1fa8db0..c7a2400 100644 --- a/go/daemon/client.go +++ b/go/daemon/client.go @@ -8,6 +8,7 @@ package daemon import ( "context" + "isle/bootstrap" "isle/daemon/jsonrpc2" "isle/nebula" ) @@ -22,24 +23,24 @@ func RPCFromClient(client jsonrpc2.Client) RPC { return &rpcClient{client} } -func (c *rpcClient) CreateHost(ctx context.Context, hostName nebula.HostName, opts CreateHostOpts) (c2 CreateHostResult, err error) { +func (c *rpcClient) CreateHost(ctx context.Context, h1 nebula.HostName, c2 CreateHostOpts) (j1 JoiningBootstrap, err error) { err = c.client.Call( ctx, - &c2, + &j1, "CreateHost", - hostName, - opts, + h1, + c2, ) return } -func (c *rpcClient) CreateNebulaCertificate(ctx context.Context, hostName nebula.HostName, hostEncryptingPublicKey nebula.EncryptingPublicKey) (c2 CreateNebulaCertificateResult, err error) { +func (c *rpcClient) CreateNebulaCertificate(ctx context.Context, h1 nebula.HostName, e1 nebula.EncryptingPublicKey) (c2 nebula.Certificate, err error) { err = c.client.Call( ctx, &c2, "CreateNebulaCertificate", - hostName, - hostEncryptingPublicKey, + h1, + e1, ) return } @@ -66,10 +67,10 @@ func (c *rpcClient) GetGarageClientParams(ctx context.Context) (g1 GarageClientP return } -func (c *rpcClient) GetHosts(ctx context.Context) (g1 GetHostsResult, err error) { +func (c *rpcClient) GetHosts(ctx context.Context) (ha1 []bootstrap.Host, err error) { err = c.client.Call( ctx, - &g1, + &ha1, "GetHosts", ) return @@ -84,12 +85,12 @@ func (c *rpcClient) GetNebulaCAPublicCredentials(ctx context.Context) (c2 nebula return } -func (c *rpcClient) JoinNetwork(ctx context.Context, req JoiningBootstrap) (err error) { +func (c *rpcClient) JoinNetwork(ctx context.Context, j1 JoiningBootstrap) (err error) { err = c.client.Call( ctx, nil, "JoinNetwork", - req, + j1, ) return } diff --git a/go/daemon/daemon.go b/go/daemon/daemon.go index e44f736..1e01f92 100644 --- a/go/daemon/daemon.go +++ b/go/daemon/daemon.go @@ -4,6 +4,7 @@ package daemon import ( "bytes" + "cmp" "context" "crypto/rand" "errors" @@ -17,97 +18,14 @@ import ( "net/netip" "os" "path/filepath" + "slices" "sync" "time" "dev.mediocregopher.com/mediocre-go-lib.git/mlog" + "golang.org/x/exp/maps" ) -// CreateHostOpts are optional parameters to the CreateHost method. -type CreateHostOpts struct { - // IP address of the new host. An IP address will be randomly chosen if one - // is not given here. - IP netip.Addr - - // CanCreateHosts indicates that the bootstrap produced by CreateHost should - // give the new host the ability to create new hosts as well. - CanCreateHosts bool - - // TODO add nebula cert tags -} - -// Daemon presents all functionality required for client frontends to interact -// with isle, typically via the unix socket. -type Daemon interface { - - // CreateNetwork will initialize a new network using the given parameters. - // - name: Human-readable name of the network. - // - domain: Primary domain name that network services are served under. - // - ipNet: An IP subnet, in CIDR form, which will be the overall range of - // possible IPs in the network. The first IP in this network range will - // become this first host's IP. - // - hostName: The name of this first host in the network. - // - // The daemon on which this is called will become the first host in the - // network, and will have full administrative privileges. - CreateNetwork( - ctx context.Context, name, domain string, - ipNet nebula.IPNet, - hostName nebula.HostName, - ) error - - // JoinNetwork joins the Daemon to an existing network using the given - // Bootstrap. - // - // Errors: - // - ErrAlreadyJoined - JoinNetwork(context.Context, JoiningBootstrap) error - - // GetBootstraps returns the currently active Bootstrap. - GetBootstrap(context.Context) (bootstrap.Bootstrap, error) - - // GetGarageClientParams returns a GarageClientParams for the current - // network state. - GetGarageClientParams(context.Context) (GarageClientParams, error) - - // RemoveHost removes the host of the given name from the network. - RemoveHost(context.Context, nebula.HostName) error - - // CreateHost creates a bootstrap for a new host with the given name and IP - // address. - CreateHost( - ctx context.Context, - hostName nebula.HostName, - opts CreateHostOpts, - ) ( - JoiningBootstrap, error, - ) - - // CreateNebulaCertificate creates and signs a new nebula certficate for an - // existing host, given the public key for that host. This is currently - // mostly useful for creating certs for mobile devices. - // - // TODO replace this with CreateHostBootstrap, and the - // CreateNebulaCertificate RPC method can just pull cert out of that. - // - // Errors: - // - ErrHostNotFound - CreateNebulaCertificate( - ctx context.Context, - hostName nebula.HostName, - hostPubKey nebula.EncryptingPublicKey, - ) ( - nebula.Certificate, error, - ) - - // Shutdown blocks until all resources held or created by the daemon, - // including child processes it has started, have been cleaned up. - // - // If this returns an error then it's possible that child processes are - // still running and are no longer managed. - Shutdown() error -} - // Opts are optional parameters which can be passed in when initializing a new // Daemon instance. A nil Opts is equivalent to a zero value. type Opts struct { @@ -145,7 +63,25 @@ const ( daemonStateShutdown ) -type daemon struct { +var _ RPC = (*Daemon)(nil) + +// Daemon implements all methods of the Daemon interface, plus others used +// to manage this particular implementation. +// +// Daemon manages all child processes and state required by the isle +// service, as well as an HTTP socket over which RPC calls will be served. +// +// Inner Children instance(s) will be wrapped such that they will be +// automatically shutdown and re-created whenever there's changes in the network +// which require the configuration to be changed (e.g. a new nebula lighthouse). +// During such an inner restart all methods will return ErrRestarting, except +// Shutdown which will block until the currently executing restart is finished +// and then shutdown cleanly from there. +// +// While still starting up the Daemon for the first time all methods will return +// ErrInitializing, except Shutdown which will block until initialization is +// canceled. +type Daemon struct { logger *mlog.Logger daemonConfig Config envBinDirPath string @@ -163,20 +99,7 @@ type daemon struct { wg sync.WaitGroup } -// NewDaemon initializes and returns a Daemon instance which will manage all -// child processes and state required by the isle service, as well as an HTTP -// socket over which RPC calls will be served. -// -// Inner Children instance(s) will be wrapped such that they will be -// automatically shutdown and re-created whenever there's changes in the network -// which require the configuration to be changed (e.g. a new nebula lighthouse). -// During such an inner restart all methods will return ErrRestarting, except -// Shutdown which will block until the currently executing restart is finished -// and then shutdown cleanly from there. -// -// While still starting up the Daemon for the first time all methods will return -// ErrInitializing, except Shutdown which will block until initialization is -// canceled. +// NewDaemon initializes and returns a Daemon. func NewDaemon( ctx context.Context, logger *mlog.Logger, @@ -184,10 +107,10 @@ func NewDaemon( envBinDirPath string, opts *Opts, ) ( - Daemon, error, + *Daemon, error, ) { var ( - d = &daemon{ + d = &Daemon{ logger: logger, daemonConfig: daemonConfig, envBinDirPath: envBinDirPath, @@ -229,7 +152,7 @@ func NewDaemon( } // initialize must be called with d.l write lock held. -func (d *daemon) initialize( +func (d *Daemon) initialize( ctx context.Context, currBootstrap bootstrap.Bootstrap, ) error { // we update this Host's data using whatever configuration has been provided @@ -295,7 +218,7 @@ func (d *daemon) initialize( } func withCurrBootstrap[Res any]( - d *daemon, fn func(bootstrap.Bootstrap) (Res, error), + d *Daemon, fn func(bootstrap.Bootstrap) (Res, error), ) (Res, error) { var zero Res d.l.RLock() @@ -320,7 +243,7 @@ func withCurrBootstrap[Res any]( // reload will check the existing hosts data from currBootstrap against a // potentially updated set of hosts data, and if there are any differences will // perform whatever changes are necessary. -func (d *daemon) reload( +func (d *Daemon) reload( ctx context.Context, currBootstrap bootstrap.Bootstrap, newHosts map[nebula.HostName]bootstrap.Host, @@ -374,7 +297,7 @@ func (d *daemon) reload( return errors.Join(errs...) } -func (d *daemon) postInit(ctx context.Context) error { +func (d *Daemon) postInit(ctx context.Context) error { if len(d.daemonConfig.Storage.Allocations) > 0 { d.logger.Info(ctx, "Applying garage layout") if err := garageApplyLayout( @@ -420,7 +343,7 @@ func (d *daemon) postInit(ctx context.Context) error { return nil } -func (d *daemon) reloadLoop(ctx context.Context) { +func (d *Daemon) reloadLoop(ctx context.Context) { ticker := time.NewTicker(3 * time.Minute) defer ticker.Stop() @@ -454,7 +377,17 @@ func (d *daemon) reloadLoop(ctx context.Context) { } } -func (d *daemon) CreateNetwork( +// CreateNetwork will initialize a new network using the given parameters. +// - name: Human-readable name of the network. +// - domain: Primary domain name that network services are served under. +// - ipNet: An IP subnet, in CIDR form, which will be the overall range of +// possible IPs in the network. The first IP in this network range will become +// this first host's IP. +// - hostName: The name of this first host in the network. +// +// The daemon on which this is called will become the first host in the network, +// and will have full administrative privileges. +func (d *Daemon) CreateNetwork( ctx context.Context, name, domain string, ipNet nebula.IPNet, hostName nebula.HostName, ) error { @@ -515,7 +448,12 @@ func (d *daemon) CreateNetwork( return nil } -func (d *daemon) JoinNetwork( +// JoinNetwork joins the Daemon to an existing network using the given +// Bootstrap. +// +// Errors: +// - ErrAlreadyJoined +func (d *Daemon) JoinNetwork( ctx context.Context, newBootstrap JoiningBootstrap, ) error { d.l.Lock() @@ -538,7 +476,7 @@ func (d *daemon) JoinNetwork( return nil } -func (d *daemon) GetBootstrap(ctx context.Context) (bootstrap.Bootstrap, error) { +func (d *Daemon) getBootstrap(ctx context.Context) (bootstrap.Bootstrap, error) { return withCurrBootstrap(d, func( currBootstrap bootstrap.Bootstrap, ) ( @@ -548,7 +486,24 @@ func (d *daemon) GetBootstrap(ctx context.Context) (bootstrap.Bootstrap, error) }) } -func (d *daemon) GetGarageClientParams( +// GetHosts returns all hosts known to the network, sorted by their name. +func (d *Daemon) GetHosts(ctx context.Context) ([]bootstrap.Host, error) { + b, err := d.getBootstrap(ctx) + if err != nil { + return nil, fmt.Errorf("retrieving bootstrap: %w", err) + } + + hosts := maps.Values(b.Hosts) + slices.SortFunc(hosts, func(a, b bootstrap.Host) int { + return cmp.Compare(a.Name, b.Name) + }) + + return hosts, nil +} + +// GetGarageClientParams returns a GarageClientParams for the current network +// state. +func (d *Daemon) GetGarageClientParams( ctx context.Context, ) ( GarageClientParams, error, @@ -562,7 +517,24 @@ func (d *daemon) GetGarageClientParams( }) } -func (d *daemon) RemoveHost(ctx context.Context, hostName nebula.HostName) error { +// GetNebulaCAPublicCredentials returns the CAPublicCredentials for the network. +func (d *Daemon) GetNebulaCAPublicCredentials( + ctx context.Context, +) ( + nebula.CAPublicCredentials, error, +) { + b, err := d.getBootstrap(ctx) + if err != nil { + return nebula.CAPublicCredentials{}, fmt.Errorf( + "retrieving bootstrap: %w", err, + ) + } + + return b.CAPublicCredentials, nil +} + +// RemoveHost removes the host of the given name from the network. +func (d *Daemon) RemoveHost(ctx context.Context, hostName nebula.HostName) error { // TODO RemoveHost should publish a certificate revocation for the host // being removed. _, err := withCurrBootstrap(d, func( @@ -670,7 +642,22 @@ func chooseAvailableIP(b bootstrap.Bootstrap) (netip.Addr, error) { panic("All ips are in-use, but somehow that wasn't determined earlier") } -func (d *daemon) CreateHost( +// CreateHostOpts are optional parameters to the CreateHost method. +type CreateHostOpts struct { + // IP address of the new host. An IP address will be randomly chosen if one + // is not given here. + IP netip.Addr + + // CanCreateHosts indicates that the bootstrap produced by CreateHost should + // give the new host the ability to create new hosts as well. + CanCreateHosts bool + + // TODO add nebula cert tags +} + +// CreateHost creates a bootstrap for a new host with the given name and IP +// address. +func (d *Daemon) CreateHost( ctx context.Context, hostName nebula.HostName, opts CreateHostOpts, @@ -746,7 +733,16 @@ func (d *daemon) CreateHost( return joiningBootstrap, nil } -func (d *daemon) CreateNebulaCertificate( +// CreateNebulaCertificate creates and signs a new nebula certficate for an +// existing host, given the public key for that host. This is currently mostly +// useful for creating certs for mobile devices. +// +// TODO replace this with CreateHostBootstrap, and the CreateNebulaCertificate +// RPC method can just pull cert out of that. +// +// Errors: +// - ErrHostNotFound +func (d *Daemon) CreateNebulaCertificate( ctx context.Context, hostName nebula.HostName, hostPubKey nebula.EncryptingPublicKey, @@ -777,7 +773,12 @@ func (d *daemon) CreateNebulaCertificate( }) } -func (d *daemon) Shutdown() error { +// Shutdown blocks until all resources held or created by the daemon, +// including child processes it has started, have been cleaned up. +// +// If this returns an error then it's possible that child processes are +// still running and are no longer managed. +func (d *Daemon) Shutdown() error { d.l.Lock() defer d.l.Unlock() diff --git a/go/daemon/garage_client_params.go b/go/daemon/garage_client_params.go index 2474241..8bc24fc 100644 --- a/go/daemon/garage_client_params.go +++ b/go/daemon/garage_client_params.go @@ -19,7 +19,7 @@ type GarageClientParams struct { RPCSecret string } -func (d *daemon) getGarageClientParams( +func (d *Daemon) getGarageClientParams( ctx context.Context, currBootstrap bootstrap.Bootstrap, ) ( GarageClientParams, error, diff --git a/go/daemon/global_bucket.go b/go/daemon/global_bucket.go index 396f978..95e1297 100644 --- a/go/daemon/global_bucket.go +++ b/go/daemon/global_bucket.go @@ -63,7 +63,7 @@ func garageInitializeGlobalBucket( // putGarageBoostrapHost places the .json.signed file for this host // into garage so that other hosts are able to see relevant configuration for // it. -func (d *daemon) putGarageBoostrapHost( +func (d *Daemon) putGarageBoostrapHost( ctx context.Context, currBootstrap bootstrap.Bootstrap, ) error { garageClientParams, err := d.getGarageClientParams(ctx, currBootstrap) @@ -112,7 +112,7 @@ func (d *daemon) putGarageBoostrapHost( return nil } -func (d *daemon) getGarageBootstrapHosts( +func (d *Daemon) getGarageBootstrapHosts( ctx context.Context, currBootstrap bootstrap.Bootstrap, ) ( map[nebula.HostName]bootstrap.Host, error, diff --git a/go/daemon/rpc.go b/go/daemon/rpc.go index 965a43c..a845295 100644 --- a/go/daemon/rpc.go +++ b/go/daemon/rpc.go @@ -1,44 +1,17 @@ package daemon import ( - "cmp" "context" - "fmt" "isle/bootstrap" + "isle/daemon/jsonrpc2" "isle/nebula" - "slices" - - "golang.org/x/exp/maps" + "net/http" ) -// GetHostsResult wraps the results from the GetHosts RPC method. -type GetHostsResult struct { - Hosts []bootstrap.Host -} - -// CreateHostResult wraps the results from the CreateHost RPC method. -type CreateHostResult struct { - JoiningBootstrap JoiningBootstrap -} - -// CreateNebulaCertificateResult wraps the results from the -// CreateNebulaCertificate RPC method. -type CreateNebulaCertificateResult struct { - HostNebulaCertificate nebula.Certificate -} - -// RPC exposes all RPC methods which are available to be called over the RPC -// interface. +// RPC defines the methods which the Daemon exposes over RPC (via the RPCHandler +// or HTTPHandler methods). Method documentation can be found on the Daemon +// type. type RPC interface { - // CreateNetwork passes through to the Daemon method of the same name. - // - // name: Human-readable name of the network. - // domain: Primary domain name that network services are served under. - // ipNet: - // An IP subnet, in CIDR form, which will be the overall range of - // possible IPs in the network. The first IP in this network range will - // become this first host's IP. - // hostName: The name of this first host in the network. CreateNetwork( ctx context.Context, name string, @@ -47,141 +20,47 @@ type RPC interface { hostName nebula.HostName, ) error - // JoinNetwork passes through to the Daemon method of the same name. - JoinNetwork(ctx context.Context, req JoiningBootstrap) error + JoinNetwork(context.Context, JoiningBootstrap) error - // GetHosts returns all hosts known to the network, sorted by their name. - GetHosts(ctx context.Context) (GetHostsResult, error) + GetHosts(context.Context) ([]bootstrap.Host, error) - // GetGarageClientParams passes the call through to the Daemon method of the - // same name. - GetGarageClientParams(ctx context.Context) (GarageClientParams, error) + GetGarageClientParams(context.Context) (GarageClientParams, error) - // GetNebulaCAPublicCredentials returns the CAPublicCredentials for the - // network. GetNebulaCAPublicCredentials( - ctx context.Context, + context.Context, ) ( nebula.CAPublicCredentials, error, ) - // RemoveHost passes the call through to the Daemon method of the same name. RemoveHost(ctx context.Context, hostName nebula.HostName) error - // CreateHost passes the call through to the Daemon method of the same name. CreateHost( - ctx context.Context, hostName nebula.HostName, opts CreateHostOpts, + context.Context, nebula.HostName, CreateHostOpts, ) ( - CreateHostResult, error, + JoiningBootstrap, error, ) - // CreateNebulaCertificate passes the call through to the Daemon method of - // the same name. CreateNebulaCertificate( - ctx context.Context, - hostName nebula.HostName, - hostEncryptingPublicKey nebula.EncryptingPublicKey, + context.Context, nebula.HostName, nebula.EncryptingPublicKey, ) ( - CreateNebulaCertificateResult, error, + nebula.Certificate, error, ) } -type rpcImpl struct { - daemon Daemon -} - -// NewRPC initializes and returns an RPC instance. -func NewRPC(daemon Daemon) RPC { - return &rpcImpl{daemon} -} - -func (r *rpcImpl) CreateNetwork( - ctx context.Context, - name string, - domain string, - ipNet nebula.IPNet, - hostName nebula.HostName, -) error { - return r.daemon.CreateNetwork( - ctx, name, domain, ipNet, hostName, +// RPCHandler returns a jsonrpc2.Handler which will use the Daemon to serve all +// methods defined on the RPC interface. +func (d *Daemon) RPCHandler() jsonrpc2.Handler { + rpc := RPC(d) + return jsonrpc2.Chain( + jsonrpc2.NewMLogMiddleware(d.logger.WithNamespace("rpc")), + jsonrpc2.ExposeServerSideErrorsMiddleware, + )( + jsonrpc2.NewDispatchHandler(&rpc), ) } -func (r *rpcImpl) JoinNetwork( - ctx context.Context, req JoiningBootstrap, -) error { - return r.daemon.JoinNetwork(ctx, req) -} - -func (r *rpcImpl) GetHosts(ctx context.Context) (GetHostsResult, error) { - b, err := r.daemon.GetBootstrap(ctx) - if err != nil { - return GetHostsResult{}, fmt.Errorf("retrieving bootstrap: %w", err) - } - - hosts := maps.Values(b.Hosts) - slices.SortFunc(hosts, func(a, b bootstrap.Host) int { - return cmp.Compare(a.Name, b.Name) - }) - - return GetHostsResult{hosts}, nil -} - -func (r *rpcImpl) GetGarageClientParams( - ctx context.Context, -) ( - GarageClientParams, error, -) { - return r.daemon.GetGarageClientParams(ctx) -} - -func (r *rpcImpl) GetNebulaCAPublicCredentials( - ctx context.Context, -) ( - nebula.CAPublicCredentials, error, -) { - b, err := r.daemon.GetBootstrap(ctx) - if err != nil { - return nebula.CAPublicCredentials{}, fmt.Errorf( - "retrieving bootstrap: %w", err, - ) - } - - return b.CAPublicCredentials, nil -} - -func (r *rpcImpl) RemoveHost( - ctx context.Context, hostName nebula.HostName, -) error { - return r.daemon.RemoveHost(ctx, hostName) -} - -func (r *rpcImpl) CreateHost( - ctx context.Context, hostName nebula.HostName, opts CreateHostOpts, -) ( - CreateHostResult, error, -) { - joiningBootstrap, err := r.daemon.CreateHost(ctx, hostName, opts) - if err != nil { - return CreateHostResult{}, err - } - - return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil -} - -func (r *rpcImpl) CreateNebulaCertificate( - ctx context.Context, - hostName nebula.HostName, - hostEncryptingPublicKey nebula.EncryptingPublicKey, -) ( - CreateNebulaCertificateResult, error, -) { - cert, err := r.daemon.CreateNebulaCertificate( - ctx, hostName, hostEncryptingPublicKey, - ) - if err != nil { - return CreateNebulaCertificateResult{}, err - } - - return CreateNebulaCertificateResult{HostNebulaCertificate: cert}, nil +// HTTPRPCHandler returns an http.Handler which will use the Daemon to serve all +// methods defined on the RPC interface via the JSONRPC2 protocol. +func (d *Daemon) HTTPRPCHandler() http.Handler { + return jsonrpc2.NewHTTPHandler(d.RPCHandler()) }