microsoft / terraform-provider-power-platform

Power Platform Terraform Provider
https://registry.terraform.io/providers/microsoft/power-platform/latest/docs
MIT License
35 stars 13 forks source link

'powerplatform_data_loss_prevention_policy' produced an unexpected new value: .business_connectors #350

Closed Jvekka closed 4 months ago

Jvekka commented 4 months ago

Describe the bug

When applying powerplatform_data_loss_prevention_policy with action_rules and endpoint_rules, terraform produces unexpected error and fails. terraform plan doesn't produce error and terraform is able to do the changes to action_rules and endpoint_rules.

To Reproduce

Steps to reproduce the behavior:

  1. Using modified version of the example code (code below)

  2. terraform plan goes through without error and shows the changes even nothing should change.

# powerplatform_data_loss_prevention_policy.my_policy will be updated in-place
  ~ resource "powerplatform_data_loss_prevention_policy" "my_policy" {
      ~ business_connectors               = [
          - {
              - action_rules                 = [
                  - {
                      - action_id = "DeleteItem_V2" -> null
                      - behavior  = "Block" -> null
                    },
                  - {
                      - action_id = "ExecutePassThroughNativeQuery_V2" -> null
                      - behavior  = "Block" -> null
                    },
                ] -> null
              - default_action_rule_behavior = "Allow" -> null
              - endpoint_rules               = [
                  - {
                      - behavior = "Allow" -> null
                      - endpoint = "contoso.com" -> null
                      - order    = 1 -> null
                    },
                  - {
                      - behavior = "Deny" -> null
                      - endpoint = "*" -> null
                      - order    = 2 -> null
                    },
                ] -> null
              - id                           = "/providers/Microsoft.PowerApps/apis/shared_sql" -> null
            },
          + {
              + action_rules                 = [
                  + {
                      + action_id = "DeleteItem_V2"
                      + behavior  = "Block"
                    },
                  + {
                      + action_id = "ExecutePassThroughNativeQuery_V2"
                      + behavior  = "Block"
                    },
                ]
              + endpoint_rules               = [
                  + {
                      + behavior = "Allow"
                      + endpoint = "contoso.com"
                      + order    = 1
                    },
                  + {
                      + behavior = "Deny"
                      + endpoint = "*"
                      + order    = 2
                    },
                ]
              + id                           = "/providers/Microsoft.PowerApps/apis/shared_sql"
                # (1 unchanged attribute hidden)
            },
            # (5 unchanged elements hidden)
        ]
        id                                = "0c54b7ac-99f5-4da6-9959-d3dc6db1655a"
      ~ last_modified_by                  = "Jere Vekka" -> (known after apply)
      ~ last_modified_time                = "2024-07-02T06:27:58.9992651Z" -> (known after apply)
        # (9 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.
  1. terraform apply goes almost through the whole process, but in the end, it produces following error
powerplatform_data_loss_prevention_policy.my_policy: Still modifying... [id=0c54b7ac-99f5-4da6-9959-d3dc6db1655a, 40s elapsed]
╷
│ Error: Provider produced inconsistent result after apply
│
│ When applying changes to powerplatform_data_loss_prevention_policy.my_policy, provider "provider[\"registry.terraform.io/microsoft/power-platform\"]" produced an unexpected new value:
│ .business_connectors: planned set element
│ cty.ObjectVal(map[string]cty.Value{"action_rules":cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"action_id":cty.StringVal("DeleteItem_V2"),
│ "behavior":cty.StringVal("Block")}), cty.ObjectVal(map[string]cty.Value{"action_id":cty.StringVal("ExecutePassThroughNativeQuery_V2"), "behavior":cty.StringVal("Block")})}),
│ "default_action_rule_behavior":cty.StringVal(""), "endpoint_rules":cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"behavior":cty.StringVal("Allow"),
│ "endpoint":cty.StringVal("contoso.com"), "order":cty.NumberIntVal(1)}), cty.ObjectVal(map[string]cty.Value{"behavior":cty.StringVal("Deny"), "endpoint":cty.StringVal("*"),
│ "order":cty.NumberIntVal(2)})}), "id":cty.StringVal("/providers/Microsoft.PowerApps/apis/shared_sql")}) does not correlate with any element in actual.
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
╵
  1. Changes are still applied and visible in portal.

Sample Terraform Code

If applicable, add terraform code to help explain your problem.

REMINDER: REMOVE SENSITIVE DATA SUCH AS SECRETS, USER NAMES, EMAILS, TENANT INFORMATION, ETC.


variable "list_default_business_connectors" {
  description = "List of allowed business connectors"
  type = map(object({
    name                         = string
    id                           = string
    default_action_rule_behavior = optional(string, "")
    action_rules                 = optional(list(map(string)), [])
    endpoint_rules               = optional(list(map(string)), [])
  }))
  default = {
    "shared_zohocalendar" = {
      "name" = "shared_zohocalendar"
      "id"   = "/providers/Microsoft.PowerApps/apis/shared_zohocalendar"
    },
    "shared_yarado" = {
      "name" = "shared_yarado"
      "id"   = "/providers/Microsoft.PowerApps/apis/shared_yarado"
    }
  }
}

