consul/command/resource/client/client.go

173 lines
4.4 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package client
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"github.com/hashicorp/consul/proto-public/pbresource"
)
const (
HeaderConsulToken = "x-consul-token"
)
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 (client *GRPCClient) Apply(parsedResource *pbresource.Resource) (*pbresource.Resource, error) {
token, err := client.Config.GetToken()
if err != nil {
return nil, err
}
ctx := context.Background()
if token != "" {
ctx = metadata.AppendToOutgoingContext(ctx, HeaderConsulToken, token)
}
defer client.Conn.Close()
writeRsp, err := client.Client.Write(ctx, &pbresource.WriteRequest{Resource: parsedResource})
if err != nil {
return nil, fmt.Errorf("error writing resource: %+v", err)
}
return writeRsp.Resource, err
}
func (client *GRPCClient) Read(resourceType *pbresource.Type, resourceTenancy *pbresource.Tenancy, resourceName string, stale bool) (*pbresource.Resource, error) {
token, err := client.Config.GetToken()
if err != nil {
return nil, err
}
ctx := context.Background()
if !stale {
ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-consistency-mode", "consistent")
}
if token != "" {
ctx = metadata.AppendToOutgoingContext(ctx, HeaderConsulToken, token)
}
defer client.Conn.Close()
readRsp, err := client.Client.Read(ctx, &pbresource.ReadRequest{
Id: &pbresource.ID{
Type: resourceType,
Tenancy: resourceTenancy,
Name: resourceName,
},
})
if err != nil {
return nil, fmt.Errorf("error reading resource: %+v", err)
}
return readRsp.Resource, err
}
func (client *GRPCClient) List(resourceType *pbresource.Type, resourceTenancy *pbresource.Tenancy, prefix string, stale bool) ([]*pbresource.Resource, error) {
token, err := client.Config.GetToken()
if err != nil {
return nil, err
}
ctx := context.Background()
if !stale {
ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-consistency-mode", "consistent")
}
if token != "" {
ctx = metadata.AppendToOutgoingContext(context.Background(), HeaderConsulToken, token)
}
defer client.Conn.Close()
listRsp, err := client.Client.List(ctx, &pbresource.ListRequest{
Type: resourceType,
Tenancy: resourceTenancy,
NamePrefix: prefix,
})
if err != nil {
return nil, fmt.Errorf("error listing resource: %+v", err)
}
return listRsp.Resources, err
}
func (client *GRPCClient) Delete(resourceType *pbresource.Type, resourceTenancy *pbresource.Tenancy, resourceName string) error {
token, err := client.Config.GetToken()
if err != nil {
return err
}
ctx := context.Background()
if token != "" {
ctx = metadata.AppendToOutgoingContext(context.Background(), HeaderConsulToken, token)
}
defer client.Conn.Close()
_, err = client.Client.Delete(ctx, &pbresource.DeleteRequest{
Id: &pbresource.ID{
Type: resourceType,
Tenancy: resourceTenancy,
Name: resourceName,
},
})
if err != nil {
return fmt.Errorf("error deleting resource: %+v", err)
}
return nil
}
func dial(c *GRPCConfig) (*grpc.ClientConn, error) {
err := checkCertificates(c)
if err != nil {
return nil, err
}
var dialOpts []grpc.DialOption
if c.GRPCTLS {
tlsConfig, err := SetupTLSConfig(c)
if err != nil {
return nil, fmt.Errorf("failed to setup tls config when tried to establish grpc call: %w", err)
}
dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
} else {
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
return grpc.Dial(c.Address, dialOpts...)
}
func checkCertificates(c *GRPCConfig) error {
if c.GRPCTLS {
certFileEmpty := c.CertFile == ""
keyFileEmpty := c.KeyFile == ""
// both files need to be empty or both files need to be provided
if certFileEmpty != keyFileEmpty {
return fmt.Errorf("you have to provide client certificate file and key file at the same time " +
"if you intend to communicate in TLS/SSL mode")
}
}
return nil
}