diff --git a/datadog/fwprovider/data_source_datadog_service_account.go b/datadog/fwprovider/data_source_datadog_service_account.go index 48bf8c3106..e6c6ee1303 100644 --- a/datadog/fwprovider/data_source_datadog_service_account.go +++ b/datadog/fwprovider/data_source_datadog_service_account.go @@ -24,6 +24,7 @@ type datadogServiceAccountDatasourceModel struct { ID types.String `tfsdk:"id"` Filter types.String `tfsdk:"filter"` FilterStatus types.String `tfsdk:"filter_status"` + ExactMatch types.Bool `tfsdk:"exact_match"` // Results Disabled types.Bool `tfsdk:"disabled"` Email types.String `tfsdk:"email"` @@ -33,7 +34,7 @@ type datadogServiceAccountDatasourceModel struct { Status types.String `tfsdk:"status"` Title types.String `tfsdk:"title"` Verified types.Bool `tfsdk:"verified"` - Roles types.List `tfsdk:"roles"` + Roles types.Set `tfsdk:"roles"` } type datadogServiceAccountDatasource struct { @@ -69,6 +70,10 @@ func (d *datadogServiceAccountDatasource) Schema(_ context.Context, _ datasource Description: "Filter on status attribute. Comma separated list, with possible values `Active`, `Pending`, and `Disabled`.", Optional: true, }, + "exact_match": schema.BoolAttribute{ + Description: "When true, `filter` string is exact matched against the user's `email`, followed by `name` attribute.", + Optional: true, + }, // Computed values "disabled": schema.BoolAttribute{ Computed: true, @@ -102,7 +107,7 @@ func (d *datadogServiceAccountDatasource) Schema(_ context.Context, _ datasource Computed: true, Description: "Whether the user is verified.", }, - "roles": schema.ListAttribute{ + "roles": schema.SetAttribute{ Computed: true, Description: "Roles assigned to this service account.", ElementType: types.StringType, @@ -133,7 +138,8 @@ func (d *datadogServiceAccountDatasource) Read(ctx context.Context, req datasour userData = ddResp.Data } else { optionalParams := datadogV2.ListUsersOptionalParameters{} - optionalParams.WithFilter(state.Filter.ValueString()) + filter := state.Filter.ValueString() + optionalParams.WithFilter(filter) if !state.FilterStatus.IsNull() { optionalParams.WithFilterStatus(state.FilterStatus.ValueString()) } @@ -151,7 +157,8 @@ func (d *datadogServiceAccountDatasource) Read(ctx context.Context, req datasour serviceAccounts = append(serviceAccounts, user) } } - if len(serviceAccounts) > 1 { + isExactMatch := state.ExactMatch.ValueBool() + if len(serviceAccounts) > 1 && !isExactMatch { resp.Diagnostics.AddError("filter keyword returned more than one result, use more specific search criteria", "") return } @@ -160,6 +167,29 @@ func (d *datadogServiceAccountDatasource) Read(ctx context.Context, req datasour return } userData = &serviceAccounts[0] + if isExactMatch { + matchCount := 0 + for _, serviceAccount := range serviceAccounts { + if *serviceAccount.GetAttributes().Email == filter { + userData = &serviceAccount + matchCount++ + continue + } + if *serviceAccount.GetAttributes().Name.Get() == filter { + userData = &serviceAccount + matchCount++ + continue + } + } + if matchCount > 1 { + resp.Diagnostics.AddError("your query returned more than one result for filter with exact match, please try a more specific search criteria", "") + return + } + if matchCount == 0 { + resp.Diagnostics.AddError("didn't find any service account matching filter string with exact match", "") + return + } + } } d.updateState(ctx, &state, userData) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) @@ -202,5 +232,5 @@ func (r *datadogServiceAccountDatasource) updateState(ctx context.Context, state } } } - state.Roles, _ = types.ListValueFrom(ctx, types.StringType, roles) + state.Roles, _ = types.SetValueFrom(ctx, types.StringType, roles) } diff --git a/datadog/fwprovider/resource_datadog_service_account.go b/datadog/fwprovider/resource_datadog_service_account.go index 2df13434b1..fd412f3408 100644 --- a/datadog/fwprovider/resource_datadog_service_account.go +++ b/datadog/fwprovider/resource_datadog_service_account.go @@ -72,6 +72,7 @@ func (r *serviceAccountResource) Schema(_ context.Context, _ resource.SchemaRequ "roles": schema.SetAttribute{ Description: "A list a role IDs to assign to the service account.", Optional: true, + Computed: true, ElementType: types.StringType, }, "id": utils.ResourceIDAttribute(), @@ -298,7 +299,9 @@ func buildDatadogServiceAccountV2Request(ctx context.Context, state *serviceAcco serviceAccountCreate.SetAttributes(*serviceAccountAttributes) var roles []string - diags.Append(state.Roles.ElementsAs(ctx, &roles, false)...) + if !state.Roles.IsUnknown() { + diags.Append(state.Roles.ElementsAs(ctx, &roles, false)...) + } rolesData := make([]datadogV2.RelationshipToRoleData, len(roles)) for i, role := range roles { roleData := datadogV2.NewRelationshipToRoleData() @@ -347,6 +350,16 @@ func (r *serviceAccountResource) updateRolesFw(ctx context.Context, userID strin rolesToRemove := utils.StringSliceDifference(oldRolesSlice, newRolesSlice) rolesToAdd := utils.StringSliceDifference(newRolesSlice, oldRolesSlice) + for _, role := range rolesToAdd { + roleRelation := datadogV2.NewRelationshipToUserWithDefaults() + roleRelationData := datadogV2.NewRelationshipToUserDataWithDefaults() + roleRelationData.SetId(userID) + roleRelation.SetData(*roleRelationData) + _, _, err := r.RolesApiV2.AddUserToRole(r.Auth, role, *roleRelation) + if err != nil { + return diag.NewErrorDiagnostic("error adding user to role: ", err.Error()) + } + } for _, role := range rolesToRemove { userRelation := datadogV2.NewRelationshipToUserWithDefaults() userRelationData := datadogV2.NewRelationshipToUserDataWithDefaults() @@ -358,15 +371,6 @@ func (r *serviceAccountResource) updateRolesFw(ctx context.Context, userID strin return diag.NewErrorDiagnostic("error removing user from role: ", err.Error()) } } - for _, role := range rolesToAdd { - roleRelation := datadogV2.NewRelationshipToUserWithDefaults() - roleRelationData := datadogV2.NewRelationshipToUserDataWithDefaults() - roleRelationData.SetId(userID) - roleRelation.SetData(*roleRelationData) - _, _, err := r.RolesApiV2.AddUserToRole(r.Auth, role, *roleRelation) - if err != nil { - return diag.NewErrorDiagnostic("error adding user to role: ", err.Error()) - } - } + return nil } diff --git a/datadog/fwprovider/resource_datadog_user_role.go b/datadog/fwprovider/resource_datadog_user_role.go index 7ecf6855be..e233db3127 100644 --- a/datadog/fwprovider/resource_datadog_user_role.go +++ b/datadog/fwprovider/resource_datadog_user_role.go @@ -48,7 +48,7 @@ func (r *userRoleResource) Metadata(_ context.Context, request resource.Metadata func (r *userRoleResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { response.Schema = schema.Schema{ - Description: "Provides a Datadog UserRole resource. This can be used to create and manage Datadog User Roles. Conflicts may occur if used together with the `datadog_user` resource's `roles` attribute. This resource is in beta and is subject to change.", + Description: "Provides a Datadog UserRole resource. This can be used to create and manage Datadog User Roles. Conflicts may occur if used together with the `datadog_user` resource's `roles` attribute or the `datadog_service_account` resource's `roles` attribute. This resource is in beta and is subject to change.", Attributes: map[string]schema.Attribute{ "id": utils.ResourceIDAttribute(), "role_id": schema.StringAttribute{ diff --git a/docs/data-sources/service_account.md b/docs/data-sources/service_account.md index ecf93576aa..9e0c82139d 100644 --- a/docs/data-sources/service_account.md +++ b/docs/data-sources/service_account.md @@ -17,6 +17,7 @@ Use this data source to retrieve information about an existing Datadog service a ### Optional +- `exact_match` (Boolean) When true, `filter` string is exact matched against the user's `email`, followed by `name` attribute. - `filter` (String) Filter all users and service accounts by name, email, or role. - `filter_status` (String) Filter on status attribute. Comma separated list, with possible values `Active`, `Pending`, and `Disabled`. - `id` (String) The service account's ID. @@ -28,7 +29,7 @@ Use this data source to retrieve information about an existing Datadog service a - `handle` (String) Handle of the user. - `icon` (String) URL of the user's icon. - `name` (String) Name of the user. -- `roles` (List of String) Roles assigned to this service account. +- `roles` (Set of String) Roles assigned to this service account. - `status` (String) Status of the user. - `title` (String) Title of the user. - `verified` (Boolean) Whether the user is verified. diff --git a/docs/resources/user_role.md b/docs/resources/user_role.md index c6a7407867..95a3943ad1 100644 --- a/docs/resources/user_role.md +++ b/docs/resources/user_role.md @@ -3,12 +3,12 @@ page_title: "datadog_user_role Resource - terraform-provider-datadog" subcategory: "" description: |- - Provides a Datadog UserRole resource. This can be used to create and manage Datadog User Roles. Conflicts may occur if used together with the datadog_user resource's roles attribute. This resource is in beta and is subject to change. + Provides a Datadog UserRole resource. This can be used to create and manage Datadog User Roles. Conflicts may occur if used together with the datadog_user resource's roles attribute or the datadog_service_account resource's roles attribute. This resource is in beta and is subject to change. --- # datadog_user_role (Resource) -Provides a Datadog UserRole resource. This can be used to create and manage Datadog User Roles. Conflicts may occur if used together with the `datadog_user` resource's `roles` attribute. This resource is in beta and is subject to change. +Provides a Datadog UserRole resource. This can be used to create and manage Datadog User Roles. Conflicts may occur if used together with the `datadog_user` resource's `roles` attribute or the `datadog_service_account` resource's `roles` attribute. This resource is in beta and is subject to change. ## Example Usage