Make Daemon into a concrete type which implements RPC directly

This commit is contained in:
Brian Picciano 2024-09-07 13:52:32 +02:00
parent fed79c6ec7
commit ef86c1bbd1
10 changed files with 173 additions and 297 deletions

View File

@ -2,13 +2,13 @@ package main
import ( import (
"fmt" "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) res, err := ctx.daemonRPC.GetHosts(ctx)
if err != nil { if err != nil {
return daemon.GetHostsResult{}, fmt.Errorf("calling GetHosts: %w", err) return nil, fmt.Errorf("calling GetHosts: %w", err)
} }
return res, nil return res, nil
} }

View File

@ -77,7 +77,7 @@ var subCmdDaemon = subCmd{
{ {
logger := logger.WithNamespace("http") logger := logger.WithNamespace("http")
httpSrv, err := newHTTPServer( httpSrv, err := newHTTPServer(
ctx, logger, daemon.NewRPC(daemonInst), ctx, logger, daemonInst,
) )
if err != nil { if err != nil {
return fmt.Errorf("starting HTTP server: %w", err) return fmt.Errorf("starting HTTP server: %w", err)

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"isle/daemon" "isle/daemon"
"isle/daemon/jsonrpc2"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -18,7 +17,7 @@ import (
const daemonHTTPRPCPath = "/rpc/v0.json" const daemonHTTPRPCPath = "/rpc/v0.json"
func newHTTPServer( func newHTTPServer(
ctx context.Context, logger *mlog.Logger, rpc daemon.RPC, ctx context.Context, logger *mlog.Logger, daemonInst *daemon.Daemon,
) ( ) (
*http.Server, error, *http.Server, error,
) { ) {
@ -51,16 +50,8 @@ func newHTTPServer(
} }
logger.Info(ctx, "HTTP server socket created") logger.Info(ctx, "HTTP server socket created")
rpcHandler := jsonrpc2.Chain(
jsonrpc2.NewMLogMiddleware(logger.WithNamespace("rpc")),
jsonrpc2.ExposeServerSideErrorsMiddleware,
)(
jsonrpc2.NewDispatchHandler(&rpc),
)
httpMux := http.NewServeMux() httpMux := http.NewServeMux()
httpMux.Handle(daemonHTTPRPCPath, jsonrpc2.NewHTTPHandler(rpcHandler)) httpMux.Handle(daemonHTTPRPCPath, daemonInst.HTTPRPCHandler())
srv := &http.Server{Handler: httpMux} srv := &http.Server{Handler: httpMux}

View File

@ -43,7 +43,11 @@ var subCmdHostCreate = subCmd{
return errors.New("--hostname is required") 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{ ctx, hostName.V, daemon.CreateHostOpts{
IP: ip.V, IP: ip.V,
CanCreateHosts: *canCreateHosts, CanCreateHosts: *canCreateHosts,
@ -53,7 +57,7 @@ var subCmdHostCreate = subCmd{
return fmt.Errorf("calling CreateHost: %w", err) 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"` Storage bootstrap.GarageHost `json:",omitempty"`
} }
hosts := make([]host, 0, len(hostsRes.Hosts)) hosts := make([]host, 0, len(hostsRes))
for _, h := range hostsRes.Hosts { for _, h := range hostsRes {
host := host{ host := host{
Name: string(h.Name), Name: string(h.Name),

View File

@ -53,7 +53,7 @@ var subCmdNebulaCreateCert = subCmd{
return fmt.Errorf("calling CreateNebulaCertificate: %w", err) return fmt.Errorf("calling CreateNebulaCertificate: %w", err)
} }
nebulaHostCertPEM, err := res.HostNebulaCertificate.Unwrap().MarshalToPEM() nebulaHostCertPEM, err := res.Unwrap().MarshalToPEM()
if err != nil { if err != nil {
return fmt.Errorf("marshaling cert to PEM: %w", err) return fmt.Errorf("marshaling cert to PEM: %w", err)
} }
@ -112,7 +112,7 @@ var subCmdNebulaShow = subCmd{
SubnetCIDR: subnet.String(), SubnetCIDR: subnet.String(),
} }
for _, h := range hosts.Hosts { for _, h := range hosts {
if h.Nebula.PublicAddr == "" { if h.Nebula.PublicAddr == "" {
continue continue
} }

View File

@ -8,6 +8,7 @@ package daemon
import ( import (
"context" "context"
"isle/bootstrap"
"isle/daemon/jsonrpc2" "isle/daemon/jsonrpc2"
"isle/nebula" "isle/nebula"
) )
@ -22,24 +23,24 @@ func RPCFromClient(client jsonrpc2.Client) RPC {
return &rpcClient{client} 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( err = c.client.Call(
ctx, ctx,
&c2, &j1,
"CreateHost", "CreateHost",
hostName, h1,
opts, c2,
) )
return 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( err = c.client.Call(
ctx, ctx,
&c2, &c2,
"CreateNebulaCertificate", "CreateNebulaCertificate",
hostName, h1,
hostEncryptingPublicKey, e1,
) )
return return
} }
@ -66,10 +67,10 @@ func (c *rpcClient) GetGarageClientParams(ctx context.Context) (g1 GarageClientP
return 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( err = c.client.Call(
ctx, ctx,
&g1, &ha1,
"GetHosts", "GetHosts",
) )
return return
@ -84,12 +85,12 @@ func (c *rpcClient) GetNebulaCAPublicCredentials(ctx context.Context) (c2 nebula
return 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( err = c.client.Call(
ctx, ctx,
nil, nil,
"JoinNetwork", "JoinNetwork",
req, j1,
) )
return return
} }

View File

@ -4,6 +4,7 @@ package daemon
import ( import (
"bytes" "bytes"
"cmp"
"context" "context"
"crypto/rand" "crypto/rand"
"errors" "errors"
@ -17,97 +18,14 @@ import (
"net/netip" "net/netip"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"sync" "sync"
"time" "time"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog" "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 // Opts are optional parameters which can be passed in when initializing a new
// Daemon instance. A nil Opts is equivalent to a zero value. // Daemon instance. A nil Opts is equivalent to a zero value.
type Opts struct { type Opts struct {
@ -145,7 +63,25 @@ const (
daemonStateShutdown 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 logger *mlog.Logger
daemonConfig Config daemonConfig Config
envBinDirPath string envBinDirPath string
@ -163,20 +99,7 @@ type daemon struct {
wg sync.WaitGroup wg sync.WaitGroup
} }
// NewDaemon initializes and returns a Daemon instance which will manage all // NewDaemon initializes and returns a Daemon.
// 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.
func NewDaemon( func NewDaemon(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
@ -184,10 +107,10 @@ func NewDaemon(
envBinDirPath string, envBinDirPath string,
opts *Opts, opts *Opts,
) ( ) (
Daemon, error, *Daemon, error,
) { ) {
var ( var (
d = &daemon{ d = &Daemon{
logger: logger, logger: logger,
daemonConfig: daemonConfig, daemonConfig: daemonConfig,
envBinDirPath: envBinDirPath, envBinDirPath: envBinDirPath,
@ -229,7 +152,7 @@ func NewDaemon(
} }
// initialize must be called with d.l write lock held. // initialize must be called with d.l write lock held.
func (d *daemon) initialize( func (d *Daemon) initialize(
ctx context.Context, currBootstrap bootstrap.Bootstrap, ctx context.Context, currBootstrap bootstrap.Bootstrap,
) error { ) error {
// we update this Host's data using whatever configuration has been provided // we update this Host's data using whatever configuration has been provided
@ -295,7 +218,7 @@ func (d *daemon) initialize(
} }
func withCurrBootstrap[Res any]( func withCurrBootstrap[Res any](
d *daemon, fn func(bootstrap.Bootstrap) (Res, error), d *Daemon, fn func(bootstrap.Bootstrap) (Res, error),
) (Res, error) { ) (Res, error) {
var zero Res var zero Res
d.l.RLock() d.l.RLock()
@ -320,7 +243,7 @@ func withCurrBootstrap[Res any](
// reload will check the existing hosts data from currBootstrap against a // reload will check the existing hosts data from currBootstrap against a
// potentially updated set of hosts data, and if there are any differences will // potentially updated set of hosts data, and if there are any differences will
// perform whatever changes are necessary. // perform whatever changes are necessary.
func (d *daemon) reload( func (d *Daemon) reload(
ctx context.Context, ctx context.Context,
currBootstrap bootstrap.Bootstrap, currBootstrap bootstrap.Bootstrap,
newHosts map[nebula.HostName]bootstrap.Host, newHosts map[nebula.HostName]bootstrap.Host,
@ -374,7 +297,7 @@ func (d *daemon) reload(
return errors.Join(errs...) 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 { if len(d.daemonConfig.Storage.Allocations) > 0 {
d.logger.Info(ctx, "Applying garage layout") d.logger.Info(ctx, "Applying garage layout")
if err := garageApplyLayout( if err := garageApplyLayout(
@ -420,7 +343,7 @@ func (d *daemon) postInit(ctx context.Context) error {
return nil return nil
} }
func (d *daemon) reloadLoop(ctx context.Context) { func (d *Daemon) reloadLoop(ctx context.Context) {
ticker := time.NewTicker(3 * time.Minute) ticker := time.NewTicker(3 * time.Minute)
defer ticker.Stop() 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, ctx context.Context,
name, domain string, ipNet nebula.IPNet, hostName nebula.HostName, name, domain string, ipNet nebula.IPNet, hostName nebula.HostName,
) error { ) error {
@ -515,7 +448,12 @@ func (d *daemon) CreateNetwork(
return nil 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, ctx context.Context, newBootstrap JoiningBootstrap,
) error { ) error {
d.l.Lock() d.l.Lock()
@ -538,7 +476,7 @@ func (d *daemon) JoinNetwork(
return nil 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( return withCurrBootstrap(d, func(
currBootstrap bootstrap.Bootstrap, 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, ctx context.Context,
) ( ) (
GarageClientParams, error, 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 // TODO RemoveHost should publish a certificate revocation for the host
// being removed. // being removed.
_, err := withCurrBootstrap(d, func( _, 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") 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, ctx context.Context,
hostName nebula.HostName, hostName nebula.HostName,
opts CreateHostOpts, opts CreateHostOpts,
@ -746,7 +733,16 @@ func (d *daemon) CreateHost(
return joiningBootstrap, nil 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, ctx context.Context,
hostName nebula.HostName, hostName nebula.HostName,
hostPubKey nebula.EncryptingPublicKey, 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() d.l.Lock()
defer d.l.Unlock() defer d.l.Unlock()

View File

@ -19,7 +19,7 @@ type GarageClientParams struct {
RPCSecret string RPCSecret string
} }
func (d *daemon) getGarageClientParams( func (d *Daemon) getGarageClientParams(
ctx context.Context, currBootstrap bootstrap.Bootstrap, ctx context.Context, currBootstrap bootstrap.Bootstrap,
) ( ) (
GarageClientParams, error, GarageClientParams, error,

View File

@ -63,7 +63,7 @@ func garageInitializeGlobalBucket(
// putGarageBoostrapHost places the <hostname>.json.signed file for this host // putGarageBoostrapHost places the <hostname>.json.signed file for this host
// into garage so that other hosts are able to see relevant configuration for // into garage so that other hosts are able to see relevant configuration for
// it. // it.
func (d *daemon) putGarageBoostrapHost( func (d *Daemon) putGarageBoostrapHost(
ctx context.Context, currBootstrap bootstrap.Bootstrap, ctx context.Context, currBootstrap bootstrap.Bootstrap,
) error { ) error {
garageClientParams, err := d.getGarageClientParams(ctx, currBootstrap) garageClientParams, err := d.getGarageClientParams(ctx, currBootstrap)
@ -112,7 +112,7 @@ func (d *daemon) putGarageBoostrapHost(
return nil return nil
} }
func (d *daemon) getGarageBootstrapHosts( func (d *Daemon) getGarageBootstrapHosts(
ctx context.Context, currBootstrap bootstrap.Bootstrap, ctx context.Context, currBootstrap bootstrap.Bootstrap,
) ( ) (
map[nebula.HostName]bootstrap.Host, error, map[nebula.HostName]bootstrap.Host, error,

View File

@ -1,44 +1,17 @@
package daemon package daemon
import ( import (
"cmp"
"context" "context"
"fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon/jsonrpc2"
"isle/nebula" "isle/nebula"
"slices" "net/http"
"golang.org/x/exp/maps"
) )
// GetHostsResult wraps the results from the GetHosts RPC method. // RPC defines the methods which the Daemon exposes over RPC (via the RPCHandler
type GetHostsResult struct { // or HTTPHandler methods). Method documentation can be found on the Daemon
Hosts []bootstrap.Host // type.
}
// 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.
type RPC interface { 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( CreateNetwork(
ctx context.Context, ctx context.Context,
name string, name string,
@ -47,141 +20,47 @@ type RPC interface {
hostName nebula.HostName, hostName nebula.HostName,
) error ) error
// JoinNetwork passes through to the Daemon method of the same name. JoinNetwork(context.Context, JoiningBootstrap) error
JoinNetwork(ctx context.Context, req JoiningBootstrap) error
// GetHosts returns all hosts known to the network, sorted by their name. GetHosts(context.Context) ([]bootstrap.Host, error)
GetHosts(ctx context.Context) (GetHostsResult, error)
// GetGarageClientParams passes the call through to the Daemon method of the GetGarageClientParams(context.Context) (GarageClientParams, error)
// same name.
GetGarageClientParams(ctx context.Context) (GarageClientParams, error)
// GetNebulaCAPublicCredentials returns the CAPublicCredentials for the
// network.
GetNebulaCAPublicCredentials( GetNebulaCAPublicCredentials(
ctx context.Context, context.Context,
) ( ) (
nebula.CAPublicCredentials, error, nebula.CAPublicCredentials, error,
) )
// RemoveHost passes the call through to the Daemon method of the same name.
RemoveHost(ctx context.Context, hostName nebula.HostName) error RemoveHost(ctx context.Context, hostName nebula.HostName) error
// CreateHost passes the call through to the Daemon method of the same name.
CreateHost( 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( CreateNebulaCertificate(
ctx context.Context, context.Context, nebula.HostName, nebula.EncryptingPublicKey,
hostName nebula.HostName,
hostEncryptingPublicKey nebula.EncryptingPublicKey,
) ( ) (
CreateNebulaCertificateResult, error, nebula.Certificate, error,
) )
} }
type rpcImpl struct { // RPCHandler returns a jsonrpc2.Handler which will use the Daemon to serve all
daemon Daemon // methods defined on the RPC interface.
} func (d *Daemon) RPCHandler() jsonrpc2.Handler {
rpc := RPC(d)
// NewRPC initializes and returns an RPC instance. return jsonrpc2.Chain(
func NewRPC(daemon Daemon) RPC { jsonrpc2.NewMLogMiddleware(d.logger.WithNamespace("rpc")),
return &rpcImpl{daemon} jsonrpc2.ExposeServerSideErrorsMiddleware,
} )(
jsonrpc2.NewDispatchHandler(&rpc),
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,
) )
} }
func (r *rpcImpl) JoinNetwork( // HTTPRPCHandler returns an http.Handler which will use the Daemon to serve all
ctx context.Context, req JoiningBootstrap, // methods defined on the RPC interface via the JSONRPC2 protocol.
) error { func (d *Daemon) HTTPRPCHandler() http.Handler {
return r.daemon.JoinNetwork(ctx, req) return jsonrpc2.NewHTTPHandler(d.RPCHandler())
}
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
} }