1package customizations_test 2 3import ( 4 "context" 5 "fmt" 6 "strconv" 7 "strings" 8 "testing" 9 10 "github.com/aws/smithy-go/ptr" 11 12 "github.com/aws/aws-sdk-go-v2/aws" 13 awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" 14 "github.com/aws/aws-sdk-go-v2/internal/awstesting/unit" 15 "github.com/aws/aws-sdk-go-v2/service/s3control" 16 17 "github.com/aws/smithy-go/middleware" 18 smithyhttp "github.com/aws/smithy-go/transport/http" 19) 20 21type s3controlEndpointTest struct { 22 bucket string 23 accountID string 24 url string 25 err string 26} 27 28func TestUpdateEndpointBuild(t *testing.T) { 29 cases := map[string]map[string]struct { 30 tests []s3controlEndpointTest 31 useDualstack bool 32 customEndpoint *aws.Endpoint 33 }{ 34 "default endpoint": { 35 "default": { 36 tests: []s3controlEndpointTest{ 37 {"abc", "123456789012", "https://123456789012.s3-control.mock-region.amazonaws.com/v20180820/bucket/abc", ""}, 38 {"a.b.c", "123456789012", "https://123456789012.s3-control.mock-region.amazonaws.com/v20180820/bucket/a.b.c", ""}, 39 {"a$b$c", "123456789012", "https://123456789012.s3-control.mock-region.amazonaws.com/v20180820/bucket/a%24b%24c", ""}, 40 }, 41 }, 42 "DualStack": { 43 useDualstack: true, 44 tests: []s3controlEndpointTest{ 45 {"abc", "123456789012", "https://123456789012.s3-control.dualstack.mock-region.amazonaws.com/v20180820/bucket/abc", ""}, 46 {"a.b.c", "123456789012", "https://123456789012.s3-control.dualstack.mock-region.amazonaws.com/v20180820/bucket/a.b.c", ""}, 47 {"a$b$c", "123456789012", "https://123456789012.s3-control.dualstack.mock-region.amazonaws.com/v20180820/bucket/a%24b%24c", ""}, 48 }, 49 }, 50 }, 51 52 "immutable endpoint": { 53 "default": { 54 customEndpoint: &aws.Endpoint{ 55 URL: "https://example.region.amazonaws.com", 56 HostnameImmutable: true, 57 }, 58 tests: []s3controlEndpointTest{ 59 {"abc", "123456789012", "https://example.region.amazonaws.com/v20180820/bucket/abc", ""}, 60 {"a.b.c", "123456789012", "https://example.region.amazonaws.com/v20180820/bucket/a.b.c", ""}, 61 {"a$b$c", "123456789012", "https://example.region.amazonaws.com/v20180820/bucket/a%24b%24c", ""}, 62 }, 63 }, 64 "DualStack": { 65 useDualstack: true, 66 customEndpoint: &aws.Endpoint{ 67 URL: "https://example.region.amazonaws.com", 68 HostnameImmutable: true, 69 }, 70 tests: []s3controlEndpointTest{ 71 {"abc", "123456789012", "https://example.region.amazonaws.com/v20180820/bucket/abc", ""}, 72 {"a.b.c", "123456789012", "https://example.region.amazonaws.com/v20180820/bucket/a.b.c", ""}, 73 {"a$b$c", "123456789012", "https://example.region.amazonaws.com/v20180820/bucket/a%24b%24c", ""}, 74 }, 75 }, 76 }, 77 } 78 79 for suitName, cs := range cases { 80 t.Run(suitName, func(t *testing.T) { 81 for unitName, c := range cs { 82 t.Run(unitName, func(t *testing.T) { 83 84 options := s3control.Options{ 85 Credentials: unit.StubCredentialsProvider{}, 86 Retryer: aws.NopRetryer{}, 87 Region: "mock-region", 88 89 HTTPClient: smithyhttp.NopClient{}, 90 91 UseDualstack: c.useDualstack, 92 } 93 94 if c.customEndpoint != nil { 95 options.EndpointResolver = s3control.EndpointResolverFunc( 96 func(region string, options s3control.EndpointResolverOptions) (aws.Endpoint, error) { 97 return *c.customEndpoint, nil 98 }) 99 } 100 101 svc := s3control.New(options) 102 for i, test := range c.tests { 103 t.Run(strconv.Itoa(i), func(t *testing.T) { 104 fm := requestRetrieverMiddleware{} 105 _, err := svc.DeleteBucket(context.Background(), 106 &s3control.DeleteBucketInput{ 107 Bucket: &test.bucket, 108 AccountId: &test.accountID, 109 }, 110 func(options *s3control.Options) { 111 options.APIOptions = append(options.APIOptions, 112 func(stack *middleware.Stack) error { 113 stack.Serialize.Insert(&fm, 114 "OperationSerializer", middleware.Before) 115 return nil 116 }) 117 118 }, 119 ) 120 121 if test.err != "" { 122 if err == nil { 123 t.Fatalf("test %d: expected error, got none", i) 124 } 125 if a, e := err.Error(), test.err; !strings.Contains(a, e) { 126 t.Fatalf("expect error code to contain %q, got %q", e, a) 127 } 128 return 129 } 130 if err != nil { 131 t.Fatalf("expect no error, got %v", err) 132 } 133 134 req := fm.request.Build(context.Background()) 135 if e, a := test.url, req.URL.String(); e != a { 136 t.Fatalf("expect URL %s, got %s", e, a) 137 } 138 }) 139 } 140 }) 141 } 142 }) 143 } 144} 145 146func TestEndpointWithARN(t *testing.T) { 147 // test cases 148 cases := map[string]struct { 149 options s3control.Options 150 bucket string 151 expectedErr string 152 expectedReqURL string 153 expectedSigningName string 154 expectedSigningRegion string 155 expectedHeaderForOutpostID string 156 expectedHeaderForAccountID bool 157 }{ 158 "Outpost AccessPoint with no S3UseARNRegion flag set": { 159 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 160 options: s3control.Options{ 161 Region: "us-west-2", 162 }, 163 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/bucket/myaccesspoint", 164 expectedSigningName: "s3-outposts", 165 expectedSigningRegion: "us-west-2", 166 expectedHeaderForAccountID: true, 167 expectedHeaderForOutpostID: "op-01234567890123456", 168 }, 169 "Outpost AccessPoint Cross-Region Enabled": { 170 bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 171 options: s3control.Options{ 172 Region: "us-west-2", 173 UseARNRegion: true, 174 }, 175 expectedReqURL: "https://s3-outposts.us-east-1.amazonaws.com/v20180820/bucket/myaccesspoint", 176 expectedSigningName: "s3-outposts", 177 expectedSigningRegion: "us-east-1", 178 expectedHeaderForAccountID: true, 179 expectedHeaderForOutpostID: "op-01234567890123456", 180 }, 181 "Outpost AccessPoint Cross-Region Disabled": { 182 bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 183 options: s3control.Options{ 184 Region: "us-west-2", 185 }, 186 expectedErr: "client region does not match provided ARN region", 187 }, 188 "Outpost AccessPoint other partition": { 189 bucket: "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 190 options: s3control.Options{ 191 Region: "us-west-2", 192 UseARNRegion: true, 193 }, 194 expectedErr: "ConfigurationError : client partition does not match provided ARN partition", 195 }, 196 "Outpost AccessPoint us-gov region": { 197 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 198 options: s3control.Options{ 199 Region: "us-gov-east-1", 200 UseARNRegion: true, 201 }, 202 expectedReqURL: "https://s3-outposts.us-gov-east-1.amazonaws.com/v20180820/bucket/myaccesspoint", 203 expectedSigningName: "s3-outposts", 204 expectedSigningRegion: "us-gov-east-1", 205 expectedHeaderForAccountID: true, 206 expectedHeaderForOutpostID: "op-01234567890123456", 207 }, 208 "Outpost AccessPoint with client region as FIPS": { 209 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 210 options: s3control.Options{ 211 Region: "us-gov-east-1-fips", 212 }, 213 expectedErr: "use of ARN is not supported when client or request is configured for FIPS", 214 }, 215 "Outpost AccessPoint with client FIPS region and use arn region enabled": { 216 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 217 options: s3control.Options{ 218 Region: "us-gov-east-1-fips", 219 UseARNRegion: true, 220 }, 221 expectedErr: "use of ARN is not supported when client or request is configured for FIPS", 222 }, 223 "Outpost AccessPoint client FIPS region in Arn": { 224 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 225 options: s3control.Options{ 226 Region: "us-gov-east-1-fips", 227 UseARNRegion: true, 228 }, 229 expectedErr: "use of ARN is not supported when client or request is configured for FIPS", 230 }, 231 "Outpost AccessPoint client FIPS region with valid ARN region": { 232 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 233 options: s3control.Options{ 234 Region: "us-gov-east-1-fips", 235 UseARNRegion: true, 236 }, 237 expectedErr: "use of ARN is not supported when client or request is configured for FIPS", 238 }, 239 "Outpost AccessPoint with DualStack": { 240 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 241 options: s3control.Options{ 242 Region: "us-west-2", 243 UseARNRegion: true, 244 UseDualstack: true, 245 }, 246 expectedErr: "ConfigurationError : client configured for S3 Dual-stack but is not supported with resource ARN", 247 }, 248 "Invalid outpost resource format": { 249 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost", 250 options: s3control.Options{ 251 Region: "us-west-2", 252 }, 253 expectedErr: "outpost resource-id not set", 254 }, 255 "Missing access point for outpost resource": { 256 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456", 257 options: s3control.Options{ 258 Region: "us-west-2", 259 }, 260 expectedErr: "incomplete outpost resource type", 261 }, 262 "access point": { 263 bucket: "myaccesspoint", 264 options: s3control.Options{ 265 Region: "us-west-2", 266 }, 267 expectedReqURL: "https://123456789012.s3-control.us-west-2.amazonaws.com/v20180820/bucket/myaccesspoint", 268 expectedHeaderForAccountID: true, 269 expectedSigningRegion: "us-west-2", 270 expectedSigningName: "s3", 271 }, 272 "outpost access point with unsupported sub-resource": { 273 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:mybucket:object:foo", 274 options: s3control.Options{ 275 Region: "us-west-2", 276 }, 277 expectedErr: "sub resource not supported", 278 }, 279 "Missing outpost identifiers in outpost access point arn": { 280 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:accesspoint:myendpoint", 281 options: s3control.Options{ 282 Region: "us-west-2", 283 }, 284 expectedErr: "invalid Amazon s3-outposts ARN", 285 }, 286 "Outpost Bucket with no S3UseARNRegion flag set": { 287 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket", 288 options: s3control.Options{ 289 Region: "us-west-2", 290 }, 291 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/bucket/mybucket", 292 expectedSigningName: "s3-outposts", 293 expectedSigningRegion: "us-west-2", 294 expectedHeaderForOutpostID: "op-01234567890123456", 295 expectedHeaderForAccountID: true, 296 }, 297 "Outpost Bucket Cross-Region Enabled": { 298 bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 299 options: s3control.Options{ 300 Region: "us-west-2", 301 UseARNRegion: true, 302 }, 303 expectedReqURL: "https://s3-outposts.us-east-1.amazonaws.com/v20180820/bucket/mybucket", 304 expectedSigningName: "s3-outposts", 305 expectedSigningRegion: "us-east-1", 306 expectedHeaderForOutpostID: "op-01234567890123456", 307 expectedHeaderForAccountID: true, 308 }, 309 "Outpost Bucket Cross-Region Disabled": { 310 bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 311 options: s3control.Options{ 312 Region: "us-west-2", 313 }, 314 expectedErr: "client region does not match provided ARN region", 315 }, 316 "Outpost Bucket other partition": { 317 bucket: "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 318 options: s3control.Options{ 319 Region: "us-west-2", 320 UseARNRegion: true, 321 }, 322 expectedErr: "ConfigurationError : client partition does not match provided ARN partition", 323 }, 324 "Outpost Bucket us-gov region": { 325 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 326 options: s3control.Options{ 327 Region: "us-gov-east-1", 328 UseARNRegion: true, 329 }, 330 expectedReqURL: "https://s3-outposts.us-gov-east-1.amazonaws.com/v20180820/bucket/mybucket", 331 expectedSigningName: "s3-outposts", 332 expectedSigningRegion: "us-gov-east-1", 333 expectedHeaderForOutpostID: "op-01234567890123456", 334 expectedHeaderForAccountID: true, 335 }, 336 "Outpost Bucket client FIPS region, cross-region ARN": { 337 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 338 options: s3control.Options{ 339 Region: "us-gov-west-1-fips", 340 }, 341 expectedErr: "ConfigurationError : client region does not match provided ARN region", 342 }, 343 "Outpost Bucket client FIPS region with non cross-region ARN region": { 344 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 345 options: s3control.Options{ 346 Region: "us-gov-east-1-fips", 347 UseARNRegion: true, 348 }, 349 expectedErr: "use of ARN is not supported when client or request is configured for FIPS", 350 }, 351 "Outpost Bucket with DualStack": { 352 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket", 353 options: s3control.Options{ 354 Region: "us-west-2", 355 UseDualstack: true, 356 }, 357 expectedErr: "ConfigurationError : client configured for S3 Dual-stack but is not supported with resource ARN", 358 }, 359 "Missing bucket id": { 360 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket", 361 options: s3control.Options{ 362 Region: "us-west-2", 363 }, 364 expectedErr: "invalid Amazon s3-outposts ARN", 365 }, 366 "Invalid ARN": { 367 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:bucket:mybucket", 368 options: s3control.Options{ 369 Region: "us-west-2", 370 }, 371 expectedErr: "invalid Amazon s3-outposts ARN, unknown resource type", 372 }, 373 "Invalid Outpost Bucket ARN with FIPS pseudo-region (prefix)": { 374 bucket: "arn:aws:s3-outposts:fips-us-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 375 options: s3control.Options{ 376 Region: "us-west-2", 377 UseARNRegion: true, 378 }, 379 expectedErr: "FIPS region not allowed in ARN", 380 }, 381 "Invalid Outpost Bucket ARN with FIPS pseudo-region (suffix)": { 382 bucket: "arn:aws:s3-outposts:us-east-1-fips:123456789012:outpost:op-01234567890123456:bucket:mybucket", 383 options: s3control.Options{ 384 Region: "us-west-2", 385 UseARNRegion: true, 386 }, 387 expectedErr: "FIPS region not allowed in ARN", 388 }, 389 "Invalid Outpost AccessPoint ARN with FIPS pseudo-region (prefix)": { 390 bucket: "arn:aws-us-gov:s3-outposts:fips-us-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 391 options: s3control.Options{ 392 Region: "us-west-2", 393 UseARNRegion: true, 394 }, 395 expectedErr: "FIPS region not allowed in ARN", 396 }, 397 "Invalid Outpost AccessPoint ARN with FIPS pseudo-region (suffix)": { 398 bucket: "arn:aws-us-gov:s3-outposts:us-east-1-fips:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 399 options: s3control.Options{ 400 Region: "us-west-2", 401 UseARNRegion: true, 402 }, 403 expectedErr: "FIPS region not allowed in ARN", 404 }, 405 } 406 407 for name, c := range cases { 408 t.Run(name, func(t *testing.T) { 409 410 // options 411 opts := c.options.Copy() 412 opts.Credentials = unit.StubCredentialsProvider{} 413 opts.HTTPClient = smithyhttp.NopClient{} 414 opts.Retryer = aws.NopRetryer{} 415 416 // build an s3control client 417 svc := s3control.New(opts) 418 // setup a request retriever middleware 419 fm := requestRetrieverMiddleware{} 420 421 ctx := context.Background() 422 423 // call an operation 424 _, err := svc.GetBucket(ctx, &s3control.GetBucketInput{ 425 Bucket: ptr.String(c.bucket), 426 AccountId: ptr.String("123456789012"), 427 }, func(options *s3control.Options) { 428 // append request retriever middleware for request inspection 429 options.APIOptions = append(options.APIOptions, 430 func(stack *middleware.Stack) error { 431 // adds AFTER operation serializer middleware 432 stack.Serialize.Insert(&fm, "OperationSerializer", middleware.After) 433 return nil 434 }) 435 }) 436 437 // inspect any errors 438 if len(c.expectedErr) != 0 { 439 if err == nil { 440 t.Fatalf("expected error, got none") 441 } 442 if a, e := err.Error(), c.expectedErr; !strings.Contains(a, e) { 443 t.Fatalf("expect error code to contain %q, got %q", e, a) 444 } 445 return 446 } 447 if err != nil { 448 t.Fatalf("expect no error, got %v", err) 449 } 450 451 // build the captured request 452 req := fm.request.Build(ctx) 453 // verify the built request is as expected 454 if e, a := c.expectedReqURL, req.URL.String(); e != a { 455 t.Fatalf("expect url %s, got %s", e, a) 456 } 457 458 if e, a := c.expectedSigningRegion, fm.signingRegion; !strings.EqualFold(e, a) { 459 t.Fatalf("expect signing region as %s, got %s", e, a) 460 } 461 462 if e, a := c.expectedSigningName, fm.signingName; !strings.EqualFold(e, a) { 463 t.Fatalf("expect signing name as %s, got %s", e, a) 464 } 465 466 if c.expectedHeaderForAccountID { 467 if e, a := "123456789012", req.Header.Get("x-amz-account-id"); e != a { 468 t.Fatalf("expect account id header value to be %v, got %v", e, a) 469 } 470 } 471 472 if e, a := c.expectedHeaderForOutpostID, req.Header.Get("x-amz-outpost-id"); e != a { 473 t.Fatalf("expect outpost id header value to be %v, got %v", e, a) 474 } 475 }) 476 477 } 478} 479 480type requestRetrieverMiddleware struct { 481 request *smithyhttp.Request 482 signingRegion string 483 signingName string 484} 485 486func TestCustomEndpoint_SpecialOperations(t *testing.T) { 487 cases := map[string]testCaseForEndpointCustomization{ 488 "CreateBucketOperation": { 489 options: s3control.Options{ 490 Region: "us-west-2", 491 }, 492 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 493 return svc.CreateBucket(ctx, &s3control.CreateBucketInput{ 494 Bucket: aws.String("mockBucket"), 495 OutpostId: aws.String("op-01234567890123456"), 496 }, func(options *s3control.Options) { 497 // append request retriever middleware for request inspection 498 options.APIOptions = append(options.APIOptions, 499 func(stack *middleware.Stack) error { 500 // adds AFTER operation serializer middleware 501 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 502 return nil 503 }) 504 }) 505 }, 506 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/bucket/mockBucket", 507 expectedSigningName: "s3-outposts", 508 expectedSigningRegion: "us-west-2", 509 expectedHeaderForOutpostID: "op-01234567890123456", 510 expectedHeaderForAccountID: false, 511 }, 512 "ListRegionalBucketsOperation": { 513 options: s3control.Options{ 514 Region: "us-west-2", 515 }, 516 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 517 return svc.ListRegionalBuckets(ctx, &s3control.ListRegionalBucketsInput{ 518 AccountId: aws.String("123456789012"), 519 OutpostId: aws.String("op-01234567890123456"), 520 }, func(options *s3control.Options) { 521 // append request retriever middleware for request inspection 522 options.APIOptions = append(options.APIOptions, 523 func(stack *middleware.Stack) error { 524 // adds AFTER operation serializer middleware 525 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 526 return nil 527 }) 528 }) 529 }, 530 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/bucket", 531 expectedSigningName: "s3-outposts", 532 expectedSigningRegion: "us-west-2", 533 expectedHeaderForOutpostID: "op-01234567890123456", 534 expectedHeaderForAccountID: true, 535 }, 536 "CreateAccessPoint bucket arn": { 537 options: s3control.Options{ 538 Region: "us-west-2", 539 }, 540 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 541 return svc.CreateAccessPoint(ctx, &s3control.CreateAccessPointInput{ 542 AccountId: aws.String("123456789012"), 543 Bucket: aws.String("arn:aws:s3:us-west-2:123456789012:bucket:mockBucket"), 544 Name: aws.String("mockName"), 545 }, func(options *s3control.Options) { 546 // append request retriever middleware for request inspection 547 options.APIOptions = append(options.APIOptions, 548 func(stack *middleware.Stack) error { 549 // adds AFTER operation serializer middleware 550 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 551 return nil 552 }) 553 }) 554 }, 555 expectedErr: "invalid Amazon s3 ARN, unknown resource type", 556 }, 557 "CreateAccessPoint outpost bucket arn": { 558 options: s3control.Options{ 559 Region: "us-west-2", 560 }, 561 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 562 return svc.CreateAccessPoint(ctx, &s3control.CreateAccessPointInput{ 563 AccountId: aws.String("123456789012"), 564 Bucket: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mockBucket"), 565 Name: aws.String("mockName"), 566 }, func(options *s3control.Options) { 567 // append request retriever middleware for request inspection 568 options.APIOptions = append(options.APIOptions, 569 func(stack *middleware.Stack) error { 570 // adds AFTER operation serializer middleware 571 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 572 return nil 573 }) 574 }) 575 }, 576 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/accesspoint/mockName", 577 expectedSigningName: "s3-outposts", 578 expectedSigningRegion: "us-west-2", 579 expectedHeaderForOutpostID: "op-01234567890123456", 580 }, 581 } 582 583 for name, c := range cases { 584 t.Run(name, func(t *testing.T) { 585 runValidations(t, c) 586 }) 587 } 588} 589 590func runValidations(t *testing.T, c testCaseForEndpointCustomization) { 591 // options 592 opts := c.options.Copy() 593 opts.Credentials = unit.StubCredentialsProvider{} 594 opts.HTTPClient = smithyhttp.NopClient{} 595 opts.Retryer = aws.NopRetryer{} 596 597 // build an s3control client 598 svc := s3control.New(opts) 599 // setup a request retriever middleware 600 fm := requestRetrieverMiddleware{} 601 602 ctx := context.Background() 603 604 // call an operation 605 _, err := c.operation(ctx, svc, &fm) 606 607 // inspect any errors 608 if len(c.expectedErr) != 0 { 609 if err == nil { 610 t.Fatalf("expected error, got none") 611 } 612 if a, e := err.Error(), c.expectedErr; !strings.Contains(a, e) { 613 t.Fatalf("expect error code to contain %q, got %q", e, a) 614 } 615 return 616 } 617 if err != nil { 618 t.Fatalf("expect no error, got %v", err) 619 } 620 621 // build the captured request 622 req := fm.request.Build(ctx) 623 // verify the built request is as expected 624 if e, a := c.expectedReqURL, req.URL.String(); e != a { 625 t.Fatalf("expect url %s, got %s", e, a) 626 } 627 628 if e, a := c.expectedSigningRegion, fm.signingRegion; !strings.EqualFold(e, a) { 629 t.Fatalf("expect signing region as %s, got %s", e, a) 630 } 631 632 if e, a := c.expectedSigningName, fm.signingName; !strings.EqualFold(e, a) { 633 t.Fatalf("expect signing name as %s, got %s", e, a) 634 } 635 636 if c.expectedHeaderForAccountID { 637 if e, a := "123456789012", req.Header.Get("x-amz-account-id"); e != a { 638 t.Fatalf("expect account id header value to be %v, got %v", e, a) 639 } 640 } 641 642 if e, a := c.expectedHeaderForOutpostID, req.Header.Get("x-amz-outpost-id"); e != a { 643 t.Fatalf("expect outpost id header value to be %v, got %v", e, a) 644 } 645} 646 647type testCaseForEndpointCustomization struct { 648 options s3control.Options 649 operation func(context.Context, *s3control.Client, *requestRetrieverMiddleware) (interface{}, error) 650 expectedReqURL string 651 expectedSigningName string 652 expectedSigningRegion string 653 expectedHeaderForOutpostID string 654 expectedErr string 655 expectedHeaderForAccountID bool 656} 657 658func TestVPC_CustomEndpoint(t *testing.T) { 659 account := "123456789012" 660 cases := map[string]testCaseForEndpointCustomization{ 661 "standard GetAccesspoint with custom endpoint url": { 662 options: s3control.Options{ 663 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 664 Region: "us-west-2", 665 }, 666 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 667 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 668 AccountId: aws.String(account), 669 Name: aws.String("apname"), 670 }, addRequestRetriever(fm)) 671 }, 672 expectedReqURL: "https://123456789012.beta.example.com/v20180820/accesspoint/apname", 673 expectedSigningName: "s3", 674 expectedSigningRegion: "us-west-2", 675 }, 676 "Outpost Accesspoint ARN with GetAccesspoint and custom endpoint url": { 677 options: s3control.Options{ 678 EndpointResolver: s3control.EndpointResolverFromURL( 679 "https://beta.example.com", 680 ), 681 Region: "us-west-2", 682 }, 683 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 684 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 685 AccountId: aws.String(account), 686 Name: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint"), 687 }, addRequestRetriever(fm)) 688 }, 689 expectedReqURL: "https://beta.example.com/v20180820/accesspoint/myaccesspoint", 690 expectedSigningName: "s3-outposts", 691 expectedSigningRegion: "us-west-2", 692 expectedHeaderForOutpostID: "op-01234567890123456", 693 }, 694 "standard CreateBucket with custom endpoint url": { 695 options: s3control.Options{ 696 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 697 Region: "us-west-2", 698 }, 699 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 700 return svc.CreateBucket(ctx, &s3control.CreateBucketInput{ 701 Bucket: aws.String("bucketname"), 702 OutpostId: aws.String("op-01234567890123456"), 703 }, addRequestRetriever(fm)) 704 }, 705 expectedReqURL: "https://beta.example.com/v20180820/bucket/bucketname", 706 expectedSigningName: "s3-outposts", 707 expectedSigningRegion: "us-west-2", 708 expectedHeaderForOutpostID: "op-01234567890123456", 709 }, 710 "Outpost Accesspoint for GetBucket with custom endpoint url": { 711 options: s3control.Options{ 712 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 713 Region: "us-west-2", 714 }, 715 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 716 return svc.GetBucket(ctx, &s3control.GetBucketInput{ 717 Bucket: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket"), 718 }, addRequestRetriever(fm)) 719 }, 720 expectedReqURL: "https://beta.example.com/v20180820/bucket/mybucket", 721 expectedSigningName: "s3-outposts", 722 expectedSigningRegion: "us-west-2", 723 expectedHeaderForOutpostID: "op-01234567890123456", 724 }, 725 "GetAccesspoint with dualstack and custom endpoint url": { 726 options: s3control.Options{ 727 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 728 Region: "us-west-2", 729 UseDualstack: true, 730 }, 731 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 732 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 733 AccountId: aws.String(account), 734 Name: aws.String("apname"), 735 }, addRequestRetriever(fm)) 736 }, 737 expectedReqURL: "https://123456789012.beta.example.com/v20180820/accesspoint/apname", 738 expectedSigningName: "s3", 739 expectedSigningRegion: "us-west-2", 740 }, 741 "GetAccesspoint with Outposts accesspoint ARN and dualstack": { 742 options: s3control.Options{ 743 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 744 Region: "us-west-2", 745 UseDualstack: true, 746 }, 747 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 748 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 749 AccountId: aws.String(account), 750 Name: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint"), 751 }, addRequestRetriever(fm)) 752 }, 753 expectedErr: "client configured for S3 Dual-stack but is not supported with resource ARN", 754 }, 755 "standard CreateBucket with dualstack": { 756 options: s3control.Options{ 757 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 758 Region: "us-west-2", 759 UseDualstack: true, 760 }, 761 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 762 return svc.CreateBucket(ctx, &s3control.CreateBucketInput{ 763 Bucket: aws.String("bucketname"), 764 OutpostId: aws.String("op-1234567890123456"), 765 }, addRequestRetriever(fm)) 766 }, 767 expectedErr: " dualstack is not supported for outposts request", 768 }, 769 "GetBucket with Outpost bucket ARN": { 770 options: s3control.Options{ 771 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 772 Region: "us-west-2", 773 UseDualstack: true, 774 }, 775 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 776 return svc.GetBucket(ctx, &s3control.GetBucketInput{ 777 Bucket: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket"), 778 }, addRequestRetriever(fm)) 779 }, 780 expectedErr: "client configured for S3 Dual-stack but is not supported with resource ARN", 781 }, 782 } 783 784 for name, c := range cases { 785 t.Run(name, func(t *testing.T) { 786 runValidations(t, c) 787 }) 788 } 789} 790 791func TestInputIsNotModified(t *testing.T) { 792 inputBucket := "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket" 793 794 // build options 795 opts := s3control.Options{} 796 opts.Credentials = unit.StubCredentialsProvider{} 797 opts.HTTPClient = smithyhttp.NopClient{} 798 opts.Retryer = aws.NopRetryer{} 799 opts.Region = "us-west-2" 800 opts.UseARNRegion = true 801 802 ctx := context.Background() 803 fm := requestRetrieverMiddleware{} 804 svc := s3control.New(opts) 805 params := s3control.DeleteBucketInput{Bucket: ptr.String(inputBucket)} 806 _, err := svc.DeleteBucket(ctx, ¶ms, func(options *s3control.Options) { 807 // append request retriever middleware for request inspection 808 options.APIOptions = append(options.APIOptions, 809 func(stack *middleware.Stack) error { 810 // adds AFTER operation serializer middleware 811 stack.Serialize.Insert(&fm, "OperationSerializer", middleware.After) 812 return nil 813 }) 814 }) 815 816 if err != nil { 817 t.Fatalf("expect no error, got %v", err.Error()) 818 } 819 820 // check if req params were modified 821 if e, a := *params.Bucket, inputBucket; !strings.EqualFold(e, a) { 822 t.Fatalf("expected no modification for operation input, "+ 823 "expected %v, got %v as bucket input", e, a) 824 } 825 826 if params.AccountId != nil { 827 t.Fatalf("expected original input to be unmodified, but account id was backfilled") 828 } 829 830 req := fm.request.Build(ctx) 831 modifiedAccountID := req.Header.Get("x-amz-account-id") 832 if len(modifiedAccountID) == 0 { 833 t.Fatalf("expected account id to be backfilled/modified, was not") 834 } 835 if e, a := "123456789012", modifiedAccountID; !strings.EqualFold(e, a) { 836 t.Fatalf("unexpected diff in account id backfilled from arn, expected %v, got %v", e, a) 837 } 838} 839 840func (*requestRetrieverMiddleware) ID() string { return "S3:requestRetrieverMiddleware" } 841 842func (rm *requestRetrieverMiddleware) HandleSerialize( 843 ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler, 844) ( 845 out middleware.SerializeOutput, metadata middleware.Metadata, err error, 846) { 847 req, ok := in.Request.(*smithyhttp.Request) 848 if !ok { 849 return out, metadata, fmt.Errorf("unknown request type %T", req) 850 } 851 rm.request = req 852 853 rm.signingName = awsmiddleware.GetSigningName(ctx) 854 rm.signingRegion = awsmiddleware.GetSigningRegion(ctx) 855 856 return next.HandleSerialize(ctx, in) 857} 858 859var addRequestRetriever = func(fm *requestRetrieverMiddleware) func(options *s3control.Options) { 860 return func(options *s3control.Options) { 861 // append request retriever middleware for request inspection 862 options.APIOptions = append(options.APIOptions, 863 func(stack *middleware.Stack) error { 864 // adds AFTER operation serializer middleware 865 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 866 return nil 867 }) 868 } 869} 870