Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2267556
Add Azure Durable Task service implementation with comprehensive tests
stemaMSFT Nov 13, 2025
e77acc1
upgrade sdk
stemaMSFT Feb 11, 2026
4728bb7
following contributor guidelines added docs and testing. tests pass
stemaMSFT Feb 13, 2026
0f8a50c
fixing website test failing
stemaMSFT Feb 13, 2026
9cc8735
renamed files per guidelines
stemaMSFT Feb 13, 2026
812b391
fixing doc errors
stemaMSFT Feb 17, 2026
e114fdd
fix make generate issue
stemaMSFT Feb 23, 2026
24f0cf4
added forcenew to sku
stemaMSFT Feb 24, 2026
feb5fc1
fix copyright headers - no code changes made
stemaMSFT Mar 3, 2026
d2dd8dd
fixed static analysis test fails
stemaMSFT Mar 3, 2026
6f41cc8
add data source/test for durable task scheduler. fixed docs.
stemaMSFT Mar 9, 2026
ef8c492
Remove unrelated managed redis changes from branch
stemaMSFT Mar 9, 2026
11324da
fix: regenerate API section in durable task data source docs
stemaMSFT Apr 16, 2026
f6a8894
resync and push with doc fixes
stemaMSFT Apr 16, 2026
6bd2218
go mod tidy && go mod vendor after rebase
stemaMSFT Apr 16, 2026
36ea7cd
revert unrelated managedredis changes
stemaMSFT Apr 16, 2026
60b697e
fix: address PR #32194 review - schema ordering, delta update, Custom…
stemaMSFT Apr 16, 2026
6eb4255
style: remove unnecessary comments and redundant test checks per review
stemaMSFT Apr 17, 2026
18c813f
test: align acceptance tests with contributor guidelines
stemaMSFT Apr 17, 2026
4e8c68f
style: fix gofmt formatting in scheduler resource
stemaMSFT Apr 17, 2026
56217a1
address PR #32194 review feedback from WodansSon
stemaMSFT Apr 24, 2026
cc2ce17
fix: gofmt alignment on sku_name field in scheduler resource
stemaMSFT Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/labeler-issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ service/dns:
service/domain-services:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_active_directory_domain_service((.|\n)*)###'

service/durable-task:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_durable_task_((.|\n)*)###'

service/dynatrace:
- '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_dynatrace_((.|\n)*)###'

Expand Down
5 changes: 5 additions & 0 deletions .github/labeler-pull-request-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ service/domain-services:
- any-glob-to-any-file:
- internal/services/domainservices/**/*

service/durable-task:
- changed-files:
- any-glob-to-any-file:
- internal/services/durabletask/**/*

