| /* |
| Copyright 2015 Google LLC |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| // Package option contains common code for dealing with client options. |
| package option |
| |
| import ( |
| "context" |
| "encoding/base64" |
| "fmt" |
| "os" |
| |
| btpb "google.golang.org/genproto/googleapis/bigtable/v2" |
| "google.golang.org/protobuf/proto" |
| |
| "cloud.google.com/go/bigtable/internal" |
| "cloud.google.com/go/internal/version" |
| gax "github.com/googleapis/gax-go/v2" |
| "google.golang.org/api/option" |
| "google.golang.org/api/option/internaloption" |
| "google.golang.org/grpc" |
| "google.golang.org/grpc/metadata" |
| ) |
| |
| // mergeOutgoingMetadata returns a context populated by the existing outgoing |
| // metadata merged with the provided mds. |
| func mergeOutgoingMetadata(ctx context.Context, mds ...metadata.MD) context.Context { |
| // There may not be metadata in the context, only insert the existing |
| // metadata if it exists (ok). |
| ctxMD, ok := metadata.FromOutgoingContext(ctx) |
| if ok { |
| // The ordering matters, hence why ctxMD is added to the front. |
| mds = append([]metadata.MD{ctxMD}, mds...) |
| } |
| |
| return metadata.NewOutgoingContext(ctx, metadata.Join(mds...)) |
| } |
| |
| // withGoogleClientInfo sets the name and version of the application in |
| // the `x-goog-api-client` header passed on each request. Intended for |
| // use by Google-written clients. |
| func withGoogleClientInfo() metadata.MD { |
| kv := []string{ |
| "gl-go", |
| version.Go(), |
| "gax", |
| gax.Version, |
| "grpc", |
| grpc.Version, |
| "gccl", |
| internal.Version, |
| } |
| return metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...)) |
| } |
| |
| func makeFeatureFlags() string { |
| ff := btpb.FeatureFlags{ReverseScans: true, LastScannedRowResponses: true} |
| b, err := proto.Marshal(&ff) |
| if err != nil { |
| return "" |
| } |
| |
| return base64.URLEncoding.EncodeToString(b) |
| } |
| |
| var featureFlags = makeFeatureFlags() |
| |
| // WithFeatureFlags set the feature flags the client supports in the |
| // `bigtable-features` header sent on each request. Intended for |
| // use by Google-written clients. |
| func WithFeatureFlags() metadata.MD { |
| return metadata.Pairs("bigtable-features", featureFlags) |
| } |
| |
| // streamInterceptor intercepts the creation of ClientStream within the bigtable |
| // client to inject Google client information into the context metadata for |
| // streaming RPCs. |
| func streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { |
| ctx = mergeOutgoingMetadata(ctx, withGoogleClientInfo()) |
| return streamer(ctx, desc, cc, method, opts...) |
| } |
| |
| // unaryInterceptor intercepts the creation of UnaryInvoker within the bigtable |
| // client to inject Google client information into the context metadata for |
| // unary RPCs. |
| func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { |
| ctx = mergeOutgoingMetadata(ctx, withGoogleClientInfo()) |
| return invoker(ctx, method, req, reply, cc, opts...) |
| } |
| |
| // DefaultClientOptions returns the default client options to use for the |
| // client's gRPC connection. |
| func DefaultClientOptions(endpoint, mtlsEndpoint, scope, userAgent string) ([]option.ClientOption, error) { |
| var o []option.ClientOption |
| // Check the environment variables for the bigtable emulator. |
| // Dial it directly and don't pass any credentials. |
| if addr := os.Getenv("BIGTABLE_EMULATOR_HOST"); addr != "" { |
| conn, err := grpc.Dial(addr, grpc.WithInsecure()) |
| if err != nil { |
| return nil, fmt.Errorf("emulator grpc.Dial: %w", err) |
| } |
| o = []option.ClientOption{option.WithGRPCConn(conn)} |
| } else { |
| o = []option.ClientOption{ |
| internaloption.WithDefaultEndpointTemplate(endpoint), |
| internaloption.WithDefaultMTLSEndpoint(mtlsEndpoint), |
| option.WithScopes(scope), |
| option.WithUserAgent(userAgent), |
| } |
| } |
| return o, nil |
| } |
| |
| // ClientInterceptorOptions returns client options to use for the client's gRPC |
| // connection, using the given streaming and unary RPC interceptors. |
| // |
| // The passed interceptors are applied after internal interceptors which inject |
| // Google client information into the gRPC context. |
| func ClientInterceptorOptions(stream []grpc.StreamClientInterceptor, unary []grpc.UnaryClientInterceptor) []option.ClientOption { |
| // By prepending the interceptors defined here, they will be applied first. |
| stream = append([]grpc.StreamClientInterceptor{streamInterceptor}, stream...) |
| unary = append([]grpc.UnaryClientInterceptor{unaryInterceptor}, unary...) |
| return []option.ClientOption{ |
| option.WithGRPCDialOption(grpc.WithChainStreamInterceptor(stream...)), |
| option.WithGRPCDialOption(grpc.WithChainUnaryInterceptor(unary...)), |
| } |
| } |