Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 76 additions & 11 deletions datadog/fwprovider/resource_datadog_application_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ func NewApplicationKeyResource() resource.Resource {
}

type applicationKeyResourceModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Key types.String `tfsdk:"key"`
Scopes types.Set `tfsdk:"scopes"`
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Key types.String `tfsdk:"key"`
Scopes types.Set `tfsdk:"scopes"`
EnableActionsApiAccess types.Bool `tfsdk:"enable_actions_api_access"`
}

type applicationKeyResource struct {
Api *datadogV2.KeyManagementApi
Auth context.Context
Api *datadogV2.KeyManagementApi
ActionApi *datadogV2.ActionConnectionApi
Auth context.Context
}

func (r *applicationKeyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
Expand Down Expand Up @@ -69,6 +71,10 @@ func (r *applicationKeyResource) Schema(_ context.Context, _ resource.SchemaRequ
setvalidator.SizeAtLeast(1),
},
},
"enable_actions_api_access": schema.BoolAttribute{
Description: "(Preview) Enable Actions API access for this application key. When true, the key will be automatically registered for use with Action Connection, App Builder, and Workflow Automation. Defaults to `false`.",
Optional: true,
},
"id": utils.ResourceIDAttribute(),
},
}
Expand All @@ -77,6 +83,7 @@ func (r *applicationKeyResource) Schema(_ context.Context, _ resource.SchemaRequ
func (r *applicationKeyResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
providerData := request.ProviderData.(*FrameworkProvider)
r.Api = providerData.DatadogApiInstances.GetKeyManagementApiV2()
r.ActionApi = providerData.DatadogApiInstances.GetActionConnectionApiV2()
r.Auth = providerData.Auth
}

Expand All @@ -100,6 +107,14 @@ func (r *applicationKeyResource) Create(ctx context.Context, request resource.Cr
state.ID = types.StringValue(applicationKeyData.GetId())
r.updateState(ctx, &state, &applicationKeyData)

// Handle Actions API access registration if enabled
if !state.EnableActionsApiAccess.IsNull() && state.EnableActionsApiAccess.ValueBool() {
if err := r.registerForActionsApi(state.ID.ValueString()); err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error registering application key for Actions API access"))
return
}
}

response.Diagnostics.Append(response.State.Set(ctx, &state)...)

}
Expand All @@ -124,28 +139,52 @@ func (r *applicationKeyResource) Read(ctx context.Context, request resource.Read
applicationKeyData := resp.GetData()
r.updateState(ctx, &state, &applicationKeyData)

// Check Actions API registration status
isRegistered := r.isRegisteredForActionsApi(state.ID.ValueString())
state.EnableActionsApiAccess = types.BoolValue(isRegistered)

response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func (r *applicationKeyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var state applicationKeyResourceModel
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
var plan applicationKeyResourceModel
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

resp, _, err := r.Api.UpdateCurrentUserApplicationKey(r.Auth, state.ID.ValueString(), *r.buildDatadogApplicationKeyUpdateV2Struct(&state))
resp, _, err := r.Api.UpdateCurrentUserApplicationKey(r.Auth, state.ID.ValueString(), *r.buildDatadogApplicationKeyUpdateV2Struct(&plan))

if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating application key"))
return
}

applicationKeyData := resp.GetData()
state.ID = types.StringValue(applicationKeyData.GetId())
r.updateState(ctx, &state, &applicationKeyData)
plan.ID = types.StringValue(applicationKeyData.GetId())
r.updateState(ctx, &plan, &applicationKeyData)

// Handle Actions API access changes
oldEnabled := !state.EnableActionsApiAccess.IsNull() && state.EnableActionsApiAccess.ValueBool()
newEnabled := !plan.EnableActionsApiAccess.IsNull() && plan.EnableActionsApiAccess.ValueBool()

if oldEnabled != newEnabled {
if newEnabled {
if err := r.registerForActionsApi(plan.ID.ValueString()); err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error registering application key for Actions API access"))
return
}
} else {
if err := r.unregisterFromActionsApi(plan.ID.ValueString()); err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error unregistering application key from Actions API access"))
return
}
}
}

response.Diagnostics.Append(response.State.Set(ctx, &state)...)
response.Diagnostics.Append(response.State.Set(ctx, &plan)...)
}

func (r *applicationKeyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
Expand All @@ -155,6 +194,14 @@ func (r *applicationKeyResource) Delete(ctx context.Context, request resource.De
return
}

// Unregister from Actions API if registered
if !state.EnableActionsApiAccess.IsNull() && state.EnableActionsApiAccess.ValueBool() {
if err := r.unregisterFromActionsApi(state.ID.ValueString()); err != nil {
// Log warning but don't fail deletion
response.Diagnostics.AddWarning("Failed to unregister from Actions API", err.Error())
}
}

if _, err := r.Api.DeleteCurrentUserApplicationKey(r.Auth, state.ID.ValueString()); err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting application key"))
}
Expand Down Expand Up @@ -198,3 +245,21 @@ func getScopesFromStateAttribute(scopes types.Set) []string {
}
return scopesList
}

