New Resource: azurerm_durable_task_scheduler, azurerm_durable_task_hub, azurerm_durable_task_retention_policy & New Data Source: azurerm_durable_task_scheduler#32194
Conversation
Core Implementation: - Add durabletask service with typed resource implementation - Implement Scheduler, TaskHub, and RetentionPolicy resources - Add resource ID parsing and validation utilities - Include implementation guide for next steps Unit Tests: - Add parse_test.go with resource ID parsing tests for all resource types - Add validate_test.go with validation function tests - Cover edge cases, error scenarios, and Azure-specific patterns - Follow external test package convention (durabletask_test) - Use testify assertions for clear test failures - Test resource ID generation and case-insensitivity Files Added: - DURABLE_TASK_IMPLEMENTATION.md - internal/services/durabletask/parse.go - internal/services/durabletask/parse_test.go - internal/services/durabletask/registration.go - internal/services/durabletask/retention_policy_resource.go - internal/services/durabletask/scheduler_resource.go - internal/services/durabletask/task_hub_resource.go - internal/services/durabletask/validate.go - internal/services/durabletask/validate_test.go
The document-validate CI check was failing because the API Providers section in the durable task scheduler data source documentation said "This resource uses" instead of "This data source uses". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
azurerm_durable_task_scheduler, azurerm_durable_task_hub, `azurerm_durable_task_retention_policy
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e, CustomizeDiff - Fix schema field ordering: Required fields alphabetically before Optional - Update method now uses delta/partial pattern with HasChange checks - Remove sku_name from Update (ForceNew, never updated in-place) - Add CustomizeDiff validating capacity only allowed with Dedicated SKU - Add model struct comments separating Arguments from Attributes - Add test case for Dedicated SKU with capacity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extract template() helpers in all resource test files - Add complete data source test with Dedicated SKU - Fix data source test variable naming (d -> r) - Add computed field checks (id, ip_allow_list.#, tags.%) - Create separate update() config to avoid ForceNew field changes - Remove comments violating zero-tolerance policy Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
azurerm_durable_task_scheduler, azurerm_durable_task_hub, `azurerm_durable_task_retention_policyazurerm_durable_task_scheduler, azurerm_durable_task_hub, azurerm_durable_task_retention_policy & New Data Source: azurerm_durable_task_scheduler
WodansSon
left a comment
There was a problem hiding this comment.
Hey @stemaMSFT — thanks for this PR, I appreciate your work here!
I reviewed the PR and found some issues that need to be addressed.
Status: Changes Requested
Next steps
- Address/Reply to the PR comments.
- Run the usual checks locally, re-run the tests and attach the results again to the PR.
- When done, push the changes and reply here so I can re-check quickly — happy to re-review the updated code/docs.
Thanks again for the work here! 🚀
| "retention_period_in_days": { | ||
| Type: pluginsdk.TypeInt, | ||
| Required: true, | ||
| ValidateFunc: validation.IntAtLeast(1), |
There was a problem hiding this comment.
Can we have a better validation function here, what is the upper bound limit here?
There was a problem hiding this comment.
added IntBetween(1, 90)
| return []sdk.Resource{ | ||
| SchedulerResource{}, | ||
| TaskHubResource{}, | ||
| RetentionPolicyResource{}, | ||
| } |
There was a problem hiding this comment.
Could we reorder the entries in the Resources() return slice so they are alphabetical as required by the contributing guidelines?
| return []sdk.Resource{ | |
| SchedulerResource{}, | |
| TaskHubResource{}, | |
| RetentionPolicyResource{}, | |
| } | |
| return []sdk.Resource{ | |
| RetentionPolicyResource{}, | |
| SchedulerResource{}, | |
| TaskHubResource{}, | |
| } |
Reference: guide-breaking-changes
There was a problem hiding this comment.
Updated — the Resources() return slice is now alphabetically ordered: RetentionPolicyResource, SchedulerResource,
TaskHubResource.
| metadata.Logger.Infof("Updating retention policy on %s", schedulerId.ID()) | ||
|
|
||
| policies := make([]retentionpolicies.RetentionPolicyDetails, 0) | ||
| for _, item := range model.RetentionPolicy { | ||
| policy := retentionpolicies.RetentionPolicyDetails{ | ||
| RetentionPeriodInDays: item.RetentionPeriodInDays, | ||
| } | ||
|
|
||
| if item.OrchestrationState != "" { | ||
| state := retentionpolicies.PurgeableOrchestrationState(item.OrchestrationState) | ||
| policy.OrchestrationState = &state | ||
| } | ||
|
|
||
| policies = append(policies, policy) | ||
| } | ||
|
|
||
| properties := retentionpolicies.RetentionPolicyUpdate{ | ||
| Properties: &retentionpolicies.RetentionPolicyProperties{ | ||
| RetentionPolicies: &policies, | ||
| }, | ||
| } | ||
|
|
||
| if err := client.UpdateThenPoll(ctx, schedulerId, properties); err != nil { | ||
| return fmt.Errorf("updating retention policy on %s: %+v", schedulerId.ID(), err) | ||
| } | ||
|
|
||
| return nil |
There was a problem hiding this comment.
Can we add a change check for the updatable field(s) before performing the update? For example, short-circuit when the retention_policy hasn't changed to avoid unnecessary API calls:
| metadata.Logger.Infof("Updating retention policy on %s", schedulerId.ID()) | |
| policies := make([]retentionpolicies.RetentionPolicyDetails, 0) | |
| for _, item := range model.RetentionPolicy { | |
| policy := retentionpolicies.RetentionPolicyDetails{ | |
| RetentionPeriodInDays: item.RetentionPeriodInDays, | |
| } | |
| if item.OrchestrationState != "" { | |
| state := retentionpolicies.PurgeableOrchestrationState(item.OrchestrationState) | |
| policy.OrchestrationState = &state | |
| } | |
| policies = append(policies, policy) | |
| } | |
| properties := retentionpolicies.RetentionPolicyUpdate{ | |
| Properties: &retentionpolicies.RetentionPolicyProperties{ | |
| RetentionPolicies: &policies, | |
| }, | |
| } | |
| if err := client.UpdateThenPoll(ctx, schedulerId, properties); err != nil { | |
| return fmt.Errorf("updating retention policy on %s: %+v", schedulerId.ID(), err) | |
| } | |
| return nil | |
| metadata.Logger.Infof("Updating retention policy on %s", schedulerId.ID()) | |
| if !metadata.ResourceData.HasChanges("retention_policy") { | |
| metadata.Logger.Infof("No changes detected for retention_policy on %s", schedulerId.ID()) | |
| return nil | |
| } | |
| policies := make([]retentionpolicies.RetentionPolicyDetails, 0) | |
| for _, item := range model.RetentionPolicy { | |
| policy := retentionpolicies.RetentionPolicyDetails{ | |
| RetentionPeriodInDays: item.RetentionPeriodInDays, | |
| } | |
| if item.OrchestrationState != "" { | |
| state := retentionpolicies.PurgeableOrchestrationState(item.OrchestrationState) | |
| policy.OrchestrationState = &state | |
| } | |
| policies = append(policies, policy) | |
| } | |
| properties := retentionpolicies.RetentionPolicyUpdate{ | |
| Properties: &retentionpolicies.RetentionPolicyProperties{ | |
| RetentionPolicies: &policies, | |
| }, | |
| } | |
| if err := client.UpdateThenPoll(ctx, schedulerId, properties); err != nil { | |
| return fmt.Errorf("updating retention policy on %s: %+v", schedulerId.ID(), err) | |
| } | |
| return nil |
This follows the guidance in best-practices.md to ensure Update only performs work for changed fields (using d.HasChanges or the equivalent on the metadata object).
Reference: best-practices
There was a problem hiding this comment.
Added — the update function now short-circuits with metadata.ResourceData.HasChanges("retention_policy") to avoid unnecessary API calls when the retention policy hasn't changed.
| Check: acceptance.ComposeTestCheckFunc( | ||
| check.That(data.ResourceName).Key("id").Exists(), | ||
| check.That(data.ResourceName).Key("sku_name").HasValue("Consumption"), | ||
| check.That(data.ResourceName).Key("location").Exists(), | ||
| check.That(data.ResourceName).Key("endpoint").Exists(), | ||
| check.That(data.ResourceName).Key("ip_allow_list.#").Exists(), | ||
| check.That(data.ResourceName).Key("tags.%").HasValue("0"), | ||
| ), |
There was a problem hiding this comment.
This depends on the answers to my earlier comments. I would avoid asserting redundancy_state here unless we first confirm that the field should remain exposed in the schema, is something the provider should surface to end users, and is reliably populated for the basic scheduler scenario. Likewise, I would only assert capacity here if we can confirm the API always returns it for the basic consumption configuration; otherwise the complete test seems like the better place to cover it.
There was a problem hiding this comment.
Resolved — redundancy_state has been removed from both the resource and data source schemas entirely, so it's no longer asserted in tests.
| * `sku_name` - (Required) The SKU of the Durable Task Scheduler. Possible values include `Consumption` and `Dedicated`. Changing this forces a new resource to be created. | ||
|
|
||
| * `ip_allow_list` - (Required) A list of IP addresses or CIDR ranges that are allowed to access the Durable Task Scheduler. |
There was a problem hiding this comment.
Required argument ordering, required arguments after location must be alphabetically sorted.
| * `sku_name` - (Required) The SKU of the Durable Task Scheduler. Possible values include `Consumption` and `Dedicated`. Changing this forces a new resource to be created. | |
| * `ip_allow_list` - (Required) A list of IP addresses or CIDR ranges that are allowed to access the Durable Task Scheduler. | |
| * `ip_allow_list` - (Required) A list of IP addresses or CIDR ranges that are allowed to access the Durable Task Scheduler. | |
| * `sku_name` - (Required) The SKU of the Durable Task Scheduler. Possible values include `Consumption` and `Dedicated`. Changing this forces a new resource to be created. |
Reference: reference-documentation-standards
There was a problem hiding this comment.
Updated to the canonical phrasing: "Gets information about an existing Durable Task Scheduler."
| Type: pluginsdk.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| ValidateFunc: ValidateSchedulerName, |
There was a problem hiding this comment.
Can we move the validate code and tests into their own validate sub-folder/package to follow the providers resource validation pattern.
| ValidateFunc: ValidateSchedulerName, | |
| ValidateFunc: validate.SchedulerName, |
| parsedId, err := schedulers.ParseSchedulerID(model.SchedulerId) | ||
| if err != nil { | ||
| return fmt.Errorf("parsing scheduler ID: %+v", err) | ||
| } | ||
|
|
||
| schedulerId := retentionpolicies.NewSchedulerID(parsedId.SubscriptionId, parsedId.ResourceGroupName, parsedId.SchedulerName) | ||
| id := NewRetentionPolicyID(parsedId.SubscriptionId, parsedId.ResourceGroupName, parsedId.SchedulerName) |
There was a problem hiding this comment.
Can you explain this to me? I do not understand yet why this resource needs a custom RetentionPolicyID / ParseRetentionPolicyID when the SDK already provides scheduler ID helpers. Is the intention that the Terraform state/import ID for this resource is the full singleton child path ending in /retentionPolicies/default, rather than the parent scheduler ID? If so, could you add a short comment explaining that design choice?
| Type: pluginsdk.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| ValidateFunc: ValidateTaskHubName, |
There was a problem hiding this comment.
Can we move the validate code and tests into their own validate sub-folder/package to follow the providers resource validation pattern.
| ValidateFunc: ValidateTaskHubName, | |
| ValidateFunc: validate.TaskHubName, |
| if !rawCapacity.IsNull() { | ||
| skuName := metadata.ResourceDiff.Get("sku_name").(string) | ||
| if skuName != string(schedulers.SchedulerSkuNameDedicated) { | ||
| return fmt.Errorf("`capacity` can only be configured when `sku_name` is set to `Dedicated`") |
There was a problem hiding this comment.
This is a static error message and does not require the fmt.Errorf formatting functionality, can we update this?
| return fmt.Errorf("`capacity` can only be configured when `sku_name` is set to `Dedicated`") | |
| return errors.New("`capacity` can only be configured when `sku_name` is set to `Dedicated`") |
| // Copyright IBM Corp. 2014, 2025 | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| package durabletask_test |
There was a problem hiding this comment.
I noticed that these tests do not have a negative test for the CustomizeDiff validation where it checks for capacity + sku_name = Consumption. The guard is implemented in the resource, but the current acceptance tests only cover the happy-path configurations and contains no ExpectError tests for this condition. Can we add that test case here?
WodansSon
left a comment
There was a problem hiding this comment.
@stemaMSFT, I have another question about the RedundancyState field...
| Tags map[string]string `tfschema:"tags"` | ||
|
|
||
| Endpoint string `tfschema:"endpoint"` | ||
| RedundancyState string `tfschema:"redundancy_state"` |
There was a problem hiding this comment.
redundancy_state is a service-reported computed field, not a configurable argument. Do we want to expose this in the public schema at all? If it is not user-actionable or needed for parity with established provider patterns, I would prefer to remove it from both the resource and data source schemas.
- Move validate functions to validate/ subfolder - Add consumptionWithCapacity test helper for CustomizeDiff validation - Add parse_test.go for scheduler/task hub ID parsing - Use SDK enum for SKU, errors.New, IntBetween(1,90) - Add HasChanges guard, alphabetical registration - Fix doc formatting across all 4 markdown files - Remove redundancy_state from scheduler resource Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Community Note
Description
This PR adds support for the Azure Durable Task Framework service with the following new resources and data source:
New Resources:
azurerm_durable_task_scheduler— Manages a Durable Task Scheduler, supporting Consumption and Dedicated SKUs, IP allow lists, and tagging.azurerm_durable_task_hub— Manages a Task Hub within a Durable Task Scheduler.azurerm_durable_task_retention_policy— Manages the retention policy for a Durable Task Hub, configuring automatic purge of orchestration history.New Data Source:
azurerm_durable_task_scheduler— Reads an existing Durable Task Scheduler resource.All resources use
hashicorp/go-azure-sdktyped resources with thedurabletask/2025-04-01-previewAPI version.PR Checklist
For example: "
resource_name_here- description of change e.g. adding propertynew_property_name_here"New Resource / Data Source
This PR introduces a new service package (
durabletask) with three resources and one data source.Testing
Acceptance tests cover:
TestAccDurableTaskSchedulerResource_basic— Create/destroy lifecycleTestAccDurableTaskSchedulerResource_requiresImport— Import detectionTestAccDurableTaskSchedulerResource_complete— All optional propertiesTestAccDurableTaskSchedulerResource_update— In-place update (tags, IP allow list)TestAccDurableTaskHubResource_basic— Create/destroy lifecycleTestAccDurableTaskHubResource_requiresImport— Import detectionTestAccDurableTaskRetentionPolicyResource_basic— Create/destroy lifecycleTestAccDurableTaskRetentionPolicyResource_complete— All retention policy propertiesTestAccDurableTaskRetentionPolicyResource_update— In-place updateTestAccDurableTaskSchedulerDataSource_complete— Data source with full field validationCI Results (commit
4e8c68f)All CI checks passing ✅
test — PASS (4860 tests, 78 skipped in 193.753s)
Go 1.25.9, ubuntu runner. Acceptance tests skipped (requires
TF_ACC— runs in TeamCity).golint — PASS (golangci-lint v2.4.0, 25 active linters, 0 issues)
gencheck — PASS (generated code matches committed code)
No diff — generated code is in sync.
tflint — PASS (provider linting + terraform format check)
depscheck — PASS (go mod vendor clean)
No diff — vendor directory is in sync.
website-lint — PASS (documentation validated)
Other checks — all PASS
teamcity-test✅secrets-check✅detect✅preview-api-version-linter✅Change Log
azurerm_durable_task_scheduler[New Resource:azurerm_durable_task_scheduler,azurerm_durable_task_hub,azurerm_durable_task_retention_policy& New Data Source:azurerm_durable_task_scheduler#32194]azurerm_durable_task_hub[New Resource:azurerm_durable_task_scheduler,azurerm_durable_task_hub,azurerm_durable_task_retention_policy& New Data Source:azurerm_durable_task_scheduler#32194]azurerm_durable_task_retention_policy[New Resource:azurerm_durable_task_scheduler,azurerm_durable_task_hub,azurerm_durable_task_retention_policy& New Data Source:azurerm_durable_task_scheduler#32194]azurerm_durable_task_scheduler[New Resource:azurerm_durable_task_scheduler,azurerm_durable_task_hub,azurerm_durable_task_retention_policy& New Data Source:azurerm_durable_task_scheduler#32194]This is a (please select all that apply):
Related Issue(s)
N/A — New service support for Azure Durable Task Framework.
AI Assistance Disclosure
Rollback Plan
If a change needs to be reverted, we will publish an updated version of the provider.
Changes to Security Controls
No changes to security controls. This PR adds new resources for the Azure Durable Task service and does not modify existing access controls, encryption, or logging.
Note
If this PR changes meaningfully during the course of review please update the title and description as required.