service/dynatrace:
- changed-files:
- any-glob-to-any-file:
Expand Down
1 change: 1 addition & 0 deletions .teamcity/components/generated/services.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var services = mapOf(
"devtestlabs" to "Dev Test",
"digitaltwins" to "Digital Twins",
"domainservices" to "DomainServices",
"durabletask" to "Durable Task",
"dynatrace" to "Dynatrace",
"elastic" to "Elastic",
"elasticsan" to "ElasticSan",
Expand Down
5 changes: 5 additions & 0 deletions internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import (
digitaltwins "github.com/hashicorp/terraform-provider-azurerm/internal/services/digitaltwins/client"
dns "github.com/hashicorp/terraform-provider-azurerm/internal/services/dns/client"
domainservices "github.com/hashicorp/terraform-provider-azurerm/internal/services/domainservices/client"
durabletask "github.com/hashicorp/terraform-provider-azurerm/internal/services/durabletask/client"
dynatrace "github.com/hashicorp/terraform-provider-azurerm/internal/services/dynatrace/client"
elastic "github.com/hashicorp/terraform-provider-azurerm/internal/services/elastic/client"
elasticsan "github.com/hashicorp/terraform-provider-azurerm/internal/services/elasticsan/client"
Expand Down Expand Up @@ -205,6 +206,7 @@ type Client struct {
DigitalTwins *digitaltwins.Client
Dns *dns_v2018_05_01.Client
DomainServices *domainservices.Client
DurableTask *durabletask.Client
Dynatrace *dynatrace.Client
Elastic *elastic.Client
ElasticSan *elasticsan.Client
Expand Down Expand Up @@ -438,6 +440,9 @@ func (client *Client) Build(ctx context.Context, o *common.ClientOptions) error
if client.DomainServices, err = domainservices.NewClient(o); err != nil {
return fmt.Errorf("building clients for DomainServices: %+v", err)
}
if client.DurableTask, err = durabletask.NewClient(o); err != nil {
return fmt.Errorf("building clients for DurableTask: %+v", err)
}
if client.Elastic, err = elastic.NewClient(o); err != nil {
return fmt.Errorf("building clients for Elastic: %+v", err)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/provider/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/services/digitaltwins"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/dns"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/domainservices"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/durabletask"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/dynatrace"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/elastic"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/elasticsan"
Expand Down Expand Up @@ -174,6 +175,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration {
digitaltwins.Registration{},
dns.Registration{},
domainservices.Registration{},
durabletask.Registration{},
dynatrace.Registration{},
elasticsan.Registration{},
eventgrid.Registration{},
Expand Down
45 changes: 45 additions & 0 deletions internal/services/durabletask/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright IBM Corp. 2014, 2025
// SPDX-License-Identifier: MPL-2.0

package client

import (
"fmt"

"github.com/hashicorp/go-azure-sdk/resource-manager/durabletask/2025-11-01/retentionpolicies"
"github.com/hashicorp/go-azure-sdk/resource-manager/durabletask/2025-11-01/schedulers"
"github.com/hashicorp/go-azure-sdk/resource-manager/durabletask/2025-11-01/taskhubs"
"github.com/hashicorp/terraform-provider-azurerm/internal/common"
)

type Client struct {
SchedulersClient *schedulers.SchedulersClient
TaskHubsClient *taskhubs.TaskHubsClient
RetentionPoliciesClient *retentionpolicies.RetentionPoliciesClient
}

func NewClient(o *common.ClientOptions) (*Client, error) {
schedulersClient, err := schedulers.NewSchedulersClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Schedulers client: %+v", err)
}
o.Configure(schedulersClient.Client, o.Authorizers.ResourceManager)

taskHubsClient, err := taskhubs.NewTaskHubsClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building TaskHubs client: %+v", err)
}
o.Configure(taskHubsClient.Client, o.Authorizers.ResourceManager)

retentionPoliciesClient, err := retentionpolicies.NewRetentionPoliciesClientWithBaseURI(o.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building RetentionPolicies client: %+v", err)
}
o.Configure(retentionPoliciesClient.Client, o.Authorizers.ResourceManager)

return &Client{
SchedulersClient: schedulersClient,
TaskHubsClient: taskHubsClient,
RetentionPoliciesClient: retentionPoliciesClient,
}, nil
}
173 changes: 173 additions & 0 deletions internal/services/durabletask/durable_task_hub_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright IBM Corp. 2014, 2025
// SPDX-License-Identifier: MPL-2.0

package durabletask

import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-sdk/resource-manager/durabletask/2025-11-01/schedulers"
"github.com/hashicorp/go-azure-sdk/resource-manager/durabletask/2025-11-01/taskhubs"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

type TaskHubResourceModel struct {
Name string `tfschema:"name"`
SchedulerId string `tfschema:"scheduler_id"`
DashboardUrl string `tfschema:"dashboard_url"`
}

type TaskHubResource struct{}

var _ sdk.Resource = TaskHubResource{}

func (r TaskHubResource) ResourceType() string {
return "azurerm_durable_task_hub"
}

func (r TaskHubResource) ModelObject() interface{} {
return &TaskHubResourceModel{}
}

func (r TaskHubResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return taskhubs.ValidateTaskHubID
}

func (r TaskHubResource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: ValidateTaskHubName,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the validate code and tests into their own validate sub-folder/package to follow the providers resource validation pattern.

Suggested change
ValidateFunc: ValidateTaskHubName,
ValidateFunc: validate.TaskHubName,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

},

"scheduler_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: schedulers.ValidateSchedulerID,
},
}
}

func (r TaskHubResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"dashboard_url": {
Type: pluginsdk.TypeString,
Computed: true,
},
}
}

func (r TaskHubResource) Create() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.DurableTask.TaskHubsClient

var model TaskHubResourceModel
if err := metadata.Decode(&model); err != nil {
return fmt.Errorf("decoding: %+v", err)
}

