Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 42 additions & 6 deletions internal/services/datafactory/data_factory_pipeline_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/hashicorp/go-azure-sdk/resource-manager/datafactory/2018-06-01/pipelines"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/features"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/datafactory/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
Expand All @@ -23,7 +24,7 @@ import (
)

func resourceDataFactoryPipeline() *pluginsdk.Resource {
return &pluginsdk.Resource{
resource := &pluginsdk.Resource{
Create: resourceDataFactoryPipelineCreateUpdate,
Read: resourceDataFactoryPipelineRead,
Update: resourceDataFactoryPipelineCreateUpdate,
Expand Down Expand Up @@ -104,12 +105,33 @@ func resourceDataFactoryPipeline() *pluginsdk.Resource {
ValidateFunc: validation.StringIsNotEmpty,
},

"moniter_metrics_after_duration": {
Type: pluginsdk.TypeString,
Optional: true,
"monitor_metrics_after_duration": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringIsNotEmpty,
},
},
}

if !features.FivePointOh() {
resource.Schema["moniter_metrics_after_duration"] = &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringIsNotEmpty,
ConflictsWith: []string{"monitor_metrics_after_duration"},
Deprecated: "`moniter_metrics_after_duration` has been deprecated in favour of `monitor_metrics_after_duration` and will be removed in v5.0 of the AzureRM Provider",
}
resource.Schema["monitor_metrics_after_duration"] = &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringIsNotEmpty,
ConflictsWith: []string{"moniter_metrics_after_duration"},
}
}

return resource
}

func resourceDataFactoryPipelineCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -187,14 +209,24 @@ func resourceDataFactoryPipelineCreateUpdate(d *pluginsdk.ResourceData, meta int
payload.Properties.Concurrency = pointer.To(int64(v.(int)))
}

