From b7b100fa8151fddcce75de81a883209aff804adc Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:29:40 +0100 Subject: [PATCH 1/7] refactor: Update assignment filter resource construction The code changes in `object.go` refactor the construction of the assignment filter resource. The `constructResource` function now marshals the request body to JSON and logs the constructed assignment filter resource. This improves code readability and provides better visibility into the constructed resource. --- go.mod | 1 + go.sum | 2 + .../beta/assignmentFilter/resource.go | 12 ++- .../beta/assignmentFilter/validators.go | 81 +++++++++---------- 4 files changed, 50 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index 8c1937f4..396dbc7c 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/hashicorp/hc-install v0.7.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 // indirect github.com/hashicorp/terraform-plugin-go v0.23.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect diff --git a/go.sum b/go.sum index ce94ca37..20b064c7 100644 --- a/go.sum +++ b/go.sum @@ -104,6 +104,8 @@ github.com/hashicorp/terraform-plugin-framework v1.10.0 h1:xXhICE2Fns1RYZxEQebwk github.com/hashicorp/terraform-plugin-framework v1.10.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaKFhm4h2TgvMUlNzFAtUqlcOWnWPm+9E= github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1/go.mod h1:MsjL1sQ9L7wGwzJ5RjcI6FzEMdyoBnw+XK8ZnOvQOLY= +github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E= +github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go index a71cd612..3de1bf47 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go @@ -4,10 +4,12 @@ package graphBetaAssignmentFilter import ( "context" "fmt" + "strings" "time" "github.com/deploymenttheory/terraform-provider-microsoft365/internal/client" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -104,10 +106,14 @@ func (r *AssignmentFilterResource) Schema(ctx context.Context, req resource.Sche Description: "The optional description of the assignment filter.", }, "platform": schema.StringAttribute{ - Required: true, - Description: fmt.Sprintf("The Intune device management type (platform) for the assignment filter. Supported types: %v", getAllPlatformStrings()), + Required: true, + Description: fmt.Sprintf( + "The Intune device management type (platform) for the assignment filter. "+ + "Must be one of the following values: %s. "+ + "This specifies the platform type for which the assignment filter will be applied.", + strings.Join(validPlatformTypes, ", ")), Validators: []validator.String{ - platformValidator{}, + stringvalidator.OneOf(validPlatformTypes...), }, }, "rule": schema.StringAttribute{ diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go index af9d4cf0..2edbdc44 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go @@ -3,64 +3,59 @@ package graphBetaAssignmentFilter import ( "context" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/microsoftgraph/msgraph-beta-sdk-go/models" ) -// platformValidator is the custom validator type -type platformValidator struct{} +/* platform type validator */ +type platformTypeValidator struct{} -// ValidateString performs the validation. -func (v platformValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { - if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { +func (v platformTypeValidator) Description(ctx context.Context) string { + return fmt.Sprintf("platform must be one of: %s", strings.Join(validPlatformTypes, ", ")) +} + +func (v platformTypeValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v platformTypeValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { return } - _, err := models.ParseDevicePlatformType(req.ConfigValue.ValueString()) - if err != nil { - resp.Diagnostics.AddError( - "Invalid Device Platform Type", - fmt.Sprintf("The platform type '%s' is not valid. Supported types: %v", req.ConfigValue.ValueString(), getAllPlatformStrings()), - ) + value := req.ConfigValue.ValueString() + for _, validType := range validPlatformTypes { + if value == validType { + return + } } -} -// Description describes the validation in plain text. -func (v platformValidator) Description(ctx context.Context) string { - return "must be a valid device platform type" + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Platform Type", + fmt.Sprintf("Platform must be one of: %s", strings.Join(validPlatformTypes, ", ")), + ) } -// MarkdownDescription describes the validation in Markdown. -func (v platformValidator) MarkdownDescription(ctx context.Context) string { - return "must be a valid device platform type" +var validPlatformTypes = []string{ + "android", + "androidForWork", + "iOS", + "macOS", + "windowsPhone81", + "windows81AndLater", + "windows10AndLater", + "androidWorkProfile", + "unknown", + "androidAOSP", + "androidMobileApplicationManagement", + "iOSMobileApplicationManagement", + "windowsMobileApplicationManagement", } -// getAllPlatformStrings returns all the valid platform strings -func getAllPlatformStrings() []string { - platformTypes := []models.DevicePlatformType{ - models.ANDROID_DEVICEPLATFORMTYPE, - models.ANDROIDFORWORK_DEVICEPLATFORMTYPE, - models.IOS_DEVICEPLATFORMTYPE, - models.MACOS_DEVICEPLATFORMTYPE, - models.WINDOWSPHONE81_DEVICEPLATFORMTYPE, - models.WINDOWS81ANDLATER_DEVICEPLATFORMTYPE, - models.WINDOWS10ANDLATER_DEVICEPLATFORMTYPE, - models.ANDROIDWORKPROFILE_DEVICEPLATFORMTYPE, - models.UNKNOWN_DEVICEPLATFORMTYPE, - models.ANDROIDAOSP_DEVICEPLATFORMTYPE, - models.ANDROIDMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, - models.IOSMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, - models.UNKNOWNFUTUREVALUE_DEVICEPLATFORMTYPE, - models.WINDOWSMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, - } - - var platformStrings []string - for _, platform := range platformTypes { - platformStrings = append(platformStrings, platform.String()) - } - return platformStrings -} +/* assignmentFilterManagement Type validator */ // assignmentFilterManagementTypeValidator is the custom validator type type assignmentFilterManagementTypeValidator struct{} From 1a7ea517cefc07a94ce2ce21a78d1c53474530f9 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:48:30 +0100 Subject: [PATCH 2/7] refactor: Update assignment filter resource construction Refactor the construction of the assignment filter resource in `object.go` to improve code readability and provide better visibility into the constructed resource. The `constructResource` function now marshals the request body to JSON and logs the constructed assignment filter resource. --- .../beta/assignmentFilter/resource.go | 24 +-- .../beta/assignmentFilter/state.go | 15 +- .../beta/assignmentFilter/validators.go | 152 +++--------------- 3 files changed, 41 insertions(+), 150 deletions(-) diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go index 3de1bf47..d6c03604 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go @@ -110,7 +110,7 @@ func (r *AssignmentFilterResource) Schema(ctx context.Context, req resource.Sche Description: fmt.Sprintf( "The Intune device management type (platform) for the assignment filter. "+ "Must be one of the following values: %s. "+ - "This specifies the platform type for which the assignment filter will be applied.", + "This specifies the OS platform type for which the assignment filter will be applied.", strings.Join(validPlatformTypes, ", ")), Validators: []validator.String{ stringvalidator.OneOf(validPlatformTypes...), @@ -122,12 +122,11 @@ func (r *AssignmentFilterResource) Schema(ctx context.Context, req resource.Sche }, "assignment_filter_management_type": schema.StringAttribute{ Optional: true, - Description: fmt.Sprintf("Indicates filter is applied to either 'devices' or 'apps' management type. Possible values are: %v. Default filter will be applied to 'devices'.", getAllManagementTypeStrings()), + Description: fmt.Sprintf("Indicates filter is applied to either 'devices' or 'apps' management type. Possible values are: %s. Default filter will be applied to 'devices'.", strings.Join(validAssignmentFilterManagementTypes, ", ")), Validators: []validator.String{ - assignmentFilterManagementTypeValidator{}, + stringvalidator.OneOf(validAssignmentFilterManagementTypes...), }, }, - "created_date_time": schema.StringAttribute{ Computed: true, Description: "The creation time of the assignment filter.", @@ -159,10 +158,11 @@ func (r *AssignmentFilterResource) Schema(ctx context.Context, req resource.Sche Description: "The group ID associated with the payload.", }, "assignment_filter_type": schema.StringAttribute{ - Required: true, - Description: fmt.Sprintf("The assignment filter type. Supported types: %v", getAllAssignmentFilterTypes()), + Required: true, + Description: fmt.Sprintf("The assignment filter type. Supported types: %s", + strings.Join(getValidAssignmentFilterTypes(), ", ")), Validators: []validator.String{ - assignmentFilterTypeValidator{}, + stringvalidator.OneOf(getValidAssignmentFilterTypes()...), }, }, }, @@ -189,25 +189,15 @@ func (r *AssignmentFilterResource) Create(ctx context.Context, req resource.Crea return } - if r.client == nil { - resp.Diagnostics.AddError( - "Client is not initialized", - "Cannot create assignment filter because the client is not initialized.", - ) - return - } - createTimeout, diags := data.Timeouts.Create(ctx, 30*time.Second) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - ctx, cancel := context.WithTimeout(ctx, createTimeout) defer cancel() requestBody, err := constructResource(ctx, &data) - if err != nil { resp.Diagnostics.AddError( "Error constructing assignment filter", diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go index b7d2f51b..db8b02b6 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go @@ -1,6 +1,7 @@ package graphBetaAssignmentFilter import ( + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/helpers" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/microsoftgraph/msgraph-beta-sdk-go/models" @@ -13,8 +14,18 @@ func mapRemoteStateToTerraform(data *AssignmentFilterResourceModel, remoteResour data.Platform = types.StringValue(remoteResource.GetPlatform().String()) data.Rule = types.StringValue(*remoteResource.GetRule()) data.AssignmentFilterManagementType = types.StringValue(remoteResource.GetAssignmentFilterManagementType().String()) - data.CreatedDateTime = types.StringValue(remoteResource.GetCreatedDateTime().String()) - data.LastModifiedDateTime = types.StringValue(remoteResource.GetLastModifiedDateTime().String()) + + if createdDateTime := remoteResource.GetCreatedDateTime(); createdDateTime != nil { + data.CreatedDateTime = types.StringValue(createdDateTime.Format(helpers.TimeFormatRFC3339)) + } else { + data.CreatedDateTime = types.StringNull() + } + + if lastModifiedDateTime := remoteResource.GetLastModifiedDateTime(); lastModifiedDateTime != nil { + data.LastModifiedDateTime = types.StringValue(lastModifiedDateTime.Format(helpers.TimeFormatRFC3339)) + } else { + data.LastModifiedDateTime = types.StringNull() + } // Set RoleScopeTags roleScopeTags := remoteResource.GetRoleScopeTags() diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go index 2edbdc44..021eb2a5 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/validators.go @@ -1,43 +1,6 @@ package graphBetaAssignmentFilter -import ( - "context" - "fmt" - "strings" - - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/microsoftgraph/msgraph-beta-sdk-go/models" -) - /* platform type validator */ -type platformTypeValidator struct{} - -func (v platformTypeValidator) Description(ctx context.Context) string { - return fmt.Sprintf("platform must be one of: %s", strings.Join(validPlatformTypes, ", ")) -} - -func (v platformTypeValidator) MarkdownDescription(ctx context.Context) string { - return v.Description(ctx) -} - -func (v platformTypeValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { - return - } - - value := req.ConfigValue.ValueString() - for _, validType := range validPlatformTypes { - if value == validType { - return - } - } - - resp.Diagnostics.AddAttributeError( - req.Path, - "Invalid Platform Type", - fmt.Sprintf("Platform must be one of: %s", strings.Join(validPlatformTypes, ", ")), - ) -} var validPlatformTypes = []string{ "android", @@ -55,99 +18,26 @@ var validPlatformTypes = []string{ "windowsMobileApplicationManagement", } -/* assignmentFilterManagement Type validator */ - -// assignmentFilterManagementTypeValidator is the custom validator type -type assignmentFilterManagementTypeValidator struct{} - -// Description describes the validation in plain text. -func (v assignmentFilterManagementTypeValidator) Description(ctx context.Context) string { - return "must be a valid assignment filter management type" -} - -// MarkdownDescription describes the validation in Markdown. -func (v assignmentFilterManagementTypeValidator) MarkdownDescription(ctx context.Context) string { - return "must be a valid assignment filter management type" -} - -// ValidateString performs the validation. -func (v assignmentFilterManagementTypeValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { - if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { - return - } - - validTypes := getAllManagementTypeStrings() - value := req.ConfigValue.ValueString() - for _, validType := range validTypes { - if value == validType { - return - } - } - - resp.Diagnostics.AddError( - "Invalid Assignment Filter Management Type", - fmt.Sprintf("The management type '%s' is not valid. Supported types: %v", value, validTypes), - ) -} - -// getAllManagementTypeStrings returns all the valid management type strings -func getAllManagementTypeStrings() []string { - return []string{"devices", "apps", "unknownFutureValue"} -} - -// assignmentFilterTypeValidator is the custom validator type -type assignmentFilterTypeValidator struct{} - -// ValidateString performs the validation. -func (v assignmentFilterTypeValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { - if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { - return - } - - validTypes := getAllAssignmentFilterTypes() - value := req.ConfigValue.ValueString() - for _, validType := range validTypes { - if value == validType { - return - } - } - - resp.Diagnostics.AddError( - "Invalid Assignment Filter Type", - fmt.Sprintf("The assignment filter type '%s' is not valid. Supported types: %v", value, validTypes), - ) -} - -// Description describes the validation in plain text. -func (v assignmentFilterTypeValidator) Description(ctx context.Context) string { - return "must be a valid assignment filter type" -} - -// MarkdownDescription describes the validation in Markdown. -func (v assignmentFilterTypeValidator) MarkdownDescription(ctx context.Context) string { - return "must be a valid assignment filter type" -} - -// getAllAssignmentFilterTypes returns all the valid assignment filter type strings -func getAllAssignmentFilterTypes() []string { - types := []models.AssociatedAssignmentPayloadType{ - models.UNKNOWN_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.DEVICECONFIGURATIONANDCOMPLIANCE_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.APPLICATION_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.ANDROIDENTERPRISEAPP_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.ENROLLMENTCONFIGURATION_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.GROUPPOLICYCONFIGURATION_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.ZEROTOUCHDEPLOYMENTDEVICECONFIGPROFILE_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.ANDROIDENTERPRISECONFIGURATION_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.DEVICEFIRMWARECONFIGURATIONINTERFACEPOLICY_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.RESOURCEACCESSPOLICY_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.WIN32APP_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - models.DEVICEMANAGMENTCONFIGURATIONANDCOMPLIANCEPOLICY_ASSOCIATEDASSIGNMENTPAYLOADTYPE, - } - - var typeStrings []string - for _, t := range types { - typeStrings = append(typeStrings, t.String()) +var validAssignmentFilterManagementTypes = []string{ + "devices", + "apps", + "unknownFutureValue", +} + +func getValidAssignmentFilterTypes() []string { + // This reflects the order in the SDK's String() method + return []string{ + "unknown", + "deviceConfigurationAndCompliance", + "application", + "androidEnterpriseApp", + "enrollmentConfiguration", + "groupPolicyConfiguration", + "zeroTouchDeploymentDeviceConfigProfile", + "androidEnterpriseConfiguration", + "deviceFirmwareConfigurationInterfacePolicy", + "resourceAccessPolicy", + "win32app", + "deviceManagmentConfigurationAndCompliancePolicy", } - return typeStrings } From 7af720bebc6e2de899943185a042679cf2d392a8 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:59:22 +0100 Subject: [PATCH 3/7] refactor: Refactor assignment filter resource construction for improved readability and visibility The code changes in `state.go` refactor the construction of the assignment filter resource in the `mapRemoteStateToTerraform` function. The changes include: - Safely dereferencing string pointers to avoid nil pointer errors - Safely converting enums to their string representation - Handling nil values for various fields in the assignment filter resource These refactorings improve code readability and provide better visibility into the constructed assignment filter resource. Note: This commit message follows the established convention of using a prefix to indicate the type of change (`refactor` for code refactoring) and provides a clear and concise description of the changes made. --- internal/resources/common/state.go | 23 ++++ .../beta/assignmentFilter/state.go | 101 +++++++++++------- 2 files changed, 84 insertions(+), 40 deletions(-) diff --git a/internal/resources/common/state.go b/internal/resources/common/state.go index 97f3f44d..3b29fe66 100644 --- a/internal/resources/common/state.go +++ b/internal/resources/common/state.go @@ -1,6 +1,8 @@ package common import ( + "fmt" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -31,3 +33,24 @@ func SetParsedValueFromAttributes[T any](attrs map[string]attr.Value, key string } return nil } + +// safeDeref safely dereferences a string pointer. +// It returns an empty string if the pointer is nil, +// otherwise it returns the dereferenced string value. +func SafeDeref(s *string) string { + if s == nil { + return "" + } + return *s +} + +// safeEnumString safely converts an enum to its string representation. +// It returns an empty string if the enum is nil, +// otherwise it calls the String() method on the enum. +// This function expects the input to implement the fmt.Stringer interface. +func SafeEnumString(e fmt.Stringer) string { + if e == nil { + return "" + } + return e.String() +} diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go index db8b02b6..d3cd8b63 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/state.go @@ -2,6 +2,7 @@ package graphBetaAssignmentFilter import ( "github.com/deploymenttheory/terraform-provider-microsoft365/internal/helpers" + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/microsoftgraph/msgraph-beta-sdk-go/models" @@ -9,11 +10,35 @@ import ( // mapRemoteStateToTerraform func mapRemoteStateToTerraform(data *AssignmentFilterResourceModel, remoteResource models.DeviceAndAppManagementAssignmentFilterable) { - data.DisplayName = types.StringValue(*remoteResource.GetDisplayName()) - data.Description = types.StringValue(*remoteResource.GetDescription()) - data.Platform = types.StringValue(remoteResource.GetPlatform().String()) - data.Rule = types.StringValue(*remoteResource.GetRule()) - data.AssignmentFilterManagementType = types.StringValue(remoteResource.GetAssignmentFilterManagementType().String()) + if displayName := remoteResource.GetDisplayName(); displayName != nil { + data.DisplayName = types.StringValue(*displayName) + } else { + data.DisplayName = types.StringNull() + } + + if description := remoteResource.GetDescription(); description != nil { + data.Description = types.StringValue(*description) + } else { + data.Description = types.StringNull() + } + + if platform := remoteResource.GetPlatform(); platform != nil { + data.Platform = types.StringValue(platform.String()) + } else { + data.Platform = types.StringNull() + } + + if rule := remoteResource.GetRule(); rule != nil { + data.Rule = types.StringValue(*rule) + } else { + data.Rule = types.StringNull() + } + + if managementType := remoteResource.GetAssignmentFilterManagementType(); managementType != nil { + data.AssignmentFilterManagementType = types.StringValue(managementType.String()) + } else { + data.AssignmentFilterManagementType = types.StringNull() + } if createdDateTime := remoteResource.GetCreatedDateTime(); createdDateTime != nil { data.CreatedDateTime = types.StringValue(createdDateTime.Format(helpers.TimeFormatRFC3339)) @@ -28,58 +53,54 @@ func mapRemoteStateToTerraform(data *AssignmentFilterResourceModel, remoteResour } // Set RoleScopeTags - roleScopeTags := remoteResource.GetRoleScopeTags() - if roleScopeTags != nil { + if roleScopeTags := remoteResource.GetRoleScopeTags(); roleScopeTags != nil { tagList := make([]attr.Value, len(roleScopeTags)) for i, tag := range roleScopeTags { tagList[i] = types.StringValue(tag) } - roleScopeTagsList := types.ListValueMust(types.StringType, tagList) - data.RoleScopeTags = roleScopeTagsList + data.RoleScopeTags = types.ListValueMust(types.StringType, tagList) } else { - roleScopeTagsList := types.ListValueMust(types.StringType, []attr.Value{}) - data.RoleScopeTags = roleScopeTagsList + data.RoleScopeTags = types.ListValueMust(types.StringType, []attr.Value{}) } // Set Payloads - payloads := remoteResource.GetPayloads() - if payloads != nil { - payloadList := make([]attr.Value, len(payloads)) - for i, payload := range payloads { - payloadType := payload.GetPayloadType().String() - assignmentFilterType := payload.GetAssignmentFilterType().String() + if payloads := remoteResource.GetPayloads(); payloads != nil { + payloadList := make([]attr.Value, 0, len(payloads)) + for _, payload := range payloads { payloadMap := map[string]attr.Value{ - "payload_id": types.StringValue(*payload.GetPayloadId()), - "payload_type": types.StringValue(payloadType), - "group_id": types.StringValue(*payload.GetGroupId()), - "assignment_remoteResource_type": types.StringValue(assignmentFilterType), + "payload_id": types.StringValue(common.SafeDeref(payload.GetPayloadId())), + "payload_type": types.StringValue(common.SafeEnumString(payload.GetPayloadType())), + "group_id": types.StringValue(common.SafeDeref(payload.GetGroupId())), + "assignment_filter_type": types.StringValue(common.SafeEnumString(payload.GetAssignmentFilterType())), } - payloadList[i] = types.ObjectValueMust(map[string]attr.Type{ - "payload_id": types.StringType, - "payload_type": types.StringType, - "group_id": types.StringType, - "assignment_remoteResource_type": types.StringType, + payloadObj, diags := types.ObjectValue(map[string]attr.Type{ + "payload_id": types.StringType, + "payload_type": types.StringType, + "group_id": types.StringType, + "assignment_filter_type": types.StringType, }, payloadMap) + if diags.HasError() { + // Handle or log the error + continue + } + payloadList = append(payloadList, payloadObj) } - payloadsList := types.ListValueMust(types.ObjectType{ + data.Payloads, _ = types.ListValue(types.ObjectType{ AttrTypes: map[string]attr.Type{ - "payload_id": types.StringType, - "payload_type": types.StringType, - "group_id": types.StringType, - "assignment_remoteResource_type": types.StringType, + "payload_id": types.StringType, + "payload_type": types.StringType, + "group_id": types.StringType, + "assignment_filter_type": types.StringType, }, }, payloadList) - data.Payloads = payloadsList } else { - payloadsList := types.ListValueMust(types.ObjectType{ + data.Payloads = types.ListNull(types.ObjectType{ AttrTypes: map[string]attr.Type{ - "payload_id": types.StringType, - "payload_type": types.StringType, - "group_id": types.StringType, - "assignment_remoteResource_type": types.StringType, + "payload_id": types.StringType, + "payload_type": types.StringType, + "group_id": types.StringType, + "assignment_filter_type": types.StringType, }, - }, []attr.Value{}) - data.Payloads = payloadsList + }) } - } From 4bc78c68e01148dcdf5455e61a65fecd2f7db5d9 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:27:32 +0100 Subject: [PATCH 4/7] moved crud functons to seperate file --- .../beta/assignmentFilter/crud.go | 159 ++++++++++++++++++ .../beta/assignmentFilter/resource.go | 152 ----------------- 2 files changed, 159 insertions(+), 152 deletions(-) create mode 100644 internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go new file mode 100644 index 00000000..3432fc5f --- /dev/null +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go @@ -0,0 +1,159 @@ +package graphBetaAssignmentFilter + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Create handles the Create operation. +func (r *AssignmentFilterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data AssignmentFilterResourceModel + + tflog.Debug(ctx, fmt.Sprintf("Starting creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + createTimeout, diags := data.Timeouts.Create(ctx, 30*time.Second) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + requestBody, err := constructResource(ctx, &data) + if err != nil { + resp.Diagnostics.AddError( + "Error constructing assignment filter", + fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), + ) + return + } + + assignmentFilter, err := r.client.DeviceManagement().AssignmentFilters().Post(ctx, requestBody, nil) + if err != nil { + resp.Diagnostics.AddError( + "Error creating assignment filter", + fmt.Sprintf("Could not create assignment filter: %s", err.Error()), + ) + return + } + + data.ID = types.StringValue(*assignmentFilter.GetId()) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + tflog.Debug(ctx, fmt.Sprintf("Finished creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) +} + +// Read handles the read operation and stating. +func (r *AssignmentFilterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data AssignmentFilterResourceModel + + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := data.Timeouts.Read(ctx, 30*time.Second) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + remoteResource, err := r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Get(ctx, nil) + if err != nil { + resp.Diagnostics.AddError( + "Error reading assignment filter", + fmt.Sprintf("Could not read assignment filter: %s", err.Error()), + ) + return + } + + mapRemoteStateToTerraform(&data, remoteResource) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Update handles the Update operation. +func (r *AssignmentFilterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data AssignmentFilterResourceModel + + tflog.Debug(ctx, fmt.Sprintf("Starting Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + updateTimeout, diags := data.Timeouts.Update(ctx, 30*time.Second) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, updateTimeout) + defer cancel() + + requestBody, err := constructResource(ctx, &data) + + if err != nil { + resp.Diagnostics.AddError( + "Error constructing assignment filter", + fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), + ) + return + } + + _, err = r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Patch(ctx, requestBody, nil) + if err != nil { + resp.Diagnostics.AddError( + "Error updating assignment filter", + fmt.Sprintf("Could not update assignment filter: %s", err.Error()), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + tflog.Debug(ctx, fmt.Sprintf("Finished Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) +} + +// Delete handles the Delete operation. +func (r *AssignmentFilterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data AssignmentFilterResourceModel + + tflog.Debug(ctx, fmt.Sprintf("Starting deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + deleteTimeout, diags := data.Timeouts.Delete(ctx, 30*time.Second) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, deleteTimeout) + defer cancel() + + err := r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Delete(ctx, nil) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("Client error when deleting %s_%s", r.ProviderTypeName, r.TypeName), err.Error()) + return + } + + tflog.Debug(ctx, fmt.Sprintf("Completed deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.State.RemoveResource(ctx) +} diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go index d6c03604..5fe081d9 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "strings" - "time" "github.com/deploymenttheory/terraform-provider-microsoft365/internal/client" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" @@ -177,154 +176,3 @@ func (r *AssignmentFilterResource) Schema(ctx context.Context, req resource.Sche }, } } - -// Create handles the Create operation. -func (r *AssignmentFilterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data AssignmentFilterResourceModel - - tflog.Debug(ctx, fmt.Sprintf("Starting creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) - - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { - return - } - - createTimeout, diags := data.Timeouts.Create(ctx, 30*time.Second) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - ctx, cancel := context.WithTimeout(ctx, createTimeout) - defer cancel() - - requestBody, err := constructResource(ctx, &data) - if err != nil { - resp.Diagnostics.AddError( - "Error constructing assignment filter", - fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), - ) - return - } - - assignmentFilter, err := r.client.DeviceManagement().AssignmentFilters().Post(ctx, requestBody, nil) - if err != nil { - resp.Diagnostics.AddError( - "Error creating assignment filter", - fmt.Sprintf("Could not create assignment filter: %s", err.Error()), - ) - return - } - - data.ID = types.StringValue(*assignmentFilter.GetId()) - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) - - tflog.Debug(ctx, fmt.Sprintf("Finished creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) -} - -// Read handles the read operation and stating. -func (r *AssignmentFilterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data AssignmentFilterResourceModel - - diags := req.State.Get(ctx, &data) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - readTimeout, diags := data.Timeouts.Read(ctx, 30*time.Second) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - ctx, cancel := context.WithTimeout(ctx, readTimeout) - defer cancel() - - remoteResource, err := r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Get(ctx, nil) - if err != nil { - resp.Diagnostics.AddError( - "Error reading assignment filter", - fmt.Sprintf("Could not read assignment filter: %s", err.Error()), - ) - return - } - - mapRemoteStateToTerraform(&data, remoteResource) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -// Update handles the Update operation. -func (r *AssignmentFilterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data AssignmentFilterResourceModel - - tflog.Debug(ctx, fmt.Sprintf("Starting Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) - - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { - return - } - - updateTimeout, diags := data.Timeouts.Update(ctx, 30*time.Second) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - ctx, cancel := context.WithTimeout(ctx, updateTimeout) - defer cancel() - - requestBody, err := constructResource(ctx, &data) - - if err != nil { - resp.Diagnostics.AddError( - "Error constructing assignment filter", - fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), - ) - return - } - - _, err = r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Patch(ctx, requestBody, nil) - if err != nil { - resp.Diagnostics.AddError( - "Error updating assignment filter", - fmt.Sprintf("Could not update assignment filter: %s", err.Error()), - ) - return - } - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) - - tflog.Debug(ctx, fmt.Sprintf("Finished Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) -} - -// Delete handles the Delete operation. -func (r *AssignmentFilterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data AssignmentFilterResourceModel - - tflog.Debug(ctx, fmt.Sprintf("Starting deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName)) - - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { - return - } - - deleteTimeout, diags := data.Timeouts.Delete(ctx, 30*time.Second) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - ctx, cancel := context.WithTimeout(ctx, deleteTimeout) - defer cancel() - - err := r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Delete(ctx, nil) - if err != nil { - resp.Diagnostics.AddError(fmt.Sprintf("Client error when deleting %s_%s", r.ProviderTypeName, r.TypeName), err.Error()) - return - } - - tflog.Debug(ctx, fmt.Sprintf("Completed deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName)) - - resp.State.RemoveResource(ctx) -} From 017414f52d057b651d0889c6a96ff3cf12e1a9cf Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:28:04 +0100 Subject: [PATCH 5/7] chore: file rename --- .../beta/assignmentFilter/{object.go => construct.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename internal/resources/deviceandappmanagement/beta/assignmentFilter/{object.go => construct.go} (100%) diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/object.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/construct.go similarity index 100% rename from internal/resources/deviceandappmanagement/beta/assignmentFilter/object.go rename to internal/resources/deviceandappmanagement/beta/assignmentFilter/construct.go From c6da38cdd11afb922bc48e524b219a8fa2384ed2 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:00:37 +0100 Subject: [PATCH 6/7] refactor: error logging pattern --- .../beta/assignmentFilter/crud.go | 64 +++++++++++++++++-- .../beta/assignmentFilter/resource.go | 1 + 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go index 3432fc5f..fcbc6eae 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go @@ -3,11 +3,13 @@ package graphBetaAssignmentFilter import ( "context" "fmt" + "strings" "time" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" ) // Create handles the Create operation. @@ -49,7 +51,15 @@ func (r *AssignmentFilterResource) Create(ctx context.Context, req resource.Crea data.ID = types.StringValue(*assignmentFilter.GetId()) - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + r.isCreate = true + + readResp := resource.ReadResponse{ + State: resp.State, + } + r.Read(ctx, resource.ReadRequest{State: resp.State}, &readResp) + resp.Diagnostics.Append(readResp.Diagnostics...) + + r.isCreate = false tflog.Debug(ctx, fmt.Sprintf("Finished creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) } @@ -74,9 +84,17 @@ func (r *AssignmentFilterResource) Read(ctx context.Context, req resource.ReadRe remoteResource, err := r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Get(ctx, nil) if err != nil { + if isNotFoundError(err) && !r.isCreate { + resp.Diagnostics.AddWarning( + "Resource Not Found", + fmt.Sprintf("The resource: %s_%s with ID %s was not found and will be removed from the state.", r.ProviderTypeName, r.TypeName, data.ID.ValueString()), + ) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError( "Error reading assignment filter", - fmt.Sprintf("Could not read assignment filter: %s", err.Error()), + fmt.Sprintf("Could not read resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), ) return } @@ -116,9 +134,17 @@ func (r *AssignmentFilterResource) Update(ctx context.Context, req resource.Upda _, err = r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Patch(ctx, requestBody, nil) if err != nil { + if isNotFoundError(err) && !r.isCreate { + resp.Diagnostics.AddWarning( + "Resource Not Found", + fmt.Sprintf("The resource: %s_%s with ID %s was not found and will be removed from the state.", r.ProviderTypeName, r.TypeName, data.ID.ValueString()), + ) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError( - "Error updating assignment filter", - fmt.Sprintf("Could not update assignment filter: %s", err.Error()), + "Error reading assignment filter", + fmt.Sprintf("Could not update resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), ) return } @@ -157,3 +183,33 @@ func (r *AssignmentFilterResource) Delete(ctx context.Context, req resource.Dele resp.State.RemoveResource(ctx) } + +// isNotFoundError checks if the error is a not found error. +func isNotFoundError(err error) bool { + if err == nil { + return false + } + + odataErr, ok := err.(*odataerrors.ODataError) + if !ok { + return false + } + + mainError := odataErr.GetErrorEscaped() + if mainError != nil { + if code := mainError.GetCode(); code != nil { + switch strings.ToLower(*code) { + case "request_resourcenotfound", "resourcenotfound": + return true + } + } + + if message := mainError.GetMessage(); message != nil { + if strings.Contains(strings.ToLower(*message), "not found") { + return true + } + } + } + + return false +} diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go index 5fe081d9..ce4bf0c9 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/resource.go @@ -31,6 +31,7 @@ type AssignmentFilterResource struct { client *msgraphbetasdk.GraphServiceClient ProviderTypeName string TypeName string + isCreate bool } type AssignmentFilterResourceModel struct { From 545bfd9fb6f5bc06267867e5526211bda44e8620 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:07:31 +0100 Subject: [PATCH 7/7] chore: moved the error message to a common package and updated comments --- internal/resources/common/error.go | 64 +++++++++++++++++++ .../beta/assignmentFilter/crud.go | 37 +---------- 2 files changed, 67 insertions(+), 34 deletions(-) create mode 100644 internal/resources/common/error.go diff --git a/internal/resources/common/error.go b/internal/resources/common/error.go new file mode 100644 index 00000000..966ee41b --- /dev/null +++ b/internal/resources/common/error.go @@ -0,0 +1,64 @@ +package common + +import ( + "strings" + + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" +) + +// IsNotFoundError checks if the given error is an OData error indicating that a resource was not found. +// The function first verifies if the error is not nil. Then, it attempts to cast the error to an ODataError +// type from the Microsoft Graph SDK. If the casting is successful, the function retrieves the main error +// details using the GetErrorEscaped method of the ODataError struct. It then checks if the error code or +// message contains indications of a "not found" error. +// +// Specifically, the function looks for the error codes "request_resourcenotfound" and "resourcenotfound" +// (case-insensitive), or a message containing the phrase "not found" (case-insensitive). If any of these +// conditions are met, the function returns true, indicating that the error is a "not found" error. +// Otherwise, it returns false. +// +// The ODataError struct is part of the Microsoft Graph SDK and includes various methods and properties +// to handle API errors. The main error details are encapsulated in a nested structure that provides +// additional context, such as error codes and descriptive messages. +// +// Usage: +// +// if common.IsNotFoundError(err) { +// // Handle the "not found" error case +// } +// +// Parameters: +// +// err - The error to check. +// +// Returns: +// +// bool - True if the error indicates that a resource was not found, otherwise false. +func IsNotFoundError(err error) bool { + if err == nil { + return false + } + + odataErr, ok := err.(*odataerrors.ODataError) + if !ok { + return false + } + + mainError := odataErr.GetErrorEscaped() + if mainError != nil { + if code := mainError.GetCode(); code != nil { + switch strings.ToLower(*code) { + case "request_resourcenotfound", "resourcenotfound": + return true + } + } + + if message := mainError.GetMessage(); message != nil { + if strings.Contains(strings.ToLower(*message), "not found") { + return true + } + } + } + + return false +} diff --git a/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go b/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go index fcbc6eae..97f199f7 100644 --- a/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go +++ b/internal/resources/deviceandappmanagement/beta/assignmentFilter/crud.go @@ -3,13 +3,12 @@ package graphBetaAssignmentFilter import ( "context" "fmt" - "strings" "time" + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" ) // Create handles the Create operation. @@ -84,7 +83,7 @@ func (r *AssignmentFilterResource) Read(ctx context.Context, req resource.ReadRe remoteResource, err := r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Get(ctx, nil) if err != nil { - if isNotFoundError(err) && !r.isCreate { + if common.IsNotFoundError(err) && !r.isCreate { resp.Diagnostics.AddWarning( "Resource Not Found", fmt.Sprintf("The resource: %s_%s with ID %s was not found and will be removed from the state.", r.ProviderTypeName, r.TypeName, data.ID.ValueString()), @@ -134,7 +133,7 @@ func (r *AssignmentFilterResource) Update(ctx context.Context, req resource.Upda _, err = r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Patch(ctx, requestBody, nil) if err != nil { - if isNotFoundError(err) && !r.isCreate { + if common.IsNotFoundError(err) && !r.isCreate { resp.Diagnostics.AddWarning( "Resource Not Found", fmt.Sprintf("The resource: %s_%s with ID %s was not found and will be removed from the state.", r.ProviderTypeName, r.TypeName, data.ID.ValueString()), @@ -183,33 +182,3 @@ func (r *AssignmentFilterResource) Delete(ctx context.Context, req resource.Dele resp.State.RemoveResource(ctx) } - -// isNotFoundError checks if the error is a not found error. -func isNotFoundError(err error) bool { - if err == nil { - return false - } - - odataErr, ok := err.(*odataerrors.ODataError) - if !ok { - return false - } - - mainError := odataErr.GetErrorEscaped() - if mainError != nil { - if code := mainError.GetCode(); code != nil { - switch strings.ToLower(*code) { - case "request_resourcenotfound", "resourcenotfound": - return true - } - } - - if message := mainError.GetMessage(); message != nil { - if strings.Contains(strings.ToLower(*message), "not found") { - return true - } - } - } - - return false -}