diff --git a/docs/guides/authentication_rules.md b/docs/guides/authentication_rules.md new file mode 100644 index 0000000..00cf355 --- /dev/null +++ b/docs/guides/authentication_rules.md @@ -0,0 +1,92 @@ +--- +subcategory: "Guides" +page_title: "Authentication Rules" +description: |- + Authentication Rules +--- + +# Authentication Rules + +This example demonstrates how the provider can be used to configure a network access authentication rules. The full example can be found here: [https://github.com/CiscoDevNet/terraform-provider-ise/tree/main/examples/basic/authentication_rules](https://github.com/CiscoDevNet/terraform-provider-ise/tree/main/examples/basic/authentication_rules) + +First of all we need to add the necessary provider configuration to the Terraform configuration file: + +```hcl +terraform { + required_providers { + ise = { + source = "CiscoDevNet/ise" + } + } +} + +provider "ise" { + username = "admin" + password = "password" + url = "https://10.1.1.1" +} +``` + +Next we add the configuration for a network access policy set, under which we will later configure authentication rules. + +```hcl +resource "ise_network_access_policy_set" "policy_set_1" { + name = "PolicySet1" + description = "My first policy set" + rank = 0 + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} +``` + +Next we add the configuration for the authentication rules. We make use of `network_access_authentication_rule` and `network_access_authentication_rule_update_rank` resources. The first resource manages all fields except for the rank, while the second resource specifically updates the rank field. This is a workaround for the ISE API/Backend limitation that enforces strictly incremental rank assignments. By using both resources, you can bypass this limitation. The network_access_authentication_rule_update_rank resource performs a PUT operation to update the rank and only tracks that field. When destroyed, it is simply removed from the state without affecting the ISE configuration. This ensures the correct sequence of resource configuration. + +```hcl +locals { + rules = [ + { name = "rule_0" }, + { name = "rule_1" }, + { name = "rule_2" }, + { name = "rule_3" }, + { name = "rule_4" }, + { name = "rule_5" } + ] +} + +locals { + rules_with_ranks = [ + for idx, rule in local.rules : merge(rule, { + rank = idx + }) + ] +} + +resource "ise_network_access_authentication_rule" "auth_rule" { + for_each = { for rule in local.rules_with_ranks : rule.name => rule } + policy_set_id = ise_network_access_policy_set.policy_set_1.id + name = each.value.name + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + identity_source_name = "Internal Endpoints" + if_auth_fail = "REJECT" + if_process_fail = "DROP" + if_user_not_found = "REJECT" +} + +resource "ise_network_access_authentication_rule_update_rank" "example_with_rank" { + for_each = { for rule in local.rules_with_ranks : rule.name => rule } + policy_set_id = ise_network_access_policy_set.policy_set_1.id + rule_id = ise_network_access_authentication_rule.auth_rule[each.value.name].id + rank = each.value.rank +} +``` diff --git a/docs/index.md b/docs/index.md index 40a6da1..6879075 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,6 +24,7 @@ All resources and data sources have been tested with the following releases. The following guides with examples exist to demonstrate the use of the provider: - [Getting Started](https://registry.terraform.io/providers/CiscoDevNet/ise/latest/docs/guides/getting_started) +- [Authentication Rules](https://registry.terraform.io/providers/CiscoDevNet/ise/latest/docs/guides/authentication_rules) ## Example Usage diff --git a/docs/resources/device_admin_authentication_rule_update_rank.md b/docs/resources/device_admin_authentication_rule_update_rank.md new file mode 100644 index 0000000..3706d3d --- /dev/null +++ b/docs/resources/device_admin_authentication_rule_update_rank.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_device_admin_authentication_rule_update_rank Resource - terraform-provider-ise" +subcategory: "Device Administration" +description: |- + This resource is used to update rank field in device admin authentication rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authentication_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_device_admin_authentication_rule_update_rank (Resource) + +This resource is used to update rank field in device admin authentication rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authentication_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_device_admin_authentication_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `policy_set_id` (String) Policy set ID +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authentication rule ID + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/device_admin_authorization_exception_rule_update_rank.md b/docs/resources/device_admin_authorization_exception_rule_update_rank.md new file mode 100644 index 0000000..e305979 --- /dev/null +++ b/docs/resources/device_admin_authorization_exception_rule_update_rank.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_device_admin_authorization_exception_rule_update_rank Resource - terraform-provider-ise" +subcategory: "Device Administration" +description: |- + This resource is used to update rank field in device admin Authorization exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_device_admin_authorization_exception_rule_update_rank (Resource) + +This resource is used to update rank field in device admin Authorization exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_device_admin_authorization_exception_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `policy_set_id` (String) Policy set ID +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authorization exception rule ID + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/device_admin_authorization_global_exception_rule_update_rank.md b/docs/resources/device_admin_authorization_global_exception_rule_update_rank.md new file mode 100644 index 0000000..87d21cd --- /dev/null +++ b/docs/resources/device_admin_authorization_global_exception_rule_update_rank.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_device_admin_authorization_global_exception_rule_update_rank Resource - terraform-provider-ise" +subcategory: "Device Administration" +description: |- + This resource is used to update rank field in device admin authorization global exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_global_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_device_admin_authorization_global_exception_rule_update_rank (Resource) + +This resource is used to update rank field in device admin authorization global exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_global_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_device_admin_authorization_global_exception_rule_update_rank" "example" { + rule_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authorization global exception rule ID + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/device_admin_authorization_rule_update_rank.md b/docs/resources/device_admin_authorization_rule_update_rank.md new file mode 100644 index 0000000..d0a6a2e --- /dev/null +++ b/docs/resources/device_admin_authorization_rule_update_rank.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_device_admin_authorization_rule_update_rank Resource - terraform-provider-ise" +subcategory: "Device Administration" +description: |- + This resource is used to update rank field in device admin authorization rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_device_admin_authorization_rule_update_rank (Resource) + +This resource is used to update rank field in device admin authorization rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_device_admin_authorization_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `policy_set_id` (String) Policy set ID +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authorization rule ID + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/device_admin_policy_set_update_rank.md b/docs/resources/device_admin_policy_set_update_rank.md new file mode 100644 index 0000000..575bf84 --- /dev/null +++ b/docs/resources/device_admin_policy_set_update_rank.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_device_admin_policy_set_update_rank Resource - terraform-provider-ise" +subcategory: "Device Administration" +description: |- + This resource is used to update rank field in device admin policy set. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_policy_set resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_device_admin_policy_set_update_rank (Resource) + +This resource is used to update rank field in device admin policy set. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_policy_set resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_device_admin_policy_set_update_rank" "example" { + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `policy_set_id` (String) Policy set ID +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/network_access_authentication_rule_update_rank.md b/docs/resources/network_access_authentication_rule_update_rank.md index d01df1b..0e24a66 100644 --- a/docs/resources/network_access_authentication_rule_update_rank.md +++ b/docs/resources/network_access_authentication_rule_update_rank.md @@ -14,7 +14,7 @@ This resource is used to update rank field in network access authentication rule ```terraform resource "ise_network_access_authentication_rule_update_rank" "example" { - auth_rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" rank = 0 } @@ -25,9 +25,9 @@ resource "ise_network_access_authentication_rule_update_rank" "example" { ### Required -- `auth_rule_id` (String) Authentication rule ID - `policy_set_id` (String) Policy set ID - `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authentication rule ID ### Read-Only diff --git a/docs/resources/network_access_authorization_exception_rule_update_rank.md b/docs/resources/network_access_authorization_exception_rule_update_rank.md new file mode 100644 index 0000000..6d4b7db --- /dev/null +++ b/docs/resources/network_access_authorization_exception_rule_update_rank.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_network_access_authorization_exception_rule_update_rank Resource - terraform-provider-ise" +subcategory: "Network Access" +description: |- + This resource is used to update rank field in network access authorization exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_network_access_authorization_exception_rule_update_rank (Resource) + +This resource is used to update rank field in network access authorization exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_network_access_authorization_exception_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `policy_set_id` (String) Policy set ID +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authorization exception rule ID + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/network_access_authorization_global_exception_rule_update_rank.md b/docs/resources/network_access_authorization_global_exception_rule_update_rank.md new file mode 100644 index 0000000..bb6f910 --- /dev/null +++ b/docs/resources/network_access_authorization_global_exception_rule_update_rank.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_network_access_authorization_global_exception_rule_update_rank Resource - terraform-provider-ise" +subcategory: "Network Access" +description: |- + This resource is used to update rank field in network access authorization global exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_global_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_network_access_authorization_global_exception_rule_update_rank (Resource) + +This resource is used to update rank field in network access authorization global exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_global_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_network_access_authorization_global_exception_rule_update_rank" "example" { + rule_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authorization global exception rule ID + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/network_access_authorization_rule_update_rank.md b/docs/resources/network_access_authorization_rule_update_rank.md new file mode 100644 index 0000000..03261e2 --- /dev/null +++ b/docs/resources/network_access_authorization_rule_update_rank.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_network_access_authorization_rule_update_rank Resource - terraform-provider-ise" +subcategory: "Network Access" +description: |- + This resource is used to update rank field in network access authorization rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_network_access_authorization_rule_update_rank (Resource) + +This resource is used to update rank field in network access authorization rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_network_access_authorization_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `policy_set_id` (String) Policy set ID +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. +- `rule_id` (String) Authorization rule ID + +### Read-Only + +- `id` (String) The id of the object diff --git a/docs/resources/network_access_policy_set_update_rank.md b/docs/resources/network_access_policy_set_update_rank.md new file mode 100644 index 0000000..0079d13 --- /dev/null +++ b/docs/resources/network_access_policy_set_update_rank.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ise_network_access_policy_set_update_rank Resource - terraform-provider-ise" +subcategory: "Network Access" +description: |- + This resource is used to update rank field in network access policy set. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_policy_set resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. +--- + +# ise_network_access_policy_set_update_rank (Resource) + +This resource is used to update rank field in network access policy set. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_policy_set resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state. + +## Example Usage + +```terraform +resource "ise_network_access_policy_set_update_rank" "example" { + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} +``` + + +## Schema + +### Required + +- `policy_set_id` (String) Policy set ID +- `rank` (Number) The rank (priority) in relation to other rules. Lower rank is higher priority. + +### Read-Only + +- `id` (String) The id of the object diff --git a/examples/basic/authentication_rules/main.tf b/examples/basic/authentication_rules/main.tf new file mode 100644 index 0000000..8c1f4fe --- /dev/null +++ b/examples/basic/authentication_rules/main.tf @@ -0,0 +1,55 @@ +resource "ise_network_access_policy_set" "policy_set_1" { + name = "PolicySet1" + description = "My first policy set" + rank = 0 + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} + +locals { + rules = [ + { name = "rule_0" }, + { name = "rule_1" }, + { name = "rule_2" }, + { name = "rule_3" }, + { name = "rule_4" }, + { name = "rule_5" } + ] +} + +locals { + rules_with_ranks = [ + for idx, rule in local.rules : merge(rule, { + rank = idx + }) + ] +} + +resource "ise_network_access_authentication_rule" "auth_rule" { + for_each = { for rule in local.rules_with_ranks : rule.name => rule } + policy_set_id = ise_network_access_policy_set.policy_set_1.id + name = each.value.name + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + identity_source_name = "Internal Endpoints" + if_auth_fail = "REJECT" + if_process_fail = "DROP" + if_user_not_found = "REJECT" +} + +resource "ise_network_access_authentication_rule_update_rank" "example_with_rank" { + for_each = { for rule in local.rules_with_ranks : rule.name => rule } + policy_set_id = ise_network_access_policy_set.policy_set_1.id + rule_id = ise_network_access_authentication_rule.auth_rule[each.value.name].id + rank = each.value.rank +} \ No newline at end of file diff --git a/examples/basic/authentication_rules/provider.tf b/examples/basic/authentication_rules/provider.tf new file mode 100644 index 0000000..b15040e --- /dev/null +++ b/examples/basic/authentication_rules/provider.tf @@ -0,0 +1,11 @@ +terraform { + required_providers { + ise = { + source = "CiscoDevNet/ise" + } + } +} + +provider "ise" { + // By default uses env $ISE_USERNAME $ISE_PASSWORD $ISE_URL +} diff --git a/examples/resources/ise_device_admin_authentication_rule_update_rank/resource.tf b/examples/resources/ise_device_admin_authentication_rule_update_rank/resource.tf new file mode 100644 index 0000000..e4f91c2 --- /dev/null +++ b/examples/resources/ise_device_admin_authentication_rule_update_rank/resource.tf @@ -0,0 +1,5 @@ +resource "ise_device_admin_authentication_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_device_admin_authorization_exception_rule_update_rank/resource.tf b/examples/resources/ise_device_admin_authorization_exception_rule_update_rank/resource.tf new file mode 100644 index 0000000..46c5c4b --- /dev/null +++ b/examples/resources/ise_device_admin_authorization_exception_rule_update_rank/resource.tf @@ -0,0 +1,5 @@ +resource "ise_device_admin_authorization_exception_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_device_admin_authorization_global_exception_rule_update_rank/resource.tf b/examples/resources/ise_device_admin_authorization_global_exception_rule_update_rank/resource.tf new file mode 100644 index 0000000..da864ba --- /dev/null +++ b/examples/resources/ise_device_admin_authorization_global_exception_rule_update_rank/resource.tf @@ -0,0 +1,4 @@ +resource "ise_device_admin_authorization_global_exception_rule_update_rank" "example" { + rule_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_device_admin_authorization_rule_update_rank/resource.tf b/examples/resources/ise_device_admin_authorization_rule_update_rank/resource.tf new file mode 100644 index 0000000..bae3cdc --- /dev/null +++ b/examples/resources/ise_device_admin_authorization_rule_update_rank/resource.tf @@ -0,0 +1,5 @@ +resource "ise_device_admin_authorization_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_device_admin_policy_set_update_rank/resource.tf b/examples/resources/ise_device_admin_policy_set_update_rank/resource.tf new file mode 100644 index 0000000..5fdad1c --- /dev/null +++ b/examples/resources/ise_device_admin_policy_set_update_rank/resource.tf @@ -0,0 +1,4 @@ +resource "ise_device_admin_policy_set_update_rank" "example" { + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_network_access_authentication_rule_update_rank/resource.tf b/examples/resources/ise_network_access_authentication_rule_update_rank/resource.tf index c984172..71e1c12 100644 --- a/examples/resources/ise_network_access_authentication_rule_update_rank/resource.tf +++ b/examples/resources/ise_network_access_authentication_rule_update_rank/resource.tf @@ -1,5 +1,5 @@ resource "ise_network_access_authentication_rule_update_rank" "example" { - auth_rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" rank = 0 } diff --git a/examples/resources/ise_network_access_authorization_exception_rule_update_rank/resource.tf b/examples/resources/ise_network_access_authorization_exception_rule_update_rank/resource.tf new file mode 100644 index 0000000..4c55d14 --- /dev/null +++ b/examples/resources/ise_network_access_authorization_exception_rule_update_rank/resource.tf @@ -0,0 +1,5 @@ +resource "ise_network_access_authorization_exception_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_network_access_authorization_global_exception_rule_update_rank/resource.tf b/examples/resources/ise_network_access_authorization_global_exception_rule_update_rank/resource.tf new file mode 100644 index 0000000..b0c4b80 --- /dev/null +++ b/examples/resources/ise_network_access_authorization_global_exception_rule_update_rank/resource.tf @@ -0,0 +1,4 @@ +resource "ise_network_access_authorization_global_exception_rule_update_rank" "example" { + rule_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_network_access_authorization_rule_update_rank/resource.tf b/examples/resources/ise_network_access_authorization_rule_update_rank/resource.tf new file mode 100644 index 0000000..f38ff23 --- /dev/null +++ b/examples/resources/ise_network_access_authorization_rule_update_rank/resource.tf @@ -0,0 +1,5 @@ +resource "ise_network_access_authorization_rule_update_rank" "example" { + rule_id = "9b3680da-0165-44f6-9cff-88e778d98020" + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/examples/resources/ise_network_access_policy_set_update_rank/resource.tf b/examples/resources/ise_network_access_policy_set_update_rank/resource.tf new file mode 100644 index 0000000..e5f0e17 --- /dev/null +++ b/examples/resources/ise_network_access_policy_set_update_rank/resource.tf @@ -0,0 +1,4 @@ +resource "ise_network_access_policy_set_update_rank" "example" { + policy_set_id = "d82952cb-b901-4b09-b363-5ebf39bdbaf9" + rank = 0 +} diff --git a/gen/definitions/device_admin_authentication_rule.yaml b/gen/definitions/device_admin_authentication_rule.yaml index 6dec7f7..81fbaba 100644 --- a/gen/definitions/device_admin_authentication_rule.yaml +++ b/gen/definitions/device_admin_authentication_rule.yaml @@ -3,6 +3,7 @@ name: Device Admin Authentication Rule rest_endpoint: /api/v1/policy/device-admin/policy-set/%v/authentication data_source_name_query: true id_path: response.rule.id +update_default: true ignore_delete_error: Attempted to delete default doc_category: Device Administration attributes: diff --git a/gen/definitions/device_admin_authentication_rule_update_rank.yaml b/gen/definitions/device_admin_authentication_rule_update_rank.yaml new file mode 100644 index 0000000..249cc64 --- /dev/null +++ b/gen/definitions/device_admin_authentication_rule_update_rank.yaml @@ -0,0 +1,64 @@ +--- +name: Device Admin Authentication Rule Update Rank +res_description: + 'This resource is used to update rank field in device admin authentication rule. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and device_admin_authentication_rule resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/device-admin/policy-set/%v/authentication +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Device Administration +attributes: + - tf_name: rule_id + type: String + write_only: true + mandatory: true + id: true + description: Authentication rule ID + example: 9b3680da-0165-44f6-9cff-88e778d98020 + test_value: ise_device_admin_authentication_rule.test.id + - tf_name: policy_set_id + type: String + reference: true + description: Policy set ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_device_admin_policy_set.test.id + - model_name: rank + mandatory: true + data_path: [rule] + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Device Admin" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + } + resource "ise_device_admin_authentication_rule" "test" { + policy_set_id = ise_device_admin_policy_set.test.id + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + identity_source_name = "Internal Endpoints" + if_auth_fail = "REJECT" + if_process_fail = "DROP" + if_user_not_found = "REJECT" + } diff --git a/gen/definitions/device_admin_authorization_exception_rule_update_rank.yaml b/gen/definitions/device_admin_authorization_exception_rule_update_rank.yaml new file mode 100644 index 0000000..530c492 --- /dev/null +++ b/gen/definitions/device_admin_authorization_exception_rule_update_rank.yaml @@ -0,0 +1,63 @@ +--- +name: Device Admin Authorization Exception Rule Update Rank +res_description: + 'This resource is used to update rank field in device admin Authorization exception rule. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and device_admin_authorization_exception_rule resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/device-admin/policy-set/%v/exception +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Device Administration +attributes: + - tf_name: rule_id + type: String + write_only: true + mandatory: true + id: true + description: Authorization exception rule ID + example: 9b3680da-0165-44f6-9cff-88e778d98020 + test_value: ise_device_admin_authorization_exception_rule.test.id + - tf_name: policy_set_id + type: String + reference: true + description: Policy set ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_device_admin_policy_set.test.id + - model_name: rank + mandatory: true + data_path: [rule] + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Device Admin" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + } + resource "ise_device_admin_authorization_exception_rule" "test" { + policy_set_id = ise_device_admin_policy_set.test.id + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + command_sets = ["DenyAllCommands"] + profile = "Default Shell Profile" + } + diff --git a/gen/definitions/device_admin_authorization_global_exception_rule_update_rank.yaml b/gen/definitions/device_admin_authorization_global_exception_rule_update_rank.yaml new file mode 100644 index 0000000..6f4af4d --- /dev/null +++ b/gen/definitions/device_admin_authorization_global_exception_rule_update_rank.yaml @@ -0,0 +1,45 @@ +--- +name: Device Admin Authorization Global Exception Rule Update Rank +res_description: + 'This resource is used to update rank field in device admin authorization global exception rule. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and device_admin_authorization_global_exception_rule resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/device-admin/policy-set/global-exception +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Device Administration +attributes: + - tf_name: rule_id + type: String + write_only: true + mandatory: true + id: true + description: Authorization global exception rule ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_device_admin_authorization_global_exception_rule.test.id + - model_name: rank + data_path: [rule] + mandatory: true + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_device_admin_authorization_global_exception_rule" "test" { + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + command_sets = ["DenyAllCommands"] + profile = "Default Shell Profile" + } diff --git a/gen/definitions/device_admin_authorization_rule.yaml b/gen/definitions/device_admin_authorization_rule.yaml index 6706301..9dcee96 100644 --- a/gen/definitions/device_admin_authorization_rule.yaml +++ b/gen/definitions/device_admin_authorization_rule.yaml @@ -3,6 +3,7 @@ name: Device Admin Authorization Rule rest_endpoint: /api/v1/policy/device-admin/policy-set/%v/authorization data_source_name_query: true id_path: response.rule.id +update_default: true ignore_delete_error: Attempted to delete default doc_category: Device Administration attributes: diff --git a/gen/definitions/device_admin_authorization_rule_update_rank.yaml b/gen/definitions/device_admin_authorization_rule_update_rank.yaml new file mode 100644 index 0000000..a8d19f5 --- /dev/null +++ b/gen/definitions/device_admin_authorization_rule_update_rank.yaml @@ -0,0 +1,63 @@ +--- +name: Device Admin Authorization Rule Update Rank +res_description: + 'This resource is used to update rank field in device admin authorization rule. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and device_admin_authorization_rule resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/device-admin/policy-set/%v/authorization +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Device Administration +attributes: + - tf_name: rule_id + type: String + write_only: true + mandatory: true + id: true + description: Authorization rule ID + example: 9b3680da-0165-44f6-9cff-88e778d98020 + test_value: ise_device_admin_authorization_rule.test.id + - tf_name: policy_set_id + type: String + reference: true + description: Policy set ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_device_admin_policy_set.test.id + - model_name: rank + mandatory: true + data_path: [rule] + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Device Admin" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + } + resource "ise_device_admin_authorization_rule" "test" { + policy_set_id = ise_device_admin_policy_set.test.id + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + command_sets = ["DenyAllCommands"] + profile = "Default Shell Profile" + } + diff --git a/gen/definitions/device_admin_policy_set.yaml b/gen/definitions/device_admin_policy_set.yaml index afa09ba..06df169 100644 --- a/gen/definitions/device_admin_policy_set.yaml +++ b/gen/definitions/device_admin_policy_set.yaml @@ -2,6 +2,7 @@ name: Device Admin Policy Set rest_endpoint: /api/v1/policy/device-admin/policy-set data_source_name_query: true +update_default: true id_path: response.id ignore_delete_error: Attempted to delete default doc_category: Device Administration @@ -22,7 +23,6 @@ attributes: example: false - model_name: rank type: Int64 - computed: true description: The rank (priority) in relation to other policy sets. Lower rank is higher priority. example: 0 - model_name: serviceName diff --git a/gen/definitions/device_admin_policy_set_update_rank.yaml b/gen/definitions/device_admin_policy_set_update_rank.yaml new file mode 100644 index 0000000..9d9a65c --- /dev/null +++ b/gen/definitions/device_admin_policy_set_update_rank.yaml @@ -0,0 +1,43 @@ +--- +name: Device Admin Policy Set Update Rank +res_description: + 'This resource is used to update rank field in device admin policy set. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and device_admin_policy_set resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/device-admin/policy-set +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Device Administration +attributes: + - tf_name: policy_set_id + type: String + write_only: true + mandatory: true + id: true + description: Policy set ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_device_admin_policy_set.test.id + - model_name: rank + mandatory: true + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + description = "My description" + is_proxy = false + service_name = "Default Device Admin" + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + } diff --git a/gen/definitions/network_access_authentication_rule.yaml b/gen/definitions/network_access_authentication_rule.yaml index 0b56330..be4506c 100644 --- a/gen/definitions/network_access_authentication_rule.yaml +++ b/gen/definitions/network_access_authentication_rule.yaml @@ -1,8 +1,8 @@ --- name: Network Access Authentication Rule -# Manual update in Update function in resource file to read rank from existing object and send that in PUT request rest_endpoint: /api/v1/policy/network-access/policy-set/%v/authentication data_source_name_query: true +update_default: true id_path: response.rule.id ignore_delete_error: Attempted to delete default doc_category: Network Access diff --git a/gen/definitions/network_access_authentication_rule_update_rank.yaml b/gen/definitions/network_access_authentication_rule_update_rank.yaml index 3d1dc18..a26d5d5 100644 --- a/gen/definitions/network_access_authentication_rule_update_rank.yaml +++ b/gen/definitions/network_access_authentication_rule_update_rank.yaml @@ -1,7 +1,5 @@ --- name: Network Access Authentication Rule Update Rank -# Manual update in Create function in resource file to add AuthRuleId to path -# and populate attributes from existing resource using GET request in Create and Update functions res_description: 'This resource is used to update rank field in network access authentication rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. @@ -16,7 +14,7 @@ no_data_source: true skip_minimum_test: true doc_category: Network Access attributes: - - tf_name: auth_rule_id + - tf_name: rule_id type: String write_only: true mandatory: true diff --git a/gen/definitions/network_access_authorization_exception_rule_update_rank.yaml b/gen/definitions/network_access_authorization_exception_rule_update_rank.yaml new file mode 100644 index 0000000..df3bd9d --- /dev/null +++ b/gen/definitions/network_access_authorization_exception_rule_update_rank.yaml @@ -0,0 +1,61 @@ +--- +name: Network Access Authorization Exception Rule Update Rank +res_description: + 'This resource is used to update rank field in network access authorization exception rule. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and network_access_authorization_exception_rule resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/network-access/policy-set/%v/exception +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Network Access +attributes: + - tf_name: rule_id + type: String + write_only: true + mandatory: true + id: true + description: Authorization exception rule ID + example: 9b3680da-0165-44f6-9cff-88e778d98020 + test_value: ise_network_access_authorization_exception_rule.test.id + - tf_name: policy_set_id + type: String + reference: true + description: Policy set ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_network_access_policy_set.test.id + - model_name: rank + mandatory: true + data_path: [rule] + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_network_access_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + } + resource "ise_network_access_authorization_exception_rule" "test" { + policy_set_id = ise_network_access_policy_set.test.id + name = "Rule1" + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + profiles = ["PermitAccess"] + security_group = "BYOD" + } diff --git a/gen/definitions/network_access_authorization_global_exception_rule_update_rank.yaml b/gen/definitions/network_access_authorization_global_exception_rule_update_rank.yaml new file mode 100644 index 0000000..3c87171 --- /dev/null +++ b/gen/definitions/network_access_authorization_global_exception_rule_update_rank.yaml @@ -0,0 +1,44 @@ +--- +name: Network Access Authorization Global Exception Rule Update Rank +res_description: + 'This resource is used to update rank field in network access authorization global exception rule. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and network_access_authorization_global_exception_rule resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/network-access/policy-set/global-exception +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Network Access +attributes: + - tf_name: rule_id + type: String + write_only: true + mandatory: true + id: true + description: Authorization global exception rule ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_network_access_authorization_global_exception_rule.test.id + - model_name: rank + data_path: [rule] + mandatory: true + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_network_access_authorization_global_exception_rule" "test" { + name = "Rule1" + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + profiles = ["PermitAccess"] + security_group = "BYOD" + } diff --git a/gen/definitions/network_access_authorization_rule.yaml b/gen/definitions/network_access_authorization_rule.yaml index a439f58..3fc0c07 100644 --- a/gen/definitions/network_access_authorization_rule.yaml +++ b/gen/definitions/network_access_authorization_rule.yaml @@ -3,6 +3,7 @@ name: Network Access Authorization Rule rest_endpoint: /api/v1/policy/network-access/policy-set/%v/authorization data_source_name_query: true id_path: response.rule.id +update_default: true ignore_delete_error: Attempted to delete default doc_category: Network Access attributes: diff --git a/gen/definitions/network_access_authorization_rule_update_rank.yaml b/gen/definitions/network_access_authorization_rule_update_rank.yaml new file mode 100644 index 0000000..4a00a57 --- /dev/null +++ b/gen/definitions/network_access_authorization_rule_update_rank.yaml @@ -0,0 +1,61 @@ +--- +name: Network Access Authorization Rule Update Rank +res_description: + 'This resource is used to update rank field in network access authorization rule. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and network_access_authorization_rule resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/network-access/policy-set/%v/authorization +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Network Access +attributes: + - tf_name: rule_id + type: String + write_only: true + mandatory: true + id: true + description: Authorization rule ID + example: 9b3680da-0165-44f6-9cff-88e778d98020 + test_value: ise_network_access_authorization_rule.test.id + - tf_name: policy_set_id + type: String + reference: true + description: Policy set ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_network_access_policy_set.test.id + - model_name: rank + mandatory: true + data_path: [rule] + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_network_access_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + } + resource "ise_network_access_authorization_rule" "test" { + policy_set_id = ise_network_access_policy_set.test.id + name = "Rule1" + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + profiles = ["PermitAccess"] + security_group = "BYOD" + } diff --git a/gen/definitions/network_access_policy_set.yaml b/gen/definitions/network_access_policy_set.yaml index 9770f91..78dd11b 100644 --- a/gen/definitions/network_access_policy_set.yaml +++ b/gen/definitions/network_access_policy_set.yaml @@ -2,6 +2,7 @@ name: Network Access Policy Set rest_endpoint: /api/v1/policy/network-access/policy-set data_source_name_query: true +update_default: true id_path: response.id ignore_delete_error: Attempted to delete default doc_category: Network Access @@ -22,7 +23,6 @@ attributes: example: false - model_name: rank type: Int64 - computed: true description: The rank (priority) in relation to other policy sets. Lower rank is higher priority. example: 0 - model_name: serviceName diff --git a/gen/definitions/network_access_policy_set_update_rank.yaml b/gen/definitions/network_access_policy_set_update_rank.yaml new file mode 100644 index 0000000..58786f3 --- /dev/null +++ b/gen/definitions/network_access_policy_set_update_rank.yaml @@ -0,0 +1,40 @@ +--- +name: Network Access Policy Set Update Rank +res_description: + 'This resource is used to update rank field in network access policy set. It serves as a workaround for the + ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. + By utilizing this resource and network_access_policy_set resource, you can bypass the APIs limitation. + Creation of this resource is performing PUT operation (Update) and it only tracks rank field. + When this resource is destroyed, no action is performed on ISE and resource is just removed from state.' +rest_endpoint: /api/v1/policy/network-access/policy-set +put_create: true +no_delete: true +no_import: true +no_data_source: true +skip_minimum_test: true +doc_category: Network Access +attributes: + - tf_name: policy_set_id + type: String + write_only: true + mandatory: true + id: true + description: Policy set ID + example: d82952cb-b901-4b09-b363-5ebf39bdbaf9 + test_value: ise_network_access_policy_set.test.id + - model_name: rank + mandatory: true + type: Int64 + description: The rank (priority) in relation to other rules. Lower rank is higher priority. + example: 0 +test_prerequisites: | + resource "ise_network_access_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + } diff --git a/gen/generator.go b/gen/generator.go index dfc5514..0ac2a35 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -108,6 +108,7 @@ type YamlConfig struct { PutRead bool `yaml:"put_read"` NoRead bool `yaml:"no_read"` NoUpdate bool `yaml:"no_update"` + UpdateDefault bool `yaml:"update_default"` RootList bool `yaml:"root_list"` NoReadPrefix bool `yaml:"no_read_prefix"` NoId bool `yaml:"no_id"` @@ -341,10 +342,22 @@ func IsNestedSet(attribute YamlConfigAttribute) bool { return false } +// Templating helper function to return true if resource has specific attribute +func HasAttribute(attributes []YamlConfigAttribute, attrName string) bool { + for _, attr := range attributes { + if attr.TfName == attrName { + return true + } + } + return false +} + // Map of templating functions var functions = template.FuncMap{ "toGoName": ToGoName, "camelCase": CamelCase, + "strContains": strings.Contains, + "strReplace": strings.Replace, "snakeCase": SnakeCase, "sprintf": fmt.Sprintf, "toLower": strings.ToLower, @@ -364,6 +377,7 @@ var functions = template.FuncMap{ "isNestedListSet": IsNestedListSet, "isNestedList": IsNestedList, "isNestedSet": IsNestedSet, + "hasAttribute": HasAttribute, } func augmentAttribute(attr *YamlConfigAttribute) { diff --git a/gen/schema/schema.yaml b/gen/schema/schema.yaml index 853c4d9..ed1f6c3 100644 --- a/gen/schema/schema.yaml +++ b/gen/schema/schema.yaml @@ -2,6 +2,7 @@ name: str() # Name of the resource rest_endpoint: str(required=False) # REST endpoint path delete_rest_endpoint: str(required=False) # Override DELETE REST endpoint path +update_default: bool(required=False) # Set to true to allow updating the default policy set and rules. get_no_id: bool(required=False) # Set to true if the GET request does not require an ID no_delete: bool(required=False) # Set to true if the DELETE request is not supported no_import: bool(required=False) # Set to true if the resource does not support importing diff --git a/gen/templates/resource.go b/gen/templates/resource.go index 227a9c5..edfd8e6 100644 --- a/gen/templates/resource.go +++ b/gen/templates/resource.go @@ -455,6 +455,9 @@ func (r *{{camelCase .Name}}Resource) Configure(_ context.Context, req resource. //template:begin create func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan {{camelCase .Name}} + {{- if strContains (camelCase .Name) "UpdateRank" }} + var existingData {{strReplace (camelCase .Name) "UpdateRank" "" -1}} + {{- end}} // Read plan diags := req.Plan.Get(ctx, &plan) @@ -465,9 +468,48 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + {{- if strContains (camelCase .Name) "UpdateRank" }} + // Read existing attributes from the API + {{- if strContains (camelCase .Name) "Rule" }} + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + {{- else}} + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.PolicySetId.ValueString())) + {{- end}} + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + {{- if strContains (camelCase .Name) "Rule" }} + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + {{- else}} + body, _ = sjson.Set(body, "rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.PolicySetId.ValueString()), body) + {{- end}} + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + {{- if strContains (camelCase .Name) "Rule" }} + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + {{- else}} + plan.Id = types.StringValue(fmt.Sprint(plan.PolicySetId.ValueString())) + {{- end}} + + {{- else}} + // Create object body := plan.toBody(ctx, {{camelCase .Name}}{}) + {{- if .UpdateDefault}} + if plan.Name.ValueString() != "Default" { + {{- end}} {{- if .PutCreate}} res, err := r.client.Put(plan.getPath(), body) {{- else if and (isErs .RestEndpoint) (not .IdPath) (not (hasId .Attributes))}} @@ -481,6 +523,14 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C } {{- if .IdPath}} plan.Id = types.StringValue(res.Get("{{.IdPath}}").String()) + {{- if and (not (strContains (camelCase .Name) "Rule")) .UpdateDefault }} + if plan.Description.IsUnknown() { + plan.Description = types.StringNull() + } + if plan.Rank.IsUnknown() { + plan.Rank = types.Int64Null() + } + {{- end}} {{- else if hasId .Attributes}} {{- $id := getId .Attributes}} plan.Id = types.StringValue(fmt.Sprint(plan.{{toGoName $id.TfName}}.Value{{$id.Type}}())) @@ -488,6 +538,42 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C locationElements := strings.Split(location, "/") plan.Id = types.StringValue(locationElements[len(locationElements)-1]) {{- end}} + {{- if .UpdateDefault}} + } else { + res, err := r.client.Get(plan.getPath()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) + return + } + if value := res.Get("response"); len(value.Array()) > 0 { + value.ForEach(func(k, v gjson.Result) bool { + {{- if strContains (camelCase .Name) "Rule" }} + if v.Get("rule.name").String() == plan.Name.ValueString() { + plan.Id = types.StringValue(v.Get("rule.id").String()) + return false + } + {{- else}} + if v.Get("name").String() == plan.Name.ValueString() { + plan.Id = types.StringValue(v.Get("id").String()) + plan.Description = types.StringValue(v.Get("description").String()) + plan.Rank = types.Int64Value(v.Get("rank").Int()) + return false + } + {{- end}} + return true + }) + } + {{- if not (strContains (camelCase .Name) "Rule") }} + body = plan.toBody(ctx, {{camelCase .Name}}{}) + {{- end}} + res, err = r.client.Put(plan.getPath()+"/"+plan.Id.ValueString(), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + } + {{- end}} + {{- end}} tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) @@ -537,6 +623,9 @@ func (r *{{camelCase .Name}}Resource) Read(ctx context.Context, req resource.Rea //template:begin update func (r *{{camelCase .Name}}Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan, state {{camelCase .Name}} + {{- if strContains (camelCase .Name) "UpdateRank" }} + var existingData {{strReplace (camelCase .Name) "UpdateRank" "" -1}} + {{- end}} // Read plan diags := req.Plan.Get(ctx, &plan) @@ -553,8 +642,60 @@ func (r *{{camelCase .Name}}Resource) Update(ctx context.Context, req resource.U tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + {{- if strContains (camelCase .Name) "UpdateRank" }} + + // Read existing attributes from the API + {{- if strContains (camelCase .Name) "Rule" }} + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + {{- else}} + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.PolicySetId.ValueString())) + {{- end}} + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + {{- if strContains (camelCase .Name) "Rule" }} + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + {{- else}} + body, _ = sjson.Set(body, "rank", plan.Rank.ValueInt64()) + {{- end}} + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + {{- else}} {{- if not .NoUpdate}} body := plan.toBody(ctx, state) + + {{- if hasAttribute .Attributes "rank"}} + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData {{camelCase .Name}} + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + {{- if strContains (camelCase .Name) "Rule" }} + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + {{- else}} + body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) + {{- end}} + } + {{- end}} {{if .PostUpdate}} res, _, err := r.client.Post(plan.getPath(), body) {{- else}} @@ -565,7 +706,8 @@ func (r *{{camelCase .Name}}Resource) Update(ctx context.Context, req resource.U return } {{- end}} - + {{- end}} + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) diags = resp.State.Set(ctx, &plan) diff --git a/gen/templates/resource_test.go b/gen/templates/resource_test.go index 9114f6a..090bea5 100644 --- a/gen/templates/resource_test.go +++ b/gen/templates/resource_test.go @@ -124,7 +124,7 @@ func TestAccIse{{camelCase .Name}}(t *testing.T) { Config: {{if .TestPrerequisites}}testAccIse{{camelCase .Name}}PrerequisitesConfig+{{end}}testAccIse{{camelCase .Name}}Config_all(), Check: resource.ComposeTestCheckFunc(checks...), }) - {{- if not (hasReference .Attributes)}} + {{- if and (not .NoImport) (not (hasReference .Attributes))}} steps = append(steps, resource.TestStep{ ResourceName: "ise_{{snakeCase $name}}.test", ImportState: true, diff --git a/internal/provider/model_ise_device_admin_authentication_rule_update_rank.go b/internal/provider/model_ise_device_admin_authentication_rule_update_rank.go new file mode 100644 index 0000000..96460d6 --- /dev/null +++ b/internal/provider/model_ise_device_admin_authentication_rule_update_rank.go @@ -0,0 +1,103 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type DeviceAdminAuthenticationRuleUpdateRank struct { + Id types.String `tfsdk:"id"` + RuleId types.String `tfsdk:"rule_id"` + PolicySetId types.String `tfsdk:"policy_set_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data DeviceAdminAuthenticationRuleUpdateRank) getPath() string { + return fmt.Sprintf("/api/v1/policy/device-admin/policy-set/%v/authentication", url.QueryEscape(data.PolicySetId.ValueString())) +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data DeviceAdminAuthenticationRuleUpdateRank) toBody(ctx context.Context, state DeviceAdminAuthenticationRuleUpdateRank) string { + body := "" + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *DeviceAdminAuthenticationRuleUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *DeviceAdminAuthenticationRuleUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *DeviceAdminAuthenticationRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.RuleId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_device_admin_authorization_exception_rule_update_rank.go b/internal/provider/model_ise_device_admin_authorization_exception_rule_update_rank.go new file mode 100644 index 0000000..4dd901f --- /dev/null +++ b/internal/provider/model_ise_device_admin_authorization_exception_rule_update_rank.go @@ -0,0 +1,103 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type DeviceAdminAuthorizationExceptionRuleUpdateRank struct { + Id types.String `tfsdk:"id"` + RuleId types.String `tfsdk:"rule_id"` + PolicySetId types.String `tfsdk:"policy_set_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data DeviceAdminAuthorizationExceptionRuleUpdateRank) getPath() string { + return fmt.Sprintf("/api/v1/policy/device-admin/policy-set/%v/exception", url.QueryEscape(data.PolicySetId.ValueString())) +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data DeviceAdminAuthorizationExceptionRuleUpdateRank) toBody(ctx context.Context, state DeviceAdminAuthorizationExceptionRuleUpdateRank) string { + body := "" + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *DeviceAdminAuthorizationExceptionRuleUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *DeviceAdminAuthorizationExceptionRuleUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *DeviceAdminAuthorizationExceptionRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.RuleId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_device_admin_authorization_global_exception_rule_update_rank.go b/internal/provider/model_ise_device_admin_authorization_global_exception_rule_update_rank.go new file mode 100644 index 0000000..242bc8f --- /dev/null +++ b/internal/provider/model_ise_device_admin_authorization_global_exception_rule_update_rank.go @@ -0,0 +1,100 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank struct { + Id types.String `tfsdk:"id"` + RuleId types.String `tfsdk:"rule_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank) getPath() string { + return "/api/v1/policy/device-admin/policy-set/global-exception" +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank) toBody(ctx context.Context, state DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank) string { + body := "" + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.RuleId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_device_admin_authorization_rule_update_rank.go b/internal/provider/model_ise_device_admin_authorization_rule_update_rank.go new file mode 100644 index 0000000..bd2f116 --- /dev/null +++ b/internal/provider/model_ise_device_admin_authorization_rule_update_rank.go @@ -0,0 +1,103 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type DeviceAdminAuthorizationRuleUpdateRank struct { + Id types.String `tfsdk:"id"` + RuleId types.String `tfsdk:"rule_id"` + PolicySetId types.String `tfsdk:"policy_set_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data DeviceAdminAuthorizationRuleUpdateRank) getPath() string { + return fmt.Sprintf("/api/v1/policy/device-admin/policy-set/%v/authorization", url.QueryEscape(data.PolicySetId.ValueString())) +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data DeviceAdminAuthorizationRuleUpdateRank) toBody(ctx context.Context, state DeviceAdminAuthorizationRuleUpdateRank) string { + body := "" + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *DeviceAdminAuthorizationRuleUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *DeviceAdminAuthorizationRuleUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *DeviceAdminAuthorizationRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.RuleId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_device_admin_policy_set_update_rank.go b/internal/provider/model_ise_device_admin_policy_set_update_rank.go new file mode 100644 index 0000000..4b84e30 --- /dev/null +++ b/internal/provider/model_ise_device_admin_policy_set_update_rank.go @@ -0,0 +1,100 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type DeviceAdminPolicySetUpdateRank struct { + Id types.String `tfsdk:"id"` + PolicySetId types.String `tfsdk:"policy_set_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data DeviceAdminPolicySetUpdateRank) getPath() string { + return "/api/v1/policy/device-admin/policy-set" +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data DeviceAdminPolicySetUpdateRank) toBody(ctx context.Context, state DeviceAdminPolicySetUpdateRank) string { + body := "" + if !data.PolicySetId.IsNull() { + body, _ = sjson.Set(body, "", data.PolicySetId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *DeviceAdminPolicySetUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *DeviceAdminPolicySetUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *DeviceAdminPolicySetUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.PolicySetId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_network_access_authentication_rule_update_rank.go b/internal/provider/model_ise_network_access_authentication_rule_update_rank.go index e9338d5..e4829a8 100644 --- a/internal/provider/model_ise_network_access_authentication_rule_update_rank.go +++ b/internal/provider/model_ise_network_access_authentication_rule_update_rank.go @@ -35,7 +35,7 @@ import ( //template:begin types type NetworkAccessAuthenticationRuleUpdateRank struct { Id types.String `tfsdk:"id"` - AuthRuleId types.String `tfsdk:"auth_rule_id"` + RuleId types.String `tfsdk:"rule_id"` PolicySetId types.String `tfsdk:"policy_set_id"` Rank types.Int64 `tfsdk:"rank"` } @@ -56,8 +56,8 @@ func (data NetworkAccessAuthenticationRuleUpdateRank) getPath() string { //template:begin toBody func (data NetworkAccessAuthenticationRuleUpdateRank) toBody(ctx context.Context, state NetworkAccessAuthenticationRuleUpdateRank) string { body := "" - if !data.AuthRuleId.IsNull() { - body, _ = sjson.Set(body, "", data.AuthRuleId.ValueString()) + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) } if !data.Rank.IsNull() { body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) @@ -91,7 +91,7 @@ func (data *NetworkAccessAuthenticationRuleUpdateRank) updateFromBody(ctx contex //template:begin isNull func (data *NetworkAccessAuthenticationRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { - if !data.AuthRuleId.IsNull() { + if !data.RuleId.IsNull() { return false } if !data.Rank.IsNull() { diff --git a/internal/provider/model_ise_network_access_authorization_exception_rule_update_rank.go b/internal/provider/model_ise_network_access_authorization_exception_rule_update_rank.go new file mode 100644 index 0000000..32e1cb0 --- /dev/null +++ b/internal/provider/model_ise_network_access_authorization_exception_rule_update_rank.go @@ -0,0 +1,103 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type NetworkAccessAuthorizationExceptionRuleUpdateRank struct { + Id types.String `tfsdk:"id"` + RuleId types.String `tfsdk:"rule_id"` + PolicySetId types.String `tfsdk:"policy_set_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data NetworkAccessAuthorizationExceptionRuleUpdateRank) getPath() string { + return fmt.Sprintf("/api/v1/policy/network-access/policy-set/%v/exception", url.QueryEscape(data.PolicySetId.ValueString())) +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data NetworkAccessAuthorizationExceptionRuleUpdateRank) toBody(ctx context.Context, state NetworkAccessAuthorizationExceptionRuleUpdateRank) string { + body := "" + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *NetworkAccessAuthorizationExceptionRuleUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *NetworkAccessAuthorizationExceptionRuleUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *NetworkAccessAuthorizationExceptionRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.RuleId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_network_access_authorization_global_exception_rule_update_rank.go b/internal/provider/model_ise_network_access_authorization_global_exception_rule_update_rank.go new file mode 100644 index 0000000..a5c6437 --- /dev/null +++ b/internal/provider/model_ise_network_access_authorization_global_exception_rule_update_rank.go @@ -0,0 +1,100 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank struct { + Id types.String `tfsdk:"id"` + RuleId types.String `tfsdk:"rule_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank) getPath() string { + return "/api/v1/policy/network-access/policy-set/global-exception" +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank) toBody(ctx context.Context, state NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank) string { + body := "" + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.RuleId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_network_access_authorization_rule_update_rank.go b/internal/provider/model_ise_network_access_authorization_rule_update_rank.go new file mode 100644 index 0000000..4af566a --- /dev/null +++ b/internal/provider/model_ise_network_access_authorization_rule_update_rank.go @@ -0,0 +1,103 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type NetworkAccessAuthorizationRuleUpdateRank struct { + Id types.String `tfsdk:"id"` + RuleId types.String `tfsdk:"rule_id"` + PolicySetId types.String `tfsdk:"policy_set_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data NetworkAccessAuthorizationRuleUpdateRank) getPath() string { + return fmt.Sprintf("/api/v1/policy/network-access/policy-set/%v/authorization", url.QueryEscape(data.PolicySetId.ValueString())) +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data NetworkAccessAuthorizationRuleUpdateRank) toBody(ctx context.Context, state NetworkAccessAuthorizationRuleUpdateRank) string { + body := "" + if !data.RuleId.IsNull() { + body, _ = sjson.Set(body, "", data.RuleId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rule.rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *NetworkAccessAuthorizationRuleUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *NetworkAccessAuthorizationRuleUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rule.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *NetworkAccessAuthorizationRuleUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.RuleId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/model_ise_network_access_policy_set_update_rank.go b/internal/provider/model_ise_network_access_policy_set_update_rank.go new file mode 100644 index 0000000..387b9fe --- /dev/null +++ b/internal/provider/model_ise_network_access_policy_set_update_rank.go @@ -0,0 +1,100 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin types +type NetworkAccessPolicySetUpdateRank struct { + Id types.String `tfsdk:"id"` + PolicySetId types.String `tfsdk:"policy_set_id"` + Rank types.Int64 `tfsdk:"rank"` +} + +//template:end types + +//template:begin getPath +func (data NetworkAccessPolicySetUpdateRank) getPath() string { + return "/api/v1/policy/network-access/policy-set" +} + +//template:end getPath + +//template:begin getPathDelete + +//template:end getPathDelete + +//template:begin toBody +func (data NetworkAccessPolicySetUpdateRank) toBody(ctx context.Context, state NetworkAccessPolicySetUpdateRank) string { + body := "" + if !data.PolicySetId.IsNull() { + body, _ = sjson.Set(body, "", data.PolicySetId.ValueString()) + } + if !data.Rank.IsNull() { + body, _ = sjson.Set(body, "rank", data.Rank.ValueInt64()) + } + return body +} + +//template:end toBody + +//template:begin fromBody +func (data *NetworkAccessPolicySetUpdateRank) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rank"); value.Exists() && value.Type != gjson.Null { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end fromBody + +//template:begin updateFromBody +func (data *NetworkAccessPolicySetUpdateRank) updateFromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("response.rank"); value.Exists() && !data.Rank.IsNull() { + data.Rank = types.Int64Value(value.Int()) + } else { + data.Rank = types.Int64Null() + } +} + +//template:end updateFromBody + +//template:begin isNull +func (data *NetworkAccessPolicySetUpdateRank) isNull(ctx context.Context, res gjson.Result) bool { + if !data.PolicySetId.IsNull() { + return false + } + if !data.Rank.IsNull() { + return false + } + return true +} + +//template:end isNull diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 28dafbe..2fdf8a6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -250,11 +250,16 @@ func (p *IseProvider) Resources(ctx context.Context) []func() resource.Resource NewAuthorizationProfileResource, NewCertificateAuthenticationProfileResource, NewDeviceAdminAuthenticationRuleResource, + NewDeviceAdminAuthenticationRuleUpdateRankResource, NewDeviceAdminAuthorizationExceptionRuleResource, + NewDeviceAdminAuthorizationExceptionRuleUpdateRankResource, NewDeviceAdminAuthorizationGlobalExceptionRuleResource, + NewDeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource, NewDeviceAdminAuthorizationRuleResource, + NewDeviceAdminAuthorizationRuleUpdateRankResource, NewDeviceAdminConditionResource, NewDeviceAdminPolicySetResource, + NewDeviceAdminPolicySetUpdateRankResource, NewDeviceAdminTimeAndDateConditionResource, NewDownloadableACLResource, NewEndpointResource, @@ -265,11 +270,15 @@ func (p *IseProvider) Resources(ctx context.Context) []func() resource.Resource NewNetworkAccessAuthenticationRuleResource, NewNetworkAccessAuthenticationRuleUpdateRankResource, NewNetworkAccessAuthorizationExceptionRuleResource, + NewNetworkAccessAuthorizationExceptionRuleUpdateRankResource, NewNetworkAccessAuthorizationGlobalExceptionRuleResource, + NewNetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource, NewNetworkAccessAuthorizationRuleResource, + NewNetworkAccessAuthorizationRuleUpdateRankResource, NewNetworkAccessConditionResource, NewNetworkAccessDictionaryResource, NewNetworkAccessPolicySetResource, + NewNetworkAccessPolicySetUpdateRankResource, NewNetworkAccessTimeAndDateConditionResource, NewNetworkDeviceResource, NewNetworkDeviceGroupResource, diff --git a/internal/provider/resource_ise_device_admin_authentication_rule.go b/internal/provider/resource_ise_device_admin_authentication_rule.go index ba52cea..7592f88 100644 --- a/internal/provider/resource_ise_device_admin_authentication_rule.go +++ b/internal/provider/resource_ise_device_admin_authentication_rule.go @@ -38,6 +38,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" "github.com/tidwall/gjson" + "github.com/tidwall/sjson" ) //template:end imports @@ -274,6 +275,7 @@ func (r *DeviceAdminAuthenticationRuleResource) Configure(_ context.Context, req //template:end configure +//template:begin create func (r *DeviceAdminAuthenticationRuleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan DeviceAdminAuthenticationRule @@ -323,6 +325,8 @@ func (r *DeviceAdminAuthenticationRuleResource) Create(ctx context.Context, req resp.Diagnostics.Append(diags...) } +//template:end create + //template:begin read func (r *DeviceAdminAuthenticationRuleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state DeviceAdminAuthenticationRule @@ -378,6 +382,21 @@ func (r *DeviceAdminAuthenticationRuleResource) Update(ctx context.Context, req tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData DeviceAdminAuthenticationRule + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_device_admin_authentication_rule_update_rank.go b/internal/provider/resource_ise_device_admin_authentication_rule_update_rank.go new file mode 100644 index 0000000..4ef7308 --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authentication_rule_update_rank.go @@ -0,0 +1,256 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &DeviceAdminAuthenticationRuleUpdateRankResource{} + +func NewDeviceAdminAuthenticationRuleUpdateRankResource() resource.Resource { + return &DeviceAdminAuthenticationRuleUpdateRankResource{} +} + +type DeviceAdminAuthenticationRuleUpdateRankResource struct { + client *ise.Client +} + +func (r *DeviceAdminAuthenticationRuleUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_device_admin_authentication_rule_update_rank" +} + +//template:end header + +//template:begin model +func (r *DeviceAdminAuthenticationRuleUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in device admin authentication rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authentication_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rule_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Authentication rule ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "policy_set_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Policy set ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *DeviceAdminAuthenticationRuleUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *DeviceAdminAuthenticationRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan DeviceAdminAuthenticationRuleUpdateRank + var existingData DeviceAdminAuthenticationRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *DeviceAdminAuthenticationRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state DeviceAdminAuthenticationRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *DeviceAdminAuthenticationRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state DeviceAdminAuthenticationRuleUpdateRank + var existingData DeviceAdminAuthenticationRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *DeviceAdminAuthenticationRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state DeviceAdminAuthenticationRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_device_admin_authentication_rule_update_rank_test.go b/internal/provider/resource_ise_device_admin_authentication_rule_update_rank_test.go new file mode 100644 index 0000000..3bb8685 --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authentication_rule_update_rank_test.go @@ -0,0 +1,107 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseDeviceAdminAuthenticationRuleUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_device_admin_authentication_rule_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseDeviceAdminAuthenticationRuleUpdateRankPrerequisitesConfig + testAccIseDeviceAdminAuthenticationRuleUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseDeviceAdminAuthenticationRuleUpdateRankPrerequisitesConfig = ` +resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Device Admin" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} +resource "ise_device_admin_authentication_rule" "test" { + policy_set_id = ise_device_admin_policy_set.test.id + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + identity_source_name = "Internal Endpoints" + if_auth_fail = "REJECT" + if_process_fail = "DROP" + if_user_not_found = "REJECT" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseDeviceAdminAuthenticationRuleUpdateRankConfig_minimum() string { + config := `resource "ise_device_admin_authentication_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authentication_rule.test.id` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseDeviceAdminAuthenticationRuleUpdateRankConfig_all() string { + config := `resource "ise_device_admin_authentication_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authentication_rule.test.id` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_device_admin_authorization_exception_rule.go b/internal/provider/resource_ise_device_admin_authorization_exception_rule.go index a982ffd..f8aa0be 100644 --- a/internal/provider/resource_ise_device_admin_authorization_exception_rule.go +++ b/internal/provider/resource_ise_device_admin_authorization_exception_rule.go @@ -37,6 +37,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" + "github.com/tidwall/sjson" ) //template:end imports @@ -342,6 +343,21 @@ func (r *DeviceAdminAuthorizationExceptionRuleResource) Update(ctx context.Conte tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData DeviceAdminAuthorizationExceptionRule + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_device_admin_authorization_exception_rule_update_rank.go b/internal/provider/resource_ise_device_admin_authorization_exception_rule_update_rank.go new file mode 100644 index 0000000..152ac12 --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authorization_exception_rule_update_rank.go @@ -0,0 +1,256 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &DeviceAdminAuthorizationExceptionRuleUpdateRankResource{} + +func NewDeviceAdminAuthorizationExceptionRuleUpdateRankResource() resource.Resource { + return &DeviceAdminAuthorizationExceptionRuleUpdateRankResource{} +} + +type DeviceAdminAuthorizationExceptionRuleUpdateRankResource struct { + client *ise.Client +} + +func (r *DeviceAdminAuthorizationExceptionRuleUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_device_admin_authorization_exception_rule_update_rank" +} + +//template:end header + +//template:begin model +func (r *DeviceAdminAuthorizationExceptionRuleUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in device admin Authorization exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rule_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Authorization exception rule ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "policy_set_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Policy set ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *DeviceAdminAuthorizationExceptionRuleUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *DeviceAdminAuthorizationExceptionRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan DeviceAdminAuthorizationExceptionRuleUpdateRank + var existingData DeviceAdminAuthorizationExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *DeviceAdminAuthorizationExceptionRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state DeviceAdminAuthorizationExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *DeviceAdminAuthorizationExceptionRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state DeviceAdminAuthorizationExceptionRuleUpdateRank + var existingData DeviceAdminAuthorizationExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *DeviceAdminAuthorizationExceptionRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state DeviceAdminAuthorizationExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_device_admin_authorization_exception_rule_update_rank_test.go b/internal/provider/resource_ise_device_admin_authorization_exception_rule_update_rank_test.go new file mode 100644 index 0000000..1067bdb --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authorization_exception_rule_update_rank_test.go @@ -0,0 +1,105 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseDeviceAdminAuthorizationExceptionRuleUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_device_admin_authorization_exception_rule_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseDeviceAdminAuthorizationExceptionRuleUpdateRankPrerequisitesConfig + testAccIseDeviceAdminAuthorizationExceptionRuleUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseDeviceAdminAuthorizationExceptionRuleUpdateRankPrerequisitesConfig = ` +resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Device Admin" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} +resource "ise_device_admin_authorization_exception_rule" "test" { + policy_set_id = ise_device_admin_policy_set.test.id + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + command_sets = ["DenyAllCommands"] + profile = "Default Shell Profile" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseDeviceAdminAuthorizationExceptionRuleUpdateRankConfig_minimum() string { + config := `resource "ise_device_admin_authorization_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authorization_exception_rule.test.id` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseDeviceAdminAuthorizationExceptionRuleUpdateRankConfig_all() string { + config := `resource "ise_device_admin_authorization_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authorization_exception_rule.test.id` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_device_admin_authorization_global_exception_rule.go b/internal/provider/resource_ise_device_admin_authorization_global_exception_rule.go index 5f3bda5..0fb3139 100644 --- a/internal/provider/resource_ise_device_admin_authorization_global_exception_rule.go +++ b/internal/provider/resource_ise_device_admin_authorization_global_exception_rule.go @@ -37,6 +37,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" + "github.com/tidwall/sjson" ) //template:end imports @@ -335,6 +336,21 @@ func (r *DeviceAdminAuthorizationGlobalExceptionRuleResource) Update(ctx context tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData DeviceAdminAuthorizationGlobalExceptionRule + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_device_admin_authorization_global_exception_rule_update_rank.go b/internal/provider/resource_ise_device_admin_authorization_global_exception_rule_update_rank.go new file mode 100644 index 0000000..1a3eef2 --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authorization_global_exception_rule_update_rank.go @@ -0,0 +1,249 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource{} + +func NewDeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource() resource.Resource { + return &DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource{} +} + +type DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource struct { + client *ise.Client +} + +func (r *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_device_admin_authorization_global_exception_rule_update_rank" +} + +//template:end header + +//template:begin model +func (r *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in device admin authorization global exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_global_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rule_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Authorization global exception rule ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank + var existingData DeviceAdminAuthorizationGlobalExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank + var existingData DeviceAdminAuthorizationGlobalExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *DeviceAdminAuthorizationGlobalExceptionRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state DeviceAdminAuthorizationGlobalExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_device_admin_authorization_global_exception_rule_update_rank_test.go b/internal/provider/resource_ise_device_admin_authorization_global_exception_rule_update_rank_test.go new file mode 100644 index 0000000..24ae9ec --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authorization_global_exception_rule_update_rank_test.go @@ -0,0 +1,92 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseDeviceAdminAuthorizationGlobalExceptionRuleUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_device_admin_authorization_global_exception_rule_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseDeviceAdminAuthorizationGlobalExceptionRuleUpdateRankPrerequisitesConfig + testAccIseDeviceAdminAuthorizationGlobalExceptionRuleUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseDeviceAdminAuthorizationGlobalExceptionRuleUpdateRankPrerequisitesConfig = ` +resource "ise_device_admin_authorization_global_exception_rule" "test" { + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + command_sets = ["DenyAllCommands"] + profile = "Default Shell Profile" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseDeviceAdminAuthorizationGlobalExceptionRuleUpdateRankConfig_minimum() string { + config := `resource "ise_device_admin_authorization_global_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authorization_global_exception_rule.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseDeviceAdminAuthorizationGlobalExceptionRuleUpdateRankConfig_all() string { + config := `resource "ise_device_admin_authorization_global_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authorization_global_exception_rule.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_device_admin_authorization_rule.go b/internal/provider/resource_ise_device_admin_authorization_rule.go index 2484d77..0d3e74f 100644 --- a/internal/provider/resource_ise_device_admin_authorization_rule.go +++ b/internal/provider/resource_ise_device_admin_authorization_rule.go @@ -38,6 +38,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" "github.com/tidwall/gjson" + "github.com/tidwall/sjson" ) //template:end imports @@ -258,6 +259,7 @@ func (r *DeviceAdminAuthorizationRuleResource) Configure(_ context.Context, req //template:end configure +//template:begin create func (r *DeviceAdminAuthorizationRuleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan DeviceAdminAuthorizationRule @@ -307,6 +309,8 @@ func (r *DeviceAdminAuthorizationRuleResource) Create(ctx context.Context, req r resp.Diagnostics.Append(diags...) } +//template:end create + //template:begin read func (r *DeviceAdminAuthorizationRuleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state DeviceAdminAuthorizationRule @@ -362,6 +366,21 @@ func (r *DeviceAdminAuthorizationRuleResource) Update(ctx context.Context, req r tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData DeviceAdminAuthorizationRule + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_device_admin_authorization_rule_update_rank.go b/internal/provider/resource_ise_device_admin_authorization_rule_update_rank.go new file mode 100644 index 0000000..e81bd84 --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authorization_rule_update_rank.go @@ -0,0 +1,256 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &DeviceAdminAuthorizationRuleUpdateRankResource{} + +func NewDeviceAdminAuthorizationRuleUpdateRankResource() resource.Resource { + return &DeviceAdminAuthorizationRuleUpdateRankResource{} +} + +type DeviceAdminAuthorizationRuleUpdateRankResource struct { + client *ise.Client +} + +func (r *DeviceAdminAuthorizationRuleUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_device_admin_authorization_rule_update_rank" +} + +//template:end header + +//template:begin model +func (r *DeviceAdminAuthorizationRuleUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in device admin authorization rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_authorization_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rule_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Authorization rule ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "policy_set_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Policy set ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *DeviceAdminAuthorizationRuleUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *DeviceAdminAuthorizationRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan DeviceAdminAuthorizationRuleUpdateRank + var existingData DeviceAdminAuthorizationRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *DeviceAdminAuthorizationRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state DeviceAdminAuthorizationRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *DeviceAdminAuthorizationRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state DeviceAdminAuthorizationRuleUpdateRank + var existingData DeviceAdminAuthorizationRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *DeviceAdminAuthorizationRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state DeviceAdminAuthorizationRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_device_admin_authorization_rule_update_rank_test.go b/internal/provider/resource_ise_device_admin_authorization_rule_update_rank_test.go new file mode 100644 index 0000000..41633ce --- /dev/null +++ b/internal/provider/resource_ise_device_admin_authorization_rule_update_rank_test.go @@ -0,0 +1,105 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseDeviceAdminAuthorizationRuleUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_device_admin_authorization_rule_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseDeviceAdminAuthorizationRuleUpdateRankPrerequisitesConfig + testAccIseDeviceAdminAuthorizationRuleUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseDeviceAdminAuthorizationRuleUpdateRankPrerequisitesConfig = ` +resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Device Admin" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} +resource "ise_device_admin_authorization_rule" "test" { + policy_set_id = ise_device_admin_policy_set.test.id + name = "Rule1" + default = false + rank = 0 + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + command_sets = ["DenyAllCommands"] + profile = "Default Shell Profile" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseDeviceAdminAuthorizationRuleUpdateRankConfig_minimum() string { + config := `resource "ise_device_admin_authorization_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authorization_rule.test.id` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseDeviceAdminAuthorizationRuleUpdateRankConfig_all() string { + config := `resource "ise_device_admin_authorization_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_device_admin_authorization_rule.test.id` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_device_admin_policy_set.go b/internal/provider/resource_ise_device_admin_policy_set.go index 10ae990..f109da8 100644 --- a/internal/provider/resource_ise_device_admin_policy_set.go +++ b/internal/provider/resource_ise_device_admin_policy_set.go @@ -31,7 +31,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "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" @@ -39,6 +38,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" "github.com/tidwall/gjson" + "github.com/tidwall/sjson" ) //template:end imports @@ -96,10 +96,6 @@ func (r *DeviceAdminPolicySetResource) Schema(ctx context.Context, req resource. "rank": schema.Int64Attribute{ MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other policy sets. Lower rank is higher priority.").String, Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.UseStateForUnknown(), - }, }, "service_name": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Policy set service identifier. 'Allowed Protocols' or 'Server Sequence'.").String, @@ -263,6 +259,7 @@ func (r *DeviceAdminPolicySetResource) Configure(_ context.Context, req resource //template:end configure +//template:begin create func (r *DeviceAdminPolicySetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan DeviceAdminPolicySet @@ -321,6 +318,8 @@ func (r *DeviceAdminPolicySetResource) Create(ctx context.Context, req resource. resp.Diagnostics.Append(diags...) } +//template:end create + //template:begin read func (r *DeviceAdminPolicySetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state DeviceAdminPolicySet @@ -376,6 +375,21 @@ func (r *DeviceAdminPolicySetResource) Update(ctx context.Context, req resource. tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData DeviceAdminPolicySet + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_device_admin_policy_set_update_rank.go b/internal/provider/resource_ise_device_admin_policy_set_update_rank.go new file mode 100644 index 0000000..7b53afe --- /dev/null +++ b/internal/provider/resource_ise_device_admin_policy_set_update_rank.go @@ -0,0 +1,249 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &DeviceAdminPolicySetUpdateRankResource{} + +func NewDeviceAdminPolicySetUpdateRankResource() resource.Resource { + return &DeviceAdminPolicySetUpdateRankResource{} +} + +type DeviceAdminPolicySetUpdateRankResource struct { + client *ise.Client +} + +func (r *DeviceAdminPolicySetUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_device_admin_policy_set_update_rank" +} + +//template:end header + +//template:begin model +func (r *DeviceAdminPolicySetUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in device admin policy set. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and device_admin_policy_set resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "policy_set_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Policy set ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *DeviceAdminPolicySetUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *DeviceAdminPolicySetUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan DeviceAdminPolicySetUpdateRank + var existingData DeviceAdminPolicySet + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.PolicySetId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.PolicySetId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.PolicySetId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *DeviceAdminPolicySetUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state DeviceAdminPolicySetUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *DeviceAdminPolicySetUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state DeviceAdminPolicySetUpdateRank + var existingData DeviceAdminPolicySet + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.PolicySetId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *DeviceAdminPolicySetUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state DeviceAdminPolicySetUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_device_admin_policy_set_update_rank_test.go b/internal/provider/resource_ise_device_admin_policy_set_update_rank_test.go new file mode 100644 index 0000000..46d656d --- /dev/null +++ b/internal/provider/resource_ise_device_admin_policy_set_update_rank_test.go @@ -0,0 +1,91 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseDeviceAdminPolicySetUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_device_admin_policy_set_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseDeviceAdminPolicySetUpdateRankPrerequisitesConfig + testAccIseDeviceAdminPolicySetUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseDeviceAdminPolicySetUpdateRankPrerequisitesConfig = ` +resource "ise_device_admin_policy_set" "test" { + name = "PolicySet1" + description = "My description" + is_proxy = false + service_name = "Default Device Admin" + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseDeviceAdminPolicySetUpdateRankConfig_minimum() string { + config := `resource "ise_device_admin_policy_set_update_rank" "test" {` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseDeviceAdminPolicySetUpdateRankConfig_all() string { + config := `resource "ise_device_admin_policy_set_update_rank" "test" {` + "\n" + config += ` policy_set_id = ise_device_admin_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_network_access_authentication_rule.go b/internal/provider/resource_ise_network_access_authentication_rule.go index 535d4d7..54ecae6 100644 --- a/internal/provider/resource_ise_network_access_authentication_rule.go +++ b/internal/provider/resource_ise_network_access_authentication_rule.go @@ -275,6 +275,7 @@ func (r *NetworkAccessAuthenticationRuleResource) Configure(_ context.Context, r //template:end configure +//template:begin create func (r *NetworkAccessAuthenticationRuleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan NetworkAccessAuthenticationRule @@ -289,7 +290,6 @@ func (r *NetworkAccessAuthenticationRuleResource) Create(ctx context.Context, re // Create object body := plan.toBody(ctx, NetworkAccessAuthenticationRule{}) - if plan.Name.ValueString() != "Default" { res, _, err := r.client.Post(plan.getPath(), body) if err != nil { @@ -325,6 +325,8 @@ func (r *NetworkAccessAuthenticationRuleResource) Create(ctx context.Context, re resp.Diagnostics.Append(diags...) } +//template:end create + //template:begin read func (r *NetworkAccessAuthenticationRuleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state NetworkAccessAuthenticationRule @@ -361,8 +363,9 @@ func (r *NetworkAccessAuthenticationRuleResource) Read(ctx context.Context, req //template:end read +//template:begin update func (r *NetworkAccessAuthenticationRuleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan, state, existingData NetworkAccessAuthenticationRule + var plan, state NetworkAccessAuthenticationRule // Read plan diags := req.Plan.Get(ctx, &plan) @@ -379,18 +382,19 @@ func (r *NetworkAccessAuthenticationRuleResource) Update(ctx context.Context, re tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) - - // Check if plan.Rank is null (i.e., not provided) and set rank to body from existingData to not reorder rule during update + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update if plan.Rank.IsNull() { - // Read existing attributes from the API + var existingData NetworkAccessAuthenticationRule + // Fetch existing data from the API res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) return } + // Populate existingData with current state from the API existingData.fromBody(ctx, res) - - // If plan.Rank is not provided, set it from existingData.Rank + // Set Rank in the request body from the existing data if it's missing from the plan body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) } @@ -406,6 +410,8 @@ func (r *NetworkAccessAuthenticationRuleResource) Update(ctx context.Context, re resp.Diagnostics.Append(diags...) } +//template:end update + //template:begin delete func (r *NetworkAccessAuthenticationRuleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state NetworkAccessAuthenticationRule diff --git a/internal/provider/resource_ise_network_access_authentication_rule_update_rank.go b/internal/provider/resource_ise_network_access_authentication_rule_update_rank.go index f1ee9d7..836f5cc 100644 --- a/internal/provider/resource_ise_network_access_authentication_rule_update_rank.go +++ b/internal/provider/resource_ise_network_access_authentication_rule_update_rank.go @@ -72,7 +72,7 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Schema(ctx context.C stringplanmodifier.UseStateForUnknown(), }, }, - "auth_rule_id": schema.StringAttribute{ + "rule_id": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Authentication rule ID").String, Required: true, PlanModifiers: []planmodifier.String{ @@ -107,6 +107,7 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Configure(_ context. //template:end configure +//template:begin create func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan NetworkAccessAuthenticationRuleUpdateRank var existingData NetworkAccessAuthenticationRule @@ -119,9 +120,8 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Create(ctx context.C } tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) - // Read existing attributes from the API - res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.AuthRuleId.ValueString())) + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) return @@ -133,13 +133,12 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Create(ctx context.C // Update rank body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) - - res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.AuthRuleId.ValueString()), body) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) return } - plan.Id = types.StringValue(fmt.Sprint(plan.AuthRuleId.ValueString())) + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) @@ -147,6 +146,8 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Create(ctx context.C resp.Diagnostics.Append(diags...) } +//template:end create + //template:begin read func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state NetworkAccessAuthenticationRuleUpdateRank @@ -183,6 +184,7 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Read(ctx context.Con //template:end read +//template:begin update func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan, state NetworkAccessAuthenticationRuleUpdateRank var existingData NetworkAccessAuthenticationRule @@ -203,7 +205,7 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Update(ctx context.C tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) // Read existing attributes from the API - res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.AuthRuleId.ValueString())) + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) return @@ -228,6 +230,8 @@ func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Update(ctx context.C resp.Diagnostics.Append(diags...) } +//template:end update + //template:begin delete func (r *NetworkAccessAuthenticationRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state NetworkAccessAuthenticationRuleUpdateRank diff --git a/internal/provider/resource_ise_network_access_authentication_rule_update_rank_test.go b/internal/provider/resource_ise_network_access_authentication_rule_update_rank_test.go index 47bb2ae..88f061f 100644 --- a/internal/provider/resource_ise_network_access_authentication_rule_update_rank_test.go +++ b/internal/provider/resource_ise_network_access_authentication_rule_update_rank_test.go @@ -83,7 +83,7 @@ resource "ise_network_access_authentication_rule" "test" { //template:begin testAccConfigMinimal func testAccIseNetworkAccessAuthenticationRuleUpdateRankConfig_minimum() string { config := `resource "ise_network_access_authentication_rule_update_rank" "test" {` + "\n" - config += ` auth_rule_id = ise_network_access_authentication_rule.test.id` + "\n" + config += ` rule_id = ise_network_access_authentication_rule.test.id` + "\n" config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" config += ` rank = 0` + "\n" config += `}` + "\n" @@ -95,7 +95,7 @@ func testAccIseNetworkAccessAuthenticationRuleUpdateRankConfig_minimum() string //template:begin testAccConfigAll func testAccIseNetworkAccessAuthenticationRuleUpdateRankConfig_all() string { config := `resource "ise_network_access_authentication_rule_update_rank" "test" {` + "\n" - config += ` auth_rule_id = ise_network_access_authentication_rule.test.id` + "\n" + config += ` rule_id = ise_network_access_authentication_rule.test.id` + "\n" config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" config += ` rank = 0` + "\n" config += `}` + "\n" diff --git a/internal/provider/resource_ise_network_access_authorization_exception_rule.go b/internal/provider/resource_ise_network_access_authorization_exception_rule.go index b52fb3d..beea0a2 100644 --- a/internal/provider/resource_ise_network_access_authorization_exception_rule.go +++ b/internal/provider/resource_ise_network_access_authorization_exception_rule.go @@ -37,6 +37,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" + "github.com/tidwall/sjson" ) //template:end imports @@ -342,6 +343,21 @@ func (r *NetworkAccessAuthorizationExceptionRuleResource) Update(ctx context.Con tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData NetworkAccessAuthorizationExceptionRule + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_network_access_authorization_exception_rule_update_rank.go b/internal/provider/resource_ise_network_access_authorization_exception_rule_update_rank.go new file mode 100644 index 0000000..43c7352 --- /dev/null +++ b/internal/provider/resource_ise_network_access_authorization_exception_rule_update_rank.go @@ -0,0 +1,256 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &NetworkAccessAuthorizationExceptionRuleUpdateRankResource{} + +func NewNetworkAccessAuthorizationExceptionRuleUpdateRankResource() resource.Resource { + return &NetworkAccessAuthorizationExceptionRuleUpdateRankResource{} +} + +type NetworkAccessAuthorizationExceptionRuleUpdateRankResource struct { + client *ise.Client +} + +func (r *NetworkAccessAuthorizationExceptionRuleUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_access_authorization_exception_rule_update_rank" +} + +//template:end header + +//template:begin model +func (r *NetworkAccessAuthorizationExceptionRuleUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in network access authorization exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rule_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Authorization exception rule ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "policy_set_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Policy set ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *NetworkAccessAuthorizationExceptionRuleUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *NetworkAccessAuthorizationExceptionRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan NetworkAccessAuthorizationExceptionRuleUpdateRank + var existingData NetworkAccessAuthorizationExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *NetworkAccessAuthorizationExceptionRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state NetworkAccessAuthorizationExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *NetworkAccessAuthorizationExceptionRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state NetworkAccessAuthorizationExceptionRuleUpdateRank + var existingData NetworkAccessAuthorizationExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *NetworkAccessAuthorizationExceptionRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state NetworkAccessAuthorizationExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_network_access_authorization_exception_rule_update_rank_test.go b/internal/provider/resource_ise_network_access_authorization_exception_rule_update_rank_test.go new file mode 100644 index 0000000..da8d773 --- /dev/null +++ b/internal/provider/resource_ise_network_access_authorization_exception_rule_update_rank_test.go @@ -0,0 +1,104 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseNetworkAccessAuthorizationExceptionRuleUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_network_access_authorization_exception_rule_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseNetworkAccessAuthorizationExceptionRuleUpdateRankPrerequisitesConfig + testAccIseNetworkAccessAuthorizationExceptionRuleUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseNetworkAccessAuthorizationExceptionRuleUpdateRankPrerequisitesConfig = ` +resource "ise_network_access_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} +resource "ise_network_access_authorization_exception_rule" "test" { + policy_set_id = ise_network_access_policy_set.test.id + name = "Rule1" + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + profiles = ["PermitAccess"] + security_group = "BYOD" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseNetworkAccessAuthorizationExceptionRuleUpdateRankConfig_minimum() string { + config := `resource "ise_network_access_authorization_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_network_access_authorization_exception_rule.test.id` + "\n" + config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseNetworkAccessAuthorizationExceptionRuleUpdateRankConfig_all() string { + config := `resource "ise_network_access_authorization_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_network_access_authorization_exception_rule.test.id` + "\n" + config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_network_access_authorization_global_exception_rule.go b/internal/provider/resource_ise_network_access_authorization_global_exception_rule.go index 721eb72..01c881f 100644 --- a/internal/provider/resource_ise_network_access_authorization_global_exception_rule.go +++ b/internal/provider/resource_ise_network_access_authorization_global_exception_rule.go @@ -37,6 +37,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" + "github.com/tidwall/sjson" ) //template:end imports @@ -335,6 +336,21 @@ func (r *NetworkAccessAuthorizationGlobalExceptionRuleResource) Update(ctx conte tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData NetworkAccessAuthorizationGlobalExceptionRule + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_network_access_authorization_global_exception_rule_update_rank.go b/internal/provider/resource_ise_network_access_authorization_global_exception_rule_update_rank.go new file mode 100644 index 0000000..10c7696 --- /dev/null +++ b/internal/provider/resource_ise_network_access_authorization_global_exception_rule_update_rank.go @@ -0,0 +1,249 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource{} + +func NewNetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource() resource.Resource { + return &NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource{} +} + +type NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource struct { + client *ise.Client +} + +func (r *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_access_authorization_global_exception_rule_update_rank" +} + +//template:end header + +//template:begin model +func (r *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in network access authorization global exception rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_global_exception_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rule_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Authorization global exception rule ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank + var existingData NetworkAccessAuthorizationGlobalExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank + var existingData NetworkAccessAuthorizationGlobalExceptionRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *NetworkAccessAuthorizationGlobalExceptionRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state NetworkAccessAuthorizationGlobalExceptionRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_network_access_authorization_global_exception_rule_update_rank_test.go b/internal/provider/resource_ise_network_access_authorization_global_exception_rule_update_rank_test.go new file mode 100644 index 0000000..e7a744e --- /dev/null +++ b/internal/provider/resource_ise_network_access_authorization_global_exception_rule_update_rank_test.go @@ -0,0 +1,91 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseNetworkAccessAuthorizationGlobalExceptionRuleUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_network_access_authorization_global_exception_rule_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseNetworkAccessAuthorizationGlobalExceptionRuleUpdateRankPrerequisitesConfig + testAccIseNetworkAccessAuthorizationGlobalExceptionRuleUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseNetworkAccessAuthorizationGlobalExceptionRuleUpdateRankPrerequisitesConfig = ` +resource "ise_network_access_authorization_global_exception_rule" "test" { + name = "Rule1" + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + profiles = ["PermitAccess"] + security_group = "BYOD" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseNetworkAccessAuthorizationGlobalExceptionRuleUpdateRankConfig_minimum() string { + config := `resource "ise_network_access_authorization_global_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_network_access_authorization_global_exception_rule.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseNetworkAccessAuthorizationGlobalExceptionRuleUpdateRankConfig_all() string { + config := `resource "ise_network_access_authorization_global_exception_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_network_access_authorization_global_exception_rule.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_network_access_authorization_rule.go b/internal/provider/resource_ise_network_access_authorization_rule.go index 1b7f240..1ab501b 100644 --- a/internal/provider/resource_ise_network_access_authorization_rule.go +++ b/internal/provider/resource_ise_network_access_authorization_rule.go @@ -38,6 +38,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" "github.com/tidwall/gjson" + "github.com/tidwall/sjson" ) //template:end imports @@ -258,6 +259,7 @@ func (r *NetworkAccessAuthorizationRuleResource) Configure(_ context.Context, re //template:end configure +//template:begin create func (r *NetworkAccessAuthorizationRuleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan NetworkAccessAuthorizationRule @@ -307,6 +309,8 @@ func (r *NetworkAccessAuthorizationRuleResource) Create(ctx context.Context, req resp.Diagnostics.Append(diags...) } +//template:end create + //template:begin read func (r *NetworkAccessAuthorizationRuleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state NetworkAccessAuthorizationRule @@ -362,6 +366,21 @@ func (r *NetworkAccessAuthorizationRuleResource) Update(ctx context.Context, req tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData NetworkAccessAuthorizationRule + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rule.rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_network_access_authorization_rule_update_rank.go b/internal/provider/resource_ise_network_access_authorization_rule_update_rank.go new file mode 100644 index 0000000..bcb6bd6 --- /dev/null +++ b/internal/provider/resource_ise_network_access_authorization_rule_update_rank.go @@ -0,0 +1,256 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &NetworkAccessAuthorizationRuleUpdateRankResource{} + +func NewNetworkAccessAuthorizationRuleUpdateRankResource() resource.Resource { + return &NetworkAccessAuthorizationRuleUpdateRankResource{} +} + +type NetworkAccessAuthorizationRuleUpdateRankResource struct { + client *ise.Client +} + +func (r *NetworkAccessAuthorizationRuleUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_access_authorization_rule_update_rank" +} + +//template:end header + +//template:begin model +func (r *NetworkAccessAuthorizationRuleUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in network access authorization rule. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_authorization_rule resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "rule_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Authorization rule ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "policy_set_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Policy set ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *NetworkAccessAuthorizationRuleUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *NetworkAccessAuthorizationRuleUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan NetworkAccessAuthorizationRuleUpdateRank + var existingData NetworkAccessAuthorizationRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.RuleId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.RuleId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *NetworkAccessAuthorizationRuleUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state NetworkAccessAuthorizationRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *NetworkAccessAuthorizationRuleUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state NetworkAccessAuthorizationRuleUpdateRank + var existingData NetworkAccessAuthorizationRule + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.RuleId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rule.rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *NetworkAccessAuthorizationRuleUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state NetworkAccessAuthorizationRuleUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_network_access_authorization_rule_update_rank_test.go b/internal/provider/resource_ise_network_access_authorization_rule_update_rank_test.go new file mode 100644 index 0000000..596cba2 --- /dev/null +++ b/internal/provider/resource_ise_network_access_authorization_rule_update_rank_test.go @@ -0,0 +1,104 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseNetworkAccessAuthorizationRuleUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_network_access_authorization_rule_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseNetworkAccessAuthorizationRuleUpdateRankPrerequisitesConfig + testAccIseNetworkAccessAuthorizationRuleUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseNetworkAccessAuthorizationRuleUpdateRankPrerequisitesConfig = ` +resource "ise_network_access_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} +resource "ise_network_access_authorization_rule" "test" { + policy_set_id = ise_network_access_policy_set.test.id + name = "Rule1" + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + profiles = ["PermitAccess"] + security_group = "BYOD" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseNetworkAccessAuthorizationRuleUpdateRankConfig_minimum() string { + config := `resource "ise_network_access_authorization_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_network_access_authorization_rule.test.id` + "\n" + config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseNetworkAccessAuthorizationRuleUpdateRankConfig_all() string { + config := `resource "ise_network_access_authorization_rule_update_rank" "test" {` + "\n" + config += ` rule_id = ise_network_access_authorization_rule.test.id` + "\n" + config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/internal/provider/resource_ise_network_access_policy_set.go b/internal/provider/resource_ise_network_access_policy_set.go index 39e467b..0d31846 100644 --- a/internal/provider/resource_ise_network_access_policy_set.go +++ b/internal/provider/resource_ise_network_access_policy_set.go @@ -31,7 +31,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "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" @@ -39,6 +38,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netascode/go-ise" "github.com/tidwall/gjson" + "github.com/tidwall/sjson" ) //template:end imports @@ -96,10 +96,6 @@ func (r *NetworkAccessPolicySetResource) Schema(ctx context.Context, req resourc "rank": schema.Int64Attribute{ MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other policy sets. Lower rank is higher priority.").String, Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.UseStateForUnknown(), - }, }, "service_name": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Policy set service identifier. 'Allowed Protocols' or 'Server Sequence'.").String, @@ -263,6 +259,7 @@ func (r *NetworkAccessPolicySetResource) Configure(_ context.Context, req resour //template:end configure +//template:begin create func (r *NetworkAccessPolicySetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan NetworkAccessPolicySet @@ -321,6 +318,8 @@ func (r *NetworkAccessPolicySetResource) Create(ctx context.Context, req resourc resp.Diagnostics.Append(diags...) } +//template:end create + //template:begin read func (r *NetworkAccessPolicySetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state NetworkAccessPolicySet @@ -376,6 +375,21 @@ func (r *NetworkAccessPolicySetResource) Update(ctx context.Context, req resourc tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) body := plan.toBody(ctx, state) + // Check if resource has rank attribute + // Check if plan.Rank is null (i.e., not provided) and set Rank to body from existingData to avoid reordering the rule during update + if plan.Rank.IsNull() { + var existingData NetworkAccessPolicySet + // Fetch existing data from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.Id.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + // Populate existingData with current state from the API + existingData.fromBody(ctx, res) + // Set Rank in the request body from the existing data if it's missing from the plan + body, _ = sjson.Set(body, "rank", existingData.Rank.ValueInt64()) + } res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) if err != nil { diff --git a/internal/provider/resource_ise_network_access_policy_set_update_rank.go b/internal/provider/resource_ise_network_access_policy_set_update_rank.go new file mode 100644 index 0000000..93fe9f6 --- /dev/null +++ b/internal/provider/resource_ise_network_access_policy_set_update_rank.go @@ -0,0 +1,249 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/CiscoDevNet/terraform-provider-ise/internal/provider/helpers" + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-ise" + "github.com/tidwall/sjson" +) + +//template:end imports + +//template:begin header + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &NetworkAccessPolicySetUpdateRankResource{} + +func NewNetworkAccessPolicySetUpdateRankResource() resource.Resource { + return &NetworkAccessPolicySetUpdateRankResource{} +} + +type NetworkAccessPolicySetUpdateRankResource struct { + client *ise.Client +} + +func (r *NetworkAccessPolicySetUpdateRankResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_network_access_policy_set_update_rank" +} + +//template:end header + +//template:begin model +func (r *NetworkAccessPolicySetUpdateRankResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("This resource is used to update rank field in network access policy set. It serves as a workaround for the ISE API/Backend limitation which restricts rank assignments to a strictly incremental sequence. By utilizing this resource and network_access_policy_set resource, you can bypass the APIs limitation. Creation of this resource is performing PUT operation (Update) and it only tracks rank field. When this resource is destroyed, no action is performed on ISE and resource is just removed from state.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "policy_set_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Policy set ID").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rank": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("The rank (priority) in relation to other rules. Lower rank is higher priority.").String, + Required: true, + }, + }, + } +} + +//template:end model + +//template:begin configure +func (r *NetworkAccessPolicySetUpdateRankResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*IseProviderData).Client +} + +//template:end configure + +//template:begin create +func (r *NetworkAccessPolicySetUpdateRankResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan NetworkAccessPolicySetUpdateRank + var existingData NetworkAccessPolicySet + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.PolicySetId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rank", plan.Rank.ValueInt64()) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.PolicySetId.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + plan.Id = types.StringValue(fmt.Sprint(plan.PolicySetId.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end create + +//template:begin read +func (r *NetworkAccessPolicySetUpdateRankResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state NetworkAccessPolicySetUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + res, err := r.client.Get(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString())) + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + // If every attribute is set to null we are dealing with an import operation and therefore reading all attributes + if state.isNull(ctx, res) { + state.fromBody(ctx, res) + } else { + state.updateFromBody(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +//template:end read + +//template:begin update +func (r *NetworkAccessPolicySetUpdateRankResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state NetworkAccessPolicySetUpdateRank + var existingData NetworkAccessPolicySet + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Read existing attributes from the API + res, err := r.client.Get(plan.getPath() + "/" + url.QueryEscape(plan.PolicySetId.ValueString())) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s", err)) + return + } + existingData.fromBody(ctx, res) + + // Use the `toBody` function to construct the body from existingData + body := existingData.toBody(ctx, existingData) + + // Update rank + body, _ = sjson.Set(body, "rank", plan.Rank.ValueInt64()) + + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +//template:end update + +//template:begin delete +func (r *NetworkAccessPolicySetUpdateRankResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state NetworkAccessPolicySetUpdateRank + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +//template:end delete + +//template:begin import +//template:end import diff --git a/internal/provider/resource_ise_network_access_policy_set_update_rank_test.go b/internal/provider/resource_ise_network_access_policy_set_update_rank_test.go new file mode 100644 index 0000000..2e9c4a5 --- /dev/null +++ b/internal/provider/resource_ise_network_access_policy_set_update_rank_test.go @@ -0,0 +1,88 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +//template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +//template:end imports + +//template:begin testAcc +func TestAccIseNetworkAccessPolicySetUpdateRank(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("ise_network_access_policy_set_update_rank.test", "rank", "0")) + + var steps []resource.TestStep + steps = append(steps, resource.TestStep{ + Config: testAccIseNetworkAccessPolicySetUpdateRankPrerequisitesConfig + testAccIseNetworkAccessPolicySetUpdateRankConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) +} + +//template:end testAcc + +//template:begin testPrerequisites +const testAccIseNetworkAccessPolicySetUpdateRankPrerequisitesConfig = ` +resource "ise_network_access_policy_set" "test" { + name = "PolicySet1" + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} + +` + +//template:end testPrerequisites + +//template:begin testAccConfigMinimal +func testAccIseNetworkAccessPolicySetUpdateRankConfig_minimum() string { + config := `resource "ise_network_access_policy_set_update_rank" "test" {` + "\n" + config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigMinimal + +//template:begin testAccConfigAll +func testAccIseNetworkAccessPolicySetUpdateRankConfig_all() string { + config := `resource "ise_network_access_policy_set_update_rank" "test" {` + "\n" + config += ` policy_set_id = ise_network_access_policy_set.test.id` + "\n" + config += ` rank = 0` + "\n" + config += `}` + "\n" + return config +} + +//template:end testAccConfigAll diff --git a/templates/guides/authentication_rules.md.tmpl b/templates/guides/authentication_rules.md.tmpl new file mode 100644 index 0000000..00cf355 --- /dev/null +++ b/templates/guides/authentication_rules.md.tmpl @@ -0,0 +1,92 @@ +--- +subcategory: "Guides" +page_title: "Authentication Rules" +description: |- + Authentication Rules +--- + +# Authentication Rules + +This example demonstrates how the provider can be used to configure a network access authentication rules. The full example can be found here: [https://github.com/CiscoDevNet/terraform-provider-ise/tree/main/examples/basic/authentication_rules](https://github.com/CiscoDevNet/terraform-provider-ise/tree/main/examples/basic/authentication_rules) + +First of all we need to add the necessary provider configuration to the Terraform configuration file: + +```hcl +terraform { + required_providers { + ise = { + source = "CiscoDevNet/ise" + } + } +} + +provider "ise" { + username = "admin" + password = "password" + url = "https://10.1.1.1" +} +``` + +Next we add the configuration for a network access policy set, under which we will later configure authentication rules. + +```hcl +resource "ise_network_access_policy_set" "policy_set_1" { + name = "PolicySet1" + description = "My first policy set" + rank = 0 + service_name = "Default Network Access" + condition_type = "ConditionAttributes" + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" +} +``` + +Next we add the configuration for the authentication rules. We make use of `network_access_authentication_rule` and `network_access_authentication_rule_update_rank` resources. The first resource manages all fields except for the rank, while the second resource specifically updates the rank field. This is a workaround for the ISE API/Backend limitation that enforces strictly incremental rank assignments. By using both resources, you can bypass this limitation. The network_access_authentication_rule_update_rank resource performs a PUT operation to update the rank and only tracks that field. When destroyed, it is simply removed from the state without affecting the ISE configuration. This ensures the correct sequence of resource configuration. + +```hcl +locals { + rules = [ + { name = "rule_0" }, + { name = "rule_1" }, + { name = "rule_2" }, + { name = "rule_3" }, + { name = "rule_4" }, + { name = "rule_5" } + ] +} + +locals { + rules_with_ranks = [ + for idx, rule in local.rules : merge(rule, { + rank = idx + }) + ] +} + +resource "ise_network_access_authentication_rule" "auth_rule" { + for_each = { for rule in local.rules_with_ranks : rule.name => rule } + policy_set_id = ise_network_access_policy_set.policy_set_1.id + name = each.value.name + default = false + state = "enabled" + condition_type = "ConditionAttributes" + condition_is_negate = false + condition_attribute_name = "Location" + condition_attribute_value = "All Locations" + condition_dictionary_name = "DEVICE" + condition_operator = "equals" + identity_source_name = "Internal Endpoints" + if_auth_fail = "REJECT" + if_process_fail = "DROP" + if_user_not_found = "REJECT" +} + +resource "ise_network_access_authentication_rule_update_rank" "example_with_rank" { + for_each = { for rule in local.rules_with_ranks : rule.name => rule } + policy_set_id = ise_network_access_policy_set.policy_set_1.id + rule_id = ise_network_access_authentication_rule.auth_rule[each.value.name].id + rank = each.value.rank +} +``` diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index cb6bdd9..93aceff 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -24,6 +24,7 @@ All resources and data sources have been tested with the following releases. The following guides with examples exist to demonstrate the use of the provider: - [Getting Started](https://registry.terraform.io/providers/CiscoDevNet/ise/latest/docs/guides/getting_started) +- [Authentication Rules](https://registry.terraform.io/providers/CiscoDevNet/ise/latest/docs/guides/authentication_rules) ## Example Usage