github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/internal/testing/setup/setup.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package setup // import "gocloud.dev/internal/testing/setup" 16 17 import ( 18 "context" 19 "flag" 20 "io/ioutil" 21 "net/http" 22 "os" 23 "path/filepath" 24 "testing" 25 "time" 26 27 "github.com/aws/aws-sdk-go/aws" 28 awscreds "github.com/aws/aws-sdk-go/aws/credentials" 29 "github.com/aws/aws-sdk-go/aws/session" 30 31 awsv2 "github.com/aws/aws-sdk-go-v2/aws" 32 awsv2config "github.com/aws/aws-sdk-go-v2/config" 33 awsv2creds "github.com/aws/aws-sdk-go-v2/credentials" 34 35 "gocloud.dev/gcp" 36 "gocloud.dev/internal/useragent" 37 38 "github.com/google/go-replayers/grpcreplay" 39 "github.com/google/go-replayers/httpreplay" 40 hrgoog "github.com/google/go-replayers/httpreplay/google" 41 "golang.org/x/oauth2/google" 42 "google.golang.org/api/option" 43 "google.golang.org/grpc" 44 grpccreds "google.golang.org/grpc/credentials" 45 "google.golang.org/grpc/credentials/oauth" 46 ) 47 48 // Record is true iff the tests are being run in "record" mode. 49 var Record = flag.Bool("record", false, "whether to run tests against cloud resources and record the interactions") 50 51 // FakeGCPCredentials gets fake GCP credentials. 52 func FakeGCPCredentials(ctx context.Context) (*google.Credentials, error) { 53 return google.CredentialsFromJSON(ctx, []byte(`{"type": "service_account", "project_id": "my-project-id"}`)) 54 } 55 56 func awsSession(region string, client *http.Client) (*session.Session, error) { 57 // Provide fake creds if running in replay mode. 58 var creds *awscreds.Credentials 59 if !*Record { 60 creds = awscreds.NewStaticCredentials("FAKE_ID", "FAKE_SECRET", "FAKE_TOKEN") 61 } 62 return session.NewSession(&aws.Config{ 63 HTTPClient: client, 64 Region: aws.String(region), 65 Credentials: creds, 66 MaxRetries: aws.Int(0), 67 }) 68 } 69 70 func awsV2Config(ctx context.Context, region string, client *http.Client) (awsv2.Config, error) { 71 // Provide fake creds if running in replay mode. 72 var creds awsv2.CredentialsProvider 73 if !*Record { 74 creds = awsv2creds.NewStaticCredentialsProvider("FAKE_KEY", "FAKE_SECRET", "FAKE_SESSION") 75 } 76 return awsv2config.LoadDefaultConfig( 77 ctx, 78 awsv2config.WithHTTPClient(client), 79 awsv2config.WithRegion(region), 80 awsv2config.WithCredentialsProvider(creds), 81 awsv2config.WithRetryer(func() awsv2.Retryer { return awsv2.NopRetryer{} }), 82 ) 83 } 84 85 // NewRecordReplayClient creates a new http.Client for tests. This client's 86 // activity is being either recorded to files (when *Record is set) or replayed 87 // from files. rf is a modifier function that will be invoked with the address 88 // of the httpreplay.Recorder object used to obtain the client; this function 89 // can mutate the recorder to add service-specific header filters, for example. 90 // An initState is returned for tests that need a state to have deterministic 91 // results, for example, a seed to generate random sequences. 92 func NewRecordReplayClient(ctx context.Context, t *testing.T, rf func(r *httpreplay.Recorder)) (c *http.Client, cleanup func(), initState int64) { 93 httpreplay.DebugHeaders() 94 path := filepath.Join("testdata", t.Name()+".replay") 95 if *Record { 96 t.Logf("Recording into golden file %s", path) 97 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 98 t.Fatal(err) 99 } 100 state := time.Now() 101 b, _ := state.MarshalBinary() 102 rec, err := httpreplay.NewRecorder(path, b) 103 if err != nil { 104 t.Fatal(err) 105 } 106 rf(rec) 107 cleanup = func() { 108 if err := rec.Close(); err != nil { 109 t.Fatal(err) 110 } 111 } 112 113 return rec.Client(), cleanup, state.UnixNano() 114 } 115 t.Logf("Replaying from golden file %s", path) 116 rep, err := httpreplay.NewReplayer(path) 117 if err != nil { 118 t.Fatal(err) 119 } 120 recState := new(time.Time) 121 if err := recState.UnmarshalBinary(rep.Initial()); err != nil { 122 t.Fatal(err) 123 } 124 return rep.Client(), func() { rep.Close() }, recState.UnixNano() 125 } 126 127 // NewAWSSession creates a new session for testing against AWS. 128 // If the test is in --record mode, the test will call out to AWS, and the 129 // results are recorded in a replay file. 130 // Otherwise, the session reads a replay file and runs the test as a replay, 131 // which never makes an outgoing HTTP call and uses fake credentials. 132 // An initState is returned for tests that need a state to have deterministic 133 // results, for example, a seed to generate random sequences. 134 func NewAWSSession(ctx context.Context, t *testing.T, region string) (sess *session.Session, 135 rt http.RoundTripper, cleanup func(), initState int64) { 136 client, cleanup, state := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) { 137 r.RemoveQueryParams("X-Amz-Credential", "X-Amz-Signature", "X-Amz-Security-Token") 138 r.RemoveRequestHeaders("Authorization", "Duration", "X-Amz-Security-Token") 139 r.ClearHeaders("X-Amz-Date") 140 r.ClearQueryParams("X-Amz-Date") 141 r.ClearHeaders("User-Agent") // AWS includes the Go version 142 }) 143 sess, err := awsSession(region, client) 144 if err != nil { 145 t.Fatal(err) 146 } 147 return sess, client.Transport, cleanup, state 148 } 149 150 // NewAWSv2Config creates a new aws.Config for testing against AWS. 151 // If the test is in --record mode, the test will call out to AWS, and the 152 // results are recorded in a replay file. 153 // Otherwise, the session reads a replay file and runs the test as a replay, 154 // which never makes an outgoing HTTP call and uses fake credentials. 155 // An initState is returned for tests that need a state to have deterministic 156 // results, for example, a seed to generate random sequences. 157 func NewAWSv2Config(ctx context.Context, t *testing.T, region string) (cfg awsv2.Config, rt http.RoundTripper, cleanup func(), initState int64) { 158 client, cleanup, state := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) { 159 r.RemoveQueryParams("X-Amz-Credential", "X-Amz-Signature", "X-Amz-Security-Token") 160 r.RemoveRequestHeaders("Authorization", "Duration", "X-Amz-Security-Token") 161 r.ClearHeaders("Amz-Sdk-Invocation-Id") 162 r.ClearHeaders("X-Amz-Date") 163 r.ClearQueryParams("X-Amz-Date") 164 r.ClearHeaders("User-Agent") // AWS includes the Go version 165 }) 166 cfg, err := awsV2Config(ctx, region, client) 167 if err != nil { 168 t.Fatal(err) 169 } 170 return cfg, client.Transport, cleanup, state 171 } 172 173 // NewGCPClient creates a new HTTPClient for testing against GCP. 174 // NewGCPClient creates a new HTTPClient for testing against GCP. 175 // If the test is in --record mode, the client will call out to GCP, and the 176 // results are recorded in a replay file. 177 // Otherwise, the session reads a replay file and runs the test as a replay, 178 // which never makes an outgoing HTTP call and uses fake credentials. 179 func NewGCPClient(ctx context.Context, t *testing.T) (client *gcp.HTTPClient, rt http.RoundTripper, done func()) { 180 c, cleanup, _ := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) { 181 r.ClearQueryParams("Expires") 182 r.ClearQueryParams("Signature") 183 r.ClearHeaders("Expires") 184 r.ClearHeaders("Signature") 185 }) 186 transport := c.Transport 187 if *Record { 188 creds, err := gcp.DefaultCredentials(ctx) 189 if err != nil { 190 t.Fatalf("failed to get default credentials: %v", err) 191 } 192 c, err = hrgoog.RecordClient(ctx, c, option.WithTokenSource(gcp.CredentialsTokenSource(creds))) 193 if err != nil { 194 t.Fatal(err) 195 } 196 } 197 return &gcp.HTTPClient{Client: *c}, transport, cleanup 198 } 199 200 // NewGCPgRPCConn creates a new connection for testing against GCP via gRPC. 201 // If the test is in --record mode, the client will call out to GCP, and the 202 // results are recorded in a replay file. 203 // Otherwise, the session reads a replay file and runs the test as a replay, 204 // which never makes an outgoing RPC and uses fake credentials. 205 func NewGCPgRPCConn(ctx context.Context, t *testing.T, endPoint, api string) (*grpc.ClientConn, func()) { 206 filename := t.Name() + ".replay" 207 if *Record { 208 opts, done := newGCPRecordDialOptions(t, filename) 209 opts = append(opts, useragent.GRPCDialOption(api)) 210 // Add credentials for real RPCs. 211 creds, err := gcp.DefaultCredentials(ctx) 212 if err != nil { 213 t.Fatal(err) 214 } 215 opts = append(opts, grpc.WithTransportCredentials(grpccreds.NewClientTLSFromCert(nil, ""))) 216 opts = append(opts, grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: gcp.CredentialsTokenSource(creds)})) 217 conn, err := grpc.DialContext(ctx, endPoint, opts...) 218 if err != nil { 219 t.Fatal(err) 220 } 221 return conn, done 222 } 223 rep, done := newGCPReplayer(t, filename) 224 conn, err := rep.Connection() 225 if err != nil { 226 t.Fatal(err) 227 } 228 return conn, done 229 } 230 231 // NewAzureTestBlobClient creates a new connection for testing against Azure Blob. 232 func NewAzureTestBlobClient(ctx context.Context, t *testing.T) (*http.Client, func()) { 233 client, cleanup, _ := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) { 234 r.RemoveQueryParams("se", "sig", "st") 235 r.RemoveQueryParams("X-Ms-Date") 236 r.ClearQueryParams("blockid") 237 r.ClearHeaders("X-Ms-Date") 238 r.ClearHeaders("X-Ms-Version") 239 r.ClearHeaders("User-Agent") // includes the full Go version 240 // Yes, it's true, Azure does not appear to be internally 241 // consistent about casing for BLock(l|L)ist. 242 r.ScrubBody("<Block(l|L)ist><Latest>.*</Latest></Block(l|L)ist>") 243 }) 244 return client, cleanup 245 } 246 247 // NewAzureKeyVaultTestClient creates a *http.Client for Azure KeyVault test 248 // recordings. 249 func NewAzureKeyVaultTestClient(ctx context.Context, t *testing.T) (*http.Client, func()) { 250 client, cleanup, _ := NewRecordReplayClient(ctx, t, func(r *httpreplay.Recorder) { 251 r.RemoveQueryParams("se", "sig") 252 r.RemoveQueryParams("X-Ms-Date") 253 r.ClearHeaders("X-Ms-Date") 254 r.ClearHeaders("User-Agent") // includes the full Go version 255 }) 256 return client, cleanup 257 } 258 259 // FakeGCPDefaultCredentials sets up the environment with fake GCP credentials. 260 // It returns a cleanup function. 261 func FakeGCPDefaultCredentials(t *testing.T) func() { 262 const envVar = "GOOGLE_APPLICATION_CREDENTIALS" 263 jsonCred := []byte(`{"client_id": "foo.apps.googleusercontent.com", "client_secret": "bar", "refresh_token": "baz", "type": "authorized_user"}`) 264 f, err := ioutil.TempFile("", "fake-gcp-creds") 265 if err != nil { 266 t.Fatal(err) 267 } 268 if err := ioutil.WriteFile(f.Name(), jsonCred, 0666); err != nil { 269 t.Fatal(err) 270 } 271 oldEnvVal := os.Getenv(envVar) 272 os.Setenv(envVar, f.Name()) 273 return func() { 274 os.Remove(f.Name()) 275 os.Setenv(envVar, oldEnvVal) 276 } 277 } 278 279 // newGCPRecordDialOptions return grpc.DialOptions that are to be appended to a 280 // GRPC dial request. These options allow a recorder to intercept RPCs and save 281 // RPCs to the file at filename, or read the RPCs from the file and return them. 282 func newGCPRecordDialOptions(t *testing.T, filename string) (opts []grpc.DialOption, done func()) { 283 path := filepath.Join("testdata", filename) 284 os.MkdirAll(filepath.Dir(path), os.ModePerm) 285 t.Logf("Recording into golden file %s", path) 286 r, err := grpcreplay.NewRecorder(path, nil) 287 if err != nil { 288 t.Fatal(err) 289 } 290 opts = r.DialOptions() 291 done = func() { 292 if err := r.Close(); err != nil { 293 t.Errorf("unable to close recorder: %v", err) 294 } 295 } 296 return opts, done 297 } 298 299 // newGCPReplayer returns a Replayer for GCP gRPC connections, as well as a function 300 // to call when done with the Replayer. 301 func newGCPReplayer(t *testing.T, filename string) (*grpcreplay.Replayer, func()) { 302 path := filepath.Join("testdata", filename) 303 t.Logf("Replaying from golden file %s", path) 304 r, err := grpcreplay.NewReplayer(path, nil) 305 if err != nil { 306 t.Fatal(err) 307 } 308 done := func() { 309 if err := r.Close(); err != nil { 310 t.Errorf("unable to close recorder: %v", err) 311 } 312 } 313 return r, done 314 } 315 316 // HasDockerTestEnvironment returns true when either: 317 // 1) Not on Github Actions. 318 // 2) On Github's Linux environment, where Docker is available. 319 func HasDockerTestEnvironment() bool { 320 s := os.Getenv("RUNNER_OS") 321 return s == "" || s == "Linux" 322 }