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