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: "InvalidARNError : resource ARN not supported for FIPS region", 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 expectedSigningName: "s3-outposts", 222 expectedSigningRegion: "us-gov-east-1", 223 expectedReqURL: "https://s3-outposts.us-gov-east-1.amazonaws.com/v20180820/bucket/myaccesspoint", 224 expectedHeaderForAccountID: true, 225 expectedHeaderForOutpostID: "op-01234567890123456", 226 }, 227 "Outpost AccessPoint Fips region in Arn": { 228 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1-fips:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 229 options: s3control.Options{ 230 Region: "us-gov-east-1-fips", 231 UseARNRegion: true, 232 }, 233 expectedErr: "InvalidARNError : resource ARN not supported for FIPS region", 234 }, 235 "Outpost AccessPoint Fips region with valid ARN region": { 236 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 237 options: s3control.Options{ 238 Region: "us-gov-east-1-fips", 239 UseARNRegion: true, 240 }, 241 expectedReqURL: "https://s3-outposts.us-gov-east-1.amazonaws.com/v20180820/bucket/myaccesspoint", 242 expectedSigningName: "s3-outposts", 243 expectedSigningRegion: "us-gov-east-1", 244 expectedHeaderForAccountID: true, 245 expectedHeaderForOutpostID: "op-01234567890123456", 246 }, 247 "Outpost AccessPoint with DualStack": { 248 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint", 249 options: s3control.Options{ 250 Region: "us-west-2", 251 UseARNRegion: true, 252 UseDualstack: true, 253 }, 254 expectedErr: "ConfigurationError : client configured for S3 Dual-stack but is not supported with resource ARN", 255 }, 256 "Invalid outpost resource format": { 257 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost", 258 options: s3control.Options{ 259 Region: "us-west-2", 260 }, 261 expectedErr: "outpost resource-id not set", 262 }, 263 "Missing access point for outpost resource": { 264 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456", 265 options: s3control.Options{ 266 Region: "us-west-2", 267 }, 268 expectedErr: "incomplete outpost resource type", 269 }, 270 "access point": { 271 bucket: "myaccesspoint", 272 options: s3control.Options{ 273 Region: "us-west-2", 274 }, 275 expectedReqURL: "https://123456789012.s3-control.us-west-2.amazonaws.com/v20180820/bucket/myaccesspoint", 276 expectedHeaderForAccountID: true, 277 expectedSigningRegion: "us-west-2", 278 expectedSigningName: "s3", 279 }, 280 "outpost access point with unsupported sub-resource": { 281 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:mybucket:object:foo", 282 options: s3control.Options{ 283 Region: "us-west-2", 284 }, 285 expectedErr: "sub resource not supported", 286 }, 287 "Missing outpost identifiers in outpost access point arn": { 288 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:accesspoint:myendpoint", 289 options: s3control.Options{ 290 Region: "us-west-2", 291 }, 292 expectedErr: "invalid Amazon s3-outposts ARN", 293 }, 294 "Outpost Bucket with no S3UseARNRegion flag set": { 295 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket", 296 options: s3control.Options{ 297 Region: "us-west-2", 298 }, 299 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/bucket/mybucket", 300 expectedSigningName: "s3-outposts", 301 expectedSigningRegion: "us-west-2", 302 expectedHeaderForOutpostID: "op-01234567890123456", 303 expectedHeaderForAccountID: true, 304 }, 305 "Outpost Bucket Cross-Region Enabled": { 306 bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 307 options: s3control.Options{ 308 Region: "us-west-2", 309 UseARNRegion: true, 310 }, 311 expectedReqURL: "https://s3-outposts.us-east-1.amazonaws.com/v20180820/bucket/mybucket", 312 expectedSigningName: "s3-outposts", 313 expectedSigningRegion: "us-east-1", 314 expectedHeaderForOutpostID: "op-01234567890123456", 315 expectedHeaderForAccountID: true, 316 }, 317 "Outpost Bucket Cross-Region Disabled": { 318 bucket: "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 319 options: s3control.Options{ 320 Region: "us-west-2", 321 }, 322 expectedErr: "client region does not match provided ARN region", 323 }, 324 "Outpost Bucket other partition": { 325 bucket: "arn:aws-cn:s3-outposts:cn-north-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 326 options: s3control.Options{ 327 Region: "us-west-2", 328 UseARNRegion: true, 329 }, 330 expectedErr: "ConfigurationError : client partition does not match provided ARN partition", 331 }, 332 "Outpost Bucket us-gov region": { 333 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 334 options: s3control.Options{ 335 Region: "us-gov-east-1", 336 UseARNRegion: true, 337 }, 338 expectedReqURL: "https://s3-outposts.us-gov-east-1.amazonaws.com/v20180820/bucket/mybucket", 339 expectedSigningName: "s3-outposts", 340 expectedSigningRegion: "us-gov-east-1", 341 expectedHeaderForOutpostID: "op-01234567890123456", 342 expectedHeaderForAccountID: true, 343 }, 344 "Outpost Bucket Fips region": { 345 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 346 options: s3control.Options{ 347 Region: "us-gov-west-1-fips", 348 }, 349 expectedErr: "ConfigurationError : client region does not match provided ARN region", 350 }, 351 "Outpost Bucket Fips region in Arn": { 352 bucket: "arn:aws-us-gov:s3-outposts:fips-us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 353 options: s3control.Options{ 354 Region: "us-gov-east-1-fips", 355 UseARNRegion: true, 356 }, 357 expectedErr: "InvalidARNError : resource ARN not supported for FIPS region", 358 }, 359 "Outpost Bucket Fips region with valid ARN region": { 360 bucket: "arn:aws-us-gov:s3-outposts:us-gov-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket", 361 options: s3control.Options{ 362 Region: "us-gov-east-1-fips", 363 UseARNRegion: true, 364 }, 365 expectedReqURL: "https://s3-outposts.us-gov-east-1.amazonaws.com/v20180820/bucket/mybucket", 366 expectedSigningName: "s3-outposts", 367 expectedSigningRegion: "us-gov-east-1", 368 expectedHeaderForOutpostID: "op-01234567890123456", 369 expectedHeaderForAccountID: true, 370 }, 371 "Outpost Bucket with DualStack": { 372 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket", 373 options: s3control.Options{ 374 Region: "us-west-2", 375 UseDualstack: true, 376 }, 377 expectedErr: "ConfigurationError : client configured for S3 Dual-stack but is not supported with resource ARN", 378 }, 379 "Missing bucket id": { 380 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket", 381 options: s3control.Options{ 382 Region: "us-west-2", 383 }, 384 expectedErr: "invalid Amazon s3-outposts ARN", 385 }, 386 "Invalid ARN": { 387 bucket: "arn:aws:s3-outposts:us-west-2:123456789012:bucket:mybucket", 388 options: s3control.Options{ 389 Region: "us-west-2", 390 }, 391 expectedErr: "invalid Amazon s3-outposts ARN, unknown resource type", 392 }, 393 } 394 395 for name, c := range cases { 396 t.Run(name, func(t *testing.T) { 397 398 // options 399 opts := c.options.Copy() 400 opts.Credentials = unit.StubCredentialsProvider{} 401 opts.HTTPClient = smithyhttp.NopClient{} 402 opts.Retryer = aws.NopRetryer{} 403 404 // build an s3control client 405 svc := s3control.New(opts) 406 // setup a request retriever middleware 407 fm := requestRetrieverMiddleware{} 408 409 ctx := context.Background() 410 411 // call an operation 412 _, err := svc.GetBucket(ctx, &s3control.GetBucketInput{ 413 Bucket: ptr.String(c.bucket), 414 AccountId: ptr.String("123456789012"), 415 }, func(options *s3control.Options) { 416 // append request retriever middleware for request inspection 417 options.APIOptions = append(options.APIOptions, 418 func(stack *middleware.Stack) error { 419 // adds AFTER operation serializer middleware 420 stack.Serialize.Insert(&fm, "OperationSerializer", middleware.After) 421 return nil 422 }) 423 }) 424 425 // inspect any errors 426 if len(c.expectedErr) != 0 { 427 if err == nil { 428 t.Fatalf("expected error, got none") 429 } 430 if a, e := err.Error(), c.expectedErr; !strings.Contains(a, e) { 431 t.Fatalf("expect error code to contain %q, got %q", e, a) 432 } 433 return 434 } 435 if err != nil { 436 t.Fatalf("expect no error, got %v", err) 437 } 438 439 // build the captured request 440 req := fm.request.Build(ctx) 441 // verify the built request is as expected 442 if e, a := c.expectedReqURL, req.URL.String(); e != a { 443 t.Fatalf("expect url %s, got %s", e, a) 444 } 445 446 if e, a := c.expectedSigningRegion, fm.signingRegion; !strings.EqualFold(e, a) { 447 t.Fatalf("expect signing region as %s, got %s", e, a) 448 } 449 450 if e, a := c.expectedSigningName, fm.signingName; !strings.EqualFold(e, a) { 451 t.Fatalf("expect signing name as %s, got %s", e, a) 452 } 453 454 if c.expectedHeaderForAccountID { 455 if e, a := "123456789012", req.Header.Get("x-amz-account-id"); e != a { 456 t.Fatalf("expect account id header value to be %v, got %v", e, a) 457 } 458 } 459 460 if e, a := c.expectedHeaderForOutpostID, req.Header.Get("x-amz-outpost-id"); e != a { 461 t.Fatalf("expect outpost id header value to be %v, got %v", e, a) 462 } 463 }) 464 465 } 466} 467 468type requestRetrieverMiddleware struct { 469 request *smithyhttp.Request 470 signingRegion string 471 signingName string 472} 473 474func TestCustomEndpoint_SpecialOperations(t *testing.T) { 475 cases := map[string]testCaseForEndpointCustomization{ 476 "CreateBucketOperation": { 477 options: s3control.Options{ 478 Region: "us-west-2", 479 }, 480 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 481 return svc.CreateBucket(ctx, &s3control.CreateBucketInput{ 482 Bucket: aws.String("mockBucket"), 483 OutpostId: aws.String("op-01234567890123456"), 484 }, func(options *s3control.Options) { 485 // append request retriever middleware for request inspection 486 options.APIOptions = append(options.APIOptions, 487 func(stack *middleware.Stack) error { 488 // adds AFTER operation serializer middleware 489 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 490 return nil 491 }) 492 }) 493 }, 494 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/bucket/mockBucket", 495 expectedSigningName: "s3-outposts", 496 expectedSigningRegion: "us-west-2", 497 expectedHeaderForOutpostID: "op-01234567890123456", 498 expectedHeaderForAccountID: false, 499 }, 500 "ListRegionalBucketsOperation": { 501 options: s3control.Options{ 502 Region: "us-west-2", 503 }, 504 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 505 return svc.ListRegionalBuckets(ctx, &s3control.ListRegionalBucketsInput{ 506 AccountId: aws.String("123456789012"), 507 OutpostId: aws.String("op-01234567890123456"), 508 }, func(options *s3control.Options) { 509 // append request retriever middleware for request inspection 510 options.APIOptions = append(options.APIOptions, 511 func(stack *middleware.Stack) error { 512 // adds AFTER operation serializer middleware 513 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 514 return nil 515 }) 516 }) 517 }, 518 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/bucket", 519 expectedSigningName: "s3-outposts", 520 expectedSigningRegion: "us-west-2", 521 expectedHeaderForOutpostID: "op-01234567890123456", 522 expectedHeaderForAccountID: true, 523 }, 524 "CreateAccessPoint bucket arn": { 525 options: s3control.Options{ 526 Region: "us-west-2", 527 }, 528 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 529 return svc.CreateAccessPoint(ctx, &s3control.CreateAccessPointInput{ 530 AccountId: aws.String("123456789012"), 531 Bucket: aws.String("arn:aws:s3:us-west-2:123456789012:bucket:mockBucket"), 532 Name: aws.String("mockName"), 533 }, func(options *s3control.Options) { 534 // append request retriever middleware for request inspection 535 options.APIOptions = append(options.APIOptions, 536 func(stack *middleware.Stack) error { 537 // adds AFTER operation serializer middleware 538 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 539 return nil 540 }) 541 }) 542 }, 543 expectedErr: "invalid Amazon s3 ARN, unknown resource type", 544 }, 545 "CreateAccessPoint outpost bucket arn": { 546 options: s3control.Options{ 547 Region: "us-west-2", 548 }, 549 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 550 return svc.CreateAccessPoint(ctx, &s3control.CreateAccessPointInput{ 551 AccountId: aws.String("123456789012"), 552 Bucket: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mockBucket"), 553 Name: aws.String("mockName"), 554 }, func(options *s3control.Options) { 555 // append request retriever middleware for request inspection 556 options.APIOptions = append(options.APIOptions, 557 func(stack *middleware.Stack) error { 558 // adds AFTER operation serializer middleware 559 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 560 return nil 561 }) 562 }) 563 }, 564 expectedReqURL: "https://s3-outposts.us-west-2.amazonaws.com/v20180820/accesspoint/mockName", 565 expectedSigningName: "s3-outposts", 566 expectedSigningRegion: "us-west-2", 567 expectedHeaderForOutpostID: "op-01234567890123456", 568 }, 569 } 570 571 for name, c := range cases { 572 t.Run(name, func(t *testing.T) { 573 runValidations(t, c) 574 }) 575 } 576} 577 578func runValidations(t *testing.T, c testCaseForEndpointCustomization) { 579 // options 580 opts := c.options.Copy() 581 opts.Credentials = unit.StubCredentialsProvider{} 582 opts.HTTPClient = smithyhttp.NopClient{} 583 opts.Retryer = aws.NopRetryer{} 584 585 // build an s3control client 586 svc := s3control.New(opts) 587 // setup a request retriever middleware 588 fm := requestRetrieverMiddleware{} 589 590 ctx := context.Background() 591 592 // call an operation 593 _, err := c.operation(ctx, svc, &fm) 594 595 // inspect any errors 596 if len(c.expectedErr) != 0 { 597 if err == nil { 598 t.Fatalf("expected error, got none") 599 } 600 if a, e := err.Error(), c.expectedErr; !strings.Contains(a, e) { 601 t.Fatalf("expect error code to contain %q, got %q", e, a) 602 } 603 return 604 } 605 if err != nil { 606 t.Fatalf("expect no error, got %v", err) 607 } 608 609 // build the captured request 610 req := fm.request.Build(ctx) 611 // verify the built request is as expected 612 if e, a := c.expectedReqURL, req.URL.String(); e != a { 613 t.Fatalf("expect url %s, got %s", e, a) 614 } 615 616 if e, a := c.expectedSigningRegion, fm.signingRegion; !strings.EqualFold(e, a) { 617 t.Fatalf("expect signing region as %s, got %s", e, a) 618 } 619 620 if e, a := c.expectedSigningName, fm.signingName; !strings.EqualFold(e, a) { 621 t.Fatalf("expect signing name as %s, got %s", e, a) 622 } 623 624 if c.expectedHeaderForAccountID { 625 if e, a := "123456789012", req.Header.Get("x-amz-account-id"); e != a { 626 t.Fatalf("expect account id header value to be %v, got %v", e, a) 627 } 628 } 629 630 if e, a := c.expectedHeaderForOutpostID, req.Header.Get("x-amz-outpost-id"); e != a { 631 t.Fatalf("expect outpost id header value to be %v, got %v", e, a) 632 } 633} 634 635type testCaseForEndpointCustomization struct { 636 options s3control.Options 637 operation func(context.Context, *s3control.Client, *requestRetrieverMiddleware) (interface{}, error) 638 expectedReqURL string 639 expectedSigningName string 640 expectedSigningRegion string 641 expectedHeaderForOutpostID string 642 expectedErr string 643 expectedHeaderForAccountID bool 644} 645 646func TestVPC_CustomEndpoint(t *testing.T) { 647 account := "123456789012" 648 cases := map[string]testCaseForEndpointCustomization{ 649 "standard GetAccesspoint with custom endpoint url": { 650 options: s3control.Options{ 651 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 652 Region: "us-west-2", 653 }, 654 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 655 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 656 AccountId: aws.String(account), 657 Name: aws.String("apname"), 658 }, addRequestRetriever(fm)) 659 }, 660 expectedReqURL: "https://123456789012.beta.example.com/v20180820/accesspoint/apname", 661 expectedSigningName: "s3", 662 expectedSigningRegion: "us-west-2", 663 }, 664 "Outpost Accesspoint ARN with GetAccesspoint and custom endpoint url": { 665 options: s3control.Options{ 666 EndpointResolver: s3control.EndpointResolverFromURL( 667 "https://beta.example.com", 668 ), 669 Region: "us-west-2", 670 }, 671 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 672 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 673 AccountId: aws.String(account), 674 Name: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint"), 675 }, addRequestRetriever(fm)) 676 }, 677 expectedReqURL: "https://beta.example.com/v20180820/accesspoint/myaccesspoint", 678 expectedSigningName: "s3-outposts", 679 expectedSigningRegion: "us-west-2", 680 expectedHeaderForOutpostID: "op-01234567890123456", 681 }, 682 "standard CreateBucket with custom endpoint url": { 683 options: s3control.Options{ 684 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 685 Region: "us-west-2", 686 }, 687 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 688 return svc.CreateBucket(ctx, &s3control.CreateBucketInput{ 689 Bucket: aws.String("bucketname"), 690 OutpostId: aws.String("op-01234567890123456"), 691 }, addRequestRetriever(fm)) 692 }, 693 expectedReqURL: "https://beta.example.com/v20180820/bucket/bucketname", 694 expectedSigningName: "s3-outposts", 695 expectedSigningRegion: "us-west-2", 696 expectedHeaderForOutpostID: "op-01234567890123456", 697 }, 698 "Outpost Accesspoint for GetBucket with custom endpoint url": { 699 options: s3control.Options{ 700 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 701 Region: "us-west-2", 702 }, 703 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 704 return svc.GetBucket(ctx, &s3control.GetBucketInput{ 705 Bucket: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket"), 706 }, addRequestRetriever(fm)) 707 }, 708 expectedReqURL: "https://beta.example.com/v20180820/bucket/mybucket", 709 expectedSigningName: "s3-outposts", 710 expectedSigningRegion: "us-west-2", 711 expectedHeaderForOutpostID: "op-01234567890123456", 712 }, 713 "GetAccesspoint with dualstack and custom endpoint url": { 714 options: s3control.Options{ 715 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 716 Region: "us-west-2", 717 UseDualstack: true, 718 }, 719 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 720 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 721 AccountId: aws.String(account), 722 Name: aws.String("apname"), 723 }, addRequestRetriever(fm)) 724 }, 725 expectedReqURL: "https://123456789012.beta.example.com/v20180820/accesspoint/apname", 726 expectedSigningName: "s3", 727 expectedSigningRegion: "us-west-2", 728 }, 729 "GetAccesspoint with Outposts accesspoint ARN and dualstack": { 730 options: s3control.Options{ 731 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 732 Region: "us-west-2", 733 UseDualstack: true, 734 }, 735 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 736 return svc.GetAccessPoint(ctx, &s3control.GetAccessPointInput{ 737 AccountId: aws.String(account), 738 Name: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:accesspoint:myaccesspoint"), 739 }, addRequestRetriever(fm)) 740 }, 741 expectedErr: "client configured for S3 Dual-stack but is not supported with resource ARN", 742 }, 743 "standard CreateBucket with dualstack": { 744 options: s3control.Options{ 745 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 746 Region: "us-west-2", 747 UseDualstack: true, 748 }, 749 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 750 return svc.CreateBucket(ctx, &s3control.CreateBucketInput{ 751 Bucket: aws.String("bucketname"), 752 OutpostId: aws.String("op-1234567890123456"), 753 }, addRequestRetriever(fm)) 754 }, 755 expectedErr: " dualstack is not supported for outposts request", 756 }, 757 "GetBucket with Outpost bucket ARN": { 758 options: s3control.Options{ 759 EndpointResolver: s3control.EndpointResolverFromURL("https://beta.example.com"), 760 Region: "us-west-2", 761 UseDualstack: true, 762 }, 763 operation: func(ctx context.Context, svc *s3control.Client, fm *requestRetrieverMiddleware) (interface{}, error) { 764 return svc.GetBucket(ctx, &s3control.GetBucketInput{ 765 Bucket: aws.String("arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-01234567890123456:bucket:mybucket"), 766 }, addRequestRetriever(fm)) 767 }, 768 expectedErr: "client configured for S3 Dual-stack but is not supported with resource ARN", 769 }, 770 } 771 772 for name, c := range cases { 773 t.Run(name, func(t *testing.T) { 774 runValidations(t, c) 775 }) 776 } 777} 778 779func TestInputIsNotModified(t *testing.T) { 780 inputBucket := "arn:aws:s3-outposts:us-east-1:123456789012:outpost:op-01234567890123456:bucket:mybucket" 781 782 // build options 783 opts := s3control.Options{} 784 opts.Credentials = unit.StubCredentialsProvider{} 785 opts.HTTPClient = smithyhttp.NopClient{} 786 opts.Retryer = aws.NopRetryer{} 787 opts.Region = "us-west-2" 788 opts.UseARNRegion = true 789 790 ctx := context.Background() 791 fm := requestRetrieverMiddleware{} 792 svc := s3control.New(opts) 793 params := s3control.DeleteBucketInput{Bucket: ptr.String(inputBucket)} 794 _, err := svc.DeleteBucket(ctx, ¶ms, func(options *s3control.Options) { 795 // append request retriever middleware for request inspection 796 options.APIOptions = append(options.APIOptions, 797 func(stack *middleware.Stack) error { 798 // adds AFTER operation serializer middleware 799 stack.Serialize.Insert(&fm, "OperationSerializer", middleware.After) 800 return nil 801 }) 802 }) 803 804 if err != nil { 805 t.Fatalf("expect no error, got %v", err.Error()) 806 } 807 808 // check if req params were modified 809 if e, a := *params.Bucket, inputBucket; !strings.EqualFold(e, a) { 810 t.Fatalf("expected no modification for operation input, "+ 811 "expected %v, got %v as bucket input", e, a) 812 } 813 814 if params.AccountId != nil { 815 t.Fatalf("expected original input to be unmodified, but account id was backfilled") 816 } 817 818 req := fm.request.Build(ctx) 819 modifiedAccountID := req.Header.Get("x-amz-account-id") 820 if len(modifiedAccountID) == 0 { 821 t.Fatalf("expected account id to be backfilled/modified, was not") 822 } 823 if e, a := "123456789012", modifiedAccountID; !strings.EqualFold(e, a) { 824 t.Fatalf("unexpected diff in account id backfilled from arn, expected %v, got %v", e, a) 825 } 826} 827 828func (*requestRetrieverMiddleware) ID() string { return "S3:requestRetrieverMiddleware" } 829 830func (rm *requestRetrieverMiddleware) HandleSerialize( 831 ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler, 832) ( 833 out middleware.SerializeOutput, metadata middleware.Metadata, err error, 834) { 835 req, ok := in.Request.(*smithyhttp.Request) 836 if !ok { 837 return out, metadata, fmt.Errorf("unknown request type %T", req) 838 } 839 rm.request = req 840 841 rm.signingName = awsmiddleware.GetSigningName(ctx) 842 rm.signingRegion = awsmiddleware.GetSigningRegion(ctx) 843 844 return next.HandleSerialize(ctx, in) 845} 846 847var addRequestRetriever = func(fm *requestRetrieverMiddleware) func(options *s3control.Options) { 848 return func(options *s3control.Options) { 849 // append request retriever middleware for request inspection 850 options.APIOptions = append(options.APIOptions, 851 func(stack *middleware.Stack) error { 852 // adds AFTER operation serializer middleware 853 stack.Serialize.Insert(fm, "OperationSerializer", middleware.After) 854 return nil 855 }) 856 } 857} 858