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  }