// registerForActionsApi registers the application key for Actions API access
func (r *applicationKeyResource) registerForActionsApi(keyId string) error {
_, _, err := r.ActionApi.RegisterAppKey(r.Auth, keyId)
return err
}

// unregisterFromActionsApi unregisters the application key from Actions API access
func (r *applicationKeyResource) unregisterFromActionsApi(keyId string) error {
_, err := r.ActionApi.UnregisterAppKey(r.Auth, keyId)
return err
}

// isRegisteredForActionsApi checks if the application key is registered for Actions API access
func (r *applicationKeyResource) isRegisteredForActionsApi(keyId string) bool {
_, _, err := r.ActionApi.GetAppKeyRegistration(r.Auth, keyId)
return err == nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@ var (
)

type serviceAccountApplicationKeyResource struct {
Api *datadogV2.ServiceAccountsApi
Auth context.Context
Api *datadogV2.ServiceAccountsApi
ActionApi *datadogV2.ActionConnectionApi
Auth context.Context
}

type serviceAccountApplicationKeyModel struct {
ID types.String `tfsdk:"id"`
ServiceAccountId types.String `tfsdk:"service_account_id"`
Name types.String `tfsdk:"name"`
Key types.String `tfsdk:"key"`
CreatedAt types.String `tfsdk:"created_at"`
Last4 types.String `tfsdk:"last4"`
Scopes types.Set `tfsdk:"scopes"`
ID types.String `tfsdk:"id"`
ServiceAccountId types.String `tfsdk:"service_account_id"`
Name types.String `tfsdk:"name"`
Key types.String `tfsdk:"key"`
CreatedAt types.String `tfsdk:"created_at"`
Last4 types.String `tfsdk:"last4"`
Scopes types.Set `tfsdk:"scopes"`
EnableActionsApiAccess types.Bool `tfsdk:"enable_actions_api_access"`
}

func NewServiceAccountApplicationKeyResource() resource.Resource {
Expand All @@ -46,6 +48,7 @@ func NewServiceAccountApplicationKeyResource() resource.Resource {
func (r *serviceAccountApplicationKeyResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
providerData, _ := request.ProviderData.(*FrameworkProvider)
r.Api = providerData.DatadogApiInstances.GetServiceAccountsApiV2()
r.ActionApi = providerData.DatadogApiInstances.GetActionConnectionApiV2()
r.Auth = providerData.Auth
}

Expand Down Expand Up @@ -76,6 +79,10 @@ func (r *serviceAccountApplicationKeyResource) Schema(_ context.Context, _ resou
setvalidator.SizeAtLeast(1),
},
},
"enable_actions_api_access": schema.BoolAttribute{
Description: "(Preview) Enable Actions API access for this service account application key. When true, the key will be automatically registered for use with Action Connection, App Builder, and Workflow Automation. Defaults to `false`.",
Optional: true,
},
"key": schema.StringAttribute{
Computed: true,
Sensitive: true,
Expand Down Expand Up @@ -136,6 +143,10 @@ func (r *serviceAccountApplicationKeyResource) Read(ctx context.Context, request

r.updateStatePartialKey(ctx, &state, &resp)

// Check Actions API registration status
isRegistered := r.isRegisteredForActionsApi(state.ID.ValueString())
state.EnableActionsApiAccess = types.BoolValue(isRegistered)

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}
Expand Down Expand Up @@ -166,22 +177,32 @@ func (r *serviceAccountApplicationKeyResource) Create(ctx context.Context, reque
}
r.updateStateFullKey(ctx, &state, &resp)

// Handle Actions API access registration if enabled
if !state.EnableActionsApiAccess.IsNull() && state.EnableActionsApiAccess.ValueBool() {
if err := r.registerForActionsApi(state.ID.ValueString()); err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error registering service account application key for Actions API access"))
return
}
}

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func (r *serviceAccountApplicationKeyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var state serviceAccountApplicationKeyModel
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
var plan serviceAccountApplicationKeyModel
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

serviceAccountId := state.ServiceAccountId.ValueString()
serviceAccountId := plan.ServiceAccountId.ValueString()

id := state.ID.ValueString()
id := plan.ID.ValueString()

body, diags := r.buildServiceAccountApplicationKeyUpdateRequestBody(ctx, &state)
body, diags := r.buildServiceAccountApplicationKeyUpdateRequestBody(ctx, &plan)
response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
Expand All @@ -196,10 +217,28 @@ func (r *serviceAccountApplicationKeyResource) Update(ctx context.Context, reque
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
return
}
r.updateStatePartialKey(ctx, &state, &resp)
r.updateStatePartialKey(ctx, &plan, &resp)

// Handle Actions API access changes
oldEnabled := !state.EnableActionsApiAccess.IsNull() && state.EnableActionsApiAccess.ValueBool()
newEnabled := !plan.EnableActionsApiAccess.IsNull() && plan.EnableActionsApiAccess.ValueBool()

if oldEnabled != newEnabled {
if newEnabled {
if err := r.registerForActionsApi(plan.ID.ValueString()); err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error registering service account application key for Actions API access"))
return
}
} else {
if err := r.unregisterFromActionsApi(plan.ID.ValueString()); err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error unregistering service account application key from Actions API access"))
return
}
}
}

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
response.Diagnostics.Append(response.State.Set(ctx, &plan)...)
}

func (r *serviceAccountApplicationKeyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
Expand All @@ -212,6 +251,14 @@ func (r *serviceAccountApplicationKeyResource) Delete(ctx context.Context, reque

id := state.ID.ValueString()

// Unregister from Actions API if registered
if !state.EnableActionsApiAccess.IsNull() && state.EnableActionsApiAccess.ValueBool() {
if err := r.unregisterFromActionsApi(state.ID.ValueString()); err != nil {
// Log warning but don't fail deletion
response.Diagnostics.AddWarning("Failed to unregister from Actions API", err.Error())
}
}

httpResp, err := r.Api.DeleteServiceAccountApplicationKey(r.Auth, serviceAccountId, id)
if err != nil {
if httpResp != nil && httpResp.StatusCode == 404 {
Expand Down Expand Up @@ -307,3 +354,21 @@ func (r *serviceAccountApplicationKeyResource) buildServiceAccountApplicationKey

return req, diags
}

// registerForActionsApi registers the service account application key for Actions API access
func (r *serviceAccountApplicationKeyResource) registerForActionsApi(keyId string) error {
_, _, err := r.ActionApi.RegisterAppKey(r.Auth, keyId)
return err
}

// unregisterFromActionsApi unregisters the service account application key from Actions API access
func (r *serviceAccountApplicationKeyResource) unregisterFromActionsApi(keyId string) error {
_, err := r.ActionApi.UnregisterAppKey(r.Auth, keyId)
return err
}

// isRegisteredForActionsApi checks if the service account application key is registered for Actions API access
func (r *serviceAccountApplicationKeyResource) isRegisteredForActionsApi(keyId string) bool {
_, _, err := r.ActionApi.GetAppKeyRegistration(r.Auth, keyId)
return err == nil
}
69 changes: 69 additions & 0 deletions datadog/tests/resource_datadog_application_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,72 @@ func datadogApplicationKeyDestroyHelper(ctx context.Context, s *terraform.State,

return nil
}

// TestAccDatadogApplicationKey_ActionsApiAccess tests the preview Actions API access feature
func TestAccDatadogApplicationKey_ActionsApiAccess(t *testing.T) {
if isRecording() || isReplaying() {
t.Skip("This test doesn't support recording or replaying")
}
t.Parallel()
ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t)
applicationKeyName := uniqueEntityName(ctx, t)
resourceName := "datadog_application_key.foo"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: accProviders,
CheckDestroy: testAccCheckDatadogApplicationKeyDestroy(providers.frameworkProvider),
Steps: []resource.TestStep{
{
Config: testAccCheckDatadogApplicationKeyWithActionsApiAccess(applicationKeyName, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckDatadogApplicationKeyExists(providers.frameworkProvider, resourceName),
resource.TestCheckResourceAttr(resourceName, "name", applicationKeyName),
resource.TestCheckResourceAttr(resourceName, "enable_actions_api_access", "true"),
resource.TestCheckResourceAttrSet(resourceName, "key"),
testAccCheckDatadogApplicationKeyActionsApiRegistered(providers.frameworkProvider, resourceName, true),
),
},
{
Config: testAccCheckDatadogApplicationKeyWithActionsApiAccess(applicationKeyName, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckDatadogApplicationKeyExists(providers.frameworkProvider, resourceName),
resource.TestCheckResourceAttr(resourceName, "name", applicationKeyName),
resource.TestCheckResourceAttr(resourceName, "enable_actions_api_access", "false"),
resource.TestCheckResourceAttrSet(resourceName, "key"),
testAccCheckDatadogApplicationKeyActionsApiRegistered(providers.frameworkProvider, resourceName, false),
),
},
},
})
}

func testAccCheckDatadogApplicationKeyWithActionsApiAccess(uniq string, enableActionsApi bool) string {
return fmt.Sprintf(`
resource "datadog_application_key" "foo" {
name = "%s"
enable_actions_api_access = %t
}`, uniq, enableActionsApi)
}

func testAccCheckDatadogApplicationKeyActionsApiRegistered(accProvider *fwprovider.FrameworkProvider, n string, expectedRegistered bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
apiInstances := accProvider.DatadogApiInstances
auth := accProvider.Auth
if err := datadogApplicationKeyActionsApiRegisteredHelper(auth, s, apiInstances, n, expectedRegistered); err != nil {
return err
}
return nil
}
}

func datadogApplicationKeyActionsApiRegisteredHelper(ctx context.Context, s *terraform.State, apiInstances *utils.ApiInstances, name string, expectedRegistered bool) error {
id := s.RootModule().Resources[name].Primary.ID
_, _, err := apiInstances.GetActionConnectionApiV2().GetAppKeyRegistration(ctx, id)
isRegistered := err == nil

if isRegistered != expectedRegistered {
return fmt.Errorf("application key Actions API registration status %t does not match expected %t", isRegistered, expectedRegistered)
}
return nil
}
Loading
Loading