diff --git a/internal/resources/common/errors/error_handling.go b/internal/resources/common/errors/error_handling.go index f59df15d..a55a8a6f 100644 --- a/internal/resources/common/errors/error_handling.go +++ b/internal/resources/common/errors/error_handling.go @@ -26,6 +26,7 @@ type GraphErrorInfo struct { AdditionalData map[string]interface{} Headers *abstractions.ResponseHeaders RequestDetails string + RetryAfter string } // standardErrorDescriptions provides consistent error messaging across the provider @@ -85,6 +86,9 @@ func HandleGraphError(ctx context.Context, err error, resp interface{}, operatio case 401, 403: handlePermissionError(ctx, errorInfo, resp, operation, requiredPermissions) + case 429: + handleRateLimitError(ctx, errorInfo, resp) + default: // Handle all other cases addErrorToDiagnostics(ctx, resp, errorDesc.Summary, @@ -216,6 +220,27 @@ func handlePermissionError(ctx context.Context, errorInfo GraphErrorInfo, resp i addErrorToDiagnostics(ctx, resp, errorDesc.Summary, detail) } +// handleRateLimitError processes rate limit errors and adds retry information to the error message +func handleRateLimitError(ctx context.Context, errorInfo GraphErrorInfo, resp interface{}) GraphErrorInfo { + if headers := errorInfo.Headers; headers != nil { + retryValues := headers.Get("Retry-After") + if len(retryValues) > 0 { + errorInfo.RetryAfter = retryValues[0] + } + } + + tflog.Warn(ctx, "Rate limit exceeded", map[string]interface{}{ + "retry_after": errorInfo.RetryAfter, + "details": errorInfo.ErrorMessage, + }) + + errorDesc := getErrorDescription(429) + detail := constructErrorDetail(errorDesc.Detail, errorInfo.ErrorMessage) + addErrorToDiagnostics(ctx, resp, errorDesc.Summary, detail) + + return errorInfo +} + // addErrorToDiagnostics adds an error to the response diagnostics func addErrorToDiagnostics(ctx context.Context, resp interface{}, summary, detail string) { switch r := resp.(type) { diff --git a/internal/resources/common/retry/retry_assignments.go b/internal/resources/common/retry/retry_assignments.go new file mode 100644 index 00000000..9920aba3 --- /dev/null +++ b/internal/resources/common/retry/retry_assignments.go @@ -0,0 +1,97 @@ +// REF: https://learn.microsoft.com/en-us/graph/throttling-limits#assignment-service-limits + +package retry + +import ( + "context" + "time" + + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/errors" + "github.com/hashicorp/terraform-plugin-log/tflog" + "golang.org/x/exp/rand" +) + +// RetryableAssignmentOperation executes an assignment operation with specific rate limiting +func RetryableAssignmentOperation(ctx context.Context, operation string, fn func() error) error { + var attempt int + r := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) + + const ( + tenSecondLimit = 500 // requests per 10 seconds per app per tenant + hourlyLimit = 15000 // requests per hour per app per tenant + maxBackoff = 10 * time.Second + baseDelay = 3 * time.Second // Higher base delay for assignments + ) + + for { + err := fn() + if err == nil { + return nil + } + + graphError := errors.GraphError(ctx, err) + if graphError.StatusCode != 429 { + return err + } + + // Parse throttle scope if available + var throttleScope ThrottleScope + if scope := graphError.Headers.Get("x-ms-throttle-scope"); len(scope) > 0 { + throttleScope = parseThrottleScope(scope[0]) + } + + // Get throttle information + var throttleInfo string + if info := graphError.Headers.Get("x-ms-throttle-information"); len(info) > 0 { + throttleInfo = info[0] + } + + // Use Retry-After if provided, otherwise use exponential backoff + var backoffDelay time.Duration + if graphError.RetryAfter != "" { + if seconds, err := time.ParseDuration(graphError.RetryAfter + "s"); err == nil { + backoffDelay = seconds + } + } + + if backoffDelay == 0 { + backoffDelay = baseDelay * time.Duration(1< maxBackoff { + backoffDelay = maxBackoff + } + } + + // Add jitter: randomly between 50-100% of calculated delay + jitterDelay := backoffDelay/2 + time.Duration(r.Int63n(int64(backoffDelay/2))) + attempt++ + + logDetails := map[string]interface{}{ + "operation": operation, + "attempt": attempt, + "delay_seconds": jitterDelay.Seconds(), + "status_code": graphError.StatusCode, + "rate_limit_10s": tenSecondLimit, + "rate_limit_1h": hourlyLimit, + } + + if throttleInfo != "" { + logDetails["throttle_reason"] = throttleInfo + } + if throttleScope != (ThrottleScope{}) { + logDetails["throttle_scope"] = throttleScope.Scope + logDetails["throttle_limit"] = throttleScope.Limit + } + + tflog.Info(ctx, "Microsoft Graph assignment rate limit encountered", logDetails) + + timer := time.NewTimer(jitterDelay) + defer timer.Stop() + + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue + } + } +} diff --git a/internal/resources/common/retry/retry_global.go b/internal/resources/common/retry/retry_global.go new file mode 100644 index 00000000..9c348ceb --- /dev/null +++ b/internal/resources/common/retry/retry_global.go @@ -0,0 +1,114 @@ +// REF: https://learn.microsoft.com/en-us/graph/throttling + +package retry + +import ( + "context" + "strings" + "time" + + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/errors" + "github.com/hashicorp/terraform-plugin-log/tflog" + "golang.org/x/exp/rand" +) + +// ThrottleScope represents the scope of throttling from x-ms-throttle-scope header +type ThrottleScope struct { + Scope string + Limit string + ApplicationID string + ResourceID string +} + +// parseThrottleScope parses the x-ms-throttle-scope header +func parseThrottleScope(scope string) ThrottleScope { + parts := strings.Split(scope, "/") + if len(parts) != 4 { + return ThrottleScope{} + } + return ThrottleScope{ + Scope: parts[0], + Limit: parts[1], + ApplicationID: parts[2], + ResourceID: parts[3], + } +} + +// RetryableOperation executes an operation with automatic retry on rate limit errors +func RetryableOperation(ctx context.Context, operation string, fn func() error) error { + var attempt int + r := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) + + for { + err := fn() + if err == nil { + return nil + } + + graphError := errors.GraphError(ctx, err) + if graphError.StatusCode != 429 { + return err + } + + // Parse throttle scope if available + var throttleScope ThrottleScope + if scope := graphError.Headers.Get("x-ms-throttle-scope"); len(scope) > 0 { + throttleScope = parseThrottleScope(scope[0]) + } + + // Get throttle information + var throttleInfo string + if info := graphError.Headers.Get("x-ms-throttle-information"); len(info) > 0 { + throttleInfo = info[0] + } + + const maxBackoff = 10 * time.Second + baseDelay := 2 * time.Second + + // Use Retry-After if provided, otherwise use exponential backoff + var backoffDelay time.Duration + if graphError.RetryAfter != "" { + if seconds, err := time.ParseDuration(graphError.RetryAfter + "s"); err == nil { + backoffDelay = seconds + } + } + + if backoffDelay == 0 { + backoffDelay = baseDelay * time.Duration(1< maxBackoff { + backoffDelay = maxBackoff + } + } + + // Add jitter: randomly between 50-100% of calculated delay + jitterDelay := backoffDelay/2 + time.Duration(r.Int63n(int64(backoffDelay/2))) + attempt++ + + logDetails := map[string]interface{}{ + "operation": operation, + "attempt": attempt, + "delay_seconds": jitterDelay.Seconds(), + "status_code": graphError.StatusCode, + } + + if throttleInfo != "" { + logDetails["throttle_reason"] = throttleInfo + } + if throttleScope != (ThrottleScope{}) { + logDetails["throttle_scope"] = throttleScope.Scope + logDetails["throttle_limit"] = throttleScope.Limit + } + + tflog.Info(ctx, "Microsoft Graph rate limit encountered", logDetails) + + timer := time.NewTimer(jitterDelay) + defer timer.Stop() + + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue + } + } +} diff --git a/internal/resources/common/retry/retry_intune.go b/internal/resources/common/retry/retry_intune.go new file mode 100644 index 00000000..5d425f3f --- /dev/null +++ b/internal/resources/common/retry/retry_intune.go @@ -0,0 +1,121 @@ +// REF: https://learn.microsoft.com/en-us/graph/throttling-limits#intune-service-limits + +package retry + +import ( + "context" + "time" + + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/errors" + "github.com/hashicorp/terraform-plugin-log/tflog" + "golang.org/x/exp/rand" +) + +// IntuneOperationType defines the type of Intune operation +type IntuneOperationType string + +const ( + IntuneWrite IntuneOperationType = "Write" // POST, PUT, DELETE, PATCH + IntuneRead IntuneOperationType = "Read" // GET and others +) + +// RetryableIntuneOperation executes an Intune operation with specific rate limiting +func RetryableIntuneOperation(ctx context.Context, operation string, opType IntuneOperationType, fn func() error) error { + var attempt int + r := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) + + const ( + // Write operations (POST, PUT, DELETE, PATCH) + writePerAppLimit = 100 // requests per 20 seconds + writeTenantLimit = 200 // requests per 20 seconds + + // General operations + generalPerAppLimit = 1000 // requests per 20 seconds + generalTenantLimit = 2000 // requests per 20 seconds + + maxBackoff = 10 * time.Second + baseDelay = 2 * time.Second + ) + + for { + err := fn() + if err == nil { + return nil + } + + graphError := errors.GraphError(ctx, err) + if graphError.StatusCode != 429 { + return err + } + + // Parse throttle scope if available + var throttleScope ThrottleScope + if scope := graphError.Headers.Get("x-ms-throttle-scope"); len(scope) > 0 { + throttleScope = parseThrottleScope(scope[0]) + } + + // Get throttle information + var throttleInfo string + if info := graphError.Headers.Get("x-ms-throttle-information"); len(info) > 0 { + throttleInfo = info[0] + } + + // Use Retry-After if provided, otherwise use exponential backoff + var backoffDelay time.Duration + if graphError.RetryAfter != "" { + if seconds, err := time.ParseDuration(graphError.RetryAfter + "s"); err == nil { + backoffDelay = seconds + } + } + + if backoffDelay == 0 { + backoffDelay = baseDelay * time.Duration(1< maxBackoff { + backoffDelay = maxBackoff + } + } + + // Add jitter: randomly between 50-100% of calculated delay + jitterDelay := backoffDelay/2 + time.Duration(r.Int63n(int64(backoffDelay/2))) + attempt++ + + // Enhanced logging with rate limit context + logDetails := map[string]interface{}{ + "operation": operation, + "attempt": attempt, + "delay_seconds": jitterDelay.Seconds(), + "status_code": graphError.StatusCode, + "operation_type": string(opType), + } + + if opType == IntuneWrite { + logDetails["rate_limit_per_app"] = writePerAppLimit + logDetails["rate_limit_tenant"] = writeTenantLimit + logDetails["window_seconds"] = 20 + } else { + logDetails["rate_limit_per_app"] = generalPerAppLimit + logDetails["rate_limit_tenant"] = generalTenantLimit + logDetails["window_seconds"] = 20 + } + + if throttleInfo != "" { + logDetails["throttle_reason"] = throttleInfo + } + if throttleScope != (ThrottleScope{}) { + logDetails["throttle_scope"] = throttleScope.Scope + logDetails["throttle_limit"] = throttleScope.Limit + } + + tflog.Info(ctx, "Intune service rate limit encountered", logDetails) + + timer := time.NewTimer(jitterDelay) + defer timer.Stop() + + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + continue + } + } +} diff --git a/internal/resources/device_and_app_management/beta/settings_catalog/crud.go b/internal/resources/device_and_app_management/beta/settings_catalog/crud.go index 3ab86cb8..75e4127e 100644 --- a/internal/resources/device_and_app_management/beta/settings_catalog/crud.go +++ b/internal/resources/device_and_app_management/beta/settings_catalog/crud.go @@ -9,6 +9,7 @@ import ( "github.com/deploymenttheory/terraform-provider-microsoft365/internal/client/graphcustom" "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/crud" "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/errors" + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/retry" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -37,7 +38,6 @@ var ( // (if specified) are created properly. The settings must be defined during creation // as they are required for a successful deployment, while assignments are optional. func (r *SettingsCatalogResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - mu.Lock() defer mu.Unlock() @@ -63,17 +63,21 @@ func (r *SettingsCatalogResource) Create(ctx context.Context, req resource.Creat return } - requestResource, err := r.client. - DeviceManagement(). - ConfigurationPolicies(). - Post(context.Background(), requestBody, nil) + err = retry.RetryableIntuneOperation(ctx, "create resource", retry.IntuneWrite, func() error { + var opErr error + requestBody, opErr = r.client. + DeviceManagement(). + ConfigurationPolicies(). + Post(context.Background(), requestBody, nil) + return opErr + }) if err != nil { errors.HandleGraphError(ctx, err, resp, "Create", r.WritePermissions) return } - object.ID = types.StringValue(*requestResource.GetId()) + object.ID = types.StringValue(*requestBody.GetId()) if object.Assignments != nil { requestAssignment, err := constructAssignment(ctx, &object) @@ -85,12 +89,15 @@ func (r *SettingsCatalogResource) Create(ctx context.Context, req resource.Creat return } - _, err = r.client. - DeviceManagement(). - ConfigurationPolicies(). - ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). - Assign(). - Post(ctx, requestAssignment, nil) + err = retry.RetryableAssignmentOperation(ctx, "create assignment", func() error { + _, err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Assign(). + Post(ctx, requestAssignment, nil) + return err + }) if err != nil { errors.HandleGraphError(ctx, err, resp, "Create", r.WritePermissions) @@ -118,7 +125,7 @@ func (r *SettingsCatalogResource) Create(ctx context.Context, req resource.Creat resp.State = readResp.State - tflog.Debug(ctx, fmt.Sprintf("Finished Update Method: %s_%s", r.ProviderTypeName, r.TypeName)) + tflog.Debug(ctx, fmt.Sprintf("Finished Create Method: %s_%s", r.ProviderTypeName, r.TypeName)) } // Read handles the Read operation for Settings Catalog resources. @@ -135,7 +142,6 @@ func (r *SettingsCatalogResource) Create(ctx context.Context, req resource.Creat // are properly read and mapped into the Terraform state, providing a complete view // of the resource's current configuration on the server. func (r *SettingsCatalogResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - tflog.Debug(ctx, fmt.Sprintf("Starting Read method for: %s_%s", r.ProviderTypeName, r.TypeName)) resp.Diagnostics.Append(req.State.Get(ctx, &object)...) @@ -151,19 +157,24 @@ func (r *SettingsCatalogResource) Read(ctx context.Context, req resource.ReadReq } defer cancel() - respResource, err := r.client. - DeviceManagement(). - ConfigurationPolicies(). - ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). - Get(ctx, nil) + err := retry.RetryableIntuneOperation(ctx, "read resource", retry.IntuneRead, func() error { + respResource, err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Get(ctx, nil) + if err != nil { + return err + } + MapRemoteResourceStateToTerraform(ctx, &object, respResource) + return nil + }) if err != nil { errors.HandleGraphError(ctx, err, resp, "Read", r.ReadPermissions) return } - MapRemoteResourceStateToTerraform(ctx, &object, respResource) - settingsConfig := graphcustom.GetRequestConfig{ APIVersion: graphcustom.GraphAPIBeta, Endpoint: r.ResourcePath, @@ -175,33 +186,43 @@ func (r *SettingsCatalogResource) Read(ctx context.Context, req resource.ReadReq }, } - respSettings, err := graphcustom.GetRequestByResourceId( - ctx, - r.client.GetAdapter(), - settingsConfig, - ) + err = retry.RetryableIntuneOperation(ctx, "read resource", retry.IntuneRead, func() error { + respSettings, err := graphcustom.GetRequestByResourceId( + ctx, + r.client.GetAdapter(), + settingsConfig, + ) + if err != nil { + return err + } + MapRemoteSettingsStateToTerraform(ctx, &object, respSettings) + return nil + }) if err != nil { - errors.HandleGraphError(ctx, err, resp, "Create - Settings Fetch", r.ReadPermissions) + errors.HandleGraphError(ctx, err, resp, "Read", r.ReadPermissions) return } - MapRemoteSettingsStateToTerraform(ctx, &object, respSettings) - - respAssignments, err := r.client. - DeviceManagement(). - ConfigurationPolicies(). - ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). - Assignments(). - Get(context.Background(), nil) + err = retry.RetryableAssignmentOperation(ctx, "read resource", func() error { + respAssignments, err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Assignments(). + Get(context.Background(), nil) + if err != nil { + return err + } + MapRemoteAssignmentStateToTerraform(ctx, &object, respAssignments) + return nil + }) if err != nil { errors.HandleGraphError(ctx, err, resp, "Read", r.ReadPermissions) return } - MapRemoteAssignmentStateToTerraform(ctx, &object, respAssignments) - resp.Diagnostics.Append(resp.State.Set(ctx, &object)...) if resp.Diagnostics.HasError() { return @@ -224,7 +245,6 @@ func (r *SettingsCatalogResource) Read(ctx context.Context, req resource.ReadReq // The function ensures that both the settings and assignments are updated atomically, // and the final state reflects the actual state of the resource on the server. func (r *SettingsCatalogResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - tflog.Debug(ctx, fmt.Sprintf("Starting Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) resp.Diagnostics.Append(req.Plan.Get(ctx, &object)...) @@ -254,7 +274,11 @@ func (r *SettingsCatalogResource) Update(ctx context.Context, req resource.Updat RequestBody: requestBody, } - err = graphcustom.PutRequestByResourceId(ctx, r.client.GetAdapter(), putRequest) + // Use retryableOperation for main resource update + err = retry.RetryableIntuneOperation(ctx, "update resource", retry.IntuneWrite, func() error { + return graphcustom.PutRequestByResourceId(ctx, r.client.GetAdapter(), putRequest) + }) + if err != nil { errors.HandleGraphError(ctx, err, resp, "Update", r.ReadPermissions) return @@ -269,12 +293,16 @@ func (r *SettingsCatalogResource) Update(ctx context.Context, req resource.Updat return } - _, err = r.client. - DeviceManagement(). - ConfigurationPolicies(). - ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). - Assign(). - Post(ctx, requestAssignment, nil) + // Use retryableAssignmentOperation for assignment update + err = retry.RetryableAssignmentOperation(ctx, "update assignment", func() error { + _, err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Assign(). + Post(ctx, requestAssignment, nil) + return err + }) if err != nil { errors.HandleGraphError(ctx, err, resp, "Update", r.WritePermissions) @@ -313,7 +341,6 @@ func (r *SettingsCatalogResource) Update(ctx context.Context, req resource.Updat // // All assignments and settings associated with the resource are automatically removed as part of the deletion. func (r *SettingsCatalogResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - tflog.Debug(ctx, fmt.Sprintf("Starting deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName)) resp.Diagnostics.Append(req.State.Get(ctx, &object)...) @@ -327,11 +354,13 @@ func (r *SettingsCatalogResource) Delete(ctx context.Context, req resource.Delet } defer cancel() - err := r.client. - DeviceManagement(). - ConfigurationPolicies(). - ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). - Delete(ctx, nil) + err := retry.RetryableIntuneOperation(ctx, "delete resource", retry.IntuneWrite, func() error { + return r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Delete(ctx, nil) + }) if err != nil { errors.HandleGraphError(ctx, err, resp, "Delete", r.ReadPermissions) diff --git a/internal/resources/device_and_app_management/beta/settings_catalog/crud.go.back b/internal/resources/device_and_app_management/beta/settings_catalog/crud.go.back new file mode 100644 index 00000000..3ab86cb8 --- /dev/null +++ b/internal/resources/device_and_app_management/beta/settings_catalog/crud.go.back @@ -0,0 +1,344 @@ +package graphBetaSettingsCatalog + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/client/graphcustom" + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/crud" + "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/errors" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var ( + // mutex needed to lock Create requests during parallel runs to avoid overwhelming api and resulting in stating issues + mu sync.Mutex + + // object is the resource model for the Endpoint Privilege Management resource + object SettingsCatalogProfileResourceModel +) + +// Create handles the Create operation for Settings Catalog resources. +// +// - Retrieves the planned configuration from the create request +// - Constructs the resource request body from the plan +// - Sends POST request to create the base resource and settings +// - Captures the new resource ID from the response +// - Constructs and sends assignment configuration if specified +// - Sets initial state with planned values +// - Calls Read operation to fetch the latest state from the API +// - Updates the final state with the fresh data from the API +// +// The function ensures that both the settings catalog profile and its assignments +// (if specified) are created properly. The settings must be defined during creation +// as they are required for a successful deployment, while assignments are optional. +func (r *SettingsCatalogResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + + mu.Lock() + defer mu.Unlock() + + tflog.Debug(ctx, fmt.Sprintf("Starting creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.Diagnostics.Append(req.Plan.Get(ctx, &object)...) + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := crud.HandleTimeout(ctx, object.Timeouts.Create, 30*time.Second, &resp.Diagnostics) + if cancel == nil { + return + } + defer cancel() + + requestBody, err := constructResource(ctx, &object) + if err != nil { + resp.Diagnostics.AddError( + "Error constructing resource for Create method", + fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), + ) + return + } + + requestResource, err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + Post(context.Background(), requestBody, nil) + + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Create", r.WritePermissions) + return + } + + object.ID = types.StringValue(*requestResource.GetId()) + + if object.Assignments != nil { + requestAssignment, err := constructAssignment(ctx, &object) + if err != nil { + resp.Diagnostics.AddError( + "Error constructing assignment for create method", + fmt.Sprintf("Could not construct assignment: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), + ) + return + } + + _, err = r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Assign(). + Post(ctx, requestAssignment, nil) + + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Create", r.WritePermissions) + return + } + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &object)...) + if resp.Diagnostics.HasError() { + return + } + + readResp := &resource.ReadResponse{ + State: resp.State, + } + r.Read(ctx, resource.ReadRequest{ + State: resp.State, + ProviderMeta: req.ProviderMeta, + }, readResp) + + resp.Diagnostics.Append(readResp.Diagnostics...) + if resp.Diagnostics.HasError() { + return + } + + resp.State = readResp.State + + tflog.Debug(ctx, fmt.Sprintf("Finished Update Method: %s_%s", r.ProviderTypeName, r.TypeName)) +} + +// Read handles the Read operation for Settings Catalog resources. +// +// - Retrieves the current state from the read request +// - Gets the base resource details from the API +// - Maps the base resource details to Terraform state +// - Gets the settings configuration from the API +// - Maps the settings configuration to Terraform state +// - Gets the assignments configuration from the API +// - Maps the assignments configuration to Terraform state +// +// The function ensures that all components (base resource, settings, and assignments) +// are properly read and mapped into the Terraform state, providing a complete view +// of the resource's current configuration on the server. +func (r *SettingsCatalogResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + + tflog.Debug(ctx, fmt.Sprintf("Starting Read method for: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.Diagnostics.Append(req.State.Get(ctx, &object)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("Reading %s_%s with ID: %s", r.ProviderTypeName, r.TypeName, object.ID.ValueString())) + + ctx, cancel := crud.HandleTimeout(ctx, object.Timeouts.Read, 30*time.Second, &resp.Diagnostics) + if cancel == nil { + return + } + defer cancel() + + respResource, err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Get(ctx, nil) + + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Read", r.ReadPermissions) + return + } + + MapRemoteResourceStateToTerraform(ctx, &object, respResource) + + settingsConfig := graphcustom.GetRequestConfig{ + APIVersion: graphcustom.GraphAPIBeta, + Endpoint: r.ResourcePath, + EndpointSuffix: "/settings", + ResourceIDPattern: "('id')", + ResourceID: object.ID.ValueString(), + QueryParameters: map[string]string{ + "$expand": "children", + }, + } + + respSettings, err := graphcustom.GetRequestByResourceId( + ctx, + r.client.GetAdapter(), + settingsConfig, + ) + + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Create - Settings Fetch", r.ReadPermissions) + return + } + + MapRemoteSettingsStateToTerraform(ctx, &object, respSettings) + + respAssignments, err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Assignments(). + Get(context.Background(), nil) + + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Read", r.ReadPermissions) + return + } + + MapRemoteAssignmentStateToTerraform(ctx, &object, respAssignments) + + resp.Diagnostics.Append(resp.State.Set(ctx, &object)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("Finished Read Method: %s_%s", r.ProviderTypeName, r.TypeName)) +} + +// Update handles the Update operation for Settings Catalog resources. +// +// - Retrieves the planned changes from the update request +// - Constructs the resource request body from the plan +// - Sends PUT request to update the base resource and settings +// - Constructs the assignment request body from the plan +// - Sends POST request to update the assignments +// - Sets initial state with planned values +// - Calls Read operation to fetch the latest state from the API +// - Updates the final state with the fresh data from the API +// +// The function ensures that both the settings and assignments are updated atomically, +// and the final state reflects the actual state of the resource on the server. +func (r *SettingsCatalogResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + + tflog.Debug(ctx, fmt.Sprintf("Starting Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.Diagnostics.Append(req.Plan.Get(ctx, &object)...) + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := crud.HandleTimeout(ctx, object.Timeouts.Update, 30*time.Second, &resp.Diagnostics) + if cancel == nil { + return + } + defer cancel() + + requestBody, err := constructResource(ctx, &object) + if err != nil { + resp.Diagnostics.AddError( + "Error constructing resource for Update method", + fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), + ) + return + } + + putRequest := graphcustom.PutRequestConfig{ + APIVersion: graphcustom.GraphAPIBeta, + Endpoint: r.ResourcePath, + ResourceID: object.ID.ValueString(), + RequestBody: requestBody, + } + + err = graphcustom.PutRequestByResourceId(ctx, r.client.GetAdapter(), putRequest) + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Update", r.ReadPermissions) + return + } + + requestAssignment, err := constructAssignment(ctx, &object) + if err != nil { + resp.Diagnostics.AddError( + "Error constructing assignment for update method", + fmt.Sprintf("Could not construct assignment: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), + ) + return + } + + _, err = r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Assign(). + Post(ctx, requestAssignment, nil) + + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Update", r.WritePermissions) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &object)...) + if resp.Diagnostics.HasError() { + return + } + + readResp := &resource.ReadResponse{ + State: resp.State, + } + r.Read(ctx, resource.ReadRequest{ + State: resp.State, + ProviderMeta: req.ProviderMeta, + }, readResp) + + resp.Diagnostics.Append(readResp.Diagnostics...) + if resp.Diagnostics.HasError() { + return + } + + resp.State = readResp.State + + tflog.Debug(ctx, fmt.Sprintf("Finished Update Method: %s_%s", r.ProviderTypeName, r.TypeName)) +} + +// Delete handles the Delete operation for Settings Catalog resources. +// +// - Retrieves the current state from the delete request +// - Validates the state data and timeout configuration +// - Sends DELETE request to remove the resource from the API +// - Cleans up by removing the resource from Terraform state +// +// All assignments and settings associated with the resource are automatically removed as part of the deletion. +func (r *SettingsCatalogResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + + tflog.Debug(ctx, fmt.Sprintf("Starting deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.Diagnostics.Append(req.State.Get(ctx, &object)...) + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := crud.HandleTimeout(ctx, object.Timeouts.Delete, 30*time.Second, &resp.Diagnostics) + if cancel == nil { + return + } + defer cancel() + + err := r.client. + DeviceManagement(). + ConfigurationPolicies(). + ByDeviceManagementConfigurationPolicyId(object.ID.ValueString()). + Delete(ctx, nil) + + if err != nil { + errors.HandleGraphError(ctx, err, resp, "Delete", r.ReadPermissions) + return + } + + tflog.Debug(ctx, fmt.Sprintf("Finished Delete Method: %s_%s", r.ProviderTypeName, r.TypeName)) + + resp.State.RemoveResource(ctx) +} diff --git a/internal/resources/device_and_app_management/beta/settings_catalog/model.go b/internal/resources/device_and_app_management/beta/settings_catalog/model.go index 756909a5..49b959c5 100644 --- a/internal/resources/device_and_app_management/beta/settings_catalog/model.go +++ b/internal/resources/device_and_app_management/beta/settings_catalog/model.go @@ -10,19 +10,19 @@ import ( // SettingsCatalogProfileResourceModel holds the configuration for a Settings Catalog profile. // Reference: https://learn.microsoft.com/en-us/graph/api/resources/intune-deviceconfigv2-devicemanagementconfigurationpolicy?view=graph-rest-beta type SettingsCatalogProfileResourceModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - Platforms types.String `tfsdk:"platforms"` - Technologies []types.String `tfsdk:"technologies"` - SettingsCount types.Int64 `tfsdk:"settings_count"` - RoleScopeTagIds []types.String `tfsdk:"role_scope_tag_ids"` - LastModifiedDateTime types.String `tfsdk:"last_modified_date_time"` - CreatedDateTime types.String `tfsdk:"created_date_time"` - Settings types.String `tfsdk:"settings"` - IsAssigned types.Bool `tfsdk:"is_assigned"` - Assignments *sharedmodels.SettingsCatalogSettingsAssignmentResourceModel `tfsdk:"assignments"` - Timeouts timeouts.Value `tfsdk:"timeouts"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Platforms types.String `tfsdk:"platforms"` + Technologies []types.String `tfsdk:"technologies"` + RoleScopeTagIds []types.String `tfsdk:"role_scope_tag_ids"` + //SettingsCount types.Int64 `tfsdk:"settings_count"` + //IsAssigned types.Bool `tfsdk:"is_assigned"` + //LastModifiedDateTime types.String `tfsdk:"last_modified_date_time"` + //CreatedDateTime types.String `tfsdk:"created_date_time"` + Settings types.String `tfsdk:"settings"` + Assignments *sharedmodels.SettingsCatalogSettingsAssignmentResourceModel `tfsdk:"assignments"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } // DeviceConfigV2GraphServiceModel is a struct that represents the JSON structure of settings catalog settings @@ -244,246 +244,3 @@ var DeviceConfigV2GraphServiceModel struct { } `json:"settingInstance"` } `json:"settingsDetails"` } - -// DeviceConfigV2GraphServiceModel is a struct that represents the JSON structure of settings catalog settings. -// This struct is used to unmarshal the settings JSON string into a structured format. -// It represents windows, macOS, and iOS settings settings catalog settings. -// var DeviceConfigV2GraphServiceModel struct { -// SettingsDetails []struct { -// ID string `json:"id"` -// SettingInstance struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` - -// // For choice settings -// ChoiceSettingValue *struct { -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` - -// // For SimpleSettingCollectionValue within Choice children -// SimpleSettingCollectionValue []struct { -// ODataType string `json:"@odata.type"` -// Value string `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingCollectionValue,omitempty"` - -// // For GroupSettingCollectionValue within Choice children -// GroupSettingCollectionValue []struct { -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Children []struct { -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// Value interface{} `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingValue,omitempty"` -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` -// } `json:"children"` -// } `json:"groupSettingCollectionValue,omitempty"` - -// // For simple settings within choice children -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// Value interface{} `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingValue,omitempty"` - -// // For nested choice settings within choice children -// ChoiceSettingValue *struct { -// Value string `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// } `json:"children"` -// } `json:"choiceSettingValue,omitempty"` -// } `json:"children"` - -// Value string `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"choiceSettingValue,omitempty"` - -// // For choice setting collections -// ChoiceSettingCollectionValue []struct { -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` - -// // For nested simple settings within choice setting collection -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// Value interface{} `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingValue,omitempty"` - -// // For nested simple setting collection within choice setting collection -// SimpleSettingCollectionValue []struct { -// ODataType string `json:"@odata.type"` -// Value string `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingCollectionValue,omitempty"` - -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` -// } `json:"children"` - -// Value string `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"choiceSettingCollectionValue,omitempty"` - -// // For group setting collections (Level 1) -// GroupSettingCollectionValue []struct { -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` - -// // For nested group setting collections within group setting collection (Level 2) -// GroupSettingCollectionValue []struct { -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` - -// // For nested group setting collections within group setting collection within group setting collection (Level 3) -// GroupSettingCollectionValue []struct { -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` - -// // For nested choice settings within group setting collection within group setting collection within group setting collection (Level 4) -// ChoiceSettingValue *struct { -// Value string `json:"value"` -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference,omitempty"` - -// // For nested simple settings within choice settings within group setting collection within group setting collection within group setting collection (Level 5) -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// Value interface{} `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingValue,omitempty"` -// } `json:"children"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"choiceSettingValue,omitempty"` - -// // For simple settings within group setting collection within group setting collection within group setting collection (Level 4) -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// Value interface{} `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingValue,omitempty"` - -// // For simple settings collection within group setting collection within group setting collection within group setting collection (Level 4) -// SimpleSettingCollectionValue []struct { -// ODataType string `json:"@odata.type"` -// Value string `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingCollectionValue,omitempty"` -// } `json:"children"` -// } `json:"groupSettingCollectionValue,omitempty"` - -// // For nested simple settings within group setting collection within group setting collection (Level 3) -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// Value interface{} `json:"value"` -// ValueState string `json:"valueState,omitempty"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingValue,omitempty"` - -// // For nested simple setting collections within group setting collection within group setting collection (Level 3) -// SimpleSettingCollectionValue []struct { -// ODataType string `json:"@odata.type"` -// Value string `json:"value"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingCollectionValue,omitempty"` - -// // For nested choice settings within group setting collection within group setting collection (Level 3) -// ChoiceSettingValue *struct { -// Value string `json:"value"` -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// // For nested simple setting within choice settings within group setting collection within group setting collection (Level 4) -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// Value interface{} `json:"value"` -// ValueState string `json:"valueState,omitempty"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"simpleSettingValue,omitempty"` -// } `json:"children"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"choiceSettingValue,omitempty"` -// } `json:"children"` -// } `json:"groupSettingCollectionValue,omitempty"` - -// // For nested simple settings (string, integer, secret) within group setting collection (Level 2) -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Value interface{} `json:"value"` -// ValueState string `json:"valueState,omitempty"` -// } `json:"simpleSettingValue,omitempty"` - -// // For nested choice settings within group setting collection (Level 2) -// ChoiceSettingValue *struct { -// Value string `json:"value"` -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` - -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Value interface{} `json:"value"` -// } `json:"simpleSettingValue,omitempty"` - -// ChoiceSettingValue *struct { -// Children []struct { -// ODataType string `json:"@odata.type"` -// SettingDefinitionId string `json:"settingDefinitionId"` -// } `json:"children"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Value string `json:"value"` -// } `json:"choiceSettingValue,omitempty"` -// } `json:"children"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// } `json:"choiceSettingValue,omitempty"` - -// // For nested simple setting collections within group setting collection (Level 2) -// SimpleSettingCollectionValue []struct { -// ODataType string `json:"@odata.type"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Value string `json:"value"` -// } `json:"simpleSettingCollectionValue,omitempty"` - -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` -// } `json:"children"` -// } `json:"groupSettingCollectionValue,omitempty"` - -// // For simple settings -// SimpleSettingValue *struct { -// ODataType string `json:"@odata.type"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Value interface{} `json:"value"` -// } `json:"simpleSettingValue,omitempty"` - -// // For simple collection settings -// SimpleSettingCollectionValue []struct { -// ODataType string `json:"@odata.type"` -// SettingValueTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingValueTemplateReference"` -// Value string `json:"value"` -// } `json:"simpleSettingCollectionValue,omitempty"` - -// SettingInstanceTemplateReference graphmodels.DeviceManagementConfigurationSettingValueTemplateReferenceable `json:"settingInstanceTemplateReference"` -// } `json:"settingInstance"` -// } `json:"settingsDetails"` -// } diff --git a/internal/resources/device_and_app_management/beta/settings_catalog/resource_v7.go b/internal/resources/device_and_app_management/beta/settings_catalog/resource_v7.go index 2f98ed9e..63c2cb06 100644 --- a/internal/resources/device_and_app_management/beta/settings_catalog/resource_v7.go +++ b/internal/resources/device_and_app_management/beta/settings_catalog/resource_v7.go @@ -12,7 +12,6 @@ import ( "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" msgraphbetasdk "github.com/microsoftgraph/msgraph-beta-sdk-go" @@ -166,28 +165,28 @@ func (r *SettingsCatalogResource) Schema(ctx context.Context, req resource.Schem }, }, - "created_date_time": schema.StringAttribute{ - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - MarkdownDescription: "Creation date and time of the settings catalog policy", - }, - "last_modified_date_time": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Last modification date and time of the settings catalog policy", - }, - "settings_count": schema.Int64Attribute{ - Computed: true, - MarkdownDescription: "Number of settings catalog settings with the policy. This will change over time as the resource is updated.", - }, - "is_assigned": schema.BoolAttribute{ - Computed: true, - PlanModifiers: []planmodifier.Bool{ - planmodifiers.UseStateForUnknownBool(), - }, - MarkdownDescription: "Indicates if the policy is assigned to any scope", - }, + // "created_date_time": schema.StringAttribute{ + // Computed: true, + // PlanModifiers: []planmodifier.String{ + // stringplanmodifier.UseStateForUnknown(), + // }, + // MarkdownDescription: "Creation date and time of the settings catalog policy", + // }, + // "last_modified_date_time": schema.StringAttribute{ + // Computed: true, + // MarkdownDescription: "Last modification date and time of the settings catalog policy", + // }, + // "settings_count": schema.Int64Attribute{ + // Computed: true, + // MarkdownDescription: "Number of settings catalog settings with the policy. This will change over time as the resource is updated.", + // }, + // "is_assigned": schema.BoolAttribute{ + // Computed: true, + // PlanModifiers: []planmodifier.Bool{ + // planmodifiers.UseStateForUnknownBool(), + // }, + // MarkdownDescription: "Indicates if the policy is assigned to any scope", + // }, "assignments": commonschema.SettingsCatalogAssignmentsSchema(), "timeouts": commonschema.Timeouts(ctx), }, diff --git a/internal/resources/device_and_app_management/beta/settings_catalog/state_base_resource.go b/internal/resources/device_and_app_management/beta/settings_catalog/state_base_resource.go index 61331bde..e0b13a6b 100644 --- a/internal/resources/device_and_app_management/beta/settings_catalog/state_base_resource.go +++ b/internal/resources/device_and_app_management/beta/settings_catalog/state_base_resource.go @@ -23,11 +23,11 @@ func MapRemoteResourceStateToTerraform(ctx context.Context, data *SettingsCatalo data.ID = types.StringValue(state.StringPtrToString(remoteResource.GetId())) data.Name = types.StringValue(state.StringPtrToString(remoteResource.GetName())) data.Description = types.StringValue(state.StringPtrToString(remoteResource.GetDescription())) - data.CreatedDateTime = state.TimeToString(remoteResource.GetCreatedDateTime()) - data.LastModifiedDateTime = state.TimeToString(remoteResource.GetLastModifiedDateTime()) - data.SettingsCount = state.Int32PtrToTypeInt64(remoteResource.GetSettingCount()) data.RoleScopeTagIds = state.SliceToTypeStringSlice(remoteResource.GetRoleScopeTagIds()) - data.IsAssigned = state.BoolPtrToTypeBool(remoteResource.GetIsAssigned()) + // data.IsAssigned = state.BoolPtrToTypeBool(remoteResource.GetIsAssigned()) + // data.CreatedDateTime = state.TimeToString(remoteResource.GetCreatedDateTime()) + // data.LastModifiedDateTime = state.TimeToString(remoteResource.GetLastModifiedDateTime()) + // data.SettingsCount = state.Int32PtrToTypeInt64(remoteResource.GetSettingCount()) if platforms := remoteResource.GetPlatforms(); platforms != nil { data.Platforms = state.EnumPtrToTypeString(platforms)