github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/aws/endpoint_test.go (about) 1 /* 2 Copyright 2022 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package aws 18 19 import ( 20 "fmt" 21 "testing" 22 23 "github.com/gravitational/trace" 24 "github.com/stretchr/testify/require" 25 ) 26 27 func TestParseRDSEndpoint(t *testing.T) { 28 tests := []struct { 29 name string 30 endpoint string 31 expectIsRDSEndpoint bool 32 expectDetails *RDSEndpointDetails 33 expectParseErrorIs func(error) bool 34 }{ 35 { 36 name: "RDS instance", 37 endpoint: "aurora-instance-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432", 38 expectIsRDSEndpoint: true, 39 expectDetails: &RDSEndpointDetails{ 40 InstanceID: "aurora-instance-1", 41 Region: "us-west-1", 42 EndpointType: "instance", 43 }, 44 }, 45 { 46 name: "RDS instance in cn-north-1", 47 endpoint: "aurora-instance-2.abcdefghijklmnop.rds.cn-north-1.amazonaws.com.cn", 48 expectIsRDSEndpoint: true, 49 expectDetails: &RDSEndpointDetails{ 50 InstanceID: "aurora-instance-2", 51 Region: "cn-north-1", 52 EndpointType: "instance", 53 }, 54 }, 55 { 56 name: "RDS cluster", 57 endpoint: "my-cluster.cluster-abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432", 58 expectIsRDSEndpoint: true, 59 expectDetails: &RDSEndpointDetails{ 60 ClusterID: "my-cluster", 61 Region: "us-west-1", 62 EndpointType: "primary", 63 }, 64 }, 65 { 66 name: "RDS cluster reader", 67 endpoint: "my-cluster.cluster-ro-abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432", 68 expectIsRDSEndpoint: true, 69 expectDetails: &RDSEndpointDetails{ 70 ClusterID: "my-cluster", 71 Region: "us-west-1", 72 EndpointType: "reader", 73 }, 74 }, 75 { 76 name: "RDS cluster custom endpoint", 77 endpoint: "my-custom.cluster-custom-abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432", 78 expectIsRDSEndpoint: true, 79 expectDetails: &RDSEndpointDetails{ 80 ClusterCustomEndpointName: "my-custom", 81 Region: "us-west-1", 82 EndpointType: "custom", 83 }, 84 }, 85 { 86 name: "RDS proxy", 87 endpoint: "my-proxy.proxy-abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432", 88 expectIsRDSEndpoint: true, 89 expectDetails: &RDSEndpointDetails{ 90 ProxyName: "my-proxy", 91 Region: "us-west-1", 92 }, 93 }, 94 { 95 name: "RDS proxy custom endpoint", 96 endpoint: "my-custom.endpoint.proxy-abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432", 97 expectIsRDSEndpoint: true, 98 expectDetails: &RDSEndpointDetails{ 99 ProxyCustomEndpointName: "my-custom", 100 Region: "us-west-1", 101 }, 102 }, 103 { 104 name: "localhost:5432", 105 endpoint: "localhost", 106 expectIsRDSEndpoint: false, 107 expectParseErrorIs: trace.IsBadParameter, 108 }, 109 { 110 name: "Redshift endpoint fails", 111 endpoint: "redshift-cluster-1.abcdefghijklmnop.us-east-1.redshift.amazonaws.com", 112 expectIsRDSEndpoint: false, 113 expectParseErrorIs: trace.IsBadParameter, 114 }, 115 } 116 117 for _, test := range tests { 118 test := test 119 t.Run(test.name, func(t *testing.T) { 120 t.Parallel() 121 122 require.Equal(t, test.expectIsRDSEndpoint, IsRDSEndpoint(test.endpoint)) 123 124 actualDetails, err := ParseRDSEndpoint(test.endpoint) 125 if test.expectParseErrorIs != nil { 126 require.Error(t, err) 127 require.True(t, test.expectParseErrorIs(err)) 128 } else { 129 require.NoError(t, err) 130 require.Equal(t, test.expectDetails, actualDetails) 131 } 132 }) 133 } 134 } 135 136 func TestParseRedshiftEndpoint(t *testing.T) { 137 tests := []struct { 138 name string 139 endpoint string 140 expectIsRedshiftEndpoint bool 141 expectClusterID string 142 expectRegion string 143 expectParseErrorIs func(error) bool 144 }{ 145 { 146 name: "standard", 147 endpoint: "redshift-cluster-1.abcdefghijklmnop.us-east-1.redshift.amazonaws.com:5432", 148 expectClusterID: "redshift-cluster-1", 149 expectRegion: "us-east-1", 150 expectIsRedshiftEndpoint: true, 151 }, 152 { 153 name: "cn-north-1", 154 endpoint: "redshift-cluster-2.abcdefghijklmnop.redshift.cn-north-1.amazonaws.com.cn", 155 expectClusterID: "redshift-cluster-2", 156 expectRegion: "cn-north-1", 157 expectIsRedshiftEndpoint: true, 158 }, 159 { 160 name: "localhost:5432", 161 endpoint: "localhost", 162 expectIsRedshiftEndpoint: false, 163 expectParseErrorIs: trace.IsBadParameter, 164 }, 165 { 166 name: "RDS endpoint fails", 167 endpoint: "aurora-instance-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com", 168 expectIsRedshiftEndpoint: false, 169 expectParseErrorIs: trace.IsBadParameter, 170 }, 171 } 172 173 for _, test := range tests { 174 test := test 175 t.Run(test.name, func(t *testing.T) { 176 t.Parallel() 177 178 require.Equal(t, test.expectIsRedshiftEndpoint, IsRedshiftEndpoint(test.endpoint)) 179 180 clusterID, region, err := ParseRedshiftEndpoint(test.endpoint) 181 if test.expectParseErrorIs != nil { 182 require.Error(t, err) 183 require.True(t, test.expectParseErrorIs(err)) 184 } else { 185 require.NoError(t, err) 186 require.Equal(t, test.expectClusterID, clusterID) 187 require.Equal(t, test.expectRegion, region) 188 } 189 }) 190 } 191 } 192 193 func TestParseElastiCacheEndpoint(t *testing.T) { 194 tests := []struct { 195 name string 196 inputURI string 197 expectInfo *RedisEndpointInfo 198 expectError bool 199 }{ 200 { 201 name: "configuration endpoint, TLS enabled", 202 inputURI: "clustercfg.my-redis-shards.xxxxxx.use1.cache.amazonaws.com:6379", 203 expectInfo: &RedisEndpointInfo{ 204 ID: "my-redis-shards", 205 Region: "us-east-1", 206 TransitEncryptionEnabled: true, 207 EndpointType: ElastiCacheConfigurationEndpoint, 208 }, 209 }, 210 { 211 name: "primary endpoint, TLS enabled", 212 inputURI: "master.my-redis-cluster.xxxxxx.cac1.cache.amazonaws.com:6379", 213 expectInfo: &RedisEndpointInfo{ 214 ID: "my-redis-cluster", 215 Region: "ca-central-1", 216 TransitEncryptionEnabled: true, 217 EndpointType: ElastiCachePrimaryEndpoint, 218 }, 219 }, 220 { 221 name: "reader endpoint, TLS enabled", 222 inputURI: "replica.my-redis-cluster.xxxxxx.cac1.cache.amazonaws.com:6379", 223 expectInfo: &RedisEndpointInfo{ 224 ID: "my-redis-cluster", 225 Region: "ca-central-1", 226 TransitEncryptionEnabled: true, 227 EndpointType: ElastiCacheReaderEndpoint, 228 }, 229 }, 230 { 231 name: "node endpoint, TLS enabled", 232 inputURI: "my-redis-shards-0002-001.my-redis-shards.xxxxxx.cac1.cache.amazonaws.com:6379", 233 expectInfo: &RedisEndpointInfo{ 234 ID: "my-redis-shards", 235 Region: "ca-central-1", 236 TransitEncryptionEnabled: true, 237 EndpointType: ElastiCacheNodeEndpoint, 238 }, 239 }, 240 { 241 name: "configuration endpoint, TLS disabled", 242 inputURI: "my-redis-shards.xxxxxx.clustercfg.use1.cache.amazonaws.com:6379", 243 expectInfo: &RedisEndpointInfo{ 244 ID: "my-redis-shards", 245 Region: "us-east-1", 246 EndpointType: ElastiCacheConfigurationEndpoint, 247 }, 248 }, 249 { 250 name: "primary endpoint, TLS disabled", 251 inputURI: "my-redis-cluster.xxxxxx.ng.0001.cac1.cache.amazonaws.com:6379", 252 expectInfo: &RedisEndpointInfo{ 253 ID: "my-redis-cluster", 254 Region: "ca-central-1", 255 EndpointType: ElastiCachePrimaryEndpoint, 256 }, 257 }, 258 { 259 name: "reader endpoint, TLS disabled", 260 inputURI: "my-redis-cluster-ro.xxxxxx.ng.0001.cac1.cache.amazonaws.com:6379", 261 expectInfo: &RedisEndpointInfo{ 262 ID: "my-redis-cluster", 263 Region: "ca-central-1", 264 EndpointType: ElastiCacheReaderEndpoint, 265 }, 266 }, 267 { 268 name: "node endpoint, TLS disabled", 269 inputURI: "my-redis-shards-0001-001.xxxxxx.0001.cac1.cache.amazonaws.com:6379", 270 expectInfo: &RedisEndpointInfo{ 271 ID: "my-redis-shards", 272 Region: "ca-central-1", 273 EndpointType: ElastiCacheNodeEndpoint, 274 }, 275 }, 276 { 277 name: "CN endpoint", 278 inputURI: "replica.my-redis-cluster.xxxxxx.cnn1.cache.amazonaws.com.cn:6379", 279 expectInfo: &RedisEndpointInfo{ 280 ID: "my-redis-cluster", 281 Region: "cn-north-1", 282 TransitEncryptionEnabled: true, 283 EndpointType: ElastiCacheReaderEndpoint, 284 }, 285 }, 286 { 287 name: "endpoint with schema and parameters", 288 inputURI: "redis://my-redis-cluster.xxxxxx.ng.0001.cac1.cache.amazonaws.com:6379?a=b&c=d", 289 expectInfo: &RedisEndpointInfo{ 290 ID: "my-redis-cluster", 291 Region: "ca-central-1", 292 EndpointType: ElastiCachePrimaryEndpoint, 293 }, 294 }, 295 { 296 name: "invalid suffix", 297 inputURI: "replica.my-redis-cluster.xxxxxx.cac1.cache.amazonaws.ca:6379", 298 expectError: true, 299 }, 300 { 301 name: "invalid url", 302 inputURI: "://replica.my-redis-cluster.xxxxxx.cac1.cache.amazonaws.com:6379", 303 expectError: true, 304 }, 305 { 306 name: "invalid format", 307 inputURI: "my-redis-cluster.cac1.cache.amazonaws.com:6379", 308 expectError: true, 309 }, 310 } 311 312 for _, test := range tests { 313 t.Run(test.name, func(t *testing.T) { 314 actualInfo, err := ParseElastiCacheEndpoint(test.inputURI) 315 if test.expectError { 316 require.Error(t, err) 317 } else { 318 require.NoError(t, err) 319 require.Equal(t, test.expectInfo, actualInfo) 320 } 321 }) 322 } 323 } 324 325 func TestParseMemoryDBEndpoint(t *testing.T) { 326 t.Parallel() 327 328 tests := []struct { 329 name string 330 inputURI string 331 expectInfo *RedisEndpointInfo 332 expectError bool 333 }{ 334 { 335 name: "TLS enabled cluster endpoint", 336 inputURI: "clustercfg.my-memorydb.xxxxxx.memorydb.us-east-1.amazonaws.com:6379", 337 expectInfo: &RedisEndpointInfo{ 338 ID: "my-memorydb", 339 Region: "us-east-1", 340 TransitEncryptionEnabled: true, 341 EndpointType: "cluster", 342 }, 343 }, 344 { 345 name: "TLS disabled cluster endpoint", 346 inputURI: "my-memorydb.xxxxxx.clustercfg.memorydb.us-east-1.amazonaws.com:6379", 347 expectInfo: &RedisEndpointInfo{ 348 ID: "my-memorydb", 349 Region: "us-east-1", 350 TransitEncryptionEnabled: false, 351 EndpointType: "cluster", 352 }, 353 }, 354 { 355 name: "TLS enabled node endpoint", 356 inputURI: "my-memorydb-0002-001.my-memorydb.xxxxxx.memorydb.us-east-1.amazonaws.com:6379", 357 expectInfo: &RedisEndpointInfo{ 358 ID: "my-memorydb", 359 Region: "us-east-1", 360 TransitEncryptionEnabled: true, 361 EndpointType: "node", 362 }, 363 }, 364 { 365 name: "TLS disabled node endpoint", 366 inputURI: "my-memorydb-0002-001.xxxxx.0002.memorydb.us-east-1.amazonaws.com:6379", 367 expectInfo: &RedisEndpointInfo{ 368 ID: "my-memorydb", 369 Region: "us-east-1", 370 TransitEncryptionEnabled: false, 371 EndpointType: "node", 372 }, 373 }, 374 { 375 name: "CN endpoint", 376 inputURI: "clustercfg.my-memorydb.xxxxxx.memorydb.cn-north-1.amazonaws.com.cn:6379", 377 expectInfo: &RedisEndpointInfo{ 378 ID: "my-memorydb", 379 Region: "cn-north-1", 380 TransitEncryptionEnabled: true, 381 EndpointType: "cluster", 382 }, 383 }, 384 { 385 name: "endpoint with schema and parameters", 386 inputURI: "redis://clustercfg.my-memorydb.xxxxxx.memorydb.us-east-1.amazonaws.com:6379?a=b&c=d", 387 expectInfo: &RedisEndpointInfo{ 388 ID: "my-memorydb", 389 Region: "us-east-1", 390 TransitEncryptionEnabled: true, 391 EndpointType: "cluster", 392 }, 393 }, 394 { 395 name: "invalid suffix", 396 inputURI: "clustercfg.my-memorydb.xxxxxx.memorydb.ca-central-1.amazonaws.ca:6379", 397 expectError: true, 398 }, 399 { 400 name: "invalid url", 401 inputURI: "://clustercfg.my-memorydb.xxxxxx.memorydb.ca-central-1.amazonaws.com:6379", 402 expectError: true, 403 }, 404 { 405 name: "invalid format", 406 inputURI: "unknown.format.memorydb.ca-central-1.amazonaws.com:6379", 407 expectError: true, 408 }, 409 } 410 411 for _, test := range tests { 412 t.Run(test.name, func(t *testing.T) { 413 actualInfo, err := ParseMemoryDBEndpoint(test.inputURI) 414 if test.expectError { 415 require.Error(t, err) 416 } else { 417 require.NoError(t, err) 418 require.Equal(t, test.expectInfo, actualInfo) 419 } 420 }) 421 } 422 } 423 424 func TestCassandraEndpointRegion(t *testing.T) { 425 t.Parallel() 426 427 tests := []struct { 428 name string 429 inputURI string 430 wantRegion string 431 expectError bool 432 }{ 433 { 434 name: "us-east-1", 435 inputURI: "cassandra.us-east-1.amazonaws.com", 436 wantRegion: "us-east-1", 437 expectError: false, 438 }, 439 { 440 name: "cn-north-1.", 441 inputURI: "cassandra.cn-north-1.amazonaws.com.cn", 442 wantRegion: "cn-north-1", 443 expectError: false, 444 }, 445 { 446 name: "us-gov-east-1", 447 inputURI: "cassandra.us-gov-east-1.amazonaws.com", 448 wantRegion: "us-gov-east-1", 449 expectError: false, 450 }, 451 { 452 name: "invalid uri", 453 inputURI: "foo.cassandra.us-east-1.amazonaws.com", 454 wantRegion: "us-east-1", 455 expectError: true, 456 }, 457 } 458 for _, test := range tests { 459 t.Run(test.name, func(t *testing.T) { 460 got, err := CassandraEndpointRegion(test.inputURI) 461 if test.expectError { 462 require.Error(t, err) 463 require.False(t, IsKeyspacesEndpoint(test.inputURI)) 464 } else { 465 require.NoError(t, err) 466 require.Equal(t, test.wantRegion, got) 467 require.True(t, IsKeyspacesEndpoint(test.inputURI)) 468 } 469 }) 470 } 471 472 } 473 474 func TestRedshiftServerlessEndpoint(t *testing.T) { 475 tests := []struct { 476 name string 477 endpoint string 478 expectIsRedshiftServerlessEndpoint bool 479 expectDetails *RedshiftServerlessEndpointDetails 480 }{ 481 { 482 name: "workgroup endpoint", 483 endpoint: "my-workgroup.123456789012.us-east-1.redshift-serverless.amazonaws.com:5439", 484 expectIsRedshiftServerlessEndpoint: true, 485 expectDetails: &RedshiftServerlessEndpointDetails{ 486 WorkgroupName: "my-workgroup", 487 AccountID: "123456789012", 488 Region: "us-east-1", 489 }, 490 }, 491 { 492 name: "vpc endpoint", 493 endpoint: "my-vpc-endpoint-xxxyyyzzz.123456789012.us-east-1.redshift-serverless.amazonaws.com", 494 expectIsRedshiftServerlessEndpoint: true, 495 expectDetails: &RedshiftServerlessEndpointDetails{ 496 EndpointName: "my-vpc", 497 AccountID: "123456789012", 498 Region: "us-east-1", 499 }, 500 }, 501 { 502 name: "localhost:5432", 503 endpoint: "localhost", 504 expectIsRedshiftServerlessEndpoint: false, 505 }, 506 } 507 508 for _, test := range tests { 509 test := test 510 t.Run(test.name, func(t *testing.T) { 511 t.Parallel() 512 513 require.Equal(t, test.expectIsRedshiftServerlessEndpoint, IsRedshiftServerlessEndpoint(test.endpoint)) 514 515 actualDetails, err := ParseRedshiftServerlessEndpoint(test.endpoint) 516 if !test.expectIsRedshiftServerlessEndpoint { 517 require.Error(t, err) 518 require.True(t, trace.IsBadParameter(err)) 519 } else { 520 require.NoError(t, err) 521 require.Equal(t, test.expectDetails, actualDetails) 522 } 523 }) 524 } 525 } 526 527 func TestDynamoDBURIForRegion(t *testing.T) { 528 t.Parallel() 529 tests := []struct { 530 desc string 531 region string 532 wantURI string 533 wantPartition string 534 }{ 535 { 536 desc: "region is in correct AWS partition", 537 region: "us-east-1", 538 wantURI: "aws://dynamodb.us-east-1.amazonaws.com", 539 wantPartition: ".amazonaws.com", 540 }, 541 { 542 desc: "china north region is in correct AWS partition", 543 region: "cn-north-1", 544 wantURI: "aws://dynamodb.cn-north-1.amazonaws.com.cn", 545 wantPartition: ".amazonaws.com.cn", 546 }, 547 { 548 desc: "china northwest region is in correct AWS partition", 549 region: "cn-northwest-1", 550 wantURI: "aws://dynamodb.cn-northwest-1.amazonaws.com.cn", 551 wantPartition: ".amazonaws.com.cn", 552 }, 553 } 554 for _, tt := range tests { 555 tt := tt 556 t.Run(tt.desc, func(t *testing.T) { 557 require.Equal(t, tt.wantURI, DynamoDBURIForRegion(tt.region)) 558 info, err := ParseDynamoDBEndpoint(tt.wantURI) 559 require.NoError(t, err, "endpoint generated from region could not be parsed.") 560 require.Equal(t, tt.region, info.Region) 561 require.Equal(t, "dynamodb", info.Service) 562 require.Equal(t, tt.wantPartition, info.Partition) 563 }) 564 } 565 } 566 567 func TestParseDynamoDBEndpoint(t *testing.T) { 568 t.Parallel() 569 t.Run("parses valid endpoint", func(t *testing.T) { 570 t.Parallel() 571 for _, parts := range []struct { 572 services []string 573 regions []string 574 partition string 575 }{ 576 { 577 services: []string{DynamoDBServiceName, DynamoDBFipsServiceName, DynamoDBStreamsServiceName, DAXServiceName}, 578 regions: []string{"us-east-1", "us-gov-east-1"}, 579 partition: AWSEndpointSuffix, 580 }, 581 { 582 services: []string{DynamoDBServiceName, DynamoDBStreamsServiceName, DAXServiceName}, 583 regions: []string{"cn-north-1", "cn-northwest-1"}, 584 partition: AWSCNEndpointSuffix, 585 }, 586 } { 587 parts := parts 588 for _, svc := range parts.services { 589 svc := svc 590 for _, region := range parts.regions { 591 region := region 592 endpoint := fmt.Sprintf("%s.%s%s", svc, region, parts.partition) 593 t.Run(endpoint, func(t *testing.T) { 594 t.Parallel() 595 info, err := ParseDynamoDBEndpoint(endpoint) 596 require.NoError(t, err) 597 wantInfo := DynamoDBEndpointInfo{ 598 Service: svc, 599 Region: region, 600 Partition: parts.partition, 601 } 602 require.NotNil(t, info) 603 require.Equal(t, wantInfo, *info) 604 }) 605 } 606 } 607 } 608 }) 609 610 tests := []struct { 611 desc string 612 services []string 613 regions []string 614 endpoint string 615 wantInfo *DynamoDBEndpointInfo 616 }{ 617 { 618 desc: "empty uri", 619 endpoint: "", 620 }, 621 { 622 desc: "not AWS uri", 623 endpoint: "localhost", 624 }, 625 { 626 desc: "missing region", 627 endpoint: "amazonaws.com", 628 }, 629 { 630 desc: "missing china region", 631 endpoint: "amazonaws.com.cn", 632 }, 633 { 634 desc: "unrecognized service subdomain", 635 endpoint: "foo.us-east-1.amazonaws.com", 636 }, 637 { 638 desc: "unrecognized dynamodb service subdomain", 639 endpoint: "foo.dynamodb.us-east-1.amazonaws.com", 640 }, 641 { 642 desc: "unrecognized streams service subdomain", 643 endpoint: "streams.foo.us-east-1.amazonaws.com", 644 }, 645 { 646 desc: "mismatched us region and china partition", 647 endpoint: "streams.dynamodb.us-east-1.amazonaws.com.cn", 648 }, 649 { 650 desc: "mismatched china region and non-china partition", 651 endpoint: "streams.dynamodb.cn-north-1.amazonaws.com", 652 }, 653 } 654 for _, tt := range tests { 655 tt := tt 656 t.Run("detects invalid endpoint with "+tt.desc, func(t *testing.T) { 657 t.Parallel() 658 info, err := ParseDynamoDBEndpoint(tt.endpoint) 659 require.Error(t, err, "endpoint %s should be invalid", tt.endpoint) 660 require.Nil(t, info) 661 }) 662 } 663 } 664 665 func TestParseOpensearchEndpoint(t *testing.T) { 666 t.Parallel() 667 668 t.Run("fixed example", func(t *testing.T) { 669 want := &OpenSearchEndpointInfo{ 670 Service: OpenSearchServiceName, 671 Region: "eu-central-1", 672 Partition: AWSEndpointSuffix, 673 } 674 675 endpoint := "https://search-my-opensearch-instance-xxxxxxxxxxxxxxxx.eu-central-1.es.amazonaws.com:443" 676 677 out, err := ParseOpensearchEndpoint(endpoint) 678 require.NoError(t, err) 679 require.Equal(t, want, out) 680 }) 681 682 t.Run("parses valid endpoint", func(t *testing.T) { 683 t.Parallel() 684 for _, parts := range []struct { 685 services []string 686 regions []string 687 partition string 688 }{ 689 { 690 services: []string{OpenSearchServiceName}, 691 regions: []string{"us-east-1", "us-gov-east-1"}, 692 partition: AWSEndpointSuffix, 693 }, 694 { 695 services: []string{OpenSearchServiceName}, 696 regions: []string{"cn-north-1", "cn-northwest-1"}, 697 partition: AWSCNEndpointSuffix, 698 }, 699 } { 700 parts := parts 701 for _, svc := range parts.services { 702 svc := svc 703 for _, region := range parts.regions { 704 region := region 705 endpoint := fmt.Sprintf("opensearch-instance-foo.%s.%s%s", region, svc, parts.partition) 706 t.Run(endpoint, func(t *testing.T) { 707 t.Parallel() 708 info, err := ParseOpensearchEndpoint(endpoint) 709 require.NoError(t, err) 710 wantInfo := OpenSearchEndpointInfo{ 711 Service: svc, 712 Region: region, 713 Partition: parts.partition, 714 } 715 require.NotNil(t, info) 716 require.Equal(t, wantInfo, *info) 717 }) 718 } 719 } 720 } 721 }) 722 723 tests := []struct { 724 desc string 725 endpoint string 726 }{ 727 { 728 desc: "empty uri", 729 endpoint: "", 730 }, 731 { 732 desc: "not AWS uri", 733 endpoint: "localhost", 734 }, 735 { 736 desc: "missing region", 737 endpoint: "amazonaws.com", 738 }, 739 { 740 desc: "missing china region", 741 endpoint: "amazonaws.com.cn", 742 }, 743 { 744 desc: "unrecognized service subdomain", 745 endpoint: "foo.us-east-1.amazonaws.com", 746 }, 747 { 748 desc: "unrecognized opensearch service subdomain", 749 endpoint: "foo.opensearch.us-east-1.amazonaws.com", 750 }, 751 { 752 desc: "unrecognized streams service subdomain", 753 endpoint: "streams.foo.us-east-1.amazonaws.com", 754 }, 755 { 756 desc: "mismatched us region and china partition", 757 endpoint: "streams.opensearch.us-east-1.amazonaws.com.cn", 758 }, 759 { 760 desc: "mismatched china region and non-china partition", 761 endpoint: "streams.opensearch.cn-north-1.amazonaws.com", 762 }, 763 } 764 for _, tt := range tests { 765 tt := tt 766 t.Run("detects invalid endpoint with "+tt.desc, func(t *testing.T) { 767 t.Parallel() 768 info, err := ParseOpensearchEndpoint(tt.endpoint) 769 require.Error(t, err, "endpoint %s should be invalid", tt.endpoint) 770 require.Nil(t, info) 771 }) 772 } 773 }