diff --git a/command/resource/client/client.go b/command/resource/client/client.go index 0d0d59e2df..e43bef014c 100644 --- a/command/resource/client/client.go +++ b/command/resource/client/client.go @@ -19,10 +19,11 @@ import ( "sync" "time" - "github.com/hashicorp/consul/api" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-rootcerts" + + "github.com/hashicorp/consul/api" ) // NOTE: This client is copied from the api module to temporarily facilitate the resource cli commands @@ -72,12 +73,6 @@ const ( // whether or not to disable certificate checking. HTTPSSLVerifyEnvName = "CONSUL_HTTP_SSL_VERIFY" - // GRPCAddrEnvName defines an environment variable name which sets the gRPC - // address for consul connect envoy. Note this isn't actually used by the api - // client in this package but is defined here for consistency with all the - // other ENV names we use. - GRPCAddrEnvName = "CONSUL_GRPC_ADDR" - // GRPCCAFileEnvName defines an environment variable name which sets the // CA file to use for talking to Consul gRPC over TLS. GRPCCAFileEnvName = "CONSUL_GRPC_CACERT" diff --git a/command/resource/client/grpc-client.go b/command/resource/client/grpc-client.go new file mode 100644 index 0000000000..d9f71488ad --- /dev/null +++ b/command/resource/client/grpc-client.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package client + +import ( + "fmt" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/hashicorp/consul/proto-public/pbresource" +) + +type GRPCClient struct { + Client pbresource.ResourceServiceClient + Config *GRPCConfig + Conn *grpc.ClientConn +} + +func NewGRPCClient(config *GRPCConfig) (*GRPCClient, error) { + conn, err := dial(config) + if err != nil { + return nil, fmt.Errorf("**** error dialing grpc: %+v", err) + } + return &GRPCClient{ + Client: pbresource.NewResourceServiceClient(conn), + Config: config, + Conn: conn, + }, nil +} + +func dial(c *GRPCConfig) (*grpc.ClientConn, error) { + // TODO: decide if we use TLS mode based on the config + dialOpts := []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + } + return grpc.Dial(c.Address, dialOpts...) +} diff --git a/command/resource/client/grpc-client_test.go b/command/resource/client/grpc-client_test.go new file mode 100644 index 0000000000..c6fe8da155 --- /dev/null +++ b/command/resource/client/grpc-client_test.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package client + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/hashicorp/consul/agent" + "github.com/hashicorp/consul/internal/resource/demo" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/proto/private/prototest" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/testrpc" +) + +func TestResourceRead(t *testing.T) { + t.Parallel() + + a := agent.NewTestAgent(t, "ports { grpc = 8502 }") + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + grpcConfig := GetDefaultGRPCConfig() + gRPCClient, err := NewGRPCClient(grpcConfig) + + t.Cleanup(func() { + a.Shutdown() + gRPCClient.Conn.Close() + }) + + t.Run("test", func(t *testing.T) { + if err != nil { + fmt.Println("error when create new grpc client") + } + + v2Artist, err := demo.GenerateV2Artist() + require.NoError(t, err) + + writeRsp, err := gRPCClient.Client.Write(testutil.TestContext(t), &pbresource.WriteRequest{Resource: v2Artist}) + require.NoError(t, err) + + readRsp, err := gRPCClient.Client.Read(context.Background(), &pbresource.ReadRequest{Id: v2Artist.Id}) + require.NoError(t, err) + require.Equal(t, proto.Equal(readRsp.Resource.Id.Type, demo.TypeV2Artist), true) + prototest.AssertDeepEqual(t, writeRsp.Resource, readRsp.Resource) + }) +} diff --git a/command/resource/client/grpc-config.go b/command/resource/client/grpc-config.go new file mode 100644 index 0000000000..e6d990995c --- /dev/null +++ b/command/resource/client/grpc-config.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package client + +import ( + "os" +) + +const ( + // GRPCAddrEnvName defines an environment variable name which sets the gRPC + // server address for the consul CLI. + GRPCAddrEnvName = "CONSUL_GRPC_ADDR" +) + +type GRPCConfig struct { + Address string +} + +func GetDefaultGRPCConfig() *GRPCConfig { + return &GRPCConfig{ + Address: "localhost:8502", + } +} + +func LoadGRPCConfig(defaultConfig *GRPCConfig) *GRPCConfig { + if defaultConfig == nil { + defaultConfig = GetDefaultGRPCConfig() + } + + overwrittenConfig := loadEnvToDefaultConfig(defaultConfig) + + return overwrittenConfig +} + +func loadEnvToDefaultConfig(config *GRPCConfig) *GRPCConfig { + + if addr := os.Getenv(GRPCAddrEnvName); addr != "" { + config.Address = addr + } + + return config +} diff --git a/command/resource/client/grpc-flags.go b/command/resource/client/grpc-flags.go new file mode 100644 index 0000000000..bc1d89c5a9 --- /dev/null +++ b/command/resource/client/grpc-flags.go @@ -0,0 +1,58 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package client + +import ( + "flag" +) + +type GRPCFlags struct { + address StringValue +} + +// mergeFlagsIntoGRPCConfig merges flag values into grpc config +// caller has to parse the CLI args before loading them into flag values +func (f *GRPCFlags) mergeFlagsIntoGRPCConfig(c *GRPCConfig) { + f.address.Merge(&c.Address) +} + +func (f *GRPCFlags) ClientFlags() *flag.FlagSet { + fs := flag.NewFlagSet("", flag.ContinueOnError) + fs.Var(&f.address, "grpc-addr", + "The `address` and port of the Consul GRPC agent. The value can be an IP "+ + "address or DNS address, but it must also include the port. This can "+ + "also be specified via the CONSUL_GRPC_ADDR environment variable. The "+ + "default value is 127.0.0.1:8502. It supports TLS communication "+ + "by setting the environment variable CONSUL_GRPC_TLS=true.") + return fs +} + +type StringValue struct { + v *string +} + +// Set implements the flag.Value interface. +func (s *StringValue) Set(v string) error { + if s.v == nil { + s.v = new(string) + } + *(s.v) = v + return nil +} + +// String implements the flag.Value interface. +func (s *StringValue) String() string { + var current string + if s.v != nil { + current = *(s.v) + } + return current +} + +// Merge will overlay this value if it has been set. +func (s *StringValue) Merge(onto *string) { + if s.v != nil { + *onto = *(s.v) + } +} diff --git a/command/resource/read/read.go b/command/resource/read/read.go index fe35521056..a38889ad14 100644 --- a/command/resource/read/read.go +++ b/command/resource/read/read.go @@ -86,10 +86,6 @@ func (c *cmd) Run(args []string) int { return 1 } } else { - if len(args) < 2 { - c.UI.Error("Incorrect argument format: Must specify two arguments: resource type and resource name") - return 1 - } var err error gvk, resourceName, err = resource.GetTypeAndResourceName(args) if err != nil {