schedulerId, err := schedulers.ParseSchedulerID(model.SchedulerId)
if err != nil {
return fmt.Errorf("parsing scheduler ID: %+v", err)
}

id := taskhubs.NewTaskHubID(schedulerId.SubscriptionId, schedulerId.ResourceGroupName, schedulerId.SchedulerName, model.Name)

metadata.Logger.Infof("Import check for %s", id)
existing, err := client.Get(ctx, id)
if err != nil && !response.WasNotFound(existing.HttpResponse) {
return fmt.Errorf("checking for presence of existing %s: %+v", id, err)
}

if !response.WasNotFound(existing.HttpResponse) {
return metadata.ResourceRequiresImport(r.ResourceType(), id)
}

metadata.Logger.Infof("Creating %s", id)

properties := taskhubs.TaskHub{
Properties: &taskhubs.TaskHubProperties{},
}

if err := client.CreateOrUpdateThenPoll(ctx, id, properties); err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
}

metadata.SetID(id)
return nil
},
}
}

func (r TaskHubResource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.DurableTask.TaskHubsClient

id, err := taskhubs.ParseTaskHubID(metadata.ResourceData.Id())
if err != nil {
return err
}

metadata.Logger.Infof("Reading %s", id)
resp, err := client.Get(ctx, *id)
if err != nil {
if response.WasNotFound(resp.HttpResponse) {
return metadata.MarkAsGone(id)
}
return fmt.Errorf("retrieving %s: %+v", *id, err)
}

model := resp.Model
if model == nil {
return fmt.Errorf("retrieving %s: model was nil", id)
}

schedulerId := schedulers.NewSchedulerID(id.SubscriptionId, id.ResourceGroupName, id.SchedulerName)

state := TaskHubResourceModel{
Name: id.TaskHubName,
SchedulerId: schedulerId.ID(),
}

if props := model.Properties; props != nil {
state.DashboardUrl = pointer.From(props.DashboardURL)
}

return metadata.Encode(&state)
},
}
}

func (r TaskHubResource) Delete() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.DurableTask.TaskHubsClient

id, err := taskhubs.ParseTaskHubID(metadata.ResourceData.Id())
if err != nil {
return err
}

metadata.Logger.Infof("Deleting %s", id)

if err := client.DeleteThenPoll(ctx, *id); err != nil {
return fmt.Errorf("deleting %s: %+v", id, err)
}

return nil
},
}
}
107 changes: 107 additions & 0 deletions internal/services/durabletask/durable_task_hub_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright IBM Corp. 2014, 2025
// SPDX-License-Identifier: MPL-2.0

package durabletask_test

import (
"context"
"fmt"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-sdk/resource-manager/durabletask/2025-11-01/taskhubs"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

type TaskHubResource struct{}

func TestAccDurableTaskHub_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_durable_task_hub", "test")
r := TaskHubResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccDurableTaskHub_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_durable_task_hub", "test")
r := TaskHubResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.RequiresImportErrorStep(r.requiresImport),
})
}

func (r TaskHubResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := taskhubs.ParseTaskHubID(state.ID)
if err != nil {
return nil, err
}

if _, err = client.DurableTask.TaskHubsClient.Get(ctx, *id); err != nil {
return nil, fmt.Errorf("retrieving %s: %v", id, err)
}

return pointer.To(true), nil
}

func (r TaskHubResource) template(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-durabletask-%d"
location = "%s"
}

resource "azurerm_durable_task_scheduler" "test" {
name = "acctestdts%s"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku_name = "Consumption"
ip_allow_list = ["0.0.0.0/0"]
}
`, data.RandomInteger, data.Locations.Primary, data.RandomString)
}

func (r TaskHubResource) basic(data acceptance.TestData) string {
template := r.template(data)
return fmt.Sprintf(`
%s

resource "azurerm_durable_task_hub" "test" {
name = "acctestdth%s"
scheduler_id = azurerm_durable_task_scheduler.test.id
}
`, template, data.RandomString)
}

func (r TaskHubResource) requiresImport(data acceptance.TestData) string {
template := r.basic(data)
return fmt.Sprintf(`
%s

resource "azurerm_durable_task_hub" "import" {
name = azurerm_durable_task_hub.test.name
scheduler_id = azurerm_durable_task_hub.test.scheduler_id
}
`, template)
}
Loading
Loading