diff --git a/internal/backend/remote-state/s3/backend.go b/internal/backend/remote-state/s3/backend.go index f6950320ea..2ee87b309d 100644 --- a/internal/backend/remote-state/s3/backend.go +++ b/internal/backend/remote-state/s3/backend.go @@ -84,6 +84,11 @@ func (b *Backend) ConfigSchema() *configschema.Block { Required: true, Description: "A custom endpoint for the DynamoDB API", }, + "iam": { + Type: cty.String, + Required: true, + Description: "A custom endpoint for the IAM API", + }, "s3": { Type: cty.String, Required: true, @@ -96,6 +101,7 @@ func (b *Backend) ConfigSchema() *configschema.Block { Type: cty.String, Optional: true, Description: "A custom endpoint for the IAM API", + Deprecated: true, }, "sts_endpoint": { Type: cty.String, @@ -358,11 +364,12 @@ func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) endpointFields := map[string]string{ "dynamodb_endpoint": "dynamodb", + "iam_endpoint": "iam", "endpoint": "s3", } endpoints := make(map[string]string) if val := obj.GetAttr("endpoints"); !val.IsNull() { - for _, k := range []string{"dynamodb", "s3"} { + for _, k := range []string{"dynamodb", "iam", "s3"} { if v := val.GetAttr(k); !v.IsNull() { endpoints[k] = v.AsString() } @@ -374,7 +381,7 @@ func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) if _, ok := endpoints[v]; ok { diags = diags.Append(wholeBodyErrDiag( "Conflicting Parameters", - fmt.Sprintf(`The parameters "%s" and %s" cannot be configured together.`, + fmt.Sprintf(`The parameters "%s" and "%s" cannot be configured together.`, pathString(cty.GetAttrPath(k)), pathString(cty.GetAttrPath("endpoints").GetAttr(v)), ), diff --git a/internal/backend/remote-state/s3/backend_test.go b/internal/backend/remote-state/s3/backend_test.go index d072fdae78..a5bfb3dd94 100644 --- a/internal/backend/remote-state/s3/backend_test.go +++ b/internal/backend/remote-state/s3/backend_test.go @@ -214,6 +214,7 @@ func TestBackendConfig_DynamoDBEndpoint(t *testing.T) { config: map[string]any{ "endpoints": map[string]any{ "dynamodb": "dynamo.test", + "iam": nil, "s3": nil, }, }, @@ -233,6 +234,7 @@ func TestBackendConfig_DynamoDBEndpoint(t *testing.T) { "dynamodb_endpoint": "dynamo.test", "endpoints": map[string]any{ "dynamodb": "dynamo.test", + "iam": nil, "s3": nil, }, }, @@ -241,7 +243,7 @@ func TestBackendConfig_DynamoDBEndpoint(t *testing.T) { deprecatedAttrDiag(cty.GetAttrPath("dynamodb_endpoint"), cty.GetAttrPath("endpoints").GetAttr("dynamodb")), wholeBodyErrDiag( "Conflicting Parameters", - fmt.Sprintf(`The parameters "%s" and %s" cannot be configured together.`, + fmt.Sprintf(`The parameters "%s" and "%s" cannot be configured together.`, pathString(cty.GetAttrPath("dynamodb_endpoint")), pathString(cty.GetAttrPath("endpoints").GetAttr("dynamodb")), ), @@ -303,6 +305,102 @@ func TestBackendConfig_DynamoDBEndpoint(t *testing.T) { } } +func TestBackendConfig_IAMEndpoint(t *testing.T) { + testACC(t) + + // Doesn't test for expected endpoint, since the IAM endpoint is used internally to `aws-sdk-go-base` + // The mocked tests won't work if the config parameter doesn't work + cases := map[string]struct { + config map[string]any + vars map[string]string + expectedDiags tfdiags.Diagnostics + }{ + "none": {}, + "config": { + config: map[string]any{ + "endpoints": map[string]any{ + "dynamodb": nil, + "iam": "iam.test", + "s3": nil, + }, + }, + }, + "deprecated config": { + config: map[string]any{ + "iam_endpoint": "iam.test", + }, + expectedDiags: tfdiags.Diagnostics{ + deprecatedAttrDiag(cty.GetAttrPath("iam_endpoint"), cty.GetAttrPath("endpoints").GetAttr("iam")), + }, + }, + "config conflict": { + config: map[string]any{ + "iam_endpoint": "iam.test", + "endpoints": map[string]any{ + "dynamodb": nil, + "iam": "iam.test", + "s3": nil, + }, + }, + expectedDiags: tfdiags.Diagnostics{ + deprecatedAttrDiag(cty.GetAttrPath("iam_endpoint"), cty.GetAttrPath("endpoints").GetAttr("iam")), + wholeBodyErrDiag( + "Conflicting Parameters", + fmt.Sprintf(`The parameters "%s" and "%s" cannot be configured together.`, + pathString(cty.GetAttrPath("iam_endpoint")), + pathString(cty.GetAttrPath("endpoints").GetAttr("iam")), + ), + )}, + }, + "envvar": { + vars: map[string]string{ + "AWS_ENDPOINT_URL_IAM": "iam.test", + }, + }, + "deprecated envvar": { + vars: map[string]string{ + "AWS_IAM_ENDPOINT": "iam.test", + }, + expectedDiags: tfdiags.Diagnostics{ + deprecatedEnvVarDiag("AWS_IAM_ENDPOINT", "AWS_ENDPOINT_URL_IAM"), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + config := map[string]interface{}{ + "region": "us-west-1", + "bucket": "tf-test", + "key": "state", + } + + if tc.vars != nil { + for k, v := range tc.vars { + os.Setenv(k, v) + } + t.Cleanup(func() { + for k := range tc.vars { + os.Unsetenv(k) + } + }) + } + + if tc.config != nil { + for k, v := range tc.config { + config[k] = v + } + } + + _, diags := testBackendConfigDiags(t, New(), backend.TestWrapConfig(config)) + + if diff := cmp.Diff(diags, tc.expectedDiags, cmp.Comparer(diagnosticComparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + }) + } +} + func TestBackendConfig_S3Endpoint(t *testing.T) { testACC(t) @@ -319,6 +417,7 @@ func TestBackendConfig_S3Endpoint(t *testing.T) { config: map[string]any{ "endpoints": map[string]any{ "dynamodb": nil, + "iam": nil, "s3": "s3.test", }, }, @@ -338,6 +437,7 @@ func TestBackendConfig_S3Endpoint(t *testing.T) { "endpoint": "s3.test", "endpoints": map[string]any{ "dynamodb": nil, + "iam": nil, "s3": "s3.test", }, }, @@ -346,7 +446,7 @@ func TestBackendConfig_S3Endpoint(t *testing.T) { deprecatedAttrDiag(cty.GetAttrPath("endpoint"), cty.GetAttrPath("endpoints").GetAttr("s3")), wholeBodyErrDiag( "Conflicting Parameters", - fmt.Sprintf(`The parameters "%s" and %s" cannot be configured together.`, + fmt.Sprintf(`The parameters "%s" and "%s" cannot be configured together.`, pathString(cty.GetAttrPath("endpoint")), pathString(cty.GetAttrPath("endpoints").GetAttr("s3")), ), diff --git a/website/docs/language/settings/backends/s3.mdx b/website/docs/language/settings/backends/s3.mdx index 49c0f72b92..c142aea649 100644 --- a/website/docs/language/settings/backends/s3.mdx +++ b/website/docs/language/settings/backends/s3.mdx @@ -154,7 +154,8 @@ The following configuration is optional: * `access_key` - (Optional) AWS access key. If configured, must also configure `secret_key`. This can also be sourced from the `AWS_ACCESS_KEY_ID` environment variable, AWS shared credentials file (e.g. `~/.aws/credentials`), or AWS shared configuration file (e.g. `~/.aws/config`). * `secret_key` - (Optional) AWS access key. If configured, must also configure `access_key`. This can also be sourced from the `AWS_SECRET_ACCESS_KEY` environment variable, AWS shared credentials file (e.g. `~/.aws/credentials`), or AWS shared configuration file (e.g. `~/.aws/config`). -* `iam_endpoint` - (Optional) Custom endpoint for the AWS Identity and Access Management (IAM) API. This can also be sourced from the environment variable `AWS_ENDPOINT_URL_IAM` or the deprecated environment variable `AWS_IAM_ENDPOINT`. +* `iam_endpoint` - (Optional, **Deprecated**) Custom endpoint for the AWS Identity and Access Management (IAM) API. + Use `endpoints.iam` instead. * `max_retries` - (Optional) The maximum number of times an AWS API request is retried on retryable failure. Defaults to 5. * `profile` - (Optional) Name of AWS profile in AWS shared credentials file (e.g. `~/.aws/credentials`) or AWS shared configuration file (e.g. `~/.aws/config`) to use for credentials and/or configuration. This can also be sourced from the `AWS_PROFILE` environment variable. * `shared_credentials_file` - (Optional) Path to the AWS shared credentials file. Defaults to `~/.aws/credentials`. @@ -170,6 +171,8 @@ The optional argument `endpoints` contains the following arguments: * `dynamodb` - (Optional) Custom endpoint for the AWS DynamoDB API. This can also be sourced from the environment variable `AWS_ENDPOINT_URL_DYNAMODB` or the deprecated environment variable `AWS_DYNAMODB_ENDPOINT`. +* `iam` - (Optional) Custom endpoint for the AWS IAM API. + This can also be sourced from the environment variable `AWS_ENDPOINT_URL_IAM` or the deprecated environment variable `AWS_IAM_ENDPOINT`. * `s3` - (Optional) Custom endpoint for the AWS S3 API. This can also be sourced from the environment variable `AWS_ENDPOINT_URL_S3` or the deprecated environment variable `AWS_S3_ENDPOINT`.