if v, ok := d.GetOk("moniter_metrics_after_duration"); ok {
if v, ok := d.GetOk("monitor_metrics_after_duration"); ok {
payload.Properties.Policy = &pipelines.PipelinePolicy{
ElapsedTimeMetric: &pipelines.PipelineElapsedTimeMetricPolicy{
Duration: pointer.To(v),
},
}
}

if !features.FivePointOh() {
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.

In the CreateUpdate function, the deprecated field check will always overwrite the new field's policy value in pre-5.0 mode:

// First: sets policy from the NEW field ✅
if v, ok := d.GetOk("monitor_metrics_after_duration"); ok {
    payload.Properties.Policy = &pipelines.PipelinePolicy{...}
}

// Second: OVERWRITES policy from the deprecated field ❌
if !features.FivePointOh() {
    if !pluginsdk.IsExplicitlyNullInConfig(d, "moniter_metrics_after_duration") {
        payload.Properties.Policy = &pipelines.PipelinePolicy{
            Duration: pointer.To(d.Get("moniter_metrics_after_duration")),  // empty/zero value
        }
    }
}

IsExplicitlyNullInConfig returns true only when a field is explicitly set to null in HCL. When the field is simply absent from config, it returns false, so !false = true — the block executes and overwrites the policy with d.Get("moniter_metrics_after_duration") which returns the zero/computed value.

Result: When a user sets monitor_metrics_after_duration = "00:01:00", the policy is correctly set, then immediately overwritten with an empty value from the deprecated field.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hey @liuwuliuyun thanks for the review and pointing out the situation. I checked the IsExplicitlyNullInConfig function, from the comment above the function, seems like it will return true when the field is absent. Source code here.
So, I did some test with debug log on, it did return true when the field is not presents. I guess the function name is kind of misleading....

if !pluginsdk.IsExplicitlyNullInConfig(d, "moniter_metrics_after_duration") {
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.

pluginsdk.IsExplicitlyNullInConfig is intended for CustomizeDiff scenarios where distinguishing "not set" vs "explicitly null" matters. The standard pattern for checking whether an optional field was provided in Create/Update functions is d.GetOk(). Using IsExplicitlyNullInConfig here deviates from established provider patterns

Copy link
Copy Markdown
Contributor Author

@LingyuTang LingyuTang Apr 22, 2026

Choose a reason for hiding this comment

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

Thanks for the review! I’m using this approach because it’s recommended in the https://hashicorp.github.io/terraform-provider-azurerm/topics/guide-breaking-changes/ in the contribution docs.
When drafting the change, I initially tried GetOk, but it failed the update test because GetOk can’t distinguish whether the user explicitly set the value in the config. In this case, if a user had previously set moniter_metrics_after_duration, that value would already exist in state. Later, when the user switches to monitor_metrics_after_duration (both map to the same state field), the old moniter_metrics_after_duration value would overwrite the user’s new change.

payload.Properties.Policy = &pipelines.PipelinePolicy{
ElapsedTimeMetric: &pipelines.PipelineElapsedTimeMetricPolicy{
Duration: pointer.To(d.Get("moniter_metrics_after_duration")),
},
}
}
}

if v, ok := d.GetOk("folder"); ok {
payload.Properties.Folder = &pipelines.PipelineFolder{
Name: pointer.To(v.(string)),
Expand Down Expand Up @@ -258,7 +290,11 @@ func resourceDataFactoryPipelineRead(d *pluginsdk.ResourceData, meta interface{}
elapsedTimeMetricDuration = v
}
}
d.Set("moniter_metrics_after_duration", elapsedTimeMetricDuration)

d.Set("monitor_metrics_after_duration", elapsedTimeMetricDuration)
if !features.FivePointOh() {
d.Set("moniter_metrics_after_duration", elapsedTimeMetricDuration)
}

if folder := props.Folder; folder != nil {
d.Set("folder", pointer.From(folder.Name))
Expand Down
133 changes: 44 additions & 89 deletions internal/services/datafactory/data_factory_pipeline_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"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/features"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

Expand Down Expand Up @@ -159,7 +160,7 @@ func (t PipelineResource) appendVariableActivityNameIs(expected string) func(inp
}
}

func (PipelineResource) basic(data acceptance.TestData) string {
func (PipelineResource) template(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
Expand All @@ -176,35 +177,35 @@ resource "azurerm_data_factory" "test" {
resource_group_name = azurerm_resource_group.test.name
}

resource "azurerm_data_factory_pipeline" "test" {
name = "acctest%d"
data_factory_id = azurerm_data_factory.test.id
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger)
}

func (PipelineResource) complete(data acceptance.TestData) string {
func (r PipelineResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
%[1]s

resource "azurerm_resource_group" "test" {
name = "acctestRG-df-%d"
location = "%s"
resource "azurerm_data_factory_pipeline" "test" {
name = "acctest%[2]d"
data_factory_id = azurerm_data_factory.test.id
}

resource "azurerm_data_factory" "test" {
name = "acctestdfv2%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
`, r.template(data), data.RandomInteger)
}

func (r PipelineResource) complete(data acceptance.TestData) string {
metricsFieldName := "monitor_metrics_after_duration"
if !features.FivePointOh() {
metricsFieldName = "moniter_metrics_after_duration"
}

return fmt.Sprintf(`
%[1]s

resource "azurerm_data_factory_pipeline" "test" {
name = "acctest%d"
name = "acctest%[2]d"
data_factory_id = azurerm_data_factory.test.id
annotations = ["test1", "test2", "test3"]
description = "test description"
%[3]s = "00:01:00"

parameters = {
test = "testparameter"
Expand Down Expand Up @@ -268,31 +269,24 @@ resource "azurerm_data_factory_pipeline" "test" {
]
JSON
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}

func (PipelineResource) update(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
`, r.template(data), data.RandomInteger, metricsFieldName)
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-df-%d"
location = "%s"
}
func (r PipelineResource) update(data acceptance.TestData) string {
metricsFieldName := "monitor_metrics_after_duration"
if !features.FivePointOh() {
metricsFieldName = "moniter_metrics_after_duration"
}

resource "azurerm_data_factory" "test" {
name = "acctestdfv2%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
return fmt.Sprintf(`
%[1]s

resource "azurerm_data_factory_pipeline" "test" {
name = "acctest%d"
name = "acctest%[2]d"
data_factory_id = azurerm_data_factory.test.id
annotations = ["test1", "test2"]
description = "updated description"
%[3]s = "00:02:00"

parameters = {
test = "testparameter"
Expand Down Expand Up @@ -330,28 +324,15 @@ resource "azurerm_data_factory_pipeline" "test" {
]
JSON
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
`, r.template(data), data.RandomInteger, metricsFieldName)
}

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

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

resource "azurerm_data_factory" "test" {
name = "acctestdfv2%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
%[1]s

resource "azurerm_data_factory_pipeline" "test" {
name = "acctest%d"
name = "acctest%[2]d"
data_factory_id = azurerm_data_factory.test.id
variables = {
"bob" = "item1"
Expand All @@ -371,10 +352,10 @@ resource "azurerm_data_factory_pipeline" "test" {
]
JSON
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
`, r.template(data), data.RandomInteger)
}

func (PipelineResource) webActivityHeaders(data acceptance.TestData, withHeader bool) string {
func (r PipelineResource) webActivityHeaders(data acceptance.TestData, withHeader bool) string {
headerBlock := `
"headers": {
"authorization": {
Expand All @@ -389,23 +370,10 @@ func (PipelineResource) webActivityHeaders(data acceptance.TestData, withHeader
}

return fmt.Sprintf(`
provider "azurerm" {
features {}
}

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

resource "azurerm_data_factory" "test" {
name = "acctestdfv2%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
%[1]s

resource "azurerm_data_factory_pipeline" "test" {
name = "acctest%d"
name = "acctest%[2]d"
data_factory_id = azurerm_data_factory.test.id
variables = {
"bob" = "item1"
Expand All @@ -418,36 +386,23 @@ resource "azurerm_data_factory_pipeline" "test" {
"dependsOn": [],
"userProperties": [],
"typeProperties": {
%s
%[3]s
"url": "https://test.com",
"method": "POST"
}
}
]
JSON
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, headerBlock)
`, r.template(data), data.RandomInteger, headerBlock)
}

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

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

resource "azurerm_data_factory" "test" {
name = "acctestdfv2%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}
%[1]s

resource "azurerm_data_factory_pipeline" "test" {
name = "acctest%d"
name = "acctest%[2]d"
data_factory_id = azurerm_data_factory.test.id
variables = {
"bob" = "item1"
Expand All @@ -467,5 +422,5 @@ resource "azurerm_data_factory_pipeline" "test" {
]
JSON
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
`, r.template(data), data.RandomInteger)
}
4 changes: 4 additions & 0 deletions website/docs/5.0-upgrade-guide.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ Please follow the format in the example below for listing breaking changes in re

* Validation for `rbac_authorization.resource_id` has been changed to validate for an integration runtime resource ID (case-sensitive) rather than validating for a non-empty string.

### `azurerm_data_factory_pipeline`

* The deprecated `moniter_metrics_after_duration` property has been removed in favour of the `monitor_metrics_after_duration` property.

### `azurerm_disk_encryption_set`

* The deprecated `managed_hsm_key_id` property has been removed in favour of the `key_vault_key_id` property.
Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/data_factory_pipeline.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The following arguments are supported:

* `folder` - (Optional) The folder that this Pipeline is in. If not specified, the Pipeline will appear at the root level.

* `moniter_metrics_after_duration` - (Optional) The TimeSpan value after which an Azure Monitoring Metric is fired.
* `monitor_metrics_after_duration` - (Optional) The TimeSpan value after which an Azure Monitoring Metric is fired.

* `parameters` - (Optional) A map of parameters to associate with the Data Factory Pipeline.

Expand Down
Loading