diff --git a/datadog/fwprovider/resource_datadog_service_account_application_key.go b/datadog/fwprovider/resource_datadog_service_account_application_key.go index d39865f3ed..fa78e2ada6 100644 --- a/datadog/fwprovider/resource_datadog_service_account_application_key.go +++ b/datadog/fwprovider/resource_datadog_service_account_application_key.go @@ -6,12 +6,14 @@ import ( "time" "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" @@ -34,6 +36,7 @@ type serviceAccountApplicationKeyModel struct { Key types.String `tfsdk:"key"` CreatedAt types.String `tfsdk:"created_at"` Last4 types.String `tfsdk:"last4"` + Scopes types.Set `tfsdk:"scopes"` } func NewServiceAccountApplicationKeyResource() resource.Resource { @@ -65,6 +68,14 @@ func (r *serviceAccountApplicationKeyResource) Schema(_ context.Context, _ resou Required: true, Description: "Name of the application key.", }, + "scopes": schema.SetAttribute{ + Description: "Authorization scopes for the Application Key. Application Keys configured with no scopes have full access.", + Optional: true, + ElementType: types.StringType, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + }, "key": schema.StringAttribute{ Computed: true, Sensitive: true, @@ -228,6 +239,10 @@ func (r *serviceAccountApplicationKeyResource) updateStatePartialKey(ctx context if name, ok := attributes.GetNameOk(); ok { state.Name = types.StringValue(*name) } + + if attributes.HasScopes() { + state.Scopes, _ = types.SetValueFrom(ctx, types.StringType, attributes.GetScopes()) + } } func (r *serviceAccountApplicationKeyResource) updateStateFullKey(ctx context.Context, state *serviceAccountApplicationKeyModel, resp *datadogV2.ApplicationKeyResponse) { @@ -253,6 +268,10 @@ func (r *serviceAccountApplicationKeyResource) updateStateFullKey(ctx context.Co if name, ok := attributes.GetNameOk(); ok { state.Name = types.StringValue(*name) } + + if attributes.HasScopes() { + state.Scopes, _ = types.SetValueFrom(ctx, types.StringType, attributes.GetScopes()) + } } func (r *serviceAccountApplicationKeyResource) buildServiceAccountApplicationKeyRequestBody(ctx context.Context, state *serviceAccountApplicationKeyModel) (*datadogV2.ApplicationKeyCreateRequest, diag.Diagnostics) { @@ -260,6 +279,7 @@ func (r *serviceAccountApplicationKeyResource) buildServiceAccountApplicationKey attributes := datadogV2.NewApplicationKeyCreateAttributesWithDefaults() attributes.SetName(state.Name.ValueString()) + attributes.SetScopes(getScopesFromStateAttribute(state.Scopes)) req := datadogV2.NewApplicationKeyCreateRequestWithDefaults() req.Data = *datadogV2.NewApplicationKeyCreateDataWithDefaults() @@ -276,6 +296,7 @@ func (r *serviceAccountApplicationKeyResource) buildServiceAccountApplicationKey attributes.SetName(state.Name.ValueString()) } + attributes.SetScopes(getScopesFromStateAttribute(state.Scopes)) req := datadogV2.NewApplicationKeyUpdateRequestWithDefaults() req.Data = *datadogV2.NewApplicationKeyUpdateDataWithDefaults() req.Data.SetAttributes(*attributes) diff --git a/datadog/tests/resource_datadog_application_key_test.go b/datadog/tests/resource_datadog_application_key_test.go index 630e55d411..08c7193c28 100644 --- a/datadog/tests/resource_datadog_application_key_test.go +++ b/datadog/tests/resource_datadog_application_key_test.go @@ -80,6 +80,10 @@ func TestAccDatadogApplicationKey_Error(t *testing.T) { Config: testAccCheckDatadogScopedApplicationKeyConfigRequired(applicationKeyNameUpdate, []string{}), ExpectError: regexp.MustCompile(`Attribute scopes set must contain at least 1 elements`), }, + { + Config: testAccCheckDatadogScopedApplicationKeyConfigRequired(applicationKeyNameUpdate, []string{"invalid"}), + ExpectError: regexp.MustCompile(`Invalid scopes`), + }, }, }) } diff --git a/datadog/tests/resource_datadog_service_account_application_key_test.go b/datadog/tests/resource_datadog_service_account_application_key_test.go index e3aaa6baf4..3d19ee4b89 100644 --- a/datadog/tests/resource_datadog_service_account_application_key_test.go +++ b/datadog/tests/resource_datadog_service_account_application_key_test.go @@ -3,6 +3,8 @@ package test import ( "context" "fmt" + "regexp" + "strings" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -20,6 +22,7 @@ func TestAccServiceAccountApplicationKeyBasic(t *testing.T) { ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) uniq := uniqueEntityName(ctx, t) uniqUpdated := uniq + "updated" + scopes := []string{"dashboards_read", "dashboards_write"} resource.Test(t, resource.TestCase{ ProtoV5ProviderFactories: accProviders, CheckDestroy: testAccCheckDatadogServiceAccountApplicationKeyDestroy(providers.frameworkProvider), @@ -54,8 +57,56 @@ func TestAccServiceAccountApplicationKeyBasic(t *testing.T) { "datadog_service_account_application_key.foo", "last4"), resource.TestCheckResourceAttrPair( "datadog_service_account_application_key.foo", "service_account_id", "datadog_service_account.bar", "id"), + resource.TestCheckNoResourceAttr("datadog_service_account_application_key.foo", "scopes"), ), }, + { + Config: testAccCheckDatadogServiceAccountScopedApplicationKey(uniqUpdated, scopes), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogServiceAccountApplicationKeyExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_service_account_application_key.foo", "name", uniqUpdated), + resource.TestCheckResourceAttrSet( + "datadog_service_account_application_key.foo", "key"), + resource.TestCheckResourceAttrSet( + "datadog_service_account_application_key.foo", "created_at"), + resource.TestCheckResourceAttrSet( + "datadog_service_account_application_key.foo", "last4"), + resource.TestCheckResourceAttrPair( + "datadog_service_account_application_key.foo", "service_account_id", "datadog_service_account.bar", "id"), + resource.TestCheckResourceAttrSet( + "datadog_service_account_application_key.foo", "scopes.#"), + resource.TestCheckTypeSetElemAttr( + "datadog_service_account_application_key.foo", "scopes.*", scopes[0]), + resource.TestCheckTypeSetElemAttr( + "datadog_service_account_application_key.foo", "scopes.*", scopes[1]), + ), + }, + }, + }) +} + +func TestAccServiceAccountApplicationKey_Error(t *testing.T) { + if isRecording() || isReplaying() { + t.Skip("This test doesn't support recording or replaying") + } + t.Parallel() + ctx, _, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + applicationKeyName := uniqueEntityName(ctx, t) + applicationKeyNameUpdate := applicationKeyName + "-2" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogServiceAccountScopedApplicationKey(applicationKeyNameUpdate, []string{}), + ExpectError: regexp.MustCompile(`Attribute scopes set must contain at least 1 elements`), + }, + { + Config: testAccCheckDatadogServiceAccountScopedApplicationKey(applicationKeyNameUpdate, []string{"invalid"}), + ExpectError: regexp.MustCompile(`Invalid scopes`), + }, }, }) } @@ -105,6 +156,27 @@ resource "datadog_service_account_application_key" "foo" { }`, uniq) } +func testAccCheckDatadogServiceAccountScopedApplicationKey(uniq string, scopes []string) string { + formattedScopes := "" + if len(scopes) == 0 { + formattedScopes = "[]" + } else { + formattedScopes = fmt.Sprintf("[\"%s\"]", strings.Join(scopes, "\", \"")) + } + + return fmt.Sprintf(` +resource "datadog_service_account" "bar" { + email = "new@example.com" + name = "testTerraformServiceAccountApplicationKeys" +} + +resource "datadog_service_account_application_key" "foo" { + service_account_id = datadog_service_account.bar.id + name = "%s" + scopes = %s +}`, uniq, formattedScopes) +} + func testAccCheckDatadogServiceAccountApplicationKeyDestroy(accProvider *fwprovider.FrameworkProvider) func(*terraform.State) error { return func(s *terraform.State) error { apiInstances := accProvider.DatadogApiInstances diff --git a/docs/resources/service_account_application_key.md b/docs/resources/service_account_application_key.md index 72733b6022..0a3a6f259e 100644 --- a/docs/resources/service_account_application_key.md +++ b/docs/resources/service_account_application_key.md @@ -28,6 +28,10 @@ resource "datadog_service_account_application_key" "foo" { - `name` (String) Name of the application key. - `service_account_id` (String) ID of the service account that owns this key. +### Optional + +- `scopes` (Set of String) Authorization scopes for the Application Key. Application Keys configured with no scopes have full access. + ### Read-Only - `created_at` (String) Creation date of the application key.