variable "list_dedicated_business_connectors" {
  description = "List of allowed business connectors"
  type = map(object({
    name                         = string
    id                           = string
    default_action_rule_behavior = optional(string, "")
    action_rules                 = optional(list(map(string)), [])
    endpoint_rules               = optional(list(map(string)), [])
  }))
  default = {
    "shared_sql" = {
      "name" = "shared_sql"
      "id"   = "/providers/Microsoft.PowerApps/apis/shared_sql"
      "action_rules" = [
        {
          "action_id" = "DeleteItem_V2"
          "behavior"  = "Block"
        },
        {
          "action_id" = "ExecutePassThroughNativeQuery_V2"
          "behavior"  = "Block"
        },
      ]
      "endpoint_rules" = [
        {
          "behavior" = "Allow"
          "endpoint" = "contoso.com"
          "order"    = 1
        },
        {
          "behavior" = "Deny"
          "endpoint" = "*"
          "order"    = 2
        },
      ]
    },
    "shared_approvals" = {
      "name" = "shared_approvals"
      "id"   = "/providers/Microsoft.PowerApps/apis/shared_approvals"
    },
    "shared_cloudappsecurity" = {
      "name" = "shared_cloudappsecurity"
      "id"   = "/providers/Microsoft.PowerApps/apis/shared_cloudappsecurity"
    },
    "shared_zohosign" = {
      "name" = "shared_zohosign"
      "id"   = "/providers/Microsoft.PowerApps/apis/shared_zohosign"
    }
  }
}

locals {
  default_business_connectors = toset([for f in var.list_default_business_connectors :
    {
      id                           = f.id
      default_action_rule_behavior = f.default_action_rule_behavior
      action_rules                 = f.action_rules
      endpoint_rules               = f.endpoint_rules
    }
  ])
  dedicated_business_connectors = toset([for f in var.list_dedicated_business_connectors :
    {
      id                           = f.id
      default_action_rule_behavior = f.default_action_rule_behavior
      action_rules                 = f.action_rules
      endpoint_rules               = f.endpoint_rules
    }
  ])

  business_connectors = setunion(local.default_business_connectors, local.dedicated_business_connectors)

  non_business_connectors = toset([for conn
    in data.powerplatform_connectors.all_connectors.connectors :
    {
      id                           = conn.id
      name                         = conn.name
      default_action_rule_behavior = ""
      action_rules                 = [],
      endpoint_rules               = []
    }
    if conn.unblockable == true && !contains([for bus_conn in local.business_connectors : bus_conn.id], conn.id)
  ])

  blocked_connectors = toset([for conn
    in data.powerplatform_connectors.all_connectors.connectors :
    {
      id                           = conn.id
      default_action_rule_behavior = ""
      action_rules                 = [],
      endpoint_rules               = []
    }
  if conn.unblockable == false && !contains([for bus_conn in local.business_connectors : bus_conn.id], conn.id)])
}

output "business_connectors" {
  value = local.business_connectors
}

resource "powerplatform_data_loss_prevention_policy" "my_policy" {
  display_name                      = "Block All Policy"
  default_connectors_classification = "Blocked"
  environment_type                  = "OnlyEnvironments"
  environments                      = [powerplatform_environment.development.id]

  business_connectors     = local.business_connectors
  non_business_connectors = local.non_business_connectors
  blocked_connectors      = local.blocked_connectors

  custom_connectors_patterns = toset([
    {
      order            = 1
      host_url_pattern = "https://*.contoso.com"
      data_group       = "Blocked"
    },
    {
      order            = 2
      host_url_pattern = "*"
      data_group       = "Ignore"
    }
  ])
}

resource "powerplatform_environment" "development" {
  display_name     = "example_environment"
  location         = "europe"
  azure_region     = "northeurope"
  environment_type = "Production"
  dataverse = {
    language_code     = "1033"
    currency_code     = "EUR"
    domain            = "mydomain"
    security_group_id = "00000000-0000-0000-0000-000000000000"
  }
}

resource "powerplatform_managed_environment" "managed_development" {
  environment_id             = powerplatform_environment.development.id
  is_usage_insights_disabled = true
  is_group_sharing_disabled  = true
  limit_sharing_mode         = "ExcludeSharingToSecurityGroups"
  max_limit_user_sharing     = 10
  solution_checker_mode      = "None"
  suppress_validation_emails = true
  maker_onboarding_markdown  = "this is example markdown v2"
  maker_onboarding_url       = "https://www.microsoft.com"
}

Expected behavior

Apply the code without producing error.

System Information

Additional context

The example code worked like a charm so the issue might be my code, but as the plan goes through without issues, I can't really figure the issue by myself.

Contribution

Contribution

Do you plan to raise a PR to address this issue? YES / NO?

mawasile commented 4 months ago

Thanks for rising a bug @Jvekka. The backend api that returns action_rules and endpoint_rules has changed. We should get an update in the provider pretty quickly. As a workaround for now, you would have to keep action_rules and endpoint_rules empty in your configuration in order for it to work.

mawasile commented 4 months ago

Hi @Jvekka After debugging the code I have to retract from my first comment, the provider is working correctly but terraform has issues with tracking dynamic and lists correctly. Your policy is working correctly when set like in our example

Here is similar issue reported for azurerm provider: https://github.com/hashicorp/terraform-provider-azurerm/issues/6254