From 53ad8a91b4c0ff4e165d1ba49d51cff37a05a3b1 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Wed, 4 Sep 2024 21:24:45 +0200 Subject: [PATCH] Generate RPC client wrapper --- go/cmd/entrypoint/client.go | 3 +- go/cmd/entrypoint/daemon_util.go | 2 +- go/cmd/entrypoint/garage.go | 12 +- go/cmd/entrypoint/host.go | 12 +- go/cmd/entrypoint/nebula.go | 13 +- go/cmd/entrypoint/network.go | 35 ++-- go/cmd/entrypoint/sub_cmd.go | 22 +-- go/daemon/client.go | 69 ++++++++ go/daemon/jsonrpc2/client_gen.tpl | 26 +++ go/daemon/rpc.go | 256 ++++++++++++++++++------------ nix/gowrap.nix | 17 ++ shell.nix | 1 + 12 files changed, 309 insertions(+), 159 deletions(-) create mode 100644 go/daemon/client.go create mode 100644 go/daemon/jsonrpc2/client_gen.tpl create mode 100644 nix/gowrap.nix diff --git a/go/cmd/entrypoint/client.go b/go/cmd/entrypoint/client.go index d37e4a5..ea2f716 100644 --- a/go/cmd/entrypoint/client.go +++ b/go/cmd/entrypoint/client.go @@ -6,8 +6,7 @@ import ( ) func (ctx subCmdCtx) getHosts() (daemon.GetHostsResult, error) { - var res daemon.GetHostsResult - err := ctx.daemonRCPClient.Call(ctx.ctx, &res, "GetHosts", nil) + res, err := ctx.daemonRPC.GetHosts(ctx.ctx, struct{}{}) if err != nil { return daemon.GetHostsResult{}, fmt.Errorf("calling GetHosts: %w", err) } diff --git a/go/cmd/entrypoint/daemon_util.go b/go/cmd/entrypoint/daemon_util.go index 3e933a0..2ef2f93 100644 --- a/go/cmd/entrypoint/daemon_util.go +++ b/go/cmd/entrypoint/daemon_util.go @@ -18,7 +18,7 @@ import ( const daemonHTTPRPCPath = "/rpc/v0.json" func newHTTPServer( - ctx context.Context, logger *mlog.Logger, rpc *daemon.RPC, + ctx context.Context, logger *mlog.Logger, rpc daemon.RPC, ) ( *http.Server, error, ) { diff --git a/go/cmd/entrypoint/garage.go b/go/cmd/entrypoint/garage.go index d789941..1eca175 100644 --- a/go/cmd/entrypoint/garage.go +++ b/go/cmd/entrypoint/garage.go @@ -6,8 +6,6 @@ import ( "os" "path/filepath" "syscall" - - "isle/daemon" ) // minio-client keeps a configuration directory which contains various pieces of @@ -53,9 +51,8 @@ var subCmdGarageMC = subCmd{ return fmt.Errorf("parsing flags: %w", err) } - var clientParams daemon.GarageClientParams - err := subCmdCtx.daemonRCPClient.Call( - subCmdCtx.ctx, &clientParams, "GetGarageClientParams", nil, + clientParams, err := subCmdCtx.daemonRPC.GetGarageClientParams( + subCmdCtx.ctx, struct{}{}, ) if err != nil { return fmt.Errorf("calling GetGarageClientParams: %w", err) @@ -120,9 +117,8 @@ var subCmdGarageCLI = subCmd{ descr: "Runs the garage binary, automatically configured to point to the garage sub-process of a running isle daemon", do: func(subCmdCtx subCmdCtx) error { - var clientParams daemon.GarageClientParams - err := subCmdCtx.daemonRCPClient.Call( - subCmdCtx.ctx, &clientParams, "GetGarageClientParams", nil, + clientParams, err := subCmdCtx.daemonRPC.GetGarageClientParams( + subCmdCtx.ctx, struct{}{}, ) if err != nil { return fmt.Errorf("calling GetGarageClientParams: %w", err) diff --git a/go/cmd/entrypoint/host.go b/go/cmd/entrypoint/host.go index 7ff8d4f..ff5c235 100644 --- a/go/cmd/entrypoint/host.go +++ b/go/cmd/entrypoint/host.go @@ -43,12 +43,8 @@ var subCmdHostCreate = subCmd{ return errors.New("--hostname is required") } - var res daemon.CreateHostResult - err := subCmdCtx.daemonRCPClient.Call( - subCmdCtx.ctx, - &res, - "CreateHost", - daemon.CreateHostRequest{ + res, err := subCmdCtx.daemonRPC.CreateHost( + subCmdCtx.ctx, daemon.CreateHostRequest{ HostName: hostName.V, Opts: daemon.CreateHostOpts{ IP: ip.V, @@ -123,8 +119,8 @@ var subCmdHostRemove = subCmd{ return errors.New("--hostname is required") } - err := subCmdCtx.daemonRCPClient.Call( - subCmdCtx.ctx, nil, "RemoveHost", daemon.RemoveHostRequest{ + _, err := subCmdCtx.daemonRPC.RemoveHost( + subCmdCtx.ctx, daemon.RemoveHostRequest{ HostName: hostName.V, }, ) diff --git a/go/cmd/entrypoint/nebula.go b/go/cmd/entrypoint/nebula.go index 1ec8fe6..5869afd 100644 --- a/go/cmd/entrypoint/nebula.go +++ b/go/cmd/entrypoint/nebula.go @@ -47,12 +47,8 @@ var subCmdNebulaCreateCert = subCmd{ return fmt.Errorf("unmarshaling public key as PEM: %w", err) } - var res daemon.CreateNebulaCertificateResult - err = subCmdCtx.daemonRCPClient.Call( - subCmdCtx.ctx, - &res, - "CreateNebulaCertificate", - daemon.CreateNebulaCertificateRequest{ + res, err := subCmdCtx.daemonRPC.CreateNebulaCertificate( + subCmdCtx.ctx, daemon.CreateNebulaCertificateRequest{ HostName: hostName.V, HostEncryptingPublicKey: hostPub, }, @@ -89,9 +85,8 @@ var subCmdNebulaShow = subCmd{ return fmt.Errorf("getting hosts: %w", err) } - var caPublicCreds nebula.CAPublicCredentials - err = subCmdCtx.daemonRCPClient.Call( - subCmdCtx.ctx, &caPublicCreds, "GetNebulaCAPublicCredentials", nil, + caPublicCreds, err := subCmdCtx.daemonRPC.GetNebulaCAPublicCredentials( + subCmdCtx.ctx, struct{}{}, ) if err != nil { return fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err) diff --git a/go/cmd/entrypoint/network.go b/go/cmd/entrypoint/network.go index 99a1af6..5672858 100644 --- a/go/cmd/entrypoint/network.go +++ b/go/cmd/entrypoint/network.go @@ -12,9 +12,7 @@ var subCmdNetworkCreate = subCmd{ descr: "Create's a new network, with this host being the first host in that network.", do: func(subCmdCtx subCmdCtx) error { var ( - ctx = subCmdCtx.ctx - flags = subCmdCtx.flagSet(false) - + flags = subCmdCtx.flagSet(false) ipNet ipNetFlag hostName hostNameFlag ) @@ -53,14 +51,14 @@ var subCmdNetworkCreate = subCmd{ return errors.New("--name, --domain, --ip-net, and --hostname are required") } - req := daemon.CreateNetworkRequest{ - Name: *name, - Domain: *domain, - IPNet: ipNet.V, - HostName: hostName.V, - } - - err := subCmdCtx.daemonRCPClient.Call(ctx, nil, "CreateNetwork", req) + _, err := subCmdCtx.daemonRPC.CreateNetwork( + subCmdCtx.ctx, daemon.CreateNetworkRequest{ + Name: *name, + Domain: *domain, + IPNet: ipNet.V, + HostName: hostName.V, + }, + ) if err != nil { return fmt.Errorf("creating network: %w", err) } @@ -74,12 +72,10 @@ var subCmdNetworkJoin = subCmd{ descr: "Joins this host to an existing network", do: func(subCmdCtx subCmdCtx) error { var ( - ctx = subCmdCtx.ctx - flags = subCmdCtx.flagSet(false) - ) - - bootstrapPath := flags.StringP( - "bootstrap-path", "b", "", "Path to a bootstrap.json file.", + flags = subCmdCtx.flagSet(false) + bootstrapPath = flags.StringP( + "bootstrap-path", "b", "", "Path to a bootstrap.json file.", + ) ) if err := flags.Parse(subCmdCtx.args); err != nil { @@ -97,9 +93,10 @@ var subCmdNetworkJoin = subCmd{ ) } - return subCmdCtx.daemonRCPClient.Call( - ctx, nil, "JoinNetwork", newBootstrap, + _, err := subCmdCtx.daemonRPC.JoinNetwork( + subCmdCtx.ctx, newBootstrap, ) + return err }, } diff --git a/go/cmd/entrypoint/sub_cmd.go b/go/cmd/entrypoint/sub_cmd.go index 1c1b1e6..fb0686e 100644 --- a/go/cmd/entrypoint/sub_cmd.go +++ b/go/cmd/entrypoint/sub_cmd.go @@ -34,9 +34,9 @@ type subCmdCtx struct { args []string // command-line arguments, excluding the subCmd itself. subCmdNames []string // names of subCmds so far, including this one - ctx context.Context - logger *mlog.Logger - daemonRCPClient jsonrpc2.Client + ctx context.Context + logger *mlog.Logger + daemonRPC daemon.RPC } type subCmd struct { @@ -128,17 +128,17 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error { printUsageExit(subCmdName) } - daemonRCPClient := jsonrpc2.NewUnixHTTPClient( - daemon.HTTPSocketPath(), daemonHTTPRPCPath, + daemonRPC := daemon.RPCFromClient( + jsonrpc2.NewUnixHTTPClient(daemon.HTTPSocketPath(), daemonHTTPRPCPath), ) err := subCmd.do(subCmdCtx{ - subCmd: subCmd, - args: args, - subCmdNames: append(ctx.subCmdNames, subCmdName), - ctx: ctx.ctx, - logger: ctx.logger, - daemonRCPClient: daemonRCPClient, + subCmd: subCmd, + args: args, + subCmdNames: append(ctx.subCmdNames, subCmdName), + ctx: ctx.ctx, + logger: ctx.logger, + daemonRPC: daemonRPC, }) if err != nil { diff --git a/go/daemon/client.go b/go/daemon/client.go new file mode 100644 index 0000000..4bcc0a4 --- /dev/null +++ b/go/daemon/client.go @@ -0,0 +1,69 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: jsonrpc2/client_gen.tpl +// gowrap: http://github.com/hexdigest/gowrap + +package daemon + +//go:generate gowrap gen -p isle/daemon -i RPC -t jsonrpc2/client_gen.tpl -o client.go -l "" + +import ( + "context" + "isle/daemon/jsonrpc2" + "isle/nebula" +) + +type rpcClient struct { + client jsonrpc2.Client +} + +// RPCFromClient wraps a Client so that it implements the +// RPC interface. +func RPCFromClient(client jsonrpc2.Client) RPC { + return &rpcClient{client} +} + +func (c *rpcClient) CreateHost(ctx context.Context, req CreateHostRequest) (c2 CreateHostResult, err error) { + err = c.client.Call(ctx, &c2, "CreateHost", req) + return +} + +func (c *rpcClient) CreateNebulaCertificate(ctx context.Context, req CreateNebulaCertificateRequest) (c2 CreateNebulaCertificateResult, err error) { + err = c.client.Call(ctx, &c2, "CreateNebulaCertificate", req) + return +} + +func (c *rpcClient) CreateNetwork(ctx context.Context, req CreateNetworkRequest) (st1 struct { +}, err error) { + err = c.client.Call(ctx, &st1, "CreateNetwork", req) + return +} + +func (c *rpcClient) GetGarageClientParams(ctx context.Context, req struct { +}) (g1 GarageClientParams, err error) { + err = c.client.Call(ctx, &g1, "GetGarageClientParams", req) + return +} + +func (c *rpcClient) GetHosts(ctx context.Context, req struct { +}) (g1 GetHostsResult, err error) { + err = c.client.Call(ctx, &g1, "GetHosts", req) + return +} + +func (c *rpcClient) GetNebulaCAPublicCredentials(ctx context.Context, req struct { +}) (c2 nebula.CAPublicCredentials, err error) { + err = c.client.Call(ctx, &c2, "GetNebulaCAPublicCredentials", req) + return +} + +func (c *rpcClient) JoinNetwork(ctx context.Context, req JoiningBootstrap) (st1 struct { +}, err error) { + err = c.client.Call(ctx, &st1, "JoinNetwork", req) + return +} + +func (c *rpcClient) RemoveHost(ctx context.Context, req RemoveHostRequest) (st1 struct { +}, err error) { + err = c.client.Call(ctx, &st1, "RemoveHost", req) + return +} diff --git a/go/daemon/jsonrpc2/client_gen.tpl b/go/daemon/jsonrpc2/client_gen.tpl new file mode 100644 index 0000000..ee4cbfc --- /dev/null +++ b/go/daemon/jsonrpc2/client_gen.tpl @@ -0,0 +1,26 @@ +import ( + "isle/daemon/jsonrpc2" +) + +{{ $t := printf "%sClient" (down .Interface.Name) }} + +type {{$t}} struct { + client jsonrpc2.Client +} + +// {{.Interface.Name}}FromClient wraps a Client so that it implements the +// {{.Interface.Name}} interface. +func {{.Interface.Name}}FromClient(client jsonrpc2.Client) {{.Interface.Name}} { + return &{{$t}}{client} +} + +{{range $method := .Interface.Methods}} +func (c *{{$t}}) {{$method.Declaration}} { + {{- $ctx := (index $method.Params 0).Name}} + {{- $arg := (index $method.Params 1).Name}} + {{- $rcv := (index $method.Results 0).Name}} + {{- $err := (index $method.Results 1).Name}} + {{- $err}} = c.client.Call({{$ctx}}, &{{$rcv}}, "{{$method.Name}}", {{$arg}}) + return +} +{{end}} diff --git a/go/daemon/rpc.go b/go/daemon/rpc.go index 548e439..d929ed3 100644 --- a/go/daemon/rpc.go +++ b/go/daemon/rpc.go @@ -11,17 +11,6 @@ import ( "golang.org/x/exp/maps" ) -// RPC exposes all RPC methods which are available to be called over the RPC -// interface. -type RPC struct { - daemon Daemon -} - -// NewRPC initializes and returns an RPC instance. -func NewRPC(daemon Daemon) *RPC { - return &RPC{daemon} -} - // CreateNetworkRequest contains the arguments to the CreateNetwork RPC method. // // All fields are required. @@ -41,76 +30,11 @@ type CreateNetworkRequest struct { HostName nebula.HostName } -// CreateNetwork passes through to the Daemon method of the same name. -func (r *RPC) CreateNetwork( - ctx context.Context, req CreateNetworkRequest, -) ( - struct{}, error, -) { - return struct{}{}, r.daemon.CreateNetwork( - ctx, req.Name, req.Domain, req.IPNet, req.HostName, - ) -} - -// JoinNetwork passes through to the Daemon method of the same name. -func (r *RPC) JoinNetwork( - ctx context.Context, req JoiningBootstrap, -) ( - struct{}, error, -) { - return struct{}{}, r.daemon.JoinNetwork(ctx, req) -} - // GetHostsResult wraps the results from the GetHosts RPC method. type GetHostsResult struct { Hosts []bootstrap.Host } -// GetHosts returns all hosts known to the network, sorted by their name. -func (r *RPC) GetHosts( - ctx context.Context, req struct{}, -) ( - 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 -} - -// GetGarageClientParams passes the call through to the Daemon method of the -// same name. -func (r *RPC) GetGarageClientParams( - ctx context.Context, req struct{}, -) ( - GarageClientParams, error, -) { - return r.daemon.GetGarageClientParams(ctx) -} - -// GetNebulaCAPublicCredentials returns the CAPublicCredentials for the network. -func (r *RPC) GetNebulaCAPublicCredentials( - ctx context.Context, req struct{}, -) ( - 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 -} - // RemoveHostRequest contains the arguments to the RemoveHost RPC method. // // All fields are required. @@ -118,11 +42,6 @@ type RemoveHostRequest struct { HostName nebula.HostName } -// RemoveHost passes the call through to the Daemon method of the same name. -func (r *RPC) RemoveHost(ctx context.Context, req RemoveHostRequest) (struct{}, error) { - return struct{}{}, r.daemon.RemoveHost(ctx, req.HostName) -} - // CreateHostRequest contains the arguments to the // CreateHost RPC method. // @@ -137,23 +56,6 @@ type CreateHostResult struct { JoiningBootstrap JoiningBootstrap } -// CreateHost passes the call through to the Daemon method of the -// same name. -func (r *RPC) CreateHost( - ctx context.Context, req CreateHostRequest, -) ( - CreateHostResult, error, -) { - joiningBootstrap, err := r.daemon.CreateHost( - ctx, req.HostName, req.Opts, - ) - if err != nil { - return CreateHostResult{}, err - } - - return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil -} - // CreateNebulaCertificateRequest contains the arguments to the // CreateNebulaCertificate RPC method. // @@ -169,9 +71,161 @@ type CreateNebulaCertificateResult struct { HostNebulaCertifcate nebula.Certificate } -// CreateNebulaCertificate passes the call through to the Daemon method of the -// same name. -func (r *RPC) CreateNebulaCertificate( +// RPC exposes all RPC methods which are available to be called over the RPC +// interface. +type RPC interface { + // CreateNetwork passes through to the Daemon method of the same name. + CreateNetwork( + ctx context.Context, req CreateNetworkRequest, + ) ( + struct{}, error, + ) + + // JoinNetwork passes through to the Daemon method of the same name. + JoinNetwork( + ctx context.Context, req JoiningBootstrap, + ) ( + struct{}, error, + ) + + // GetHosts returns all hosts known to the network, sorted by their name. + GetHosts( + ctx context.Context, req struct{}, + ) ( + GetHostsResult, error, + ) + + // GetGarageClientParams passes the call through to the Daemon method of the + // same name. + GetGarageClientParams( + ctx context.Context, req struct{}, + ) ( + GarageClientParams, error, + ) + + // GetNebulaCAPublicCredentials returns the CAPublicCredentials for the + // network. + GetNebulaCAPublicCredentials( + ctx context.Context, req struct{}, + ) ( + nebula.CAPublicCredentials, error, + ) + + // RemoveHost passes the call through to the Daemon method of the same name. + RemoveHost( + ctx context.Context, req RemoveHostRequest, + ) ( + struct{}, error, + ) + + // CreateHost passes the call through to the Daemon method of the same name. + CreateHost( + ctx context.Context, req CreateHostRequest, + ) ( + CreateHostResult, error, + ) + + // CreateNebulaCertificate passes the call through to the Daemon method of + // the same name. + CreateNebulaCertificate( + ctx context.Context, req CreateNebulaCertificateRequest, + ) ( + CreateNebulaCertificateResult, 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, req CreateNetworkRequest, +) ( + struct{}, error, +) { + return struct{}{}, r.daemon.CreateNetwork( + ctx, req.Name, req.Domain, req.IPNet, req.HostName, + ) +} + +func (r *rpcImpl) JoinNetwork( + ctx context.Context, req JoiningBootstrap, +) ( + struct{}, error, +) { + return struct{}{}, r.daemon.JoinNetwork(ctx, req) +} + +func (r *rpcImpl) GetHosts( + ctx context.Context, req struct{}, +) ( + 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, req struct{}, +) ( + GarageClientParams, error, +) { + return r.daemon.GetGarageClientParams(ctx) +} + +func (r *rpcImpl) GetNebulaCAPublicCredentials( + ctx context.Context, req struct{}, +) ( + 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, req RemoveHostRequest, +) ( + struct{}, error, +) { + return struct{}{}, r.daemon.RemoveHost(ctx, req.HostName) +} + +func (r *rpcImpl) CreateHost( + ctx context.Context, req CreateHostRequest, +) ( + CreateHostResult, error, +) { + joiningBootstrap, err := r.daemon.CreateHost( + ctx, req.HostName, req.Opts, + ) + if err != nil { + return CreateHostResult{}, err + } + + return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil +} + +func (r *rpcImpl) CreateNebulaCertificate( ctx context.Context, req CreateNebulaCertificateRequest, ) ( CreateNebulaCertificateResult, error, diff --git a/nix/gowrap.nix b/nix/gowrap.nix new file mode 100644 index 0000000..470e3a3 --- /dev/null +++ b/nix/gowrap.nix @@ -0,0 +1,17 @@ +{ + buildGoModule, + fetchFromGitHub, +}: let + version = "1.4.0"; +in buildGoModule { + pname = "gowrap"; + inherit version; + src = fetchFromGitHub { + owner = "hexdigest"; + repo = "gowrap"; + rev = "v${version}"; + hash = "sha256-eEaUANLnxRGfVbhOTwJV+R9iEWMObg0lHqmwO3AYuIk="; + }; + vendorHash = "sha256-xIOyXXt8WSGQYIvIam+0e25VNI7awYEEYZBe7trC6zQ="; + subPackages = [ "cmd/gowrap" ]; +} diff --git a/shell.nix b/shell.nix index f4d35e4..31d4f6a 100644 --- a/shell.nix +++ b/shell.nix @@ -14,6 +14,7 @@ in pkgs.mkShell { buildInputs = [ pkgs.go pkgs.golangci-lint + (pkgs.callPackage ./nix/gowrap.nix {}) ]; shellHook = '' true # placeholder