m/c/metroctl: move common impl to m/c/m/core
This moves the implementation shared between CLI commands into metroctl
core package.
Change-Id: I93624a07356accf3441f02e6ecd8e91d5b71e66e
Reviewed-on: https://review.monogon.dev/c/monogon/+/843
Tested-by: Jenkins CI
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/cli/metroctl/core/BUILD.bazel b/metropolis/cli/metroctl/core/BUILD.bazel
index cdde06b..af948af 100644
--- a/metropolis/cli/metroctl/core/BUILD.bazel
+++ b/metropolis/cli/metroctl/core/BUILD.bazel
@@ -5,15 +5,21 @@
srcs = [
"core.go",
"install.go",
+ "rpc.go",
],
importpath = "source.monogon.dev/metropolis/cli/metroctl/core",
visibility = ["//visibility:public"],
deps = [
+ "//metropolis/node",
+ "//metropolis/node/core/rpc",
+ "//metropolis/node/core/rpc/resolver",
"//metropolis/proto/api",
"@com_github_diskfs_go_diskfs//:go-diskfs",
"@com_github_diskfs_go_diskfs//disk",
"@com_github_diskfs_go_diskfs//filesystem",
"@com_github_diskfs_go_diskfs//partition/gpt",
+ "@org_golang_google_grpc//:go_default_library",
"@org_golang_google_protobuf//proto",
+ "@org_golang_x_net//proxy",
],
)
diff --git a/metropolis/cli/metroctl/core/rpc.go b/metropolis/cli/metroctl/core/rpc.go
new file mode 100644
index 0000000..8d7565f
--- /dev/null
+++ b/metropolis/cli/metroctl/core/rpc.go
@@ -0,0 +1,106 @@
+package core
+
+import (
+ "context"
+ "crypto/ed25519"
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "io"
+ "net"
+
+ "golang.org/x/net/proxy"
+ "google.golang.org/grpc"
+
+ "source.monogon.dev/metropolis/node"
+ "source.monogon.dev/metropolis/node/core/rpc"
+ "source.monogon.dev/metropolis/node/core/rpc/resolver"
+ "source.monogon.dev/metropolis/proto/api"
+)
+
+type ResolverLogger func(format string, args ...interface{})
+
+// DialCluster dials the cluster control address. The owner certificate, and
+// proxy address parameters are optional and can be left nil, and empty,
+// respectively. At least one cluster endpoint must be provided. A missing
+// owner certificate will result in a connection that is authenticated with
+// ephemeral credentials, restricting the available API surface. proxyAddr
+// must point at a SOCKS5 endpoint.
+func DialCluster(ctx context.Context, opkey ed25519.PrivateKey, ocert *x509.Certificate, proxyAddr string, clusterEndpoints []string, rlf ResolverLogger) (*grpc.ClientConn, error) {
+ var dialOpts []grpc.DialOption
+
+ if opkey == nil {
+ return nil, fmt.Errorf("an owner's private key must be provided")
+ }
+ if len(clusterEndpoints) == 0 {
+ return nil, fmt.Errorf("at least one cluster endpoint must be provided")
+ }
+
+ if proxyAddr != "" {
+ socksDialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct)
+ if err != nil {
+ return nil, fmt.Errorf("failed to build a SOCKS dialer: %v", err)
+ }
+ grpcd := func(_ context.Context, addr string) (net.Conn, error) {
+ return socksDialer.Dial("tcp", addr)
+ }
+ dialOpts = append(dialOpts, grpc.WithContextDialer(grpcd))
+ }
+
+ if ocert == nil {
+ creds, err := rpc.NewEphemeralCredentials(opkey, nil)
+ if err != nil {
+ return nil, fmt.Errorf("while building ephemeral credentials: %v", err)
+ }
+ dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
+ } else {
+ tlsc := tls.Certificate{
+ Certificate: [][]byte{ocert.Raw},
+ PrivateKey: opkey,
+ }
+ creds := rpc.NewAuthenticatedCredentials(tlsc, nil)
+ dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
+ }
+
+ var resolverOpts []resolver.ResolverOption
+ if rlf != nil {
+ resolverOpts = append(resolverOpts, resolver.WithLogger(rlf))
+ }
+ r := resolver.New(ctx, resolverOpts...)
+
+ for _, eps := range clusterEndpoints {
+ ep := resolver.NodeByHostPort(eps, uint16(node.CuratorServicePort))
+ r.AddEndpoint(ep)
+ }
+ dialOpts = append(dialOpts, grpc.WithResolvers(r))
+
+ c, err := grpc.Dial(resolver.MetropolisControlAddress, dialOpts...)
+ if err != nil {
+ return nil, fmt.Errorf("could not dial: %v", err)
+ }
+ return c, nil
+}
+
+// GetNodes retrieves node records, filtered by the supplied node filter
+// expression fexp.
+func GetNodes(ctx context.Context, mgmt api.ManagementClient, fexp string) ([]*api.Node, error) {
+ resN, err := mgmt.GetNodes(ctx, &api.GetNodesRequest{
+ Filter: fexp,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ var nodes []*api.Node
+ for {
+ node, err := resN.Recv()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+ nodes = append(nodes, node)
+ }
+ return nodes, nil
+}