1/* 2Copyright 2015 Google LLC 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17// Package option contains common code for dealing with client options. 18package option 19 20import ( 21 "context" 22 "fmt" 23 "os" 24 25 "cloud.google.com/go/internal/version" 26 gax "github.com/googleapis/gax-go/v2" 27 "google.golang.org/api/option" 28 "google.golang.org/api/option/internaloption" 29 "google.golang.org/grpc" 30 "google.golang.org/grpc/metadata" 31) 32 33// mergeOutgoingMetadata returns a context populated by the existing outgoing 34// metadata merged with the provided mds. 35func mergeOutgoingMetadata(ctx context.Context, mds ...metadata.MD) context.Context { 36 // There may not be metadata in the context, only insert the existing 37 // metadata if it exists (ok). 38 ctxMD, ok := metadata.FromOutgoingContext(ctx) 39 if ok { 40 // The ordering matters, hence why ctxMD is added to the front. 41 mds = append([]metadata.MD{ctxMD}, mds...) 42 } 43 44 return metadata.NewOutgoingContext(ctx, metadata.Join(mds...)) 45} 46 47// withGoogleClientInfo sets the name and version of the application in 48// the `x-goog-api-client` header passed on each request. Intended for 49// use by Google-written clients. 50func withGoogleClientInfo() metadata.MD { 51 kv := []string{ 52 "gl-go", 53 version.Go(), 54 "gax", 55 gax.Version, 56 "grpc", 57 grpc.Version, 58 } 59 return metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...)) 60} 61 62// streamInterceptor intercepts the creation of ClientStream within the bigtable 63// client to inject Google client information into the context metadata for 64// streaming RPCs. 65func streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 66 ctx = mergeOutgoingMetadata(ctx, withGoogleClientInfo()) 67 return streamer(ctx, desc, cc, method, opts...) 68} 69 70// unaryInterceptor intercepts the creation of UnaryInvoker within the bigtable 71// client to inject Google client information into the context metadata for 72// unary RPCs. 73func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 74 ctx = mergeOutgoingMetadata(ctx, withGoogleClientInfo()) 75 return invoker(ctx, method, req, reply, cc, opts...) 76} 77 78// DefaultClientOptions returns the default client options to use for the 79// client's gRPC connection. 80func DefaultClientOptions(endpoint, mtlsEndpoint, scope, userAgent string) ([]option.ClientOption, error) { 81 var o []option.ClientOption 82 // Check the environment variables for the bigtable emulator. 83 // Dial it directly and don't pass any credentials. 84 if addr := os.Getenv("BIGTABLE_EMULATOR_HOST"); addr != "" { 85 conn, err := grpc.Dial(addr, grpc.WithInsecure()) 86 if err != nil { 87 return nil, fmt.Errorf("emulator grpc.Dial: %v", err) 88 } 89 o = []option.ClientOption{option.WithGRPCConn(conn)} 90 } else { 91 o = []option.ClientOption{ 92 internaloption.WithDefaultEndpoint(endpoint), 93 internaloption.WithDefaultMTLSEndpoint(mtlsEndpoint), 94 option.WithScopes(scope), 95 option.WithUserAgent(userAgent), 96 } 97 } 98 return o, nil 99} 100 101// ClientInterceptorOptions returns client options to use for the client's gRPC 102// connection, using the given streaming and unary RPC interceptors. 103// 104// The passed interceptors are applied after internal interceptors which inject 105// Google client information into the gRPC context. 106func ClientInterceptorOptions(stream []grpc.StreamClientInterceptor, unary []grpc.UnaryClientInterceptor) []option.ClientOption { 107 // By prepending the interceptors defined here, they will be applied first. 108 stream = append([]grpc.StreamClientInterceptor{streamInterceptor}, stream...) 109 unary = append([]grpc.UnaryClientInterceptor{unaryInterceptor}, unary...) 110 return []option.ClientOption{ 111 option.WithGRPCDialOption(grpc.WithChainStreamInterceptor(stream...)), 112 option.WithGRPCDialOption(grpc.WithChainUnaryInterceptor(unary...)), 113 } 114} 115