diff --git a/.gitignore b/.gitignore
index fd3ad8e1..a68eea47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ website/node_modules
*.iml
website/vendor
+terraform-provider-meraki
# Test exclusions
!command/test-fixtures/**/*.tfstate
diff --git a/.vscode/launch.json b/.vscode/launch.json
index d2e326d8..6443fde9 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,6 +11,18 @@
"mode": "auto",
"program": "gen/generator.go",
"cwd": "./"
+ },
+ {
+ "name": "Launch definition generator",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "gen/definition.go",
+ "cwd": "./",
+ "args": [
+ "/networks/{networkId}/groupPolicies/{groupPolicyId}",
+ "Network Group Policy"
+ ]
}
]
}
\ No newline at end of file
diff --git a/docs/data-sources/admin.md b/docs/data-sources/admin.md
index 172532ea..35dc1e46 100644
--- a/docs/data-sources/admin.md
+++ b/docs/data-sources/admin.md
@@ -33,6 +33,7 @@ data "meraki_admin" "example" {
### Read-Only
+- `authentication_method` (String) No longer used as of Cisco SecureX end-of-life. Can be one of `Email`. The default is Email authentication.
- `email` (String) The email of the dashboard administrator. This attribute can not be updated.
- `networks` (Attributes List) The list of networks that the dashboard administrator has privileges on (see [below for nested schema](#nestedatt--networks))
- `org_access` (String) The privilege of the dashboard administrator on the organization. Can be one of `full`, `read-only`, `enterprise` or `none`
diff --git a/docs/data-sources/network_group_policy.md b/docs/data-sources/network_group_policy.md
new file mode 100644
index 00000000..96229b18
--- /dev/null
+++ b/docs/data-sources/network_group_policy.md
@@ -0,0 +1,128 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meraki_network_group_policy Data Source - terraform-provider-meraki"
+subcategory: "Networks"
+description: |-
+ This data source can read the Network Group Policy configuration.
+---
+
+# meraki_network_group_policy (Data Source)
+
+This data source can read the `Network Group Policy` configuration.
+
+## Example Usage
+
+```terraform
+data "meraki_network_group_policy" "example" {
+ id = "12345678"
+ network_id = "L_123456"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `network_id` (String) Network ID
+
+### Optional
+
+- `id` (String) The id of the object
+- `name` (String) The name for your group policy. Required.
+
+### Read-Only
+
+- `bandwidth_limit_down` (Number) The maximum download limit (integer, in Kbps). null indicates no limit
+- `bandwidth_limit_up` (Number) The maximum upload limit (integer, in Kbps). null indicates no limit
+- `bandwidth_settings` (String) How bandwidth limits are enforced. Can be `network default`, `ignore` or `custom`.
+- `bonjour_forwarding_rules` (Attributes List) A list of the Bonjour forwarding rules for your group policy. If `settings` is set to `custom`, at least one rule must be specified. (see [below for nested schema](#nestedatt--bonjour_forwarding_rules))
+- `bonjour_forwarding_settings` (String) How Bonjour rules are applied. Can be `network default`, `ignore` or `custom`.
+- `content_filtering_allowed_url_patterns` (List of String) A list of URL patterns that are allowed
+- `content_filtering_allowed_url_patterns_settings` (String) How URL patterns are applied. Can be `network default`, `append` or `override`.
+- `content_filtering_blocked_url_categories` (List of String) A list of URL categories to block
+- `content_filtering_blocked_url_categories_settings` (String) How URL categories are applied. Can be `network default`, `append` or `override`.
+- `content_filtering_blocked_url_patterns` (List of String) A list of URL patterns that are blocked
+- `content_filtering_blocked_url_patterns_settings` (String) How URL patterns are applied. Can be `network default`, `append` or `override`.
+- `firewall_and_traffic_shaping_settings` (String) How firewall and traffic shaping rules are enforced. Can be `network default`, `ignore` or `custom`.
+- `l3_firewall_rules` (Attributes List) An ordered array of the L3 firewall rules (see [below for nested schema](#nestedatt--l3_firewall_rules))
+- `l7_firewall_rules` (Attributes List) An ordered array of L7 firewall rules (see [below for nested schema](#nestedatt--l7_firewall_rules))
+- `scheduling_enabled` (Boolean) Whether scheduling is enabled (true) or disabled (false). Defaults to false. If true, the schedule objects for each day of the week (monday - sunday) are parsed.
+- `scheduling_friday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_friday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_friday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_monday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_monday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_monday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_saturday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_saturday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_saturday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_sunday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_sunday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_sunday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_thursday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_thursday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_thursday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_tuesday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_tuesday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_tuesday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_wednesday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_wednesday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_wednesday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `splash_auth_settings` (String) Whether clients bound to your policy will bypass splash authorization or behave according to the network`s rules. Can be one of `network default` or `bypass`. Only available if your network has a wireless configuration.
+- `traffic_shaping_rules` (Attributes List) An array of traffic shaping rules. Rules are applied in the order that they are specified in. An empty list (or null) means no rules. Note that you are allowed a maximum of 8 rules. (see [below for nested schema](#nestedatt--traffic_shaping_rules))
+- `vlan_tagging_settings` (String) How VLAN tagging is applied. Can be `network default`, `ignore` or `custom`.
+- `vlan_tagging_vlan_id` (String) The ID of the vlan you want to tag. This only applies if `settings` is set to `custom`.
+
+
+### Nested Schema for `bonjour_forwarding_rules`
+
+Read-Only:
+
+- `description` (String) A description for your Bonjour forwarding rule. Optional.
+- `services` (List of String) A list of Bonjour services. At least one service must be specified. Available services are `All Services`, `AirPlay`, `AFP`, `BitTorrent`, `FTP`, `iChat`, `iTunes`, `Printers`, `Samba`, `Scanners` and `SSH`
+- `vlan_id` (String) The ID of the service VLAN. Required.
+
+
+
+### Nested Schema for `l3_firewall_rules`
+
+Read-Only:
+
+- `comment` (String) Description of the rule (optional)
+- `dest_cidr` (String) Destination IP address (in IP or CIDR notation), a fully-qualified domain name (FQDN, if your network supports it) or `any`.
+- `dest_port` (String) Destination port (integer in the range 1-65535), a port range (e.g. 8080-9090), or `any`
+- `policy` (String) `allow` or `deny` traffic specified by this rule
+- `protocol` (String) The type of protocol (must be `tcp`, `udp`, `icmp`, `icmp6` or `any`)
+
+
+
+### Nested Schema for `l7_firewall_rules`
+
+Read-Only:
+
+- `policy` (String) The policy applied to matching traffic. Must be `deny`.
+- `type` (String) Type of the L7 Rule. Must be `application`, `applicationCategory`, `host`, `port` or `ipRange`
+- `value` (String) The `value` of what you want to block. If `type` is `host`, `port` or `ipRange`, `value` must be a string matching either a hostname (e.g. somewhere.com), a port (e.g. 8080), or an IP range (e.g. 192.1.0.0/16). If `type` is `application` or `applicationCategory`, then `value` must be an object with an ID for the application.
+
+
+
+### Nested Schema for `traffic_shaping_rules`
+
+Read-Only:
+
+- `definitions` (Attributes List) A list of objects describing the definitions of your traffic shaping rule. At least one definition is required. (see [below for nested schema](#nestedatt--traffic_shaping_rules--definitions))
+- `dscp_tag_value` (Number) The DSCP tag applied by your rule. null means `Do not change DSCP tag`. For a list of possible tag values, use the trafficShaping/dscpTaggingOptions endpoint.
+- `pcp_tag_value` (Number) The PCP tag applied by your rule. Can be 0 (lowest priority) through 7 (highest priority). null means `Do not set PCP tag`.
+- `per_client_bandwidth_limits_bandwidth_limits_limit_down` (Number) The maximum download limit (integer, in Kbps).
+- `per_client_bandwidth_limits_bandwidth_limits_limit_up` (Number) The maximum upload limit (integer, in Kbps).
+- `per_client_bandwidth_limits_settings` (String) How bandwidth limits are applied by your rule. Can be one of `network default`, `ignore` or `custom`.
+- `priority` (String) A string, indicating the priority level for packets bound to your rule. Can be `low`, `normal` or `high`.
+
+
+### Nested Schema for `traffic_shaping_rules.definitions`
+
+Read-Only:
+
+- `type` (String) The type of definition. Can be one of `application`, `applicationCategory`, `host`, `port`, `ipRange` or `localNet`.
+- `value` (String) If 'type' is `host`, `port`, `ipRange` or `localNet`, then 'value' must be a string, matching either a hostname (e.g. 'somesite.com'), a port (e.g. 8080), or an IP range ('192.1.0.0', '192.1.0.0/16', or '10.1.0.0/16:80'). `localNet` also supports CIDR notation, excluding custom ports. If 'type' is `application` or `applicationCategory`, then 'value' must be an object with the structure { 'id': 'meraki:layer7/...' }, where 'id' is the application category or application ID (for a list of IDs for your network, use the trafficShaping/applicationCategories endpoint).
diff --git a/docs/data-sources/network_settings.md b/docs/data-sources/network_settings.md
new file mode 100644
index 00000000..262680dc
--- /dev/null
+++ b/docs/data-sources/network_settings.md
@@ -0,0 +1,37 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meraki_network_settings Data Source - terraform-provider-meraki"
+subcategory: "Networks"
+description: |-
+ This data source can read the Network Settings configuration.
+---
+
+# meraki_network_settings (Data Source)
+
+This data source can read the `Network Settings` configuration.
+
+## Example Usage
+
+```terraform
+data "meraki_network_settings" "example" {
+ id = "L_123456"
+ network_id = "L_123456"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `network_id` (String) Network ID
+
+### Read-Only
+
+- `id` (String) The id of the object
+- `local_status_page_authentication_enabled` (Boolean) Enables / disables the authentication on Local Status page(s).
+- `local_status_page_authentication_password` (String) The password used for Local Status Page(s). Set this to null to clear the password.
+- `local_status_page_enabled` (Boolean) Enables / disables the local device status pages (my.meraki.com, ap.meraki.com, switch.meraki.com, wired.meraki.com). Optional (defaults to false)
+- `named_vlans_enabled` (Boolean) Enables / disables Named VLANs on the Network.
+- `remote_status_page_enabled` (Boolean) Enables / disables access to the device status page (http://[device`s LAN IP]). Optional. Can only be set if localStatusPageEnabled is set to true
+- `secure_port_enabled` (Boolean) Enables / disables SecureConnect on the network. Optional.
diff --git a/docs/resources/admin.md b/docs/resources/admin.md
index 8f810a74..dadef8cb 100644
--- a/docs/resources/admin.md
+++ b/docs/resources/admin.md
@@ -14,10 +14,11 @@ This resource can manage the `Admin` configuration.
```terraform
resource "meraki_admin" "example" {
- organization_id = "123456"
- email = "miles@meraki.com"
- name = "Miles Meraki"
- org_access = "none"
+ organization_id = "123456"
+ authentication_method = "Email"
+ email = "miles@meraki.com"
+ name = "Miles Meraki"
+ org_access = "none"
networks = [
{
access = "full"
@@ -41,11 +42,13 @@ resource "meraki_admin" "example" {
- `email` (String) The email of the dashboard administrator. This attribute can not be updated.
- `name` (String) The name of the dashboard administrator
- `org_access` (String) The privilege of the dashboard administrator on the organization. Can be one of `full`, `read-only`, `enterprise` or `none`
- - Choices: `full`, `read-only`, `enterprise`, `none`
+ - Choices: `enterprise`, `full`, `none`, `read-only`
- `organization_id` (String) Organization ID
### Optional
+- `authentication_method` (String) No longer used as of Cisco SecureX end-of-life. Can be one of `Email`. The default is Email authentication.
+ - Choices: `Email`
- `networks` (Attributes List) The list of networks that the dashboard administrator has privileges on (see [below for nested schema](#nestedatt--networks))
- `tags` (Attributes List) The list of tags that the dashboard administrator has privileges on (see [below for nested schema](#nestedatt--tags))
@@ -69,7 +72,7 @@ Required:
Required:
- `access` (String) The privilege of the dashboard administrator on the tag. Can be one of `full`, `read-only`, `guest-ambassador` or `monitor-only`
- - Choices: `full`, `read-only`, `guest-ambassador`, `monitor-only`
+ - Choices: `full`, `guest-ambassador`, `monitor-only`, `read-only`
- `tag` (String) The name of the tag
## Import
diff --git a/docs/resources/network_group_policy.md b/docs/resources/network_group_policy.md
new file mode 100644
index 00000000..587480d0
--- /dev/null
+++ b/docs/resources/network_group_policy.md
@@ -0,0 +1,228 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meraki_network_group_policy Resource - terraform-provider-meraki"
+subcategory: "Networks"
+description: |-
+ This resource can manage the Network Group Policy configuration.
+---
+
+# meraki_network_group_policy (Resource)
+
+This resource can manage the `Network Group Policy` configuration.
+
+## Example Usage
+
+```terraform
+resource "meraki_network_group_policy" "example" {
+ network_id = "L_123456"
+ name = "No video streaming"
+ splash_auth_settings = "bypass"
+ bandwidth_settings = "custom"
+ bandwidth_limit_down = 1000000
+ bandwidth_limit_up = 1000000
+ bonjour_forwarding_settings = "custom"
+ bonjour_forwarding_rules = [
+ {
+ description = "A simple bonjour rule"
+ vlan_id = "1"
+ services = ["All Services"]
+ }
+ ]
+ content_filtering_allowed_url_patterns_settings = "network default"
+ content_filtering_allowed_url_patterns = [""]
+ content_filtering_blocked_url_categories_settings = "override"
+ content_filtering_blocked_url_categories = ["meraki:contentFiltering/category/1"]
+ content_filtering_blocked_url_patterns_settings = "append"
+ content_filtering_blocked_url_patterns = ["http://www.example.com"]
+ firewall_and_traffic_shaping_settings = "custom"
+ l3_firewall_rules = [
+ {
+ comment = "Allow TCP traffic to subnet with HTTP servers."
+ dest_cidr = "192.168.1.0/24"
+ dest_port = "443"
+ policy = "allow"
+ protocol = "tcp"
+ }
+ ]
+ l7_firewall_rules = [
+ {
+ policy = "deny"
+ type = "host"
+ value = "google.com"
+ }
+ ]
+ traffic_shaping_rules = [
+ {
+ dscp_tag_value = 0
+ pcp_tag_value = 0
+ priority = "normal"
+ per_client_bandwidth_limits_settings = "custom"
+ per_client_bandwidth_limits_bandwidth_limits_limit_down = 1000000
+ per_client_bandwidth_limits_bandwidth_limits_limit_up = 1000000
+ definitions = [
+ {
+ type = "host"
+ value = "google.com"
+ }
+ ]
+ }
+ ]
+ scheduling_enabled = true
+ scheduling_friday_active = true
+ scheduling_friday_from = "09:00"
+ scheduling_friday_to = "17:00"
+ scheduling_monday_active = true
+ scheduling_monday_from = "09:00"
+ scheduling_monday_to = "17:00"
+ scheduling_saturday_active = true
+ scheduling_saturday_from = "09:00"
+ scheduling_saturday_to = "17:00"
+ scheduling_sunday_active = true
+ scheduling_sunday_from = "09:00"
+ scheduling_sunday_to = "17:00"
+ scheduling_thursday_active = true
+ scheduling_thursday_from = "09:00"
+ scheduling_thursday_to = "17:00"
+ scheduling_tuesday_active = true
+ scheduling_tuesday_from = "09:00"
+ scheduling_tuesday_to = "17:00"
+ scheduling_wednesday_active = true
+ scheduling_wednesday_from = "09:00"
+ scheduling_wednesday_to = "17:00"
+ vlan_tagging_settings = "custom"
+ vlan_tagging_vlan_id = "1"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) The name for your group policy. Required.
+- `network_id` (String) Network ID
+
+### Optional
+
+- `bandwidth_limit_down` (Number) The maximum download limit (integer, in Kbps). null indicates no limit
+- `bandwidth_limit_up` (Number) The maximum upload limit (integer, in Kbps). null indicates no limit
+- `bandwidth_settings` (String) How bandwidth limits are enforced. Can be `network default`, `ignore` or `custom`.
+ - Choices: `custom`, `ignore`, `network default`
+- `bonjour_forwarding_rules` (Attributes List) A list of the Bonjour forwarding rules for your group policy. If `settings` is set to `custom`, at least one rule must be specified. (see [below for nested schema](#nestedatt--bonjour_forwarding_rules))
+- `bonjour_forwarding_settings` (String) How Bonjour rules are applied. Can be `network default`, `ignore` or `custom`.
+ - Choices: `custom`, `ignore`, `network default`
+- `content_filtering_allowed_url_patterns` (List of String) A list of URL patterns that are allowed
+- `content_filtering_allowed_url_patterns_settings` (String) How URL patterns are applied. Can be `network default`, `append` or `override`.
+ - Choices: `append`, `network default`, `override`
+- `content_filtering_blocked_url_categories` (List of String) A list of URL categories to block
+- `content_filtering_blocked_url_categories_settings` (String) How URL categories are applied. Can be `network default`, `append` or `override`.
+ - Choices: `append`, `network default`, `override`
+- `content_filtering_blocked_url_patterns` (List of String) A list of URL patterns that are blocked
+- `content_filtering_blocked_url_patterns_settings` (String) How URL patterns are applied. Can be `network default`, `append` or `override`.
+ - Choices: `append`, `network default`, `override`
+- `firewall_and_traffic_shaping_settings` (String) How firewall and traffic shaping rules are enforced. Can be `network default`, `ignore` or `custom`.
+ - Choices: `custom`, `ignore`, `network default`
+- `l3_firewall_rules` (Attributes List) An ordered array of the L3 firewall rules (see [below for nested schema](#nestedatt--l3_firewall_rules))
+- `l7_firewall_rules` (Attributes List) An ordered array of L7 firewall rules (see [below for nested schema](#nestedatt--l7_firewall_rules))
+- `scheduling_enabled` (Boolean) Whether scheduling is enabled (true) or disabled (false). Defaults to false. If true, the schedule objects for each day of the week (monday - sunday) are parsed.
+- `scheduling_friday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_friday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_friday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_monday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_monday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_monday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_saturday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_saturday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_saturday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_sunday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_sunday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_sunday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_thursday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_thursday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_thursday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_tuesday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_tuesday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_tuesday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `scheduling_wednesday_active` (Boolean) Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+- `scheduling_wednesday_from` (String) The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+- `scheduling_wednesday_to` (String) The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+- `splash_auth_settings` (String) Whether clients bound to your policy will bypass splash authorization or behave according to the network`s rules. Can be one of `network default` or `bypass`. Only available if your network has a wireless configuration.
+ - Choices: `bypass`, `network default`
+- `traffic_shaping_rules` (Attributes List) An array of traffic shaping rules. Rules are applied in the order that they are specified in. An empty list (or null) means no rules. Note that you are allowed a maximum of 8 rules. (see [below for nested schema](#nestedatt--traffic_shaping_rules))
+- `vlan_tagging_settings` (String) How VLAN tagging is applied. Can be `network default`, `ignore` or `custom`.
+ - Choices: `custom`, `ignore`, `network default`
+- `vlan_tagging_vlan_id` (String) The ID of the vlan you want to tag. This only applies if `settings` is set to `custom`.
+
+### Read-Only
+
+- `id` (String) The id of the object
+
+
+### Nested Schema for `bonjour_forwarding_rules`
+
+Required:
+
+- `vlan_id` (String) The ID of the service VLAN. Required.
+
+Optional:
+
+- `description` (String) A description for your Bonjour forwarding rule. Optional.
+- `services` (List of String) A list of Bonjour services. At least one service must be specified. Available services are `All Services`, `AirPlay`, `AFP`, `BitTorrent`, `FTP`, `iChat`, `iTunes`, `Printers`, `Samba`, `Scanners` and `SSH`
+
+
+
+### Nested Schema for `l3_firewall_rules`
+
+Required:
+
+- `dest_cidr` (String) Destination IP address (in IP or CIDR notation), a fully-qualified domain name (FQDN, if your network supports it) or `any`.
+- `policy` (String) `allow` or `deny` traffic specified by this rule
+- `protocol` (String) The type of protocol (must be `tcp`, `udp`, `icmp`, `icmp6` or `any`)
+
+Optional:
+
+- `comment` (String) Description of the rule (optional)
+- `dest_port` (String) Destination port (integer in the range 1-65535), a port range (e.g. 8080-9090), or `any`
+
+
+
+### Nested Schema for `l7_firewall_rules`
+
+Optional:
+
+- `policy` (String) The policy applied to matching traffic. Must be `deny`.
+ - Choices: `deny`
+- `type` (String) Type of the L7 Rule. Must be `application`, `applicationCategory`, `host`, `port` or `ipRange`
+ - Choices: `application`, `applicationCategory`, `host`, `ipRange`, `port`
+- `value` (String) The `value` of what you want to block. If `type` is `host`, `port` or `ipRange`, `value` must be a string matching either a hostname (e.g. somewhere.com), a port (e.g. 8080), or an IP range (e.g. 192.1.0.0/16). If `type` is `application` or `applicationCategory`, then `value` must be an object with an ID for the application.
+
+
+
+### Nested Schema for `traffic_shaping_rules`
+
+Optional:
+
+- `definitions` (Attributes List) A list of objects describing the definitions of your traffic shaping rule. At least one definition is required. (see [below for nested schema](#nestedatt--traffic_shaping_rules--definitions))
+- `dscp_tag_value` (Number) The DSCP tag applied by your rule. null means `Do not change DSCP tag`. For a list of possible tag values, use the trafficShaping/dscpTaggingOptions endpoint.
+- `pcp_tag_value` (Number) The PCP tag applied by your rule. Can be 0 (lowest priority) through 7 (highest priority). null means `Do not set PCP tag`.
+- `per_client_bandwidth_limits_bandwidth_limits_limit_down` (Number) The maximum download limit (integer, in Kbps).
+- `per_client_bandwidth_limits_bandwidth_limits_limit_up` (Number) The maximum upload limit (integer, in Kbps).
+- `per_client_bandwidth_limits_settings` (String) How bandwidth limits are applied by your rule. Can be one of `network default`, `ignore` or `custom`.
+- `priority` (String) A string, indicating the priority level for packets bound to your rule. Can be `low`, `normal` or `high`.
+
+
+### Nested Schema for `traffic_shaping_rules.definitions`
+
+Required:
+
+- `type` (String) The type of definition. Can be one of `application`, `applicationCategory`, `host`, `port`, `ipRange` or `localNet`.
+ - Choices: `application`, `applicationCategory`, `host`, `ipRange`, `localNet`, `port`
+- `value` (String) If 'type' is `host`, `port`, `ipRange` or `localNet`, then 'value' must be a string, matching either a hostname (e.g. 'somesite.com'), a port (e.g. 8080), or an IP range ('192.1.0.0', '192.1.0.0/16', or '10.1.0.0/16:80'). `localNet` also supports CIDR notation, excluding custom ports. If 'type' is `application` or `applicationCategory`, then 'value' must be an object with the structure { 'id': 'meraki:layer7/...' }, where 'id' is the application category or application ID (for a list of IDs for your network, use the trafficShaping/applicationCategories endpoint).
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import meraki_network_group_policy.example ","
+```
diff --git a/docs/resources/network_settings.md b/docs/resources/network_settings.md
new file mode 100644
index 00000000..d67b03ca
--- /dev/null
+++ b/docs/resources/network_settings.md
@@ -0,0 +1,53 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meraki_network_settings Resource - terraform-provider-meraki"
+subcategory: "Networks"
+description: |-
+ This resource can manage the Network Settings configuration.
+---
+
+# meraki_network_settings (Resource)
+
+This resource can manage the `Network Settings` configuration.
+
+## Example Usage
+
+```terraform
+resource "meraki_network_settings" "example" {
+ network_id = "L_123456"
+ local_status_page_enabled = true
+ remote_status_page_enabled = true
+ local_status_page_authentication_enabled = false
+ local_status_page_authentication_password = "miles123"
+ named_vlans_enabled = true
+ secure_port_enabled = false
+}
+```
+
+
+## Schema
+
+### Required
+
+- `network_id` (String) Network ID
+
+### Optional
+
+- `local_status_page_authentication_enabled` (Boolean) Enables / disables the authentication on Local Status page(s).
+- `local_status_page_authentication_password` (String) The password used for Local Status Page(s). Set this to null to clear the password.
+- `local_status_page_enabled` (Boolean) Enables / disables the local device status pages (my.meraki.com, ap.meraki.com, switch.meraki.com, wired.meraki.com). Optional (defaults to false)
+- `named_vlans_enabled` (Boolean) Enables / disables Named VLANs on the Network.
+- `remote_status_page_enabled` (Boolean) Enables / disables access to the device status page (http://[device`s LAN IP]). Optional. Can only be set if localStatusPageEnabled is set to true
+- `secure_port_enabled` (Boolean) Enables / disables SecureConnect on the network. Optional.
+
+### Read-Only
+
+- `id` (String) The id of the object
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import meraki_network_settings.example ""
+```
diff --git a/docs/resources/network_snmp.md b/docs/resources/network_snmp.md
index c8adc910..f927172a 100644
--- a/docs/resources/network_snmp.md
+++ b/docs/resources/network_snmp.md
@@ -16,11 +16,11 @@ This resource can manage the `Network SNMP` configuration.
resource "meraki_network_snmp" "example" {
network_id = "L_123456"
access = "users"
- community_string = "MerakiCommunity"
+ community_string = "sample"
users = [
{
- username = "User1"
- passphrase = "Password123"
+ passphrase = "hunter2"
+ username = "AzureDiamond"
}
]
}
@@ -36,7 +36,7 @@ resource "meraki_network_snmp" "example" {
### Optional
- `access` (String) The type of SNMP access. Can be one of `none` (disabled), `community` (V1/V2c), or `users` (V3).
- - Choices: `none`, `community`, `users`
+ - Choices: `community`, `none`, `users`
- `community_string` (String) The SNMP community string. Only relevant if `access` is set to `community`.
- `users` (Attributes List) The list of SNMP users. Only relevant if `access` is set to `users`. (see [below for nested schema](#nestedatt--users))
diff --git a/examples/data-sources/meraki_network_group_policy/data-source.tf b/examples/data-sources/meraki_network_group_policy/data-source.tf
new file mode 100644
index 00000000..e3e1d5c5
--- /dev/null
+++ b/examples/data-sources/meraki_network_group_policy/data-source.tf
@@ -0,0 +1,4 @@
+data "meraki_network_group_policy" "example" {
+ id = "12345678"
+ network_id = "L_123456"
+}
diff --git a/examples/data-sources/meraki_network_settings/data-source.tf b/examples/data-sources/meraki_network_settings/data-source.tf
new file mode 100644
index 00000000..3a22e58c
--- /dev/null
+++ b/examples/data-sources/meraki_network_settings/data-source.tf
@@ -0,0 +1,4 @@
+data "meraki_network_settings" "example" {
+ id = "L_123456"
+ network_id = "L_123456"
+}
diff --git a/examples/resources/meraki_admin/resource.tf b/examples/resources/meraki_admin/resource.tf
index d34bed07..d843be50 100644
--- a/examples/resources/meraki_admin/resource.tf
+++ b/examples/resources/meraki_admin/resource.tf
@@ -1,8 +1,9 @@
resource "meraki_admin" "example" {
- organization_id = "123456"
- email = "miles@meraki.com"
- name = "Miles Meraki"
- org_access = "none"
+ organization_id = "123456"
+ authentication_method = "Email"
+ email = "miles@meraki.com"
+ name = "Miles Meraki"
+ org_access = "none"
networks = [
{
access = "full"
diff --git a/examples/resources/meraki_network_group_policy/import.sh b/examples/resources/meraki_network_group_policy/import.sh
new file mode 100644
index 00000000..dc60d851
--- /dev/null
+++ b/examples/resources/meraki_network_group_policy/import.sh
@@ -0,0 +1 @@
+terraform import meraki_network_group_policy.example ","
diff --git a/examples/resources/meraki_network_group_policy/resource.tf b/examples/resources/meraki_network_group_policy/resource.tf
new file mode 100644
index 00000000..5fed9776
--- /dev/null
+++ b/examples/resources/meraki_network_group_policy/resource.tf
@@ -0,0 +1,79 @@
+resource "meraki_network_group_policy" "example" {
+ network_id = "L_123456"
+ name = "No video streaming"
+ splash_auth_settings = "bypass"
+ bandwidth_settings = "custom"
+ bandwidth_limit_down = 1000000
+ bandwidth_limit_up = 1000000
+ bonjour_forwarding_settings = "custom"
+ bonjour_forwarding_rules = [
+ {
+ description = "A simple bonjour rule"
+ vlan_id = "1"
+ services = ["All Services"]
+ }
+ ]
+ content_filtering_allowed_url_patterns_settings = "network default"
+ content_filtering_allowed_url_patterns = [""]
+ content_filtering_blocked_url_categories_settings = "override"
+ content_filtering_blocked_url_categories = ["meraki:contentFiltering/category/1"]
+ content_filtering_blocked_url_patterns_settings = "append"
+ content_filtering_blocked_url_patterns = ["http://www.example.com"]
+ firewall_and_traffic_shaping_settings = "custom"
+ l3_firewall_rules = [
+ {
+ comment = "Allow TCP traffic to subnet with HTTP servers."
+ dest_cidr = "192.168.1.0/24"
+ dest_port = "443"
+ policy = "allow"
+ protocol = "tcp"
+ }
+ ]
+ l7_firewall_rules = [
+ {
+ policy = "deny"
+ type = "host"
+ value = "google.com"
+ }
+ ]
+ traffic_shaping_rules = [
+ {
+ dscp_tag_value = 0
+ pcp_tag_value = 0
+ priority = "normal"
+ per_client_bandwidth_limits_settings = "custom"
+ per_client_bandwidth_limits_bandwidth_limits_limit_down = 1000000
+ per_client_bandwidth_limits_bandwidth_limits_limit_up = 1000000
+ definitions = [
+ {
+ type = "host"
+ value = "google.com"
+ }
+ ]
+ }
+ ]
+ scheduling_enabled = true
+ scheduling_friday_active = true
+ scheduling_friday_from = "09:00"
+ scheduling_friday_to = "17:00"
+ scheduling_monday_active = true
+ scheduling_monday_from = "09:00"
+ scheduling_monday_to = "17:00"
+ scheduling_saturday_active = true
+ scheduling_saturday_from = "09:00"
+ scheduling_saturday_to = "17:00"
+ scheduling_sunday_active = true
+ scheduling_sunday_from = "09:00"
+ scheduling_sunday_to = "17:00"
+ scheduling_thursday_active = true
+ scheduling_thursday_from = "09:00"
+ scheduling_thursday_to = "17:00"
+ scheduling_tuesday_active = true
+ scheduling_tuesday_from = "09:00"
+ scheduling_tuesday_to = "17:00"
+ scheduling_wednesday_active = true
+ scheduling_wednesday_from = "09:00"
+ scheduling_wednesday_to = "17:00"
+ vlan_tagging_settings = "custom"
+ vlan_tagging_vlan_id = "1"
+}
diff --git a/examples/resources/meraki_network_settings/import.sh b/examples/resources/meraki_network_settings/import.sh
new file mode 100644
index 00000000..8f0ed533
--- /dev/null
+++ b/examples/resources/meraki_network_settings/import.sh
@@ -0,0 +1 @@
+terraform import meraki_network_settings.example ""
diff --git a/examples/resources/meraki_network_settings/resource.tf b/examples/resources/meraki_network_settings/resource.tf
new file mode 100644
index 00000000..93b2deaf
--- /dev/null
+++ b/examples/resources/meraki_network_settings/resource.tf
@@ -0,0 +1,9 @@
+resource "meraki_network_settings" "example" {
+ network_id = "L_123456"
+ local_status_page_enabled = true
+ remote_status_page_enabled = true
+ local_status_page_authentication_enabled = false
+ local_status_page_authentication_password = "miles123"
+ named_vlans_enabled = true
+ secure_port_enabled = false
+}
diff --git a/examples/resources/meraki_network_snmp/resource.tf b/examples/resources/meraki_network_snmp/resource.tf
index ca015749..115bacba 100644
--- a/examples/resources/meraki_network_snmp/resource.tf
+++ b/examples/resources/meraki_network_snmp/resource.tf
@@ -1,11 +1,11 @@
resource "meraki_network_snmp" "example" {
network_id = "L_123456"
access = "users"
- community_string = "MerakiCommunity"
+ community_string = "sample"
users = [
{
- username = "User1"
- passphrase = "Password123"
+ passphrase = "hunter2"
+ username = "AzureDiamond"
}
]
}
diff --git a/gen/definition.go b/gen/definition.go
new file mode 100644
index 00000000..2f01aa76
--- /dev/null
+++ b/gen/definition.go
@@ -0,0 +1,305 @@
+// Copyright © 2024 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
+
+//go:build ignore
+
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "os"
+ "regexp"
+ "slices"
+ "sort"
+ "strings"
+
+ "github.com/CiscoDevNet/terraform-provider-meraki/gen/yamlconfig"
+ "github.com/tidwall/gjson"
+ "gopkg.in/yaml.v3"
+
+ "golang.org/x/exp/maps"
+)
+
+const specPath = "./gen/models/spec3.json"
+const definitionsPath = "./gen/definitions/"
+
+const usage = `
+Usage: go run gen/definition.go
+
+Arguments:
+ endpoint The specific endpoint that is to be converted to generator specification
+ resource_name The name that will be given to the resource
+
+Example:
+ go run gen/definition.go "/networks/{networkId}/groupPolicies/{groupPolicyId}" "Network Group Policy"`
+
+func main() {
+ if len(os.Args) < 3 {
+ fmt.Println("Error: Insufficient number of arguments")
+ fmt.Println(usage)
+ os.Exit(1)
+ }
+
+ endpointPath := os.Args[1]
+ resourceName := os.Args[2]
+
+ specData, err := os.ReadFile(specPath)
+ if err != nil {
+ fmt.Printf("Error reading OpenAPI spec file: %v\n", err)
+ os.Exit(1)
+ }
+
+ var spec interface{}
+ if strings.HasSuffix(specPath, ".json") {
+ err = json.Unmarshal(specData, &spec)
+ } else {
+ err = yaml.Unmarshal(specData, &spec)
+ }
+ if err != nil {
+ fmt.Printf("Error parsing OpenAPI spec: %v\n", err)
+ os.Exit(1)
+ }
+
+ parts := strings.Split(endpointPath, "/")
+ if len(parts) > 0 {
+ parts = parts[:len(parts)-1]
+ }
+ shortEndpointPath := strings.Join(parts, "/")
+ var schema map[string]interface{}
+ paths := spec.(map[string]interface{})["paths"].(map[string]interface{})
+ // use POST schema if it exists, otherwise fall back to PUT schema
+ if endpoint, ok := paths[shortEndpointPath].(map[string]interface{})["post"]; ok {
+ schema = endpoint.(map[string]interface{})["requestBody"].(map[string]interface{})["content"].(map[string]interface{})["application/json"].(map[string]interface{})
+ } else {
+ schema = paths[endpointPath].(map[string]interface{})["put"].(map[string]interface{})["requestBody"].(map[string]interface{})["content"].(map[string]interface{})["application/json"].(map[string]interface{})
+ }
+ example := schema["schema"].(map[string]interface{})["example"].(map[string]interface{})
+ exampleStr, err := json.Marshal(&example)
+ if err != nil {
+ panic(err)
+ }
+
+ config := yamlconfig.YamlConfig{}
+
+ urlResult := parseUrl(endpointPath)
+ if urlResult.resultPath[len(urlResult.resultPath)-1] == '/' {
+ urlResult.resultPath = urlResult.resultPath[:len(urlResult.resultPath)-1]
+ }
+ config.RestEndpoint = urlResult.resultPath
+ config.DocCategory = urlResult.category
+ config.Name = resourceName
+ if urlResult.oneToOne {
+ config.PutCreate = true
+ config.NoDelete = true
+ }
+
+ attributes := []yamlconfig.YamlConfigAttribute{}
+ for i, r := range urlResult.references {
+ attr := yamlconfig.YamlConfigAttribute{}
+ attr.TfName = yamlconfig.CamelToSnake(r[1 : len(r)-1])
+ attr.Type = "String"
+ attr.Reference = true
+ if urlResult.oneToOne && i == len(urlResult.references)-1 {
+ attr.Id = true
+ }
+ attr.Description = "<>"
+ attr.TestValue = "<>"
+ attr.Example = "<>"
+ attributes = append(attributes, attr)
+ }
+ required := []string{}
+ if r, ok := schema["schema"].(map[string]interface{})["required"]; ok {
+ required = toStringSlice(r.([]interface{}))
+ }
+ attributes = append(attributes, traverseProperties(schema["schema"].(map[string]interface{})["properties"].(map[string]interface{}), []string{}, "", string(exampleStr), required)...)
+ config.Attributes = attributes
+
+ dataSourceNameQuery := false
+ for _, a := range config.Attributes {
+ if a.ModelName == "name" {
+ dataSourceNameQuery = true
+ break
+ }
+ }
+ config.DataSourceNameQuery = dataSourceNameQuery
+
+ outputPath := definitionsPath + yamlconfig.SnakeCase(resourceName) + ".yaml"
+
+ existingConfig := yamlconfig.YamlConfig{}
+ if yamlFile, err := os.ReadFile(outputPath); err == nil {
+ existingConfig = yamlconfig.YamlConfig{}
+ err = yaml.Unmarshal(yamlFile, &existingConfig)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ newConfig := yamlconfig.MergeYamlConfig(config, existingConfig)
+
+ var yamlBytes bytes.Buffer
+ yamlEncoder := yaml.NewEncoder(&yamlBytes)
+ yamlEncoder.SetIndent(2)
+ err = yamlEncoder.Encode(&newConfig)
+ if err != nil {
+ panic(err)
+ }
+
+ os.WriteFile(outputPath, yamlBytes.Bytes(), 0644)
+}
+
+func toStringSlice(i []interface{}) []string {
+ ret := []string{}
+ for _, v := range i {
+ ret = append(ret, v.(string))
+ }
+ return ret
+}
+
+var jsonTypes = map[string]string{
+ "integer": "Int64",
+ "number": "Float",
+ "boolean": "Bool",
+ "string": "String",
+}
+
+type parseUrlResult struct {
+ resultPath string
+ references []string
+ category string
+ oneToOne bool
+}
+
+func parseUrl(url string) parseUrlResult {
+ ret := parseUrlResult{}
+ r := regexp.MustCompile(`{[a-zA-Z]+}`)
+ parts := r.Split(url, -1)
+ ids := r.FindAllString(url, -1)
+ if url[len(url)-1] == '}' {
+ // one to many
+ ret.resultPath = strings.Join(parts[:len(parts)-1], "%v")
+ ret.references = ids[:len(ids)-1]
+ } else {
+ // one to one
+ ret.resultPath = strings.Join(parts, "%v")
+ ret.references = ids
+ ret.oneToOne = true
+ }
+ ret.category = parts[0][1 : len(parts[0])-1]
+ ret.category = strings.ToUpper(string(ret.category[0])) + ret.category[1:]
+ return ret
+}
+
+func traverseProperties(m map[string]interface{}, path []string, gjsonPath string, exampleStr string, requiredProperties []string) []yamlconfig.YamlConfigAttribute {
+ ret := []yamlconfig.YamlConfigAttribute{}
+
+ keys := maps.Keys(m)
+ sort.Strings(keys)
+
+ for _, propName := range keys {
+ propMap := m[propName].(map[string]interface{})
+ if propMap["type"] != "object" && propMap["type"] != "array" {
+ // primitive value
+ attr := yamlconfig.YamlConfigAttribute{}
+ attr.DataPath = path
+ attr.Type = jsonTypes[propMap["type"].(string)]
+ attr.ModelName = propName
+ childGjsonPath := (gjsonPath + "." + propName)[1:]
+ res := gjson.Get(exampleStr, childGjsonPath)
+ attr.Example = res.String()
+ if desc, ok := propMap["description"]; ok {
+ attr.Description = sanitizeDescription(desc.(string))
+ }
+ if enums, ok := propMap["enum"]; ok {
+ for _, r := range enums.([]interface{}) {
+ attr.EnumValues = append(attr.EnumValues, r.(string))
+ }
+ }
+ if min, ok := propMap["minimum"]; ok {
+ if attr.Type == "Int64" {
+ attr.MinInt = min.(int64)
+ } else if attr.Type == "Float" {
+ attr.MinFloat = min.(float64)
+ }
+ }
+ if max, ok := propMap["maximum"]; ok {
+ if attr.Type == "Int64" {
+ attr.MaxInt = max.(int64)
+ } else if attr.Type == "Float" {
+ attr.MaxFloat = max.(float64)
+ }
+ }
+ if slices.Contains(requiredProperties, propName) {
+ attr.Mandatory = true
+ }
+ ret = append(ret, attr)
+ }
+ }
+ for _, propName := range keys {
+ propMap := m[propName].(map[string]interface{})
+ if propMap["type"] == "object" {
+ childPath := append(path, propName)
+ childGjsonPath := gjsonPath + "." + propName
+ childRequired := []string{}
+ if rp, ok := propMap["required"]; ok {
+ childRequired = toStringSlice(rp.([]interface{}))
+ }
+ children := traverseProperties(propMap["properties"].(map[string]interface{}), childPath, childGjsonPath, exampleStr, childRequired)
+ ret = append(ret, children...)
+ }
+ }
+ for _, propName := range keys {
+ propMap := m[propName].(map[string]interface{})
+ if propMap["type"] == "array" {
+ attr := yamlconfig.YamlConfigAttribute{}
+ attr.DataPath = path
+ attr.Type = "List"
+ attr.ModelName = propName
+ items := propMap["items"].(map[string]interface{})
+ if desc, ok := propMap["description"]; ok {
+ attr.Description = sanitizeDescription(desc.(string))
+ }
+ if t, ok := jsonTypes[items["type"].(string)]; ok {
+ attr.ElementType = t
+ childGjsonPath := (gjsonPath + "." + propName + ".0")[1:]
+ res := gjson.Get(exampleStr, childGjsonPath)
+ attr.Example = res.String()
+ } else if items["type"].(string) == "object" {
+ childGjsonPath := gjsonPath + "." + propName + ".0"
+ childRequired := []string{}
+ if rp, ok := items["required"]; ok {
+ childRequired = toStringSlice(rp.([]interface{}))
+ }
+ children := traverseProperties(items["properties"].(map[string]interface{}), []string{}, childGjsonPath, exampleStr, childRequired)
+ attr.Attributes = children
+ }
+ ret = append(ret, attr)
+ }
+ }
+ return ret
+}
+
+func sanitizeDescription(desc string) string {
+ desc = strings.ReplaceAll(desc, "\n", " ")
+ r := regexp.MustCompile("<.*?>")
+ desc = r.ReplaceAllString(desc, "")
+ desc = strings.ReplaceAll(desc, "'", "`")
+ desc = strings.ReplaceAll(desc, "\"", "'")
+ desc = strings.Join(strings.Fields(desc), " ") // Remove extra spaces
+ return desc
+}
diff --git a/gen/definitions/admin.yaml b/gen/definitions/admin.yaml
index e449b146..2408f6af 100644
--- a/gen/definitions/admin.yaml
+++ b/gen/definitions/admin.yaml
@@ -1,4 +1,3 @@
----
name: Admin
rest_endpoint: /organizations/%v/admins
get_from_all: true
@@ -9,8 +8,13 @@ attributes:
type: String
reference: true
description: Organization ID
- example: 123456
+ example: "123456"
test_value: data.meraki_organization.test.id
+ - model_name: authenticationMethod
+ type: String
+ description: No longer used as of Cisco SecureX end-of-life. Can be one of `Email`. The default is Email authentication.
+ example: Email
+ enum_values: [Email]
- model_name: email
type: String
mandatory: true
@@ -25,8 +29,8 @@ attributes:
type: String
mandatory: true
description: The privilege of the dashboard administrator on the organization. Can be one of `full`, `read-only`, `enterprise` or `none`
- enum_values: [full, read-only, enterprise, none]
example: none
+ enum_values: [enterprise, full, none, read-only]
minimum_test_value: '"full"'
- model_name: networks
type: List
@@ -36,8 +40,8 @@ attributes:
type: String
mandatory: true
description: The privilege of the dashboard administrator on the network. Can be one of `full`, `read-only`, `guest-ambassador` or `monitor-only`
- enum_values: [full, read-only, guest-ambassador, monitor-only]
example: full
+ enum_values: [full, read-only, guest-ambassador, monitor-only]
- model_name: id
type: String
mandatory: true
@@ -52,8 +56,8 @@ attributes:
type: String
mandatory: true
description: The privilege of the dashboard administrator on the tag. Can be one of `full`, `read-only`, `guest-ambassador` or `monitor-only`
- enum_values: [full, read-only, guest-ambassador, monitor-only]
example: read-only
+ enum_values: [full, guest-ambassador, monitor-only, read-only]
- model_name: tag
type: String
mandatory: true
diff --git a/gen/definitions/network.yaml b/gen/definitions/network.yaml
index 66f0bbf5..8a892e93 100644
--- a/gen/definitions/network.yaml
+++ b/gen/definitions/network.yaml
@@ -1,4 +1,3 @@
----
name: Network
rest_endpoint: /organizations/%v/networks
data_source_name_query: true
diff --git a/gen/definitions/network_device_claim.yaml b/gen/definitions/network_device_claim.yaml
index 98e41f4c..dbbd506b 100644
--- a/gen/definitions/network_device_claim.yaml
+++ b/gen/definitions/network_device_claim.yaml
@@ -1,4 +1,3 @@
----
name: Network Device Claim
rest_endpoint: /networks/%v/devices/claim
no_data_source: true
@@ -21,7 +20,6 @@ attributes:
description: A list of serials of devices to claim
example: 1234-1234-1234
test_value: "[var.CLAIM_SERIAL_1]"
-
test_prerequisites: |
variable "CLAIM_SERIAL_1" {}
variable "CLAIM_SERIAL_2" {}
diff --git a/gen/definitions/network_group_policy.yaml b/gen/definitions/network_group_policy.yaml
new file mode 100644
index 00000000..727da3dc
--- /dev/null
+++ b/gen/definitions/network_group_policy.yaml
@@ -0,0 +1,349 @@
+name: Network Group Policy
+rest_endpoint: /networks/%v/groupPolicies
+id_name: groupPolicyId
+data_source_name_query: true
+doc_category: Networks
+attributes:
+ - tf_name: network_id
+ type: String
+ reference: true
+ description: Network ID
+ example: L_123456
+ test_value: meraki_network.test.id
+ - model_name: name
+ type: String
+ mandatory: true
+ description: The name for your group policy. Required.
+ example: No video streaming
+ - model_name: splashAuthSettings
+ type: String
+ description: Whether clients bound to your policy will bypass splash authorization or behave according to the network`s rules. Can be one of `network default` or `bypass`. Only available if your network has a wireless configuration.
+ example: bypass
+ enum_values: [bypass, network default]
+ - model_name: settings
+ type: String
+ data_path: [bandwidth]
+ description: How bandwidth limits are enforced. Can be `network default`, `ignore` or `custom`.
+ example: custom
+ enum_values: [custom, ignore, network default]
+ - model_name: limitDown
+ tf_name: bandwidth_limit_down
+ type: Int64
+ data_path: [bandwidth, bandwidthLimits]
+ description: The maximum download limit (integer, in Kbps). null indicates no limit
+ example: "1000000"
+ - model_name: limitUp
+ tf_name: bandwidth_limit_up
+ type: Int64
+ data_path: [bandwidth, bandwidthLimits]
+ description: The maximum upload limit (integer, in Kbps). null indicates no limit
+ example: "1000000"
+ - model_name: settings
+ type: String
+ data_path: [bonjourForwarding]
+ description: How Bonjour rules are applied. Can be `network default`, `ignore` or `custom`.
+ example: custom
+ enum_values: [custom, ignore, network default]
+ - model_name: rules
+ type: List
+ data_path: [bonjourForwarding]
+ description: A list of the Bonjour forwarding rules for your group policy. If `settings` is set to `custom`, at least one rule must be specified.
+ attributes:
+ - model_name: description
+ type: String
+ description: A description for your Bonjour forwarding rule. Optional.
+ example: A simple bonjour rule
+ - model_name: vlanId
+ type: String
+ id: true
+ mandatory: true
+ description: The ID of the service VLAN. Required.
+ example: "1"
+ - model_name: services
+ type: List
+ element_type: String
+ description: A list of Bonjour services. At least one service must be specified. Available services are `All Services`, `AirPlay`, `AFP`, `BitTorrent`, `FTP`, `iChat`, `iTunes`, `Printers`, `Samba`, `Scanners` and `SSH`
+ example: All Services
+ - model_name: settings
+ type: String
+ data_path: [contentFiltering, allowedUrlPatterns]
+ exclude_test: true
+ description: How URL patterns are applied. Can be `network default`, `append` or `override`.
+ example: network default
+ enum_values: [append, network default, override]
+ - model_name: patterns
+ tf_name: content_filtering_allowed_url_patterns
+ type: List
+ element_type: String
+ data_path: [contentFiltering, allowedUrlPatterns]
+ exclude_test: true
+ description: A list of URL patterns that are allowed
+ - model_name: settings
+ type: String
+ data_path: [contentFiltering, blockedUrlCategories]
+ exclude_test: true
+ description: How URL categories are applied. Can be `network default`, `append` or `override`.
+ example: override
+ enum_values: [append, network default, override]
+ - model_name: categories
+ tf_name: content_filtering_blocked_url_categories
+ type: List
+ element_type: String
+ data_path: [contentFiltering, blockedUrlCategories]
+ exclude_test: true
+ description: A list of URL categories to block
+ example: meraki:contentFiltering/category/1
+ - model_name: settings
+ type: String
+ data_path: [contentFiltering, blockedUrlPatterns]
+ exclude_test: true
+ description: How URL patterns are applied. Can be `network default`, `append` or `override`.
+ example: append
+ enum_values: [append, network default, override]
+ - model_name: patterns
+ tf_name: content_filtering_blocked_url_patterns
+ type: List
+ element_type: String
+ data_path: [contentFiltering, blockedUrlPatterns]
+ exclude_test: true
+ description: A list of URL patterns that are blocked
+ example: http://www.example.com
+ - model_name: settings
+ type: String
+ data_path: [firewallAndTrafficShaping]
+ description: How firewall and traffic shaping rules are enforced. Can be `network default`, `ignore` or `custom`.
+ example: custom
+ enum_values: [custom, ignore, network default]
+ - model_name: l3FirewallRules
+ tf_name: l3_firewall_rules
+ type: List
+ data_path: [firewallAndTrafficShaping]
+ description: An ordered array of the L3 firewall rules
+ ordered_list: true
+ attributes:
+ - model_name: comment
+ type: String
+ description: Description of the rule (optional)
+ example: Allow TCP traffic to subnet with HTTP servers.
+ - model_name: destCidr
+ type: String
+ mandatory: true
+ description: Destination IP address (in IP or CIDR notation), a fully-qualified domain name (FQDN, if your network supports it) or `any`.
+ example: 192.168.1.0/24
+ - model_name: destPort
+ type: String
+ description: Destination port (integer in the range 1-65535), a port range (e.g. 8080-9090), or `any`
+ example: "443"
+ - model_name: policy
+ type: String
+ mandatory: true
+ description: '`allow` or `deny` traffic specified by this rule'
+ example: allow
+ - model_name: protocol
+ type: String
+ mandatory: true
+ description: The type of protocol (must be `tcp`, `udp`, `icmp`, `icmp6` or `any`)
+ example: tcp
+ - model_name: l7FirewallRules
+ tf_name: l7_firewall_rules
+ type: List
+ data_path: [firewallAndTrafficShaping]
+ description: An ordered array of L7 firewall rules
+ ordered_list: true
+ attributes:
+ - model_name: policy
+ type: String
+ description: The policy applied to matching traffic. Must be `deny`.
+ example: deny
+ enum_values: [deny]
+ - model_name: type
+ type: String
+ description: Type of the L7 Rule. Must be `application`, `applicationCategory`, `host`, `port` or `ipRange`
+ example: host
+ enum_values: [application, applicationCategory, host, ipRange, port]
+ - model_name: value
+ type: String
+ description: The `value` of what you want to block. If `type` is `host`, `port` or `ipRange`, `value` must be a string matching either a hostname (e.g. somewhere.com), a port (e.g. 8080), or an IP range (e.g. 192.1.0.0/16). If `type` is `application` or `applicationCategory`, then `value` must be an object with an ID for the application.
+ example: google.com
+ - model_name: trafficShapingRules
+ tf_name: traffic_shaping_rules
+ type: List
+ data_path: [firewallAndTrafficShaping]
+ description: An array of traffic shaping rules. Rules are applied in the order that they are specified in. An empty list (or null) means no rules. Note that you are allowed a maximum of 8 rules.
+ ordered_list: true
+ attributes:
+ - model_name: dscpTagValue
+ type: Int64
+ description: The DSCP tag applied by your rule. null means `Do not change DSCP tag`. For a list of possible tag values, use the trafficShaping/dscpTaggingOptions endpoint.
+ example: "0"
+ - model_name: pcpTagValue
+ type: Int64
+ description: The PCP tag applied by your rule. Can be 0 (lowest priority) through 7 (highest priority). null means `Do not set PCP tag`.
+ example: "0"
+ - model_name: priority
+ type: String
+ write_only: true
+ description: A string, indicating the priority level for packets bound to your rule. Can be `low`, `normal` or `high`.
+ example: normal
+ - model_name: settings
+ type: String
+ data_path: [perClientBandwidthLimits]
+ description: How bandwidth limits are applied by your rule. Can be one of `network default`, `ignore` or `custom`.
+ example: custom
+ - model_name: limitDown
+ type: Int64
+ data_path: [perClientBandwidthLimits, bandwidthLimits]
+ description: The maximum download limit (integer, in Kbps).
+ example: "1000000"
+ - model_name: limitUp
+ type: Int64
+ data_path: [perClientBandwidthLimits, bandwidthLimits]
+ description: The maximum upload limit (integer, in Kbps).
+ example: "1000000"
+ - model_name: definitions
+ type: List
+ description: A list of objects describing the definitions of your traffic shaping rule. At least one definition is required.
+ attributes:
+ - model_name: type
+ type: String
+ id: true
+ mandatory: true
+ description: The type of definition. Can be one of `application`, `applicationCategory`, `host`, `port`, `ipRange` or `localNet`.
+ example: host
+ enum_values: [application, applicationCategory, host, ipRange, localNet, port]
+ - model_name: value
+ type: String
+ id: true
+ mandatory: true
+ description: 'If ''type'' is `host`, `port`, `ipRange` or `localNet`, then ''value'' must be a string, matching either a hostname (e.g. ''somesite.com''), a port (e.g. 8080), or an IP range (''192.1.0.0'', ''192.1.0.0/16'', or ''10.1.0.0/16:80''). `localNet` also supports CIDR notation, excluding custom ports. If ''type'' is `application` or `applicationCategory`, then ''value'' must be an object with the structure { ''id'': ''meraki:layer7/...'' }, where ''id'' is the application category or application ID (for a list of IDs for your network, use the trafficShaping/applicationCategories endpoint).'
+ example: google.com
+ - model_name: enabled
+ type: Bool
+ data_path: [scheduling]
+ description: Whether scheduling is enabled (true) or disabled (false). Defaults to false. If true, the schedule objects for each day of the week (monday - sunday) are parsed.
+ example: "true"
+ - model_name: active
+ type: Bool
+ data_path: [scheduling, friday]
+ description: Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+ example: "true"
+ - model_name: from
+ type: String
+ data_path: [scheduling, friday]
+ description: The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+ example: "09:00"
+ - model_name: to
+ type: String
+ data_path: [scheduling, friday]
+ description: The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+ example: "17:00"
+ - model_name: active
+ type: Bool
+ data_path: [scheduling, monday]
+ description: Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+ example: "true"
+ - model_name: from
+ type: String
+ data_path: [scheduling, monday]
+ description: The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+ example: "09:00"
+ - model_name: to
+ type: String
+ data_path: [scheduling, monday]
+ description: The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+ example: "17:00"
+ - model_name: active
+ type: Bool
+ data_path: [scheduling, saturday]
+ description: Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+ example: "true"
+ - model_name: from
+ type: String
+ data_path: [scheduling, saturday]
+ description: The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+ example: "09:00"
+ - model_name: to
+ type: String
+ data_path: [scheduling, saturday]
+ description: The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+ example: "17:00"
+ - model_name: active
+ type: Bool
+ data_path: [scheduling, sunday]
+ description: Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+ example: "true"
+ - model_name: from
+ type: String
+ data_path: [scheduling, sunday]
+ description: The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+ example: "09:00"
+ - model_name: to
+ type: String
+ data_path: [scheduling, sunday]
+ description: The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+ example: "17:00"
+ - model_name: active
+ type: Bool
+ data_path: [scheduling, thursday]
+ description: Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+ example: "true"
+ - model_name: from
+ type: String
+ data_path: [scheduling, thursday]
+ description: The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+ example: "09:00"
+ - model_name: to
+ type: String
+ data_path: [scheduling, thursday]
+ description: The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+ example: "17:00"
+ - model_name: active
+ type: Bool
+ data_path: [scheduling, tuesday]
+ description: Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+ example: "true"
+ - model_name: from
+ type: String
+ data_path: [scheduling, tuesday]
+ description: The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+ example: "09:00"
+ - model_name: to
+ type: String
+ data_path: [scheduling, tuesday]
+ description: The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+ example: "17:00"
+ - model_name: active
+ type: Bool
+ data_path: [scheduling, wednesday]
+ description: Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.
+ example: "true"
+ - model_name: from
+ type: String
+ data_path: [scheduling, wednesday]
+ description: The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.
+ example: "09:00"
+ - model_name: to
+ type: String
+ data_path: [scheduling, wednesday]
+ description: The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.
+ example: "17:00"
+ - model_name: settings
+ type: String
+ data_path: [vlanTagging]
+ description: How VLAN tagging is applied. Can be `network default`, `ignore` or `custom`.
+ example: custom
+ enum_values: [custom, ignore, network default]
+ - model_name: vlanId
+ type: String
+ data_path: [vlanTagging]
+ description: The ID of the vlan you want to tag. This only applies if `settings` is set to `custom`.
+ example: "1"
+test_prerequisites: |
+ data "meraki_organization" "test" {
+ name = "Dev"
+ }
+ resource "meraki_network" "test" {
+ organization_id = data.meraki_organization.test.id
+ name = "Network1"
+ product_types = ["switch", "wireless"]
+ }
diff --git a/gen/definitions/network_settings.yaml b/gen/definitions/network_settings.yaml
new file mode 100644
index 00000000..e9811c30
--- /dev/null
+++ b/gen/definitions/network_settings.yaml
@@ -0,0 +1,52 @@
+name: Network Settings
+rest_endpoint: /networks/%v/settings
+put_create: true
+no_delete: true
+doc_category: Networks
+attributes:
+ - tf_name: network_id
+ type: String
+ id: true
+ reference: true
+ description: Network ID
+ example: L_123456
+ test_value: meraki_network.test.id
+ - model_name: localStatusPageEnabled
+ type: Bool
+ description: Enables / disables the local device status pages (my.meraki.com, ap.meraki.com, switch.meraki.com, wired.meraki.com). Optional (defaults to false)
+ example: "true"
+ minimum_test_value: "true"
+ - model_name: remoteStatusPageEnabled
+ type: Bool
+ description: Enables / disables access to the device status page (http://[device`s LAN IP]). Optional. Can only be set if localStatusPageEnabled is set to true
+ example: "true"
+ - model_name: enabled
+ type: Bool
+ data_path: [localStatusPage, authentication]
+ description: Enables / disables the authentication on Local Status page(s).
+ example: "false"
+ - model_name: password
+ type: String
+ data_path: [localStatusPage, authentication]
+ write_only: true
+ description: The password used for Local Status Page(s). Set this to null to clear the password.
+ example: miles123
+ - model_name: enabled
+ type: Bool
+ data_path: [namedVlans]
+ description: Enables / disables Named VLANs on the Network.
+ example: "true"
+ - model_name: enabled
+ type: Bool
+ data_path: [securePort]
+ description: Enables / disables SecureConnect on the network. Optional.
+ example: "false"
+test_prerequisites: |
+ data "meraki_organization" "test" {
+ name = "Dev"
+ }
+ resource "meraki_network" "test" {
+ organization_id = data.meraki_organization.test.id
+ name = "Network1"
+ product_types = ["switch", "wireless"]
+ }
diff --git a/gen/definitions/network_snmp.yaml b/gen/definitions/network_snmp.yaml
index 5efe1980..419b77a3 100644
--- a/gen/definitions/network_snmp.yaml
+++ b/gen/definitions/network_snmp.yaml
@@ -1,8 +1,7 @@
----
name: Network SNMP
rest_endpoint: /networks/%v/snmp
-no_delete: true
put_create: true
+no_delete: true
doc_category: Networks
attributes:
- tf_name: network_id
@@ -14,32 +13,31 @@ attributes:
test_value: meraki_network.test.id
- model_name: access
type: String
- enum_values: [none, community, users]
description: The type of SNMP access. Can be one of `none` (disabled), `community` (V1/V2c), or `users` (V3).
example: users
+ enum_values: [community, none, users]
minimum_test_value: '"community"'
- model_name: communityString
type: String
- description: The SNMP community string. Only relevant if `access` is set to `community`.
- example: MerakiCommunity
- minimum_test_value: '"MerakiCommunity"'
exclude_test: true
+ description: The SNMP community string. Only relevant if `access` is set to `community`.
+ example: sample
+ minimum_test_value: '"sample"'
- model_name: users
type: List
description: The list of SNMP users. Only relevant if `access` is set to `users`.
attributes:
- - model_name: username
- type: String
- description: The username for the SNMP user. Required.
- mandatory: true
- id: true
- example: User1
- model_name: passphrase
type: String
+ mandatory: true
description: The passphrase for the SNMP user. Required.
+ example: hunter2
+ - model_name: username
+ type: String
+ id: true
mandatory: true
- example: Password123
-
+ description: The username for the SNMP user. Required.
+ example: AzureDiamond
test_prerequisites: |
data "meraki_organization" "test" {
name = "Dev"
diff --git a/gen/definitions/organization.yaml b/gen/definitions/organization.yaml
index 88186605..aac4d72e 100644
--- a/gen/definitions/organization.yaml
+++ b/gen/definitions/organization.yaml
@@ -1,4 +1,3 @@
----
name: Organization
rest_endpoint: /organizations
data_source_name_query: true
@@ -10,18 +9,18 @@ attributes:
description: The name of the organization
example: My organization
- model_name: details
+ type: List
data_path: [management]
description: Details related to organization management, possibly empty
- type: List
attributes:
- model_name: name
type: String
- mandatory: true
id: true
+ mandatory: true
description: Name of management data
example: MSP ID
- model_name: value
type: String
mandatory: true
description: Value of management data
- example: 123456
+ example: "123456"
diff --git a/gen/definitions/organization_inventory_claim.yaml b/gen/definitions/organization_inventory_claim.yaml
index 2a903c55..bb730056 100644
--- a/gen/definitions/organization_inventory_claim.yaml
+++ b/gen/definitions/organization_inventory_claim.yaml
@@ -1,4 +1,3 @@
----
name: Organization Inventory Claim
rest_endpoint: /organizations/%v/inventory/claim
no_data_source: true
@@ -21,7 +20,6 @@ attributes:
description: The list of serials to be claimed to the organization
example: 1234-1234-1234
test_value: "[var.CLAIM_SERIAL_1]"
-
test_prerequisites: |
variable "CLAIM_SERIAL_1" {}
variable "CLAIM_SERIAL_2" {}
diff --git a/gen/doc_category.go b/gen/doc_category.go
index 46cd6fe4..860b07a1 100644
--- a/gen/doc_category.go
+++ b/gen/doc_category.go
@@ -26,19 +26,14 @@ import (
"strings"
"gopkg.in/yaml.v3"
+
+ "github.com/CiscoDevNet/terraform-provider-meraki/gen/yamlconfig"
)
const (
definitionsPath = "./gen/definitions/"
)
-type YamlConfig struct {
- Name string `yaml:"name"`
- DocCategory string `yaml:"doc_category"`
- NoResource bool `yaml:"no_resource"`
- NoDataSource bool `yaml:"no_data_source"`
-}
-
var docPaths = []string{"./docs/data-sources/", "./docs/resources/"}
var extraDocs = map[string]string{}
@@ -56,7 +51,7 @@ func SnakeCase(s string) string {
func main() {
files, _ := os.ReadDir(definitionsPath)
- configs := make([]YamlConfig, len(files))
+ configs := make([]yamlconfig.YamlConfig, len(files))
// Load configs
for i, filename := range files {
@@ -65,7 +60,7 @@ func main() {
log.Fatalf("Error reading file: %v", err)
}
- config := YamlConfig{}
+ config := yamlconfig.YamlConfig{}
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
log.Fatalf("Error parsing yaml: %v", err)
diff --git a/gen/generator.go b/gen/generator.go
index 74491a9b..393907c4 100644
--- a/gen/generator.go
+++ b/gen/generator.go
@@ -30,9 +30,10 @@ import (
"regexp"
"strings"
"text/template"
- "unicode"
"gopkg.in/yaml.v3"
+
+ "github.com/CiscoDevNet/terraform-provider-meraki/gen/yamlconfig"
)
const (
@@ -93,331 +94,16 @@ var templates = []t{
},
}
-type YamlConfig struct {
- Name string `yaml:"name"`
- TfName string `yaml:"tf_name"`
- NoDataSource bool `yaml:"no_data_source"`
- NoResource bool `yaml:"no_resource"`
- RestEndpoint string `yaml:"rest_endpoint"`
- PutCreate bool `yaml:"put_create"`
- GetFromAll bool `yaml:"get_from_all"`
- NoUpdate bool `yaml:"no_update"`
- NoDelete bool `yaml:"no_delete"`
- NoImport bool `yaml:"no_import"`
- DataSourceNameQuery bool `yaml:"data_source_name_query"`
- MinimumVersion string `yaml:"minimum_version"`
- DsDescription string `yaml:"ds_description"`
- ResDescription string `yaml:"res_description"`
- DocCategory string `yaml:"doc_category"`
- ExcludeTest bool `yaml:"exclude_test"`
- SkipMinimumTest bool `yaml:"skip_minimum_test"`
- Attributes []YamlConfigAttribute `yaml:"attributes"`
- TestTags []string `yaml:"test_tags"`
- TestPrerequisites string `yaml:"test_prerequisites"`
-}
-
-type YamlConfigAttribute struct {
- ModelName string `yaml:"model_name"`
- TfName string `yaml:"tf_name"`
- Type string `yaml:"type"`
- ElementType string `yaml:"element_type"`
- DataPath []string `yaml:"data_path"`
- Id bool `yaml:"id"`
- Reference bool `yaml:"reference"`
- RequiresReplace bool `yaml:"requires_replace"`
- Mandatory bool `yaml:"mandatory"`
- WriteOnly bool `yaml:"write_only"`
- WriteChangesOnly bool `yaml:"write_changes_only"`
- ExcludeTest bool `yaml:"exclude_test"`
- ExcludeExample bool `yaml:"exclude_example"`
- Description string `yaml:"description"`
- Example string `yaml:"example"`
- EnumValues []string `yaml:"enum_values"`
- MinList int64 `yaml:"min_list"`
- MaxList int64 `yaml:"max_list"`
- MinInt int64 `yaml:"min_int"`
- MaxInt int64 `yaml:"max_int"`
- MinFloat float64 `yaml:"min_float"`
- MaxFloat float64 `yaml:"max_float"`
- OrderedList bool `yaml:"ordered_list"`
- StringPatterns []string `yaml:"string_patterns"`
- StringMinLength int64 `yaml:"string_min_length"`
- StringMaxLength int64 `yaml:"string_max_length"`
- DefaultValue string `yaml:"default_value"`
- Value string `yaml:"value"`
- TestValue string `yaml:"test_value"`
- MinimumTestValue string `yaml:"minimum_test_value"`
- TestTags []string `yaml:"test_tags"`
- Attributes []YamlConfigAttribute `yaml:"attributes"`
- GoTypeName string
-}
-
-// Templating helper function to convert TF name to GO name
-func ToGoName(s string) string {
- var g []string
-
- p := strings.Split(s, "_")
-
- for _, value := range p {
- g = append(g, strings.Title(value))
- }
- s = strings.Join(g, "")
- return s
-}
-
-// Templating helper function to convert string to camel case
-func CamelCase(s string) string {
- var g []string
-
- s = strings.ReplaceAll(s, "-", " ")
- p := strings.Fields(s)
-
- for _, value := range p {
- g = append(g, strings.Title(value))
- }
- return strings.Join(g, "")
-}
-
-// Templating helper function to convert string to snake case
-func SnakeCase(s string) string {
- var g []string
-
- s = strings.ReplaceAll(s, "-", " ")
- p := strings.Fields(s)
-
- for _, value := range p {
- g = append(g, strings.ToLower(value))
- }
- return strings.Join(g, "_")
-}
-
-// Templating helper function to fail a template mid-way
-func Errorf(s string, args ...any) (struct{}, error) {
- return struct{}{}, fmt.Errorf(s, args...)
-}
-
-// Templating helper function to build a SJSON path
-func BuildPath(s []string) string {
- return strings.Join(s, ".")
-}
-
-func contains(s []string, str string) bool {
- for _, v := range s {
- if v == str {
- return true
- }
- }
- return false
-}
-
-// Templating helper function to return true if id included in attributes
-func HasId(attributes []YamlConfigAttribute) bool {
- for _, attr := range attributes {
- if attr.Id {
- return true
- }
- }
- return false
-}
-
-// Templating helper function to return the id
-func GetId(attributes []YamlConfigAttribute) YamlConfigAttribute {
- for _, attr := range attributes {
- if attr.Id {
- return attr
- }
- }
- return YamlConfigAttribute{}
-}
-
-// Templating helper function to return true if reference included in attributes
-func HasReference(attributes []YamlConfigAttribute) bool {
- for _, attr := range attributes {
- if attr.Reference {
- return true
- }
- }
- return false
-}
-
-// Templating helper function to return true if type is a list or set without nested elements
-func IsListSet(attribute YamlConfigAttribute) bool {
- if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType != "" {
- return true
- }
- return false
-}
-
-// Templating helper function to return true if type is a list without nested elements
-func IsList(attribute YamlConfigAttribute) bool {
- if attribute.Type == "List" && attribute.ElementType != "" {
- return true
- }
- return false
-}
-
-// Templating helper function to return true if type is a set without nested elements
-func IsSet(attribute YamlConfigAttribute) bool {
- if attribute.Type == "Set" && attribute.ElementType != "" {
- return true
- }
- return false
-}
-
-// Templating helper function to return true if type is a list or set of strings without nested elements
-func IsStringListSet(attribute YamlConfigAttribute) bool {
- if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "String" {
- return true
- }
- return false
-}
-
-// Templating helper function to return true if type is a list or set of integers without nested elements
-func IsInt64ListSet(attribute YamlConfigAttribute) bool {
- if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "Int64" {
- return true
- }
- return false
-}
-
-// Templating helper function to return true if type is a list or set with nested elements
-func IsNestedListSet(attribute YamlConfigAttribute) bool {
- if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "" {
- return true
- }
- return false
-}
-
-// Templating helper function to return true if type is a list with nested elements
-func IsNestedList(attribute YamlConfigAttribute) bool {
- if attribute.Type == "List" && attribute.ElementType == "" {
- return true
- }
- return false
-}
-
-// Templating helper function to return true if type is a set with nested elements
-func IsNestedSet(attribute YamlConfigAttribute) bool {
- if attribute.Type == "Set" && attribute.ElementType == "" {
- return true
- }
- return false
-}
-
-// Templating helper function to return number of import parts
-func ImportParts(attributes []YamlConfigAttribute) int {
- parts := 1
- for _, attr := range attributes {
- if attr.Reference {
- parts += 1
- } else if attr.Id {
- parts += 1
- }
- }
- return parts
-}
-
-// Templating helper function to subtract one number from another
-func Subtract(a, b int) int {
- return a - b
-}
-
-// Map of templating functions
-var functions = template.FuncMap{
- "toGoName": ToGoName,
- "camelCase": CamelCase,
- "snakeCase": SnakeCase,
- "sprintf": fmt.Sprintf,
- "errorf": Errorf,
- "toLower": strings.ToLower,
- "path": BuildPath,
- "hasId": HasId,
- "getId": GetId,
- "hasReference": HasReference,
- "isListSet": IsListSet,
- "isList": IsList,
- "isSet": IsSet,
- "isStringListSet": IsStringListSet,
- "isInt64ListSet": IsInt64ListSet,
- "isNestedListSet": IsNestedListSet,
- "isNestedList": IsNestedList,
- "isNestedSet": IsNestedSet,
- "importParts": ImportParts,
- "subtract": Subtract,
-}
-
-func (attr *YamlConfigAttribute) init(parentGoTypeName string) error {
- // Augument
- if attr.TfName == "" {
- var words []string
- fullString := ""
- for _, s := range attr.DataPath {
- fullString += strings.ToUpper(string(s[0])) + s[1:]
- }
- fullString += strings.ToUpper(string(attr.ModelName[0])) + attr.ModelName[1:]
- l := 0
- for s := fullString; s != ""; s = s[l:] {
- l = strings.IndexFunc(s[1:], unicode.IsUpper) + 1
- if l <= 0 {
- l = len(s)
- }
- words = append(words, strings.ToLower(s[:l]))
- }
- attr.TfName = strings.Join(words, "_")
- }
-
- attr.GoTypeName = parentGoTypeName + ToGoName(attr.TfName)
-
- // Validate
- if len(attr.Attributes) > 0 && attr.Type != "List" && attr.Type != "Map" && attr.Type != "Set" {
- return fmt.Errorf("%q has type %q which cannot have `attributes`: instead use type List, Map, Set",
- attr.TfName, attr.Type)
- }
-
- if len(attr.Attributes) > 0 && attr.ElementType != "" {
- return fmt.Errorf("%q: either `attributes` or `element_type` can be specified, but not both", attr.TfName)
- }
-
- if attr.Type == "Map" && attr.ElementType != "" {
- return fmt.Errorf("%q: the `element_type` is not yet implemented for type Map", attr.TfName)
- }
-
- if attr.OrderedList {
- if attr.Type != "List" {
- return fmt.Errorf("%q has type %q which cannot use `ordered_list`: instead use type List",
- attr.TfName, attr.Type)
- }
- if HasId(attr.Attributes) {
- return fmt.Errorf("%q: the `ordered_list: true` conflicts with sub-attributes having `id: true`, as it treats list index ([i]) as the only unique id",
- attr.TfName)
- }
- }
-
- if attr.Type == "Map" && HasId(attr.Attributes) {
- return fmt.Errorf("Map %q cannot contain sub-attributes with `id: true`, as it treats map key ([k]) as the only unique id",
- attr.TfName)
- }
-
- // Recurse
- for i := range attr.Attributes {
- if err := attr.Attributes[i].init(attr.GoTypeName); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func NewYamlConfig(bytes []byte) (YamlConfig, error) {
- var config YamlConfig
+func NewYamlConfig(bytes []byte) (yamlconfig.YamlConfig, error) {
+ var config yamlconfig.YamlConfig
if err := yaml.Unmarshal(bytes, &config); err != nil {
return config, err
}
for i := range config.Attributes {
- if err := config.Attributes[i].init(CamelCase(config.Name)); err != nil {
- return YamlConfig{}, err
+ if err := config.Attributes[i].Init(yamlconfig.CamelCase(config.Name)); err != nil {
+ return yamlconfig.YamlConfig{}, err
}
}
if config.DsDescription == "" {
@@ -429,6 +115,9 @@ func NewYamlConfig(bytes []byte) (YamlConfig, error) {
if config.TfName == "" {
config.TfName = strings.Replace(config.Name, " ", "_", -1)
}
+ if config.IdName == "" {
+ config.IdName = "id"
+ }
return config, nil
}
@@ -475,7 +164,7 @@ func renderTemplate(templatePath, outputPath string, config interface{}) {
temp = temp + scanner.Text() + "\n"
}
- template, err := template.New(path.Base(templatePath)).Funcs(functions).Parse(temp)
+ template, err := template.New(path.Base(templatePath)).Funcs(yamlconfig.Functions).Parse(temp)
if err != nil {
log.Fatalf("Error parsing template: %v", err)
}
@@ -526,7 +215,7 @@ func renderTemplate(templatePath, outputPath string, config interface{}) {
func main() {
// Load configs
- var configs []YamlConfig
+ var configs []yamlconfig.YamlConfig
files, _ := os.ReadDir(definitionsPath)
for _, filename := range files {
@@ -557,7 +246,7 @@ func main() {
(configs[i].NoResource && t.path == "./gen/templates/import.sh") {
continue
}
- renderTemplate(t.path, t.prefix+SnakeCase(configs[i].Name)+t.suffix, configs[i])
+ renderTemplate(t.path, t.prefix+yamlconfig.SnakeCase(configs[i].Name)+t.suffix, configs[i])
}
providerConfig = append(providerConfig, configs[i].Name)
}
diff --git a/gen/schema/schema.yaml b/gen/schema/schema.yaml
index 7d486ec4..eff2b1e9 100644
--- a/gen/schema/schema.yaml
+++ b/gen/schema/schema.yaml
@@ -17,6 +17,7 @@ skip_minimum_test: bool(required=False) # Do not perform a "minimum" (only manda
attributes: list(include('attribute'), required=False) # List of attributes
test_tags: list(str(), required=False) # List of test tags, tests are only executed if an environment variable with for each of these tags is configured
test_prerequisites: str(required=False) # HCL code that is included in the acceptance tests to define prerequisites
+id_name: str(required=False) # the identifier field name of the resource as seen in the request body
---
attribute:
model_name: str(required=False) # Name of the attribute in the model (payload)
diff --git a/gen/templates/data_source.go b/gen/templates/data_source.go
index 9a4f9ba0..17895fb6 100644
--- a/gen/templates/data_source.go
+++ b/gen/templates/data_source.go
@@ -195,7 +195,7 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource
if len(res.Array()) > 0 {
res.ForEach(func(k, v gjson.Result) bool {
if config.Name.ValueString() == v.Get("name").String() {
- config.Id = types.StringValue(v.Get("id").String())
+ config.Id = types.StringValue(v.Get("{{.IdName}}").String())
tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%v', id: %v", config.Id.String(), config.Name.ValueString(), config.Id.String()))
res = v
return false
@@ -225,7 +225,7 @@ func (d *{{camelCase .Name}}DataSource) Read(ctx context.Context, req datasource
{{- if .GetFromAll}}
if len(res.Array()) > 0 {
res.ForEach(func(k, v gjson.Result) bool {
- if config.Id.ValueString() == v.Get("id").String() {
+ if config.Id.ValueString() == v.Get("{{.IdName}}").String() {
res = v
return false
}
diff --git a/gen/templates/model.go b/gen/templates/model.go
index 5c813790..9ff1d000 100644
--- a/gen/templates/model.go
+++ b/gen/templates/model.go
@@ -316,8 +316,8 @@ func (data *{{camelCase .Name}}) fromBodyPartial(ctx context.Context, res gjson.
res := parentRes.Get(fmt.Sprintf("{{range .DataPath}}{{.}}.{{end}}{{.ModelName}}.%d", i))
{{- else }}
for i := 0; i < len(data.{{toGoName .TfName}}); i++ {
- keys := [...]string{ {{$noId := not (hasId .Attributes)}}{{range .Attributes}}{{if or .Id (and $noId (not .Value))}}{{if or (eq .Type "Int64") (eq .Type "Bool") (eq .Type "String")}}"{{range .DataPath}}{{.}}.{{end}}{{.ModelName}}", {{end}}{{end}}{{end}} }
- keyValues := [...]string{ {{$noId := not (hasId .Attributes)}}{{range .Attributes}}{{if or .Id (and $noId (not .Value))}}{{if eq .Type "Int64"}}strconv.FormatInt(data.{{$list}}[i].{{toGoName .TfName}}.ValueInt64(), 10), {{else if eq .Type "Bool"}}strconv.FormatBool(data.{{$list}}[i].{{toGoName .TfName}}.ValueBool()), {{else if eq .Type "String"}}data.{{$list}}[i].{{toGoName .TfName}}.Value{{.Type}}(), {{end}}{{end}}{{end}} }
+ keys := [...]string{ {{$noId := not (hasId .Attributes)}}{{range .Attributes}}{{if or .Id (and $noId (not .Value) (not .WriteOnly))}}{{if or (eq .Type "Int64") (eq .Type "Bool") (eq .Type "String")}}"{{range .DataPath}}{{.}}.{{end}}{{.ModelName}}", {{end}}{{end}}{{end}} }
+ keyValues := [...]string{ {{$noId := not (hasId .Attributes)}}{{range .Attributes}}{{if or .Id (and $noId (not .Value) (not .WriteOnly))}}{{if eq .Type "Int64"}}strconv.FormatInt(data.{{$list}}[i].{{toGoName .TfName}}.ValueInt64(), 10), {{else if eq .Type "Bool"}}strconv.FormatBool(data.{{$list}}[i].{{toGoName .TfName}}.ValueBool()), {{else if eq .Type "String"}}data.{{$list}}[i].{{toGoName .TfName}}.Value{{.Type}}(), {{end}}{{end}}{{end}} }
parent := &data
data := (*parent).{{toGoName .TfName}}[i]
diff --git a/gen/templates/resource.go b/gen/templates/resource.go
index f303ebf5..ee024c3e 100644
--- a/gen/templates/resource.go
+++ b/gen/templates/resource.go
@@ -430,7 +430,7 @@ func (r *{{camelCase .Name}}Resource) Create(ctx context.Context, req resource.C
{{- if hasId .Attributes}}
plan.Id = plan.{{toGoName (getId .Attributes).TfName}}
{{- else}}
- plan.Id = types.StringValue(res.Get("id").String())
+ plan.Id = types.StringValue(res.Get("{{.IdName}}").String())
{{- end}}
tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString()))
@@ -471,7 +471,7 @@ func (r *{{camelCase .Name}}Resource) Read(ctx context.Context, req resource.Rea
{{- if .GetFromAll}}
if len(res.Array()) > 0 {
res.ForEach(func(k, v gjson.Result) bool {
- if state.Id.ValueString() == v.Get("id").String() {
+ if state.Id.ValueString() == v.Get("{{.IdName}}").String() {
res = v
return false
}
diff --git a/gen/yamlconfig/main.go b/gen/yamlconfig/main.go
new file mode 100644
index 00000000..f86a81f9
--- /dev/null
+++ b/gen/yamlconfig/main.go
@@ -0,0 +1,534 @@
+package yamlconfig
+
+import (
+ "fmt"
+ "regexp"
+ "slices"
+ "strings"
+ "text/template"
+)
+
+type YamlConfig struct {
+ Name string `yaml:"name,omitempty"`
+ TfName string `yaml:"tf_name,omitempty"`
+ RestEndpoint string `yaml:"rest_endpoint,omitempty"`
+ NoDataSource bool `yaml:"no_data_source,omitempty"`
+ NoResource bool `yaml:"no_resource,omitempty"`
+ PutCreate bool `yaml:"put_create,omitempty"`
+ GetFromAll bool `yaml:"get_from_all,omitempty"`
+ NoUpdate bool `yaml:"no_update,omitempty"`
+ NoDelete bool `yaml:"no_delete,omitempty"`
+ NoImport bool `yaml:"no_import,omitempty"`
+ IdName string `yaml:"id_name,omitempty"`
+ DataSourceNameQuery bool `yaml:"data_source_name_query,omitempty"`
+ MinimumVersion string `yaml:"minimum_version,omitempty"`
+ DsDescription string `yaml:"ds_description,omitempty"`
+ ResDescription string `yaml:"res_description,omitempty"`
+ DocCategory string `yaml:"doc_category,omitempty"`
+ ExcludeTest bool `yaml:"exclude_test,omitempty"`
+ SkipMinimumTest bool `yaml:"skip_minimum_test,omitempty"`
+ TestTags []string `yaml:"test_tags,omitempty"`
+ Attributes []YamlConfigAttribute `yaml:"attributes,omitempty"`
+ TestPrerequisites string `yaml:"test_prerequisites,omitempty"`
+}
+
+type YamlConfigAttribute struct {
+ ModelName string `yaml:"model_name,omitempty"`
+ TfName string `yaml:"tf_name,omitempty"`
+ Type string `yaml:"type,omitempty"`
+ ElementType string `yaml:"element_type,omitempty"`
+ DataPath []string `yaml:"data_path,omitempty,flow"`
+ Id bool `yaml:"id,omitempty"`
+ Reference bool `yaml:"reference,omitempty"`
+ RequiresReplace bool `yaml:"requires_replace,omitempty"`
+ Mandatory bool `yaml:"mandatory,omitempty"`
+ WriteOnly bool `yaml:"write_only,omitempty"`
+ WriteChangesOnly bool `yaml:"write_changes_only,omitempty"`
+ ExcludeTest bool `yaml:"exclude_test,omitempty"`
+ ExcludeExample bool `yaml:"exclude_example,omitempty"`
+ Description string `yaml:"description,omitempty"`
+ Example string `yaml:"example,omitempty"`
+ EnumValues []string `yaml:"enum_values,omitempty,flow"`
+ MinList int64 `yaml:"min_list,omitempty"`
+ MaxList int64 `yaml:"max_list,omitempty"`
+ MinInt int64 `yaml:"min_int,omitempty"`
+ MaxInt int64 `yaml:"max_int,omitempty"`
+ MinFloat float64 `yaml:"min_float,omitempty"`
+ MaxFloat float64 `yaml:"max_float,omitempty"`
+ OrderedList bool `yaml:"ordered_list,omitempty"`
+ StringPatterns []string `yaml:"string_patterns,omitempty,flow"`
+ StringMinLength int64 `yaml:"string_min_length,omitempty"`
+ StringMaxLength int64 `yaml:"string_max_length,omitempty"`
+ DefaultValue string `yaml:"default_value,omitempty"`
+ Value string `yaml:"value,omitempty"`
+ TestValue string `yaml:"test_value,omitempty"`
+ MinimumTestValue string `yaml:"minimum_test_value,omitempty"`
+ TestTags []string `yaml:"test_tags,omitempty,flow"`
+ Attributes []YamlConfigAttribute `yaml:"attributes,omitempty"`
+ GoTypeName string `yaml:"gotypename,omitempty"`
+}
+
+// Templating helper function to convert TF name to GO name
+func ToGoName(s string) string {
+ var g []string
+
+ p := strings.Split(s, "_")
+
+ for _, value := range p {
+ g = append(g, strings.Title(value))
+ }
+ s = strings.Join(g, "")
+ return s
+}
+
+// Templating helper function to convert string to camel case
+func CamelCase(s string) string {
+ var g []string
+
+ s = strings.ReplaceAll(s, "-", " ")
+ p := strings.Fields(s)
+
+ for _, value := range p {
+ g = append(g, strings.Title(value))
+ }
+ return strings.Join(g, "")
+}
+
+// Templating helper function to convert string to snake case
+func SnakeCase(s string) string {
+ var g []string
+
+ s = strings.ReplaceAll(s, "-", " ")
+ p := strings.Fields(s)
+
+ for _, value := range p {
+ g = append(g, strings.ToLower(value))
+ }
+ return strings.Join(g, "_")
+}
+
+// Templating helper function to fail a template mid-way
+func Errorf(s string, args ...any) (struct{}, error) {
+ return struct{}{}, fmt.Errorf(s, args...)
+}
+
+// Templating helper function to build a SJSON path
+func BuildPath(s []string) string {
+ return strings.Join(s, ".")
+}
+
+func contains(s []string, str string) bool {
+ for _, v := range s {
+ if v == str {
+ return true
+ }
+ }
+ return false
+}
+
+// Templating helper function to return true if id included in attributes
+func HasId(attributes []YamlConfigAttribute) bool {
+ for _, attr := range attributes {
+ if attr.Id {
+ return true
+ }
+ }
+ return false
+}
+
+// Templating helper function to return the id
+func GetId(attributes []YamlConfigAttribute) YamlConfigAttribute {
+ for _, attr := range attributes {
+ if attr.Id {
+ return attr
+ }
+ }
+ return YamlConfigAttribute{}
+}
+
+// Templating helper function to return true if reference included in attributes
+func HasReference(attributes []YamlConfigAttribute) bool {
+ for _, attr := range attributes {
+ if attr.Reference {
+ return true
+ }
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a list or set without nested elements
+func IsListSet(attribute YamlConfigAttribute) bool {
+ if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType != "" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a list without nested elements
+func IsList(attribute YamlConfigAttribute) bool {
+ if attribute.Type == "List" && attribute.ElementType != "" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a set without nested elements
+func IsSet(attribute YamlConfigAttribute) bool {
+ if attribute.Type == "Set" && attribute.ElementType != "" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a list or set of strings without nested elements
+func IsStringListSet(attribute YamlConfigAttribute) bool {
+ if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "String" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a list or set of integers without nested elements
+func IsInt64ListSet(attribute YamlConfigAttribute) bool {
+ if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "Int64" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a list or set with nested elements
+func IsNestedListSet(attribute YamlConfigAttribute) bool {
+ if (attribute.Type == "List" || attribute.Type == "Set") && attribute.ElementType == "" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a list with nested elements
+func IsNestedList(attribute YamlConfigAttribute) bool {
+ if attribute.Type == "List" && attribute.ElementType == "" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return true if type is a set with nested elements
+func IsNestedSet(attribute YamlConfigAttribute) bool {
+ if attribute.Type == "Set" && attribute.ElementType == "" {
+ return true
+ }
+ return false
+}
+
+// Templating helper function to return number of import parts
+func ImportParts(attributes []YamlConfigAttribute) int {
+ parts := 1
+ for _, attr := range attributes {
+ if attr.Reference {
+ parts += 1
+ } else if attr.Id {
+ parts += 1
+ }
+ }
+ return parts
+}
+
+// Templating helper function to subtract one number from another
+func Subtract(a, b int) int {
+ return a - b
+}
+
+// Map of templating functions
+var Functions = template.FuncMap{
+ "toGoName": ToGoName,
+ "camelCase": CamelCase,
+ "snakeCase": SnakeCase,
+ "sprintf": fmt.Sprintf,
+ "errorf": Errorf,
+ "toLower": strings.ToLower,
+ "path": BuildPath,
+ "hasId": HasId,
+ "getId": GetId,
+ "hasReference": HasReference,
+ "isListSet": IsListSet,
+ "isList": IsList,
+ "isSet": IsSet,
+ "isStringListSet": IsStringListSet,
+ "isInt64ListSet": IsInt64ListSet,
+ "isNestedListSet": IsNestedListSet,
+ "isNestedList": IsNestedList,
+ "isNestedSet": IsNestedSet,
+ "importParts": ImportParts,
+ "subtract": Subtract,
+}
+
+var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
+var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
+
+func CamelToSnake(str string) string {
+ snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
+ snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
+ return strings.ToLower(snake)
+}
+
+func (attr *YamlConfigAttribute) Init(parentGoTypeName string) error {
+ // Augument
+ if attr.TfName == "" {
+ // var words []string
+ fullString := ""
+ for _, s := range attr.DataPath {
+ fullString += strings.ToUpper(string(s[0])) + s[1:]
+ }
+ fullString += strings.ToUpper(string(attr.ModelName[0])) + attr.ModelName[1:]
+ // l := 0
+ // for s := fullString; s != ""; s = s[l:] {
+ // l = strings.IndexFunc(s[1:], unicode.IsUpper) + 1
+ // if l <= 0 {
+ // l = len(s)
+ // }
+ // words = append(words, strings.ToLower(s[:l]))
+ // }
+ // attr.TfName = strings.Join(words, "_")
+ attr.TfName = CamelToSnake(fullString)
+ }
+
+ attr.GoTypeName = parentGoTypeName + ToGoName(attr.TfName)
+
+ // Validate
+ if len(attr.Attributes) > 0 && attr.Type != "List" && attr.Type != "Map" && attr.Type != "Set" {
+ return fmt.Errorf("%q has type %q which cannot have `attributes`: instead use type List, Map, Set",
+ attr.TfName, attr.Type)
+ }
+
+ if len(attr.Attributes) > 0 && attr.ElementType != "" {
+ return fmt.Errorf("%q: either `attributes` or `element_type` can be specified, but not both", attr.TfName)
+ }
+
+ if attr.Type == "Map" && attr.ElementType != "" {
+ return fmt.Errorf("%q: the `element_type` is not yet implemented for type Map", attr.TfName)
+ }
+
+ if attr.OrderedList {
+ if attr.Type != "List" {
+ return fmt.Errorf("%q has type %q which cannot use `ordered_list`: instead use type List",
+ attr.TfName, attr.Type)
+ }
+ if HasId(attr.Attributes) {
+ return fmt.Errorf("%q: the `ordered_list: true` conflicts with sub-attributes having `id: true`, as it treats list index ([i]) as the only unique id",
+ attr.TfName)
+ }
+ }
+
+ if attr.Type == "Map" && HasId(attr.Attributes) {
+ return fmt.Errorf("Map %q cannot contain sub-attributes with `id: true`, as it treats map key ([k]) as the only unique id",
+ attr.TfName)
+ }
+
+ // Recurse
+ for i := range attr.Attributes {
+ if err := attr.Attributes[i].Init(attr.GoTypeName); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func MergeYamlConfig(a, b YamlConfig) YamlConfig {
+ // Merge b into a
+ if b.Name != "" {
+ a.Name = b.Name
+ }
+ if b.TfName != "" {
+ a.TfName = b.TfName
+ }
+ if b.RestEndpoint != "" {
+ a.RestEndpoint = b.RestEndpoint
+ }
+ if b.NoDataSource {
+ a.NoDataSource = b.NoDataSource
+ }
+ if b.NoResource {
+ a.NoResource = b.NoResource
+ }
+ if b.PutCreate {
+ a.PutCreate = b.PutCreate
+ }
+ if b.GetFromAll {
+ a.GetFromAll = b.GetFromAll
+ }
+ if b.NoUpdate {
+ a.NoUpdate = b.NoUpdate
+ }
+ if b.NoDelete {
+ a.NoDelete = b.NoDelete
+ }
+ if b.NoImport {
+ a.NoImport = b.NoImport
+ }
+ if b.IdName != "" {
+ a.IdName = b.IdName
+ }
+ if b.DataSourceNameQuery {
+ a.DataSourceNameQuery = b.DataSourceNameQuery
+ }
+ if b.MinimumVersion != "" {
+ a.MinimumVersion = b.MinimumVersion
+ }
+ if b.DsDescription != "" {
+ a.DsDescription = b.DsDescription
+ }
+ if b.ResDescription != "" {
+ a.ResDescription = b.ResDescription
+ }
+ if b.DocCategory != "" {
+ a.DocCategory = b.DocCategory
+ }
+ if b.ExcludeTest {
+ a.ExcludeTest = b.ExcludeTest
+ }
+ if b.SkipMinimumTest {
+ a.SkipMinimumTest = b.SkipMinimumTest
+ }
+ if b.TestTags != nil {
+ a.TestTags = b.TestTags
+ }
+ if b.Attributes != nil {
+ a.Attributes = MergeYamlConfigAttributes(a.Attributes, b.Attributes)
+ }
+ if b.TestPrerequisites != "" {
+ a.TestPrerequisites = b.TestPrerequisites
+ }
+ return a
+}
+
+func MergeYamlConfigAttributes(a, b []YamlConfigAttribute) []YamlConfigAttribute {
+ // Merge b into a
+ var c []YamlConfigAttribute
+ for _, attr := range b {
+ found := false
+ for i := range a {
+ if a[i].ModelName != "" && attr.ModelName != "" && slices.Equal(a[i].DataPath, attr.DataPath) {
+ if a[i].ModelName == attr.ModelName {
+ c = append(c, MergeYamlConfigAttribute(a[i], attr))
+ found = true
+ break
+ }
+ } else if a[i].TfName != "" && attr.TfName != "" {
+ if a[i].TfName == attr.TfName {
+ c = append(c, MergeYamlConfigAttribute(a[i], attr))
+ found = true
+ break
+ }
+ }
+ }
+ if !found {
+ c = append(c, attr)
+ }
+ }
+ return c
+}
+
+func MergeYamlConfigAttribute(a, b YamlConfigAttribute) YamlConfigAttribute {
+ // Merge b into a
+ if b.ModelName != "" {
+ a.ModelName = b.ModelName
+ }
+ if b.TfName != "" {
+ a.TfName = b.TfName
+ }
+ if b.Type != "" {
+ a.Type = b.Type
+ }
+ if b.ElementType != "" {
+ a.ElementType = b.ElementType
+ }
+ if b.DataPath != nil {
+ a.DataPath = b.DataPath
+ }
+ if b.Id {
+ a.Id = b.Id
+ }
+ if b.Reference {
+ a.Reference = b.Reference
+ }
+ if b.RequiresReplace {
+ a.RequiresReplace = b.RequiresReplace
+ }
+ if b.Mandatory {
+ a.Mandatory = b.Mandatory
+ }
+ if b.WriteOnly {
+ a.WriteOnly = b.WriteOnly
+ }
+ if b.WriteChangesOnly {
+ a.WriteChangesOnly = b.WriteChangesOnly
+ }
+ if b.ExcludeTest {
+ a.ExcludeTest = b.ExcludeTest
+ }
+ if b.ExcludeExample {
+ a.ExcludeExample = b.ExcludeExample
+ }
+ if b.Description != "" {
+ a.Description = b.Description
+ }
+ if b.Example != "" {
+ a.Example = b.Example
+ }
+ if b.EnumValues != nil {
+ a.EnumValues = b.EnumValues
+ }
+ if b.MinList != 0 {
+ a.MinList = b.MinList
+ }
+ if b.MaxList != 0 {
+ a.MaxList = b.MaxList
+ }
+ if b.MinInt != 0 {
+ a.MinInt = b.MinInt
+ }
+ if b.MaxInt != 0 {
+ a.MaxInt = b.MaxInt
+ }
+ if b.MinFloat != 0 {
+ a.MinFloat = b.MinFloat
+ }
+ if b.MaxFloat != 0 {
+ a.MaxFloat = b.MaxFloat
+ }
+ if b.OrderedList {
+ a.OrderedList = b.OrderedList
+ }
+ if b.StringPatterns != nil {
+ a.StringPatterns = b.StringPatterns
+ }
+ if b.StringMinLength != 0 {
+ a.StringMinLength = b.StringMinLength
+ }
+ if b.StringMaxLength != 0 {
+ a.StringMaxLength = b.StringMaxLength
+ }
+ if b.DefaultValue != "" {
+ a.DefaultValue = b.DefaultValue
+ }
+ if b.Value != "" {
+ a.Value = b.Value
+ }
+ if b.TestValue != "" {
+ a.TestValue = b.TestValue
+ }
+ if b.MinimumTestValue != "" {
+ a.MinimumTestValue = b.MinimumTestValue
+ }
+ if b.TestTags != nil {
+ a.TestTags = b.TestTags
+ }
+ if b.Attributes != nil {
+ a.Attributes = MergeYamlConfigAttributes(a.Attributes, b.Attributes)
+ }
+ if b.GoTypeName != "" {
+ a.GoTypeName = b.GoTypeName
+ }
+ return a
+}
diff --git a/go.mod b/go.mod
index ca53cc8b..f5b35eb7 100644
--- a/go.mod
+++ b/go.mod
@@ -14,10 +14,13 @@ require (
github.com/netascode/go-meraki v0.0.0-20240912063604-fb6bdcff45e5
github.com/tidwall/gjson v1.17.3
github.com/tidwall/sjson v1.2.5
+ golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819
golang.org/x/tools v0.25.0
gopkg.in/yaml.v3 v3.0.1
)
+replace github.com/netascode/go-meraki v0.0.0-20240901102824-a67592c39438 => /Users/maparafi/go-meraki
+
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
@@ -80,7 +83,6 @@ require (
github.com/zclconf/go-cty v1.15.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
- golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
diff --git a/go.sum b/go.sum
index 649fc7f7..b636c13e 100644
--- a/go.sum
+++ b/go.sum
@@ -283,8 +283,6 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
-google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
-google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
diff --git a/internal/provider/data_source_meraki_admin.go b/internal/provider/data_source_meraki_admin.go
index 9e62a947..d62614ca 100644
--- a/internal/provider/data_source_meraki_admin.go
+++ b/internal/provider/data_source_meraki_admin.go
@@ -69,6 +69,10 @@ func (d *AdminDataSource) Schema(ctx context.Context, req datasource.SchemaReque
MarkdownDescription: "Organization ID",
Required: true,
},
+ "authentication_method": schema.StringAttribute{
+ MarkdownDescription: "No longer used as of Cisco SecureX end-of-life. Can be one of `Email`. The default is Email authentication.",
+ Computed: true,
+ },
"email": schema.StringAttribute{
MarkdownDescription: "The email of the dashboard administrator. This attribute can not be updated.",
Computed: true,
diff --git a/internal/provider/data_source_meraki_admin_test.go b/internal/provider/data_source_meraki_admin_test.go
index 252df62a..e8b944fd 100644
--- a/internal/provider/data_source_meraki_admin_test.go
+++ b/internal/provider/data_source_meraki_admin_test.go
@@ -30,6 +30,7 @@ import (
func TestAccDataSourceMerakiAdmin(t *testing.T) {
var checks []resource.TestCheckFunc
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_admin.test", "authentication_method", "Email"))
checks = append(checks, resource.TestCheckResourceAttr("data.meraki_admin.test", "email", "miles@meraki.com"))
checks = append(checks, resource.TestCheckResourceAttr("data.meraki_admin.test", "name", "Miles Meraki"))
checks = append(checks, resource.TestCheckResourceAttr("data.meraki_admin.test", "org_access", "none"))
@@ -75,6 +76,7 @@ resource "meraki_network" "test" {
func testAccDataSourceMerakiAdminConfig() string {
config := `resource "meraki_admin" "test" {` + "\n"
config += ` organization_id = data.meraki_organization.test.id` + "\n"
+ config += ` authentication_method = "Email"` + "\n"
config += ` email = "miles@meraki.com"` + "\n"
config += ` name = "Miles Meraki"` + "\n"
config += ` org_access = "none"` + "\n"
@@ -100,6 +102,7 @@ func testAccDataSourceMerakiAdminConfig() string {
func testAccNamedDataSourceMerakiAdminConfig() string {
config := `resource "meraki_admin" "test" {` + "\n"
config += ` organization_id = data.meraki_organization.test.id` + "\n"
+ config += ` authentication_method = "Email"` + "\n"
config += ` email = "miles@meraki.com"` + "\n"
config += ` name = "Miles Meraki"` + "\n"
config += ` org_access = "none"` + "\n"
diff --git a/internal/provider/data_source_meraki_network_group_policy.go b/internal/provider/data_source_meraki_network_group_policy.go
new file mode 100644
index 00000000..ee1ce48b
--- /dev/null
+++ b/internal/provider/data_source_meraki_network_group_policy.go
@@ -0,0 +1,420 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "context"
+ "fmt"
+ "net/url"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/netascode/go-meraki"
+ "github.com/tidwall/gjson"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin model
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &NetworkGroupPolicyDataSource{}
+ _ datasource.DataSourceWithConfigure = &NetworkGroupPolicyDataSource{}
+)
+
+func NewNetworkGroupPolicyDataSource() datasource.DataSource {
+ return &NetworkGroupPolicyDataSource{}
+}
+
+type NetworkGroupPolicyDataSource struct {
+ client *meraki.Client
+}
+
+func (d *NetworkGroupPolicyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_network_group_policy"
+}
+
+func (d *NetworkGroupPolicyDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "This data source can read the `Network Group Policy` configuration.",
+
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "The id of the object",
+ Optional: true,
+ Computed: true,
+ },
+ "network_id": schema.StringAttribute{
+ MarkdownDescription: "Network ID",
+ Required: true,
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "The name for your group policy. Required.",
+ Optional: true,
+ Computed: true,
+ },
+ "splash_auth_settings": schema.StringAttribute{
+ MarkdownDescription: "Whether clients bound to your policy will bypass splash authorization or behave according to the network`s rules. Can be one of `network default` or `bypass`. Only available if your network has a wireless configuration.",
+ Computed: true,
+ },
+ "bandwidth_settings": schema.StringAttribute{
+ MarkdownDescription: "How bandwidth limits are enforced. Can be `network default`, `ignore` or `custom`.",
+ Computed: true,
+ },
+ "bandwidth_limit_down": schema.Int64Attribute{
+ MarkdownDescription: "The maximum download limit (integer, in Kbps). null indicates no limit",
+ Computed: true,
+ },
+ "bandwidth_limit_up": schema.Int64Attribute{
+ MarkdownDescription: "The maximum upload limit (integer, in Kbps). null indicates no limit",
+ Computed: true,
+ },
+ "bonjour_forwarding_settings": schema.StringAttribute{
+ MarkdownDescription: "How Bonjour rules are applied. Can be `network default`, `ignore` or `custom`.",
+ Computed: true,
+ },
+ "bonjour_forwarding_rules": schema.ListNestedAttribute{
+ MarkdownDescription: "A list of the Bonjour forwarding rules for your group policy. If `settings` is set to `custom`, at least one rule must be specified.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "description": schema.StringAttribute{
+ MarkdownDescription: "A description for your Bonjour forwarding rule. Optional.",
+ Computed: true,
+ },
+ "vlan_id": schema.StringAttribute{
+ MarkdownDescription: "The ID of the service VLAN. Required.",
+ Computed: true,
+ },
+ "services": schema.ListAttribute{
+ MarkdownDescription: "A list of Bonjour services. At least one service must be specified. Available services are `All Services`, `AirPlay`, `AFP`, `BitTorrent`, `FTP`, `iChat`, `iTunes`, `Printers`, `Samba`, `Scanners` and `SSH`",
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "content_filtering_allowed_url_patterns_settings": schema.StringAttribute{
+ MarkdownDescription: "How URL patterns are applied. Can be `network default`, `append` or `override`.",
+ Computed: true,
+ },
+ "content_filtering_allowed_url_patterns": schema.ListAttribute{
+ MarkdownDescription: "A list of URL patterns that are allowed",
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ "content_filtering_blocked_url_categories_settings": schema.StringAttribute{
+ MarkdownDescription: "How URL categories are applied. Can be `network default`, `append` or `override`.",
+ Computed: true,
+ },
+ "content_filtering_blocked_url_categories": schema.ListAttribute{
+ MarkdownDescription: "A list of URL categories to block",
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ "content_filtering_blocked_url_patterns_settings": schema.StringAttribute{
+ MarkdownDescription: "How URL patterns are applied. Can be `network default`, `append` or `override`.",
+ Computed: true,
+ },
+ "content_filtering_blocked_url_patterns": schema.ListAttribute{
+ MarkdownDescription: "A list of URL patterns that are blocked",
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ "firewall_and_traffic_shaping_settings": schema.StringAttribute{
+ MarkdownDescription: "How firewall and traffic shaping rules are enforced. Can be `network default`, `ignore` or `custom`.",
+ Computed: true,
+ },
+ "l3_firewall_rules": schema.ListNestedAttribute{
+ MarkdownDescription: "An ordered array of the L3 firewall rules",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "comment": schema.StringAttribute{
+ MarkdownDescription: "Description of the rule (optional)",
+ Computed: true,
+ },
+ "dest_cidr": schema.StringAttribute{
+ MarkdownDescription: "Destination IP address (in IP or CIDR notation), a fully-qualified domain name (FQDN, if your network supports it) or `any`.",
+ Computed: true,
+ },
+ "dest_port": schema.StringAttribute{
+ MarkdownDescription: "Destination port (integer in the range 1-65535), a port range (e.g. 8080-9090), or `any`",
+ Computed: true,
+ },
+ "policy": schema.StringAttribute{
+ MarkdownDescription: "`allow` or `deny` traffic specified by this rule",
+ Computed: true,
+ },
+ "protocol": schema.StringAttribute{
+ MarkdownDescription: "The type of protocol (must be `tcp`, `udp`, `icmp`, `icmp6` or `any`)",
+ Computed: true,
+ },
+ },
+ },
+ },
+ "l7_firewall_rules": schema.ListNestedAttribute{
+ MarkdownDescription: "An ordered array of L7 firewall rules",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "policy": schema.StringAttribute{
+ MarkdownDescription: "The policy applied to matching traffic. Must be `deny`.",
+ Computed: true,
+ },
+ "type": schema.StringAttribute{
+ MarkdownDescription: "Type of the L7 Rule. Must be `application`, `applicationCategory`, `host`, `port` or `ipRange`",
+ Computed: true,
+ },
+ "value": schema.StringAttribute{
+ MarkdownDescription: "The `value` of what you want to block. If `type` is `host`, `port` or `ipRange`, `value` must be a string matching either a hostname (e.g. somewhere.com), a port (e.g. 8080), or an IP range (e.g. 192.1.0.0/16). If `type` is `application` or `applicationCategory`, then `value` must be an object with an ID for the application.",
+ Computed: true,
+ },
+ },
+ },
+ },
+ "traffic_shaping_rules": schema.ListNestedAttribute{
+ MarkdownDescription: "An array of traffic shaping rules. Rules are applied in the order that they are specified in. An empty list (or null) means no rules. Note that you are allowed a maximum of 8 rules.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "dscp_tag_value": schema.Int64Attribute{
+ MarkdownDescription: "The DSCP tag applied by your rule. null means `Do not change DSCP tag`. For a list of possible tag values, use the trafficShaping/dscpTaggingOptions endpoint.",
+ Computed: true,
+ },
+ "pcp_tag_value": schema.Int64Attribute{
+ MarkdownDescription: "The PCP tag applied by your rule. Can be 0 (lowest priority) through 7 (highest priority). null means `Do not set PCP tag`.",
+ Computed: true,
+ },
+ "priority": schema.StringAttribute{
+ MarkdownDescription: "A string, indicating the priority level for packets bound to your rule. Can be `low`, `normal` or `high`.",
+ Computed: true,
+ },
+ "per_client_bandwidth_limits_settings": schema.StringAttribute{
+ MarkdownDescription: "How bandwidth limits are applied by your rule. Can be one of `network default`, `ignore` or `custom`.",
+ Computed: true,
+ },
+ "per_client_bandwidth_limits_bandwidth_limits_limit_down": schema.Int64Attribute{
+ MarkdownDescription: "The maximum download limit (integer, in Kbps).",
+ Computed: true,
+ },
+ "per_client_bandwidth_limits_bandwidth_limits_limit_up": schema.Int64Attribute{
+ MarkdownDescription: "The maximum upload limit (integer, in Kbps).",
+ Computed: true,
+ },
+ "definitions": schema.ListNestedAttribute{
+ MarkdownDescription: "A list of objects describing the definitions of your traffic shaping rule. At least one definition is required.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ MarkdownDescription: "The type of definition. Can be one of `application`, `applicationCategory`, `host`, `port`, `ipRange` or `localNet`.",
+ Computed: true,
+ },
+ "value": schema.StringAttribute{
+ MarkdownDescription: "If 'type' is `host`, `port`, `ipRange` or `localNet`, then 'value' must be a string, matching either a hostname (e.g. 'somesite.com'), a port (e.g. 8080), or an IP range ('192.1.0.0', '192.1.0.0/16', or '10.1.0.0/16:80'). `localNet` also supports CIDR notation, excluding custom ports. If 'type' is `application` or `applicationCategory`, then 'value' must be an object with the structure { 'id': 'meraki:layer7/...' }, where 'id' is the application category or application ID (for a list of IDs for your network, use the trafficShaping/applicationCategories endpoint).",
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "scheduling_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Whether scheduling is enabled (true) or disabled (false). Defaults to false. If true, the schedule objects for each day of the week (monday - sunday) are parsed.",
+ Computed: true,
+ },
+ "scheduling_friday_active": schema.BoolAttribute{
+ MarkdownDescription: "Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.",
+ Computed: true,
+ },
+ "scheduling_friday_from": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_friday_to": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_monday_active": schema.BoolAttribute{
+ MarkdownDescription: "Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.",
+ Computed: true,
+ },
+ "scheduling_monday_from": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_monday_to": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_saturday_active": schema.BoolAttribute{
+ MarkdownDescription: "Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.",
+ Computed: true,
+ },
+ "scheduling_saturday_from": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_saturday_to": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_sunday_active": schema.BoolAttribute{
+ MarkdownDescription: "Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.",
+ Computed: true,
+ },
+ "scheduling_sunday_from": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_sunday_to": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_thursday_active": schema.BoolAttribute{
+ MarkdownDescription: "Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.",
+ Computed: true,
+ },
+ "scheduling_thursday_from": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_thursday_to": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_tuesday_active": schema.BoolAttribute{
+ MarkdownDescription: "Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.",
+ Computed: true,
+ },
+ "scheduling_tuesday_from": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_tuesday_to": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_wednesday_active": schema.BoolAttribute{
+ MarkdownDescription: "Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.",
+ Computed: true,
+ },
+ "scheduling_wednesday_from": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "scheduling_wednesday_to": schema.StringAttribute{
+ MarkdownDescription: "The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.",
+ Computed: true,
+ },
+ "vlan_tagging_settings": schema.StringAttribute{
+ MarkdownDescription: "How VLAN tagging is applied. Can be `network default`, `ignore` or `custom`.",
+ Computed: true,
+ },
+ "vlan_tagging_vlan_id": schema.StringAttribute{
+ MarkdownDescription: "The ID of the vlan you want to tag. This only applies if `settings` is set to `custom`.",
+ Computed: true,
+ },
+ },
+ }
+}
+func (d *NetworkGroupPolicyDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator {
+ return []datasource.ConfigValidator{
+ datasourcevalidator.ExactlyOneOf(
+ path.MatchRoot("id"),
+ path.MatchRoot("name"),
+ ),
+ }
+}
+
+func (d *NetworkGroupPolicyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ d.client = req.ProviderData.(*MerakiProviderData).Client
+}
+
+// End of section. //template:end model
+
+// Section below is generated&owned by "gen/generator.go". //template:begin read
+
+func (d *NetworkGroupPolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var config NetworkGroupPolicy
+
+ // Read config
+ diags := req.Config.Get(ctx, &config)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String()))
+
+ var res gjson.Result
+ var err error
+ if config.Id.IsNull() && !config.Name.IsNull() {
+ res, err = d.client.Get(config.getPath())
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err))
+ return
+ }
+ if len(res.Array()) > 0 {
+ res.ForEach(func(k, v gjson.Result) bool {
+ if config.Name.ValueString() == v.Get("name").String() {
+ config.Id = types.StringValue(v.Get("groupPolicyId").String())
+ tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%v', id: %v", config.Id.String(), config.Name.ValueString(), config.Id.String()))
+ res = v
+ return false
+ }
+ return true
+ })
+ }
+
+ if config.Id.IsNull() {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with name: %s", config.Name.ValueString()))
+ return
+ }
+ }
+
+ if !res.Exists() {
+ res, err = d.client.Get(config.getPath() + "/" + url.QueryEscape(config.Id.ValueString()))
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err))
+ return
+ }
+ }
+
+ config.fromBody(ctx, res)
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.Id.ValueString()))
+
+ diags = resp.State.Set(ctx, &config)
+ resp.Diagnostics.Append(diags...)
+}
+
+// End of section. //template:end read
diff --git a/internal/provider/data_source_meraki_network_group_policy_test.go b/internal/provider/data_source_meraki_network_group_policy_test.go
new file mode 100644
index 00000000..b222c830
--- /dev/null
+++ b/internal/provider/data_source_meraki_network_group_policy_test.go
@@ -0,0 +1,266 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource
+
+func TestAccDataSourceMerakiNetworkGroupPolicy(t *testing.T) {
+ var checks []resource.TestCheckFunc
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "name", "No video streaming"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "splash_auth_settings", "bypass"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "bandwidth_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "bandwidth_limit_down", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "bandwidth_limit_up", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "bonjour_forwarding_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "bonjour_forwarding_rules.0.description", "A simple bonjour rule"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "bonjour_forwarding_rules.0.vlan_id", "1"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "bonjour_forwarding_rules.0.services.0", "All Services"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "firewall_and_traffic_shaping_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l3_firewall_rules.0.comment", "Allow TCP traffic to subnet with HTTP servers."))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l3_firewall_rules.0.dest_cidr", "192.168.1.0/24"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l3_firewall_rules.0.dest_port", "443"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l3_firewall_rules.0.policy", "allow"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l3_firewall_rules.0.protocol", "tcp"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l7_firewall_rules.0.policy", "deny"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l7_firewall_rules.0.type", "host"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "l7_firewall_rules.0.value", "google.com"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "traffic_shaping_rules.0.dscp_tag_value", "0"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "traffic_shaping_rules.0.pcp_tag_value", "0"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "traffic_shaping_rules.0.per_client_bandwidth_limits_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "traffic_shaping_rules.0.per_client_bandwidth_limits_bandwidth_limits_limit_down", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "traffic_shaping_rules.0.per_client_bandwidth_limits_bandwidth_limits_limit_up", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "traffic_shaping_rules.0.definitions.0.type", "host"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "traffic_shaping_rules.0.definitions.0.value", "google.com"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_friday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_friday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_friday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_monday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_monday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_monday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_saturday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_saturday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_saturday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_sunday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_sunday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_sunday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_thursday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_thursday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_thursday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_tuesday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_tuesday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_tuesday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_wednesday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_wednesday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "scheduling_wednesday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "vlan_tagging_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_group_policy.test", "vlan_tagging_vlan_id", "1"))
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceMerakiNetworkGroupPolicyPrerequisitesConfig + testAccDataSourceMerakiNetworkGroupPolicyConfig(),
+ Check: resource.ComposeTestCheckFunc(checks...),
+ },
+ {
+ Config: testAccDataSourceMerakiNetworkGroupPolicyPrerequisitesConfig + testAccNamedDataSourceMerakiNetworkGroupPolicyConfig(),
+ Check: resource.ComposeTestCheckFunc(checks...),
+ },
+ },
+ })
+}
+
+// End of section. //template:end testAccDataSource
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites
+
+const testAccDataSourceMerakiNetworkGroupPolicyPrerequisitesConfig = `
+data "meraki_organization" "test" {
+ name = "Dev"
+}
+resource "meraki_network" "test" {
+ organization_id = data.meraki_organization.test.id
+ name = "Network1"
+ product_types = ["switch", "wireless"]
+}
+
+`
+
+// End of section. //template:end testPrerequisites
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig
+
+func testAccDataSourceMerakiNetworkGroupPolicyConfig() string {
+ config := `resource "meraki_network_group_policy" "test" {` + "\n"
+ config += ` network_id = meraki_network.test.id` + "\n"
+ config += ` name = "No video streaming"` + "\n"
+ config += ` splash_auth_settings = "bypass"` + "\n"
+ config += ` bandwidth_settings = "custom"` + "\n"
+ config += ` bandwidth_limit_down = 1000000` + "\n"
+ config += ` bandwidth_limit_up = 1000000` + "\n"
+ config += ` bonjour_forwarding_settings = "custom"` + "\n"
+ config += ` bonjour_forwarding_rules = [{` + "\n"
+ config += ` description = "A simple bonjour rule"` + "\n"
+ config += ` vlan_id = "1"` + "\n"
+ config += ` services = ["All Services"]` + "\n"
+ config += ` }]` + "\n"
+ config += ` firewall_and_traffic_shaping_settings = "custom"` + "\n"
+ config += ` l3_firewall_rules = [{` + "\n"
+ config += ` comment = "Allow TCP traffic to subnet with HTTP servers."` + "\n"
+ config += ` dest_cidr = "192.168.1.0/24"` + "\n"
+ config += ` dest_port = "443"` + "\n"
+ config += ` policy = "allow"` + "\n"
+ config += ` protocol = "tcp"` + "\n"
+ config += ` }]` + "\n"
+ config += ` l7_firewall_rules = [{` + "\n"
+ config += ` policy = "deny"` + "\n"
+ config += ` type = "host"` + "\n"
+ config += ` value = "google.com"` + "\n"
+ config += ` }]` + "\n"
+ config += ` traffic_shaping_rules = [{` + "\n"
+ config += ` dscp_tag_value = 0` + "\n"
+ config += ` pcp_tag_value = 0` + "\n"
+ config += ` priority = "normal"` + "\n"
+ config += ` per_client_bandwidth_limits_settings = "custom"` + "\n"
+ config += ` per_client_bandwidth_limits_bandwidth_limits_limit_down = 1000000` + "\n"
+ config += ` per_client_bandwidth_limits_bandwidth_limits_limit_up = 1000000` + "\n"
+ config += ` definitions = [{` + "\n"
+ config += ` type = "host"` + "\n"
+ config += ` value = "google.com"` + "\n"
+ config += ` }]` + "\n"
+ config += ` }]` + "\n"
+ config += ` scheduling_enabled = true` + "\n"
+ config += ` scheduling_friday_active = true` + "\n"
+ config += ` scheduling_friday_from = "09:00"` + "\n"
+ config += ` scheduling_friday_to = "17:00"` + "\n"
+ config += ` scheduling_monday_active = true` + "\n"
+ config += ` scheduling_monday_from = "09:00"` + "\n"
+ config += ` scheduling_monday_to = "17:00"` + "\n"
+ config += ` scheduling_saturday_active = true` + "\n"
+ config += ` scheduling_saturday_from = "09:00"` + "\n"
+ config += ` scheduling_saturday_to = "17:00"` + "\n"
+ config += ` scheduling_sunday_active = true` + "\n"
+ config += ` scheduling_sunday_from = "09:00"` + "\n"
+ config += ` scheduling_sunday_to = "17:00"` + "\n"
+ config += ` scheduling_thursday_active = true` + "\n"
+ config += ` scheduling_thursday_from = "09:00"` + "\n"
+ config += ` scheduling_thursday_to = "17:00"` + "\n"
+ config += ` scheduling_tuesday_active = true` + "\n"
+ config += ` scheduling_tuesday_from = "09:00"` + "\n"
+ config += ` scheduling_tuesday_to = "17:00"` + "\n"
+ config += ` scheduling_wednesday_active = true` + "\n"
+ config += ` scheduling_wednesday_from = "09:00"` + "\n"
+ config += ` scheduling_wednesday_to = "17:00"` + "\n"
+ config += ` vlan_tagging_settings = "custom"` + "\n"
+ config += ` vlan_tagging_vlan_id = "1"` + "\n"
+ config += `}` + "\n"
+
+ config += `
+ data "meraki_network_group_policy" "test" {
+ id = meraki_network_group_policy.test.id
+ network_id = meraki_network.test.id
+ }
+ `
+ return config
+}
+
+func testAccNamedDataSourceMerakiNetworkGroupPolicyConfig() string {
+ config := `resource "meraki_network_group_policy" "test" {` + "\n"
+ config += ` network_id = meraki_network.test.id` + "\n"
+ config += ` name = "No video streaming"` + "\n"
+ config += ` splash_auth_settings = "bypass"` + "\n"
+ config += ` bandwidth_settings = "custom"` + "\n"
+ config += ` bandwidth_limit_down = 1000000` + "\n"
+ config += ` bandwidth_limit_up = 1000000` + "\n"
+ config += ` bonjour_forwarding_settings = "custom"` + "\n"
+ config += ` bonjour_forwarding_rules = [{` + "\n"
+ config += ` description = "A simple bonjour rule"` + "\n"
+ config += ` vlan_id = "1"` + "\n"
+ config += ` services = ["All Services"]` + "\n"
+ config += ` }]` + "\n"
+ config += ` firewall_and_traffic_shaping_settings = "custom"` + "\n"
+ config += ` l3_firewall_rules = [{` + "\n"
+ config += ` comment = "Allow TCP traffic to subnet with HTTP servers."` + "\n"
+ config += ` dest_cidr = "192.168.1.0/24"` + "\n"
+ config += ` dest_port = "443"` + "\n"
+ config += ` policy = "allow"` + "\n"
+ config += ` protocol = "tcp"` + "\n"
+ config += ` }]` + "\n"
+ config += ` l7_firewall_rules = [{` + "\n"
+ config += ` policy = "deny"` + "\n"
+ config += ` type = "host"` + "\n"
+ config += ` value = "google.com"` + "\n"
+ config += ` }]` + "\n"
+ config += ` traffic_shaping_rules = [{` + "\n"
+ config += ` dscp_tag_value = 0` + "\n"
+ config += ` pcp_tag_value = 0` + "\n"
+ config += ` priority = "normal"` + "\n"
+ config += ` per_client_bandwidth_limits_settings = "custom"` + "\n"
+ config += ` per_client_bandwidth_limits_bandwidth_limits_limit_down = 1000000` + "\n"
+ config += ` per_client_bandwidth_limits_bandwidth_limits_limit_up = 1000000` + "\n"
+ config += ` definitions = [{` + "\n"
+ config += ` type = "host"` + "\n"
+ config += ` value = "google.com"` + "\n"
+ config += ` }]` + "\n"
+ config += ` }]` + "\n"
+ config += ` scheduling_enabled = true` + "\n"
+ config += ` scheduling_friday_active = true` + "\n"
+ config += ` scheduling_friday_from = "09:00"` + "\n"
+ config += ` scheduling_friday_to = "17:00"` + "\n"
+ config += ` scheduling_monday_active = true` + "\n"
+ config += ` scheduling_monday_from = "09:00"` + "\n"
+ config += ` scheduling_monday_to = "17:00"` + "\n"
+ config += ` scheduling_saturday_active = true` + "\n"
+ config += ` scheduling_saturday_from = "09:00"` + "\n"
+ config += ` scheduling_saturday_to = "17:00"` + "\n"
+ config += ` scheduling_sunday_active = true` + "\n"
+ config += ` scheduling_sunday_from = "09:00"` + "\n"
+ config += ` scheduling_sunday_to = "17:00"` + "\n"
+ config += ` scheduling_thursday_active = true` + "\n"
+ config += ` scheduling_thursday_from = "09:00"` + "\n"
+ config += ` scheduling_thursday_to = "17:00"` + "\n"
+ config += ` scheduling_tuesday_active = true` + "\n"
+ config += ` scheduling_tuesday_from = "09:00"` + "\n"
+ config += ` scheduling_tuesday_to = "17:00"` + "\n"
+ config += ` scheduling_wednesday_active = true` + "\n"
+ config += ` scheduling_wednesday_from = "09:00"` + "\n"
+ config += ` scheduling_wednesday_to = "17:00"` + "\n"
+ config += ` vlan_tagging_settings = "custom"` + "\n"
+ config += ` vlan_tagging_vlan_id = "1"` + "\n"
+ config += `}` + "\n"
+
+ config += `
+ data "meraki_network_group_policy" "test" {
+ name = meraki_network_group_policy.test.name
+ network_id = meraki_network.test.id
+ }
+ `
+ return config
+}
+
+// End of section. //template:end testAccDataSourceConfig
diff --git a/internal/provider/data_source_meraki_network_settings.go b/internal/provider/data_source_meraki_network_settings.go
new file mode 100644
index 00000000..550096aa
--- /dev/null
+++ b/internal/provider/data_source_meraki_network_settings.go
@@ -0,0 +1,140 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/netascode/go-meraki"
+ "github.com/tidwall/gjson"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin model
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &NetworkSettingsDataSource{}
+ _ datasource.DataSourceWithConfigure = &NetworkSettingsDataSource{}
+)
+
+func NewNetworkSettingsDataSource() datasource.DataSource {
+ return &NetworkSettingsDataSource{}
+}
+
+type NetworkSettingsDataSource struct {
+ client *meraki.Client
+}
+
+func (d *NetworkSettingsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_network_settings"
+}
+
+func (d *NetworkSettingsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "This data source can read the `Network Settings` configuration.",
+
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "The id of the object",
+ Computed: true,
+ },
+ "network_id": schema.StringAttribute{
+ MarkdownDescription: "Network ID",
+ Required: true,
+ },
+ "local_status_page_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Enables / disables the local device status pages (my.meraki.com, ap.meraki.com, switch.meraki.com, wired.meraki.com). Optional (defaults to false)",
+ Computed: true,
+ },
+ "remote_status_page_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Enables / disables access to the device status page (http://[device`s LAN IP]). Optional. Can only be set if localStatusPageEnabled is set to true",
+ Computed: true,
+ },
+ "local_status_page_authentication_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Enables / disables the authentication on Local Status page(s).",
+ Computed: true,
+ },
+ "local_status_page_authentication_password": schema.StringAttribute{
+ MarkdownDescription: "The password used for Local Status Page(s). Set this to null to clear the password.",
+ Computed: true,
+ },
+ "named_vlans_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Enables / disables Named VLANs on the Network.",
+ Computed: true,
+ },
+ "secure_port_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Enables / disables SecureConnect on the network. Optional.",
+ Computed: true,
+ },
+ },
+ }
+}
+
+func (d *NetworkSettingsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ d.client = req.ProviderData.(*MerakiProviderData).Client
+}
+
+// End of section. //template:end model
+
+// Section below is generated&owned by "gen/generator.go". //template:begin read
+
+func (d *NetworkSettingsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var config NetworkSettings
+
+ // Read config
+ diags := req.Config.Get(ctx, &config)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String()))
+
+ var res gjson.Result
+ var err error
+
+ if !res.Exists() {
+ res, err = d.client.Get(config.getPath())
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err))
+ return
+ }
+ }
+
+ config.fromBody(ctx, res)
+ config.Id = config.NetworkId
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.Id.ValueString()))
+
+ diags = resp.State.Set(ctx, &config)
+ resp.Diagnostics.Append(diags...)
+}
+
+// End of section. //template:end read
diff --git a/internal/provider/data_source_meraki_network_settings_test.go b/internal/provider/data_source_meraki_network_settings_test.go
new file mode 100644
index 00000000..170065ba
--- /dev/null
+++ b/internal/provider/data_source_meraki_network_settings_test.go
@@ -0,0 +1,90 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource
+
+func TestAccDataSourceMerakiNetworkSettings(t *testing.T) {
+ var checks []resource.TestCheckFunc
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "local_status_page_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "remote_status_page_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "local_status_page_authentication_enabled", "false"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "named_vlans_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_settings.test", "secure_port_enabled", "false"))
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceMerakiNetworkSettingsPrerequisitesConfig + testAccDataSourceMerakiNetworkSettingsConfig(),
+ Check: resource.ComposeTestCheckFunc(checks...),
+ },
+ },
+ })
+}
+
+// End of section. //template:end testAccDataSource
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites
+
+const testAccDataSourceMerakiNetworkSettingsPrerequisitesConfig = `
+data "meraki_organization" "test" {
+ name = "Dev"
+}
+resource "meraki_network" "test" {
+ organization_id = data.meraki_organization.test.id
+ name = "Network1"
+ product_types = ["switch", "wireless"]
+}
+
+`
+
+// End of section. //template:end testPrerequisites
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig
+
+func testAccDataSourceMerakiNetworkSettingsConfig() string {
+ config := `resource "meraki_network_settings" "test" {` + "\n"
+ config += ` network_id = meraki_network.test.id` + "\n"
+ config += ` local_status_page_enabled = true` + "\n"
+ config += ` remote_status_page_enabled = true` + "\n"
+ config += ` local_status_page_authentication_enabled = false` + "\n"
+ config += ` local_status_page_authentication_password = "miles123"` + "\n"
+ config += ` named_vlans_enabled = true` + "\n"
+ config += ` secure_port_enabled = false` + "\n"
+ config += `}` + "\n"
+
+ config += `
+ data "meraki_network_settings" "test" {
+ network_id = meraki_network.test.id
+ depends_on = [meraki_network_settings.test]
+ }
+ `
+ return config
+}
+
+// End of section. //template:end testAccDataSourceConfig
diff --git a/internal/provider/data_source_meraki_network_snmp.go b/internal/provider/data_source_meraki_network_snmp.go
index 83e82900..ae0d994b 100644
--- a/internal/provider/data_source_meraki_network_snmp.go
+++ b/internal/provider/data_source_meraki_network_snmp.go
@@ -78,14 +78,14 @@ func (d *NetworkSNMPDataSource) Schema(ctx context.Context, req datasource.Schem
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
- "username": schema.StringAttribute{
- MarkdownDescription: "The username for the SNMP user. Required.",
- Computed: true,
- },
"passphrase": schema.StringAttribute{
MarkdownDescription: "The passphrase for the SNMP user. Required.",
Computed: true,
},
+ "username": schema.StringAttribute{
+ MarkdownDescription: "The username for the SNMP user. Required.",
+ Computed: true,
+ },
},
},
},
diff --git a/internal/provider/data_source_meraki_network_snmp_test.go b/internal/provider/data_source_meraki_network_snmp_test.go
index 08b3727d..c8b65f44 100644
--- a/internal/provider/data_source_meraki_network_snmp_test.go
+++ b/internal/provider/data_source_meraki_network_snmp_test.go
@@ -31,8 +31,8 @@ import (
func TestAccDataSourceMerakiNetworkSNMP(t *testing.T) {
var checks []resource.TestCheckFunc
checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_snmp.test", "access", "users"))
- checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_snmp.test", "users.0.username", "User1"))
- checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_snmp.test", "users.0.passphrase", "Password123"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_snmp.test", "users.0.passphrase", "hunter2"))
+ checks = append(checks, resource.TestCheckResourceAttr("data.meraki_network_snmp.test", "users.0.username", "AzureDiamond"))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
@@ -70,8 +70,8 @@ func testAccDataSourceMerakiNetworkSNMPConfig() string {
config += ` network_id = meraki_network.test.id` + "\n"
config += ` access = "users"` + "\n"
config += ` users = [{` + "\n"
- config += ` username = "User1"` + "\n"
- config += ` passphrase = "Password123"` + "\n"
+ config += ` passphrase = "hunter2"` + "\n"
+ config += ` username = "AzureDiamond"` + "\n"
config += ` }]` + "\n"
config += `}` + "\n"
diff --git a/internal/provider/model_meraki_admin.go b/internal/provider/model_meraki_admin.go
index 90529b24..640b3615 100644
--- a/internal/provider/model_meraki_admin.go
+++ b/internal/provider/model_meraki_admin.go
@@ -35,13 +35,14 @@ import (
// Section below is generated&owned by "gen/generator.go". //template:begin types
type Admin struct {
- Id types.String `tfsdk:"id"`
- OrganizationId types.String `tfsdk:"organization_id"`
- Email types.String `tfsdk:"email"`
- Name types.String `tfsdk:"name"`
- OrgAccess types.String `tfsdk:"org_access"`
- Networks []AdminNetworks `tfsdk:"networks"`
- Tags []AdminTags `tfsdk:"tags"`
+ Id types.String `tfsdk:"id"`
+ OrganizationId types.String `tfsdk:"organization_id"`
+ AuthenticationMethod types.String `tfsdk:"authentication_method"`
+ Email types.String `tfsdk:"email"`
+ Name types.String `tfsdk:"name"`
+ OrgAccess types.String `tfsdk:"org_access"`
+ Networks []AdminNetworks `tfsdk:"networks"`
+ Tags []AdminTags `tfsdk:"tags"`
}
type AdminNetworks struct {
@@ -68,6 +69,9 @@ func (data Admin) getPath() string {
func (data Admin) toBody(ctx context.Context, state Admin) string {
body := ""
+ if !data.AuthenticationMethod.IsNull() {
+ body, _ = sjson.Set(body, "authenticationMethod", data.AuthenticationMethod.ValueString())
+ }
if !data.Email.IsNull() {
body, _ = sjson.Set(body, "email", data.Email.ValueString())
}
@@ -111,6 +115,11 @@ func (data Admin) toBody(ctx context.Context, state Admin) string {
// Section below is generated&owned by "gen/generator.go". //template:begin fromBody
func (data *Admin) fromBody(ctx context.Context, res gjson.Result) {
+ if value := res.Get("authenticationMethod"); value.Exists() {
+ data.AuthenticationMethod = types.StringValue(value.String())
+ } else {
+ data.AuthenticationMethod = types.StringNull()
+ }
if value := res.Get("email"); value.Exists() {
data.Email = types.StringValue(value.String())
} else {
@@ -175,6 +184,11 @@ func (data *Admin) fromBody(ctx context.Context, res gjson.Result) {
// easily change across versions of the backend API.) For List/Set/Map attributes, the func only updates the
// "managed" elements, instead of all elements.
func (data *Admin) fromBodyPartial(ctx context.Context, res gjson.Result) {
+ if value := res.Get("authenticationMethod"); value.Exists() && !data.AuthenticationMethod.IsNull() {
+ data.AuthenticationMethod = types.StringValue(value.String())
+ } else {
+ data.AuthenticationMethod = types.StringNull()
+ }
if value := res.Get("email"); value.Exists() && !data.Email.IsNull() {
data.Email = types.StringValue(value.String())
} else {
diff --git a/internal/provider/model_meraki_network_group_policy.go b/internal/provider/model_meraki_network_group_policy.go
new file mode 100644
index 00000000..9a88768d
--- /dev/null
+++ b/internal/provider/model_meraki_network_group_policy.go
@@ -0,0 +1,1087 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "slices"
+
+ "github.com/CiscoDevNet/terraform-provider-meraki/internal/provider/helpers"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin types
+
+type NetworkGroupPolicy struct {
+ Id types.String `tfsdk:"id"`
+ NetworkId types.String `tfsdk:"network_id"`
+ Name types.String `tfsdk:"name"`
+ SplashAuthSettings types.String `tfsdk:"splash_auth_settings"`
+ BandwidthSettings types.String `tfsdk:"bandwidth_settings"`
+ BandwidthLimitDown types.Int64 `tfsdk:"bandwidth_limit_down"`
+ BandwidthLimitUp types.Int64 `tfsdk:"bandwidth_limit_up"`
+ BonjourForwardingSettings types.String `tfsdk:"bonjour_forwarding_settings"`
+ BonjourForwardingRules []NetworkGroupPolicyBonjourForwardingRules `tfsdk:"bonjour_forwarding_rules"`
+ ContentFilteringAllowedUrlPatternsSettings types.String `tfsdk:"content_filtering_allowed_url_patterns_settings"`
+ ContentFilteringAllowedUrlPatterns types.List `tfsdk:"content_filtering_allowed_url_patterns"`
+ ContentFilteringBlockedUrlCategoriesSettings types.String `tfsdk:"content_filtering_blocked_url_categories_settings"`
+ ContentFilteringBlockedUrlCategories types.List `tfsdk:"content_filtering_blocked_url_categories"`
+ ContentFilteringBlockedUrlPatternsSettings types.String `tfsdk:"content_filtering_blocked_url_patterns_settings"`
+ ContentFilteringBlockedUrlPatterns types.List `tfsdk:"content_filtering_blocked_url_patterns"`
+ FirewallAndTrafficShapingSettings types.String `tfsdk:"firewall_and_traffic_shaping_settings"`
+ L3FirewallRules []NetworkGroupPolicyL3FirewallRules `tfsdk:"l3_firewall_rules"`
+ L7FirewallRules []NetworkGroupPolicyL7FirewallRules `tfsdk:"l7_firewall_rules"`
+ TrafficShapingRules []NetworkGroupPolicyTrafficShapingRules `tfsdk:"traffic_shaping_rules"`
+ SchedulingEnabled types.Bool `tfsdk:"scheduling_enabled"`
+ SchedulingFridayActive types.Bool `tfsdk:"scheduling_friday_active"`
+ SchedulingFridayFrom types.String `tfsdk:"scheduling_friday_from"`
+ SchedulingFridayTo types.String `tfsdk:"scheduling_friday_to"`
+ SchedulingMondayActive types.Bool `tfsdk:"scheduling_monday_active"`
+ SchedulingMondayFrom types.String `tfsdk:"scheduling_monday_from"`
+ SchedulingMondayTo types.String `tfsdk:"scheduling_monday_to"`
+ SchedulingSaturdayActive types.Bool `tfsdk:"scheduling_saturday_active"`
+ SchedulingSaturdayFrom types.String `tfsdk:"scheduling_saturday_from"`
+ SchedulingSaturdayTo types.String `tfsdk:"scheduling_saturday_to"`
+ SchedulingSundayActive types.Bool `tfsdk:"scheduling_sunday_active"`
+ SchedulingSundayFrom types.String `tfsdk:"scheduling_sunday_from"`
+ SchedulingSundayTo types.String `tfsdk:"scheduling_sunday_to"`
+ SchedulingThursdayActive types.Bool `tfsdk:"scheduling_thursday_active"`
+ SchedulingThursdayFrom types.String `tfsdk:"scheduling_thursday_from"`
+ SchedulingThursdayTo types.String `tfsdk:"scheduling_thursday_to"`
+ SchedulingTuesdayActive types.Bool `tfsdk:"scheduling_tuesday_active"`
+ SchedulingTuesdayFrom types.String `tfsdk:"scheduling_tuesday_from"`
+ SchedulingTuesdayTo types.String `tfsdk:"scheduling_tuesday_to"`
+ SchedulingWednesdayActive types.Bool `tfsdk:"scheduling_wednesday_active"`
+ SchedulingWednesdayFrom types.String `tfsdk:"scheduling_wednesday_from"`
+ SchedulingWednesdayTo types.String `tfsdk:"scheduling_wednesday_to"`
+ VlanTaggingSettings types.String `tfsdk:"vlan_tagging_settings"`
+ VlanTaggingVlanId types.String `tfsdk:"vlan_tagging_vlan_id"`
+}
+
+type NetworkGroupPolicyBonjourForwardingRules struct {
+ Description types.String `tfsdk:"description"`
+ VlanId types.String `tfsdk:"vlan_id"`
+ Services types.List `tfsdk:"services"`
+}
+
+type NetworkGroupPolicyL3FirewallRules struct {
+ Comment types.String `tfsdk:"comment"`
+ DestCidr types.String `tfsdk:"dest_cidr"`
+ DestPort types.String `tfsdk:"dest_port"`
+ Policy types.String `tfsdk:"policy"`
+ Protocol types.String `tfsdk:"protocol"`
+}
+
+type NetworkGroupPolicyL7FirewallRules struct {
+ Policy types.String `tfsdk:"policy"`
+ Type types.String `tfsdk:"type"`
+ Value types.String `tfsdk:"value"`
+}
+
+type NetworkGroupPolicyTrafficShapingRules struct {
+ DscpTagValue types.Int64 `tfsdk:"dscp_tag_value"`
+ PcpTagValue types.Int64 `tfsdk:"pcp_tag_value"`
+ Priority types.String `tfsdk:"priority"`
+ PerClientBandwidthLimitsSettings types.String `tfsdk:"per_client_bandwidth_limits_settings"`
+ PerClientBandwidthLimitsBandwidthLimitsLimitDown types.Int64 `tfsdk:"per_client_bandwidth_limits_bandwidth_limits_limit_down"`
+ PerClientBandwidthLimitsBandwidthLimitsLimitUp types.Int64 `tfsdk:"per_client_bandwidth_limits_bandwidth_limits_limit_up"`
+ Definitions []NetworkGroupPolicyTrafficShapingRulesDefinitions `tfsdk:"definitions"`
+}
+
+type NetworkGroupPolicyTrafficShapingRulesDefinitions struct {
+ Type types.String `tfsdk:"type"`
+ Value types.String `tfsdk:"value"`
+}
+
+// End of section. //template:end types
+
+// Section below is generated&owned by "gen/generator.go". //template:begin getPath
+
+func (data NetworkGroupPolicy) getPath() string {
+ return fmt.Sprintf("/networks/%v/groupPolicies", url.QueryEscape(data.NetworkId.ValueString()))
+}
+
+// End of section. //template:end getPath
+
+// Section below is generated&owned by "gen/generator.go". //template:begin toBody
+
+func (data NetworkGroupPolicy) toBody(ctx context.Context, state NetworkGroupPolicy) string {
+ body := ""
+ if !data.Name.IsNull() {
+ body, _ = sjson.Set(body, "name", data.Name.ValueString())
+ }
+ if !data.SplashAuthSettings.IsNull() {
+ body, _ = sjson.Set(body, "splashAuthSettings", data.SplashAuthSettings.ValueString())
+ }
+ if !data.BandwidthSettings.IsNull() {
+ body, _ = sjson.Set(body, "bandwidth.settings", data.BandwidthSettings.ValueString())
+ }
+ if !data.BandwidthLimitDown.IsNull() {
+ body, _ = sjson.Set(body, "bandwidth.bandwidthLimits.limitDown", data.BandwidthLimitDown.ValueInt64())
+ }
+ if !data.BandwidthLimitUp.IsNull() {
+ body, _ = sjson.Set(body, "bandwidth.bandwidthLimits.limitUp", data.BandwidthLimitUp.ValueInt64())
+ }
+ if !data.BonjourForwardingSettings.IsNull() {
+ body, _ = sjson.Set(body, "bonjourForwarding.settings", data.BonjourForwardingSettings.ValueString())
+ }
+ if len(data.BonjourForwardingRules) > 0 {
+ body, _ = sjson.Set(body, "bonjourForwarding.rules", []interface{}{})
+ for _, item := range data.BonjourForwardingRules {
+ itemBody := ""
+ if !item.Description.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "description", item.Description.ValueString())
+ }
+ if !item.VlanId.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "vlanId", item.VlanId.ValueString())
+ }
+ if !item.Services.IsNull() {
+ var values []string
+ item.Services.ElementsAs(ctx, &values, false)
+ itemBody, _ = sjson.Set(itemBody, "services", values)
+ }
+ body, _ = sjson.SetRaw(body, "bonjourForwarding.rules.-1", itemBody)
+ }
+ }
+ if !data.ContentFilteringAllowedUrlPatternsSettings.IsNull() {
+ body, _ = sjson.Set(body, "contentFiltering.allowedUrlPatterns.settings", data.ContentFilteringAllowedUrlPatternsSettings.ValueString())
+ }
+ if !data.ContentFilteringAllowedUrlPatterns.IsNull() {
+ var values []string
+ data.ContentFilteringAllowedUrlPatterns.ElementsAs(ctx, &values, false)
+ body, _ = sjson.Set(body, "contentFiltering.allowedUrlPatterns.patterns", values)
+ }
+ if !data.ContentFilteringBlockedUrlCategoriesSettings.IsNull() {
+ body, _ = sjson.Set(body, "contentFiltering.blockedUrlCategories.settings", data.ContentFilteringBlockedUrlCategoriesSettings.ValueString())
+ }
+ if !data.ContentFilteringBlockedUrlCategories.IsNull() {
+ var values []string
+ data.ContentFilteringBlockedUrlCategories.ElementsAs(ctx, &values, false)
+ body, _ = sjson.Set(body, "contentFiltering.blockedUrlCategories.categories", values)
+ }
+ if !data.ContentFilteringBlockedUrlPatternsSettings.IsNull() {
+ body, _ = sjson.Set(body, "contentFiltering.blockedUrlPatterns.settings", data.ContentFilteringBlockedUrlPatternsSettings.ValueString())
+ }
+ if !data.ContentFilteringBlockedUrlPatterns.IsNull() {
+ var values []string
+ data.ContentFilteringBlockedUrlPatterns.ElementsAs(ctx, &values, false)
+ body, _ = sjson.Set(body, "contentFiltering.blockedUrlPatterns.patterns", values)
+ }
+ if !data.FirewallAndTrafficShapingSettings.IsNull() {
+ body, _ = sjson.Set(body, "firewallAndTrafficShaping.settings", data.FirewallAndTrafficShapingSettings.ValueString())
+ }
+ if len(data.L3FirewallRules) > 0 {
+ body, _ = sjson.Set(body, "firewallAndTrafficShaping.l3FirewallRules", []interface{}{})
+ for _, item := range data.L3FirewallRules {
+ itemBody := ""
+ if !item.Comment.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "comment", item.Comment.ValueString())
+ }
+ if !item.DestCidr.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "destCidr", item.DestCidr.ValueString())
+ }
+ if !item.DestPort.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "destPort", item.DestPort.ValueString())
+ }
+ if !item.Policy.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "policy", item.Policy.ValueString())
+ }
+ if !item.Protocol.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "protocol", item.Protocol.ValueString())
+ }
+ body, _ = sjson.SetRaw(body, "firewallAndTrafficShaping.l3FirewallRules.-1", itemBody)
+ }
+ }
+ if len(data.L7FirewallRules) > 0 {
+ body, _ = sjson.Set(body, "firewallAndTrafficShaping.l7FirewallRules", []interface{}{})
+ for _, item := range data.L7FirewallRules {
+ itemBody := ""
+ if !item.Policy.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "policy", item.Policy.ValueString())
+ }
+ if !item.Type.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "type", item.Type.ValueString())
+ }
+ if !item.Value.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "value", item.Value.ValueString())
+ }
+ body, _ = sjson.SetRaw(body, "firewallAndTrafficShaping.l7FirewallRules.-1", itemBody)
+ }
+ }
+ if len(data.TrafficShapingRules) > 0 {
+ body, _ = sjson.Set(body, "firewallAndTrafficShaping.trafficShapingRules", []interface{}{})
+ for _, item := range data.TrafficShapingRules {
+ itemBody := ""
+ if !item.DscpTagValue.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "dscpTagValue", item.DscpTagValue.ValueInt64())
+ }
+ if !item.PcpTagValue.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "pcpTagValue", item.PcpTagValue.ValueInt64())
+ }
+ if !item.Priority.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "priority", item.Priority.ValueString())
+ }
+ if !item.PerClientBandwidthLimitsSettings.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "perClientBandwidthLimits.settings", item.PerClientBandwidthLimitsSettings.ValueString())
+ }
+ if !item.PerClientBandwidthLimitsBandwidthLimitsLimitDown.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "perClientBandwidthLimits.bandwidthLimits.limitDown", item.PerClientBandwidthLimitsBandwidthLimitsLimitDown.ValueInt64())
+ }
+ if !item.PerClientBandwidthLimitsBandwidthLimitsLimitUp.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "perClientBandwidthLimits.bandwidthLimits.limitUp", item.PerClientBandwidthLimitsBandwidthLimitsLimitUp.ValueInt64())
+ }
+ if len(item.Definitions) > 0 {
+ itemBody, _ = sjson.Set(itemBody, "definitions", []interface{}{})
+ for _, childItem := range item.Definitions {
+ itemChildBody := ""
+ if !childItem.Type.IsNull() {
+ itemChildBody, _ = sjson.Set(itemChildBody, "type", childItem.Type.ValueString())
+ }
+ if !childItem.Value.IsNull() {
+ itemChildBody, _ = sjson.Set(itemChildBody, "value", childItem.Value.ValueString())
+ }
+ itemBody, _ = sjson.SetRaw(itemBody, "definitions.-1", itemChildBody)
+ }
+ }
+ body, _ = sjson.SetRaw(body, "firewallAndTrafficShaping.trafficShapingRules.-1", itemBody)
+ }
+ }
+ if !data.SchedulingEnabled.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.enabled", data.SchedulingEnabled.ValueBool())
+ }
+ if !data.SchedulingFridayActive.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.friday.active", data.SchedulingFridayActive.ValueBool())
+ }
+ if !data.SchedulingFridayFrom.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.friday.from", data.SchedulingFridayFrom.ValueString())
+ }
+ if !data.SchedulingFridayTo.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.friday.to", data.SchedulingFridayTo.ValueString())
+ }
+ if !data.SchedulingMondayActive.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.monday.active", data.SchedulingMondayActive.ValueBool())
+ }
+ if !data.SchedulingMondayFrom.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.monday.from", data.SchedulingMondayFrom.ValueString())
+ }
+ if !data.SchedulingMondayTo.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.monday.to", data.SchedulingMondayTo.ValueString())
+ }
+ if !data.SchedulingSaturdayActive.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.saturday.active", data.SchedulingSaturdayActive.ValueBool())
+ }
+ if !data.SchedulingSaturdayFrom.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.saturday.from", data.SchedulingSaturdayFrom.ValueString())
+ }
+ if !data.SchedulingSaturdayTo.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.saturday.to", data.SchedulingSaturdayTo.ValueString())
+ }
+ if !data.SchedulingSundayActive.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.sunday.active", data.SchedulingSundayActive.ValueBool())
+ }
+ if !data.SchedulingSundayFrom.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.sunday.from", data.SchedulingSundayFrom.ValueString())
+ }
+ if !data.SchedulingSundayTo.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.sunday.to", data.SchedulingSundayTo.ValueString())
+ }
+ if !data.SchedulingThursdayActive.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.thursday.active", data.SchedulingThursdayActive.ValueBool())
+ }
+ if !data.SchedulingThursdayFrom.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.thursday.from", data.SchedulingThursdayFrom.ValueString())
+ }
+ if !data.SchedulingThursdayTo.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.thursday.to", data.SchedulingThursdayTo.ValueString())
+ }
+ if !data.SchedulingTuesdayActive.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.tuesday.active", data.SchedulingTuesdayActive.ValueBool())
+ }
+ if !data.SchedulingTuesdayFrom.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.tuesday.from", data.SchedulingTuesdayFrom.ValueString())
+ }
+ if !data.SchedulingTuesdayTo.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.tuesday.to", data.SchedulingTuesdayTo.ValueString())
+ }
+ if !data.SchedulingWednesdayActive.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.wednesday.active", data.SchedulingWednesdayActive.ValueBool())
+ }
+ if !data.SchedulingWednesdayFrom.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.wednesday.from", data.SchedulingWednesdayFrom.ValueString())
+ }
+ if !data.SchedulingWednesdayTo.IsNull() {
+ body, _ = sjson.Set(body, "scheduling.wednesday.to", data.SchedulingWednesdayTo.ValueString())
+ }
+ if !data.VlanTaggingSettings.IsNull() {
+ body, _ = sjson.Set(body, "vlanTagging.settings", data.VlanTaggingSettings.ValueString())
+ }
+ if !data.VlanTaggingVlanId.IsNull() {
+ body, _ = sjson.Set(body, "vlanTagging.vlanId", data.VlanTaggingVlanId.ValueString())
+ }
+ return body
+}
+
+// End of section. //template:end toBody
+
+// Section below is generated&owned by "gen/generator.go". //template:begin fromBody
+
+func (data *NetworkGroupPolicy) fromBody(ctx context.Context, res gjson.Result) {
+ if value := res.Get("name"); value.Exists() {
+ data.Name = types.StringValue(value.String())
+ } else {
+ data.Name = types.StringNull()
+ }
+ if value := res.Get("splashAuthSettings"); value.Exists() {
+ data.SplashAuthSettings = types.StringValue(value.String())
+ } else {
+ data.SplashAuthSettings = types.StringNull()
+ }
+ if value := res.Get("bandwidth.settings"); value.Exists() {
+ data.BandwidthSettings = types.StringValue(value.String())
+ } else {
+ data.BandwidthSettings = types.StringNull()
+ }
+ if value := res.Get("bandwidth.bandwidthLimits.limitDown"); value.Exists() {
+ data.BandwidthLimitDown = types.Int64Value(value.Int())
+ } else {
+ data.BandwidthLimitDown = types.Int64Null()
+ }
+ if value := res.Get("bandwidth.bandwidthLimits.limitUp"); value.Exists() {
+ data.BandwidthLimitUp = types.Int64Value(value.Int())
+ } else {
+ data.BandwidthLimitUp = types.Int64Null()
+ }
+ if value := res.Get("bonjourForwarding.settings"); value.Exists() {
+ data.BonjourForwardingSettings = types.StringValue(value.String())
+ } else {
+ data.BonjourForwardingSettings = types.StringNull()
+ }
+ if value := res.Get("bonjourForwarding.rules"); value.Exists() {
+ data.BonjourForwardingRules = make([]NetworkGroupPolicyBonjourForwardingRules, 0)
+ value.ForEach(func(k, res gjson.Result) bool {
+ parent := &data
+ data := NetworkGroupPolicyBonjourForwardingRules{}
+ if value := res.Get("description"); value.Exists() {
+ data.Description = types.StringValue(value.String())
+ } else {
+ data.Description = types.StringNull()
+ }
+ if value := res.Get("vlanId"); value.Exists() {
+ data.VlanId = types.StringValue(value.String())
+ } else {
+ data.VlanId = types.StringNull()
+ }
+ if value := res.Get("services"); value.Exists() {
+ data.Services = helpers.GetStringList(value.Array())
+ } else {
+ data.Services = types.ListNull(types.StringType)
+ }
+ (*parent).BonjourForwardingRules = append((*parent).BonjourForwardingRules, data)
+ return true
+ })
+ }
+ if value := res.Get("contentFiltering.allowedUrlPatterns.settings"); value.Exists() {
+ data.ContentFilteringAllowedUrlPatternsSettings = types.StringValue(value.String())
+ } else {
+ data.ContentFilteringAllowedUrlPatternsSettings = types.StringNull()
+ }
+ if value := res.Get("contentFiltering.allowedUrlPatterns.patterns"); value.Exists() {
+ data.ContentFilteringAllowedUrlPatterns = helpers.GetStringList(value.Array())
+ } else {
+ data.ContentFilteringAllowedUrlPatterns = types.ListNull(types.StringType)
+ }
+ if value := res.Get("contentFiltering.blockedUrlCategories.settings"); value.Exists() {
+ data.ContentFilteringBlockedUrlCategoriesSettings = types.StringValue(value.String())
+ } else {
+ data.ContentFilteringBlockedUrlCategoriesSettings = types.StringNull()
+ }
+ if value := res.Get("contentFiltering.blockedUrlCategories.categories"); value.Exists() {
+ data.ContentFilteringBlockedUrlCategories = helpers.GetStringList(value.Array())
+ } else {
+ data.ContentFilteringBlockedUrlCategories = types.ListNull(types.StringType)
+ }
+ if value := res.Get("contentFiltering.blockedUrlPatterns.settings"); value.Exists() {
+ data.ContentFilteringBlockedUrlPatternsSettings = types.StringValue(value.String())
+ } else {
+ data.ContentFilteringBlockedUrlPatternsSettings = types.StringNull()
+ }
+ if value := res.Get("contentFiltering.blockedUrlPatterns.patterns"); value.Exists() {
+ data.ContentFilteringBlockedUrlPatterns = helpers.GetStringList(value.Array())
+ } else {
+ data.ContentFilteringBlockedUrlPatterns = types.ListNull(types.StringType)
+ }
+ if value := res.Get("firewallAndTrafficShaping.settings"); value.Exists() {
+ data.FirewallAndTrafficShapingSettings = types.StringValue(value.String())
+ } else {
+ data.FirewallAndTrafficShapingSettings = types.StringNull()
+ }
+ if value := res.Get("firewallAndTrafficShaping.l3FirewallRules"); value.Exists() {
+ data.L3FirewallRules = make([]NetworkGroupPolicyL3FirewallRules, 0)
+ value.ForEach(func(k, res gjson.Result) bool {
+ parent := &data
+ data := NetworkGroupPolicyL3FirewallRules{}
+ if value := res.Get("comment"); value.Exists() {
+ data.Comment = types.StringValue(value.String())
+ } else {
+ data.Comment = types.StringNull()
+ }
+ if value := res.Get("destCidr"); value.Exists() {
+ data.DestCidr = types.StringValue(value.String())
+ } else {
+ data.DestCidr = types.StringNull()
+ }
+ if value := res.Get("destPort"); value.Exists() {
+ data.DestPort = types.StringValue(value.String())
+ } else {
+ data.DestPort = types.StringNull()
+ }
+ if value := res.Get("policy"); value.Exists() {
+ data.Policy = types.StringValue(value.String())
+ } else {
+ data.Policy = types.StringNull()
+ }
+ if value := res.Get("protocol"); value.Exists() {
+ data.Protocol = types.StringValue(value.String())
+ } else {
+ data.Protocol = types.StringNull()
+ }
+ (*parent).L3FirewallRules = append((*parent).L3FirewallRules, data)
+ return true
+ })
+ }
+ if value := res.Get("firewallAndTrafficShaping.l7FirewallRules"); value.Exists() {
+ data.L7FirewallRules = make([]NetworkGroupPolicyL7FirewallRules, 0)
+ value.ForEach(func(k, res gjson.Result) bool {
+ parent := &data
+ data := NetworkGroupPolicyL7FirewallRules{}
+ if value := res.Get("policy"); value.Exists() {
+ data.Policy = types.StringValue(value.String())
+ } else {
+ data.Policy = types.StringNull()
+ }
+ if value := res.Get("type"); value.Exists() {
+ data.Type = types.StringValue(value.String())
+ } else {
+ data.Type = types.StringNull()
+ }
+ if value := res.Get("value"); value.Exists() {
+ data.Value = types.StringValue(value.String())
+ } else {
+ data.Value = types.StringNull()
+ }
+ (*parent).L7FirewallRules = append((*parent).L7FirewallRules, data)
+ return true
+ })
+ }
+ if value := res.Get("firewallAndTrafficShaping.trafficShapingRules"); value.Exists() {
+ data.TrafficShapingRules = make([]NetworkGroupPolicyTrafficShapingRules, 0)
+ value.ForEach(func(k, res gjson.Result) bool {
+ parent := &data
+ data := NetworkGroupPolicyTrafficShapingRules{}
+ if value := res.Get("dscpTagValue"); value.Exists() {
+ data.DscpTagValue = types.Int64Value(value.Int())
+ } else {
+ data.DscpTagValue = types.Int64Null()
+ }
+ if value := res.Get("pcpTagValue"); value.Exists() {
+ data.PcpTagValue = types.Int64Value(value.Int())
+ } else {
+ data.PcpTagValue = types.Int64Null()
+ }
+ if value := res.Get("perClientBandwidthLimits.settings"); value.Exists() {
+ data.PerClientBandwidthLimitsSettings = types.StringValue(value.String())
+ } else {
+ data.PerClientBandwidthLimitsSettings = types.StringNull()
+ }
+ if value := res.Get("perClientBandwidthLimits.bandwidthLimits.limitDown"); value.Exists() {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitDown = types.Int64Value(value.Int())
+ } else {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitDown = types.Int64Null()
+ }
+ if value := res.Get("perClientBandwidthLimits.bandwidthLimits.limitUp"); value.Exists() {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitUp = types.Int64Value(value.Int())
+ } else {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitUp = types.Int64Null()
+ }
+ if value := res.Get("definitions"); value.Exists() {
+ data.Definitions = make([]NetworkGroupPolicyTrafficShapingRulesDefinitions, 0)
+ value.ForEach(func(k, res gjson.Result) bool {
+ parent := &data
+ data := NetworkGroupPolicyTrafficShapingRulesDefinitions{}
+ if value := res.Get("type"); value.Exists() {
+ data.Type = types.StringValue(value.String())
+ } else {
+ data.Type = types.StringNull()
+ }
+ if value := res.Get("value"); value.Exists() {
+ data.Value = types.StringValue(value.String())
+ } else {
+ data.Value = types.StringNull()
+ }
+ (*parent).Definitions = append((*parent).Definitions, data)
+ return true
+ })
+ }
+ (*parent).TrafficShapingRules = append((*parent).TrafficShapingRules, data)
+ return true
+ })
+ }
+ if value := res.Get("scheduling.enabled"); value.Exists() {
+ data.SchedulingEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingEnabled = types.BoolNull()
+ }
+ if value := res.Get("scheduling.friday.active"); value.Exists() {
+ data.SchedulingFridayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingFridayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.friday.from"); value.Exists() {
+ data.SchedulingFridayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingFridayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.friday.to"); value.Exists() {
+ data.SchedulingFridayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingFridayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.monday.active"); value.Exists() {
+ data.SchedulingMondayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingMondayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.monday.from"); value.Exists() {
+ data.SchedulingMondayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingMondayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.monday.to"); value.Exists() {
+ data.SchedulingMondayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingMondayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.saturday.active"); value.Exists() {
+ data.SchedulingSaturdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingSaturdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.saturday.from"); value.Exists() {
+ data.SchedulingSaturdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingSaturdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.saturday.to"); value.Exists() {
+ data.SchedulingSaturdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingSaturdayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.sunday.active"); value.Exists() {
+ data.SchedulingSundayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingSundayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.sunday.from"); value.Exists() {
+ data.SchedulingSundayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingSundayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.sunday.to"); value.Exists() {
+ data.SchedulingSundayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingSundayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.thursday.active"); value.Exists() {
+ data.SchedulingThursdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingThursdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.thursday.from"); value.Exists() {
+ data.SchedulingThursdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingThursdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.thursday.to"); value.Exists() {
+ data.SchedulingThursdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingThursdayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.tuesday.active"); value.Exists() {
+ data.SchedulingTuesdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingTuesdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.tuesday.from"); value.Exists() {
+ data.SchedulingTuesdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingTuesdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.tuesday.to"); value.Exists() {
+ data.SchedulingTuesdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingTuesdayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.wednesday.active"); value.Exists() {
+ data.SchedulingWednesdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingWednesdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.wednesday.from"); value.Exists() {
+ data.SchedulingWednesdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingWednesdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.wednesday.to"); value.Exists() {
+ data.SchedulingWednesdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingWednesdayTo = types.StringNull()
+ }
+ if value := res.Get("vlanTagging.settings"); value.Exists() {
+ data.VlanTaggingSettings = types.StringValue(value.String())
+ } else {
+ data.VlanTaggingSettings = types.StringNull()
+ }
+ if value := res.Get("vlanTagging.vlanId"); value.Exists() {
+ data.VlanTaggingVlanId = types.StringValue(value.String())
+ } else {
+ data.VlanTaggingVlanId = types.StringNull()
+ }
+}
+
+// End of section. //template:end fromBody
+
+// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyPartial
+
+// fromBodyPartial reads values from a gjson.Result into a tfstate model. It ignores null attributes in order to
+// uncouple the provider from the exact values that the backend API might summon to replace nulls. (Such behavior might
+// easily change across versions of the backend API.) For List/Set/Map attributes, the func only updates the
+// "managed" elements, instead of all elements.
+func (data *NetworkGroupPolicy) fromBodyPartial(ctx context.Context, res gjson.Result) {
+ if value := res.Get("name"); value.Exists() && !data.Name.IsNull() {
+ data.Name = types.StringValue(value.String())
+ } else {
+ data.Name = types.StringNull()
+ }
+ if value := res.Get("splashAuthSettings"); value.Exists() && !data.SplashAuthSettings.IsNull() {
+ data.SplashAuthSettings = types.StringValue(value.String())
+ } else {
+ data.SplashAuthSettings = types.StringNull()
+ }
+ if value := res.Get("bandwidth.settings"); value.Exists() && !data.BandwidthSettings.IsNull() {
+ data.BandwidthSettings = types.StringValue(value.String())
+ } else {
+ data.BandwidthSettings = types.StringNull()
+ }
+ if value := res.Get("bandwidth.bandwidthLimits.limitDown"); value.Exists() && !data.BandwidthLimitDown.IsNull() {
+ data.BandwidthLimitDown = types.Int64Value(value.Int())
+ } else {
+ data.BandwidthLimitDown = types.Int64Null()
+ }
+ if value := res.Get("bandwidth.bandwidthLimits.limitUp"); value.Exists() && !data.BandwidthLimitUp.IsNull() {
+ data.BandwidthLimitUp = types.Int64Value(value.Int())
+ } else {
+ data.BandwidthLimitUp = types.Int64Null()
+ }
+ if value := res.Get("bonjourForwarding.settings"); value.Exists() && !data.BonjourForwardingSettings.IsNull() {
+ data.BonjourForwardingSettings = types.StringValue(value.String())
+ } else {
+ data.BonjourForwardingSettings = types.StringNull()
+ }
+ for i := 0; i < len(data.BonjourForwardingRules); i++ {
+ keys := [...]string{"vlanId"}
+ keyValues := [...]string{data.BonjourForwardingRules[i].VlanId.ValueString()}
+
+ parent := &data
+ data := (*parent).BonjourForwardingRules[i]
+ parentRes := &res
+ var res gjson.Result
+
+ parentRes.Get("bonjourForwarding.rules").ForEach(
+ func(_, v gjson.Result) bool {
+ found := false
+ for ik := range keys {
+ if v.Get(keys[ik]).String() != keyValues[ik] {
+ found = false
+ break
+ }
+ found = true
+ }
+ if found {
+ res = v
+ return false
+ }
+ return true
+ },
+ )
+ if !res.Exists() {
+ tflog.Debug(ctx, fmt.Sprintf("removing BonjourForwardingRules[%d] = %+v",
+ i,
+ (*parent).BonjourForwardingRules[i],
+ ))
+ (*parent).BonjourForwardingRules = slices.Delete((*parent).BonjourForwardingRules, i, i+1)
+ i--
+
+ continue
+ }
+ if value := res.Get("description"); value.Exists() && !data.Description.IsNull() {
+ data.Description = types.StringValue(value.String())
+ } else {
+ data.Description = types.StringNull()
+ }
+ if value := res.Get("vlanId"); value.Exists() && !data.VlanId.IsNull() {
+ data.VlanId = types.StringValue(value.String())
+ } else {
+ data.VlanId = types.StringNull()
+ }
+ if value := res.Get("services"); value.Exists() && !data.Services.IsNull() {
+ data.Services = helpers.GetStringList(value.Array())
+ } else {
+ data.Services = types.ListNull(types.StringType)
+ }
+ (*parent).BonjourForwardingRules[i] = data
+ }
+ if value := res.Get("contentFiltering.allowedUrlPatterns.settings"); value.Exists() && !data.ContentFilteringAllowedUrlPatternsSettings.IsNull() {
+ data.ContentFilteringAllowedUrlPatternsSettings = types.StringValue(value.String())
+ } else {
+ data.ContentFilteringAllowedUrlPatternsSettings = types.StringNull()
+ }
+ if value := res.Get("contentFiltering.allowedUrlPatterns.patterns"); value.Exists() && !data.ContentFilteringAllowedUrlPatterns.IsNull() {
+ data.ContentFilteringAllowedUrlPatterns = helpers.GetStringList(value.Array())
+ } else {
+ data.ContentFilteringAllowedUrlPatterns = types.ListNull(types.StringType)
+ }
+ if value := res.Get("contentFiltering.blockedUrlCategories.settings"); value.Exists() && !data.ContentFilteringBlockedUrlCategoriesSettings.IsNull() {
+ data.ContentFilteringBlockedUrlCategoriesSettings = types.StringValue(value.String())
+ } else {
+ data.ContentFilteringBlockedUrlCategoriesSettings = types.StringNull()
+ }
+ if value := res.Get("contentFiltering.blockedUrlCategories.categories"); value.Exists() && !data.ContentFilteringBlockedUrlCategories.IsNull() {
+ data.ContentFilteringBlockedUrlCategories = helpers.GetStringList(value.Array())
+ } else {
+ data.ContentFilteringBlockedUrlCategories = types.ListNull(types.StringType)
+ }
+ if value := res.Get("contentFiltering.blockedUrlPatterns.settings"); value.Exists() && !data.ContentFilteringBlockedUrlPatternsSettings.IsNull() {
+ data.ContentFilteringBlockedUrlPatternsSettings = types.StringValue(value.String())
+ } else {
+ data.ContentFilteringBlockedUrlPatternsSettings = types.StringNull()
+ }
+ if value := res.Get("contentFiltering.blockedUrlPatterns.patterns"); value.Exists() && !data.ContentFilteringBlockedUrlPatterns.IsNull() {
+ data.ContentFilteringBlockedUrlPatterns = helpers.GetStringList(value.Array())
+ } else {
+ data.ContentFilteringBlockedUrlPatterns = types.ListNull(types.StringType)
+ }
+ if value := res.Get("firewallAndTrafficShaping.settings"); value.Exists() && !data.FirewallAndTrafficShapingSettings.IsNull() {
+ data.FirewallAndTrafficShapingSettings = types.StringValue(value.String())
+ } else {
+ data.FirewallAndTrafficShapingSettings = types.StringNull()
+ }
+ {
+ l := len(res.Get("firewallAndTrafficShaping.l3FirewallRules").Array())
+ tflog.Debug(ctx, fmt.Sprintf("firewallAndTrafficShaping.l3FirewallRules array resizing from %d to %d", len(data.L3FirewallRules), l))
+ for i := len(data.L3FirewallRules); i < l; i++ {
+ data.L3FirewallRules = append(data.L3FirewallRules, NetworkGroupPolicyL3FirewallRules{})
+ }
+ if len(data.L3FirewallRules) > l {
+ data.L3FirewallRules = data.L3FirewallRules[:l]
+ }
+ }
+ for i := range data.L3FirewallRules {
+ parent := &data
+ data := (*parent).L3FirewallRules[i]
+ parentRes := &res
+ res := parentRes.Get(fmt.Sprintf("firewallAndTrafficShaping.l3FirewallRules.%d", i))
+ if value := res.Get("comment"); value.Exists() && !data.Comment.IsNull() {
+ data.Comment = types.StringValue(value.String())
+ } else {
+ data.Comment = types.StringNull()
+ }
+ if value := res.Get("destCidr"); value.Exists() && !data.DestCidr.IsNull() {
+ data.DestCidr = types.StringValue(value.String())
+ } else {
+ data.DestCidr = types.StringNull()
+ }
+ if value := res.Get("destPort"); value.Exists() && !data.DestPort.IsNull() {
+ data.DestPort = types.StringValue(value.String())
+ } else {
+ data.DestPort = types.StringNull()
+ }
+ if value := res.Get("policy"); value.Exists() && !data.Policy.IsNull() {
+ data.Policy = types.StringValue(value.String())
+ } else {
+ data.Policy = types.StringNull()
+ }
+ if value := res.Get("protocol"); value.Exists() && !data.Protocol.IsNull() {
+ data.Protocol = types.StringValue(value.String())
+ } else {
+ data.Protocol = types.StringNull()
+ }
+ (*parent).L3FirewallRules[i] = data
+ }
+ {
+ l := len(res.Get("firewallAndTrafficShaping.l7FirewallRules").Array())
+ tflog.Debug(ctx, fmt.Sprintf("firewallAndTrafficShaping.l7FirewallRules array resizing from %d to %d", len(data.L7FirewallRules), l))
+ for i := len(data.L7FirewallRules); i < l; i++ {
+ data.L7FirewallRules = append(data.L7FirewallRules, NetworkGroupPolicyL7FirewallRules{})
+ }
+ if len(data.L7FirewallRules) > l {
+ data.L7FirewallRules = data.L7FirewallRules[:l]
+ }
+ }
+ for i := range data.L7FirewallRules {
+ parent := &data
+ data := (*parent).L7FirewallRules[i]
+ parentRes := &res
+ res := parentRes.Get(fmt.Sprintf("firewallAndTrafficShaping.l7FirewallRules.%d", i))
+ if value := res.Get("policy"); value.Exists() && !data.Policy.IsNull() {
+ data.Policy = types.StringValue(value.String())
+ } else {
+ data.Policy = types.StringNull()
+ }
+ if value := res.Get("type"); value.Exists() && !data.Type.IsNull() {
+ data.Type = types.StringValue(value.String())
+ } else {
+ data.Type = types.StringNull()
+ }
+ if value := res.Get("value"); value.Exists() && !data.Value.IsNull() {
+ data.Value = types.StringValue(value.String())
+ } else {
+ data.Value = types.StringNull()
+ }
+ (*parent).L7FirewallRules[i] = data
+ }
+ {
+ l := len(res.Get("firewallAndTrafficShaping.trafficShapingRules").Array())
+ tflog.Debug(ctx, fmt.Sprintf("firewallAndTrafficShaping.trafficShapingRules array resizing from %d to %d", len(data.TrafficShapingRules), l))
+ for i := len(data.TrafficShapingRules); i < l; i++ {
+ data.TrafficShapingRules = append(data.TrafficShapingRules, NetworkGroupPolicyTrafficShapingRules{})
+ }
+ if len(data.TrafficShapingRules) > l {
+ data.TrafficShapingRules = data.TrafficShapingRules[:l]
+ }
+ }
+ for i := range data.TrafficShapingRules {
+ parent := &data
+ data := (*parent).TrafficShapingRules[i]
+ parentRes := &res
+ res := parentRes.Get(fmt.Sprintf("firewallAndTrafficShaping.trafficShapingRules.%d", i))
+ if value := res.Get("dscpTagValue"); value.Exists() && !data.DscpTagValue.IsNull() {
+ data.DscpTagValue = types.Int64Value(value.Int())
+ } else {
+ data.DscpTagValue = types.Int64Null()
+ }
+ if value := res.Get("pcpTagValue"); value.Exists() && !data.PcpTagValue.IsNull() {
+ data.PcpTagValue = types.Int64Value(value.Int())
+ } else {
+ data.PcpTagValue = types.Int64Null()
+ }
+ if value := res.Get("perClientBandwidthLimits.settings"); value.Exists() && !data.PerClientBandwidthLimitsSettings.IsNull() {
+ data.PerClientBandwidthLimitsSettings = types.StringValue(value.String())
+ } else {
+ data.PerClientBandwidthLimitsSettings = types.StringNull()
+ }
+ if value := res.Get("perClientBandwidthLimits.bandwidthLimits.limitDown"); value.Exists() && !data.PerClientBandwidthLimitsBandwidthLimitsLimitDown.IsNull() {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitDown = types.Int64Value(value.Int())
+ } else {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitDown = types.Int64Null()
+ }
+ if value := res.Get("perClientBandwidthLimits.bandwidthLimits.limitUp"); value.Exists() && !data.PerClientBandwidthLimitsBandwidthLimitsLimitUp.IsNull() {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitUp = types.Int64Value(value.Int())
+ } else {
+ data.PerClientBandwidthLimitsBandwidthLimitsLimitUp = types.Int64Null()
+ }
+ for i := 0; i < len(data.Definitions); i++ {
+ keys := [...]string{"type", "value"}
+ keyValues := [...]string{data.Definitions[i].Type.ValueString(), data.Definitions[i].Value.ValueString()}
+
+ parent := &data
+ data := (*parent).Definitions[i]
+ parentRes := &res
+ var res gjson.Result
+
+ parentRes.Get("definitions").ForEach(
+ func(_, v gjson.Result) bool {
+ found := false
+ for ik := range keys {
+ if v.Get(keys[ik]).String() != keyValues[ik] {
+ found = false
+ break
+ }
+ found = true
+ }
+ if found {
+ res = v
+ return false
+ }
+ return true
+ },
+ )
+ if !res.Exists() {
+ tflog.Debug(ctx, fmt.Sprintf("removing Definitions[%d] = %+v",
+ i,
+ (*parent).Definitions[i],
+ ))
+ (*parent).Definitions = slices.Delete((*parent).Definitions, i, i+1)
+ i--
+
+ continue
+ }
+ if value := res.Get("type"); value.Exists() && !data.Type.IsNull() {
+ data.Type = types.StringValue(value.String())
+ } else {
+ data.Type = types.StringNull()
+ }
+ if value := res.Get("value"); value.Exists() && !data.Value.IsNull() {
+ data.Value = types.StringValue(value.String())
+ } else {
+ data.Value = types.StringNull()
+ }
+ (*parent).Definitions[i] = data
+ }
+ (*parent).TrafficShapingRules[i] = data
+ }
+ if value := res.Get("scheduling.enabled"); value.Exists() && !data.SchedulingEnabled.IsNull() {
+ data.SchedulingEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingEnabled = types.BoolNull()
+ }
+ if value := res.Get("scheduling.friday.active"); value.Exists() && !data.SchedulingFridayActive.IsNull() {
+ data.SchedulingFridayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingFridayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.friday.from"); value.Exists() && !data.SchedulingFridayFrom.IsNull() {
+ data.SchedulingFridayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingFridayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.friday.to"); value.Exists() && !data.SchedulingFridayTo.IsNull() {
+ data.SchedulingFridayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingFridayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.monday.active"); value.Exists() && !data.SchedulingMondayActive.IsNull() {
+ data.SchedulingMondayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingMondayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.monday.from"); value.Exists() && !data.SchedulingMondayFrom.IsNull() {
+ data.SchedulingMondayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingMondayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.monday.to"); value.Exists() && !data.SchedulingMondayTo.IsNull() {
+ data.SchedulingMondayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingMondayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.saturday.active"); value.Exists() && !data.SchedulingSaturdayActive.IsNull() {
+ data.SchedulingSaturdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingSaturdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.saturday.from"); value.Exists() && !data.SchedulingSaturdayFrom.IsNull() {
+ data.SchedulingSaturdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingSaturdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.saturday.to"); value.Exists() && !data.SchedulingSaturdayTo.IsNull() {
+ data.SchedulingSaturdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingSaturdayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.sunday.active"); value.Exists() && !data.SchedulingSundayActive.IsNull() {
+ data.SchedulingSundayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingSundayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.sunday.from"); value.Exists() && !data.SchedulingSundayFrom.IsNull() {
+ data.SchedulingSundayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingSundayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.sunday.to"); value.Exists() && !data.SchedulingSundayTo.IsNull() {
+ data.SchedulingSundayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingSundayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.thursday.active"); value.Exists() && !data.SchedulingThursdayActive.IsNull() {
+ data.SchedulingThursdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingThursdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.thursday.from"); value.Exists() && !data.SchedulingThursdayFrom.IsNull() {
+ data.SchedulingThursdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingThursdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.thursday.to"); value.Exists() && !data.SchedulingThursdayTo.IsNull() {
+ data.SchedulingThursdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingThursdayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.tuesday.active"); value.Exists() && !data.SchedulingTuesdayActive.IsNull() {
+ data.SchedulingTuesdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingTuesdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.tuesday.from"); value.Exists() && !data.SchedulingTuesdayFrom.IsNull() {
+ data.SchedulingTuesdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingTuesdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.tuesday.to"); value.Exists() && !data.SchedulingTuesdayTo.IsNull() {
+ data.SchedulingTuesdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingTuesdayTo = types.StringNull()
+ }
+ if value := res.Get("scheduling.wednesday.active"); value.Exists() && !data.SchedulingWednesdayActive.IsNull() {
+ data.SchedulingWednesdayActive = types.BoolValue(value.Bool())
+ } else {
+ data.SchedulingWednesdayActive = types.BoolNull()
+ }
+ if value := res.Get("scheduling.wednesday.from"); value.Exists() && !data.SchedulingWednesdayFrom.IsNull() {
+ data.SchedulingWednesdayFrom = types.StringValue(value.String())
+ } else {
+ data.SchedulingWednesdayFrom = types.StringNull()
+ }
+ if value := res.Get("scheduling.wednesday.to"); value.Exists() && !data.SchedulingWednesdayTo.IsNull() {
+ data.SchedulingWednesdayTo = types.StringValue(value.String())
+ } else {
+ data.SchedulingWednesdayTo = types.StringNull()
+ }
+ if value := res.Get("vlanTagging.settings"); value.Exists() && !data.VlanTaggingSettings.IsNull() {
+ data.VlanTaggingSettings = types.StringValue(value.String())
+ } else {
+ data.VlanTaggingSettings = types.StringNull()
+ }
+ if value := res.Get("vlanTagging.vlanId"); value.Exists() && !data.VlanTaggingVlanId.IsNull() {
+ data.VlanTaggingVlanId = types.StringValue(value.String())
+ } else {
+ data.VlanTaggingVlanId = types.StringNull()
+ }
+}
+
+// End of section. //template:end fromBodyPartial
diff --git a/internal/provider/model_meraki_network_settings.go b/internal/provider/model_meraki_network_settings.go
new file mode 100644
index 00000000..42d6d0af
--- /dev/null
+++ b/internal/provider/model_meraki_network_settings.go
@@ -0,0 +1,149 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "context"
+ "fmt"
+ "net/url"
+
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin types
+
+type NetworkSettings struct {
+ Id types.String `tfsdk:"id"`
+ NetworkId types.String `tfsdk:"network_id"`
+ LocalStatusPageEnabled types.Bool `tfsdk:"local_status_page_enabled"`
+ RemoteStatusPageEnabled types.Bool `tfsdk:"remote_status_page_enabled"`
+ LocalStatusPageAuthenticationEnabled types.Bool `tfsdk:"local_status_page_authentication_enabled"`
+ LocalStatusPageAuthenticationPassword types.String `tfsdk:"local_status_page_authentication_password"`
+ NamedVlansEnabled types.Bool `tfsdk:"named_vlans_enabled"`
+ SecurePortEnabled types.Bool `tfsdk:"secure_port_enabled"`
+}
+
+// End of section. //template:end types
+
+// Section below is generated&owned by "gen/generator.go". //template:begin getPath
+
+func (data NetworkSettings) getPath() string {
+ return fmt.Sprintf("/networks/%v/settings", url.QueryEscape(data.NetworkId.ValueString()))
+}
+
+// End of section. //template:end getPath
+
+// Section below is generated&owned by "gen/generator.go". //template:begin toBody
+
+func (data NetworkSettings) toBody(ctx context.Context, state NetworkSettings) string {
+ body := ""
+ if !data.LocalStatusPageEnabled.IsNull() {
+ body, _ = sjson.Set(body, "localStatusPageEnabled", data.LocalStatusPageEnabled.ValueBool())
+ }
+ if !data.RemoteStatusPageEnabled.IsNull() {
+ body, _ = sjson.Set(body, "remoteStatusPageEnabled", data.RemoteStatusPageEnabled.ValueBool())
+ }
+ if !data.LocalStatusPageAuthenticationEnabled.IsNull() {
+ body, _ = sjson.Set(body, "localStatusPage.authentication.enabled", data.LocalStatusPageAuthenticationEnabled.ValueBool())
+ }
+ if !data.LocalStatusPageAuthenticationPassword.IsNull() {
+ body, _ = sjson.Set(body, "localStatusPage.authentication.password", data.LocalStatusPageAuthenticationPassword.ValueString())
+ }
+ if !data.NamedVlansEnabled.IsNull() {
+ body, _ = sjson.Set(body, "namedVlans.enabled", data.NamedVlansEnabled.ValueBool())
+ }
+ if !data.SecurePortEnabled.IsNull() {
+ body, _ = sjson.Set(body, "securePort.enabled", data.SecurePortEnabled.ValueBool())
+ }
+ return body
+}
+
+// End of section. //template:end toBody
+
+// Section below is generated&owned by "gen/generator.go". //template:begin fromBody
+
+func (data *NetworkSettings) fromBody(ctx context.Context, res gjson.Result) {
+ if value := res.Get("localStatusPageEnabled"); value.Exists() {
+ data.LocalStatusPageEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.LocalStatusPageEnabled = types.BoolNull()
+ }
+ if value := res.Get("remoteStatusPageEnabled"); value.Exists() {
+ data.RemoteStatusPageEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.RemoteStatusPageEnabled = types.BoolNull()
+ }
+ if value := res.Get("localStatusPage.authentication.enabled"); value.Exists() {
+ data.LocalStatusPageAuthenticationEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.LocalStatusPageAuthenticationEnabled = types.BoolNull()
+ }
+ if value := res.Get("namedVlans.enabled"); value.Exists() {
+ data.NamedVlansEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.NamedVlansEnabled = types.BoolNull()
+ }
+ if value := res.Get("securePort.enabled"); value.Exists() {
+ data.SecurePortEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.SecurePortEnabled = types.BoolNull()
+ }
+}
+
+// End of section. //template:end fromBody
+
+// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyPartial
+
+// fromBodyPartial reads values from a gjson.Result into a tfstate model. It ignores null attributes in order to
+// uncouple the provider from the exact values that the backend API might summon to replace nulls. (Such behavior might
+// easily change across versions of the backend API.) For List/Set/Map attributes, the func only updates the
+// "managed" elements, instead of all elements.
+func (data *NetworkSettings) fromBodyPartial(ctx context.Context, res gjson.Result) {
+ if value := res.Get("localStatusPageEnabled"); value.Exists() && !data.LocalStatusPageEnabled.IsNull() {
+ data.LocalStatusPageEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.LocalStatusPageEnabled = types.BoolNull()
+ }
+ if value := res.Get("remoteStatusPageEnabled"); value.Exists() && !data.RemoteStatusPageEnabled.IsNull() {
+ data.RemoteStatusPageEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.RemoteStatusPageEnabled = types.BoolNull()
+ }
+ if value := res.Get("localStatusPage.authentication.enabled"); value.Exists() && !data.LocalStatusPageAuthenticationEnabled.IsNull() {
+ data.LocalStatusPageAuthenticationEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.LocalStatusPageAuthenticationEnabled = types.BoolNull()
+ }
+ if value := res.Get("namedVlans.enabled"); value.Exists() && !data.NamedVlansEnabled.IsNull() {
+ data.NamedVlansEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.NamedVlansEnabled = types.BoolNull()
+ }
+ if value := res.Get("securePort.enabled"); value.Exists() && !data.SecurePortEnabled.IsNull() {
+ data.SecurePortEnabled = types.BoolValue(value.Bool())
+ } else {
+ data.SecurePortEnabled = types.BoolNull()
+ }
+}
+
+// End of section. //template:end fromBodyPartial
diff --git a/internal/provider/model_meraki_network_snmp.go b/internal/provider/model_meraki_network_snmp.go
index 38422793..664896d2 100644
--- a/internal/provider/model_meraki_network_snmp.go
+++ b/internal/provider/model_meraki_network_snmp.go
@@ -43,8 +43,8 @@ type NetworkSNMP struct {
}
type NetworkSNMPUsers struct {
- Username types.String `tfsdk:"username"`
Passphrase types.String `tfsdk:"passphrase"`
+ Username types.String `tfsdk:"username"`
}
// End of section. //template:end types
@@ -71,12 +71,12 @@ func (data NetworkSNMP) toBody(ctx context.Context, state NetworkSNMP) string {
body, _ = sjson.Set(body, "users", []interface{}{})
for _, item := range data.Users {
itemBody := ""
- if !item.Username.IsNull() {
- itemBody, _ = sjson.Set(itemBody, "username", item.Username.ValueString())
- }
if !item.Passphrase.IsNull() {
itemBody, _ = sjson.Set(itemBody, "passphrase", item.Passphrase.ValueString())
}
+ if !item.Username.IsNull() {
+ itemBody, _ = sjson.Set(itemBody, "username", item.Username.ValueString())
+ }
body, _ = sjson.SetRaw(body, "users.-1", itemBody)
}
}
@@ -103,16 +103,16 @@ func (data *NetworkSNMP) fromBody(ctx context.Context, res gjson.Result) {
value.ForEach(func(k, res gjson.Result) bool {
parent := &data
data := NetworkSNMPUsers{}
- if value := res.Get("username"); value.Exists() {
- data.Username = types.StringValue(value.String())
- } else {
- data.Username = types.StringNull()
- }
if value := res.Get("passphrase"); value.Exists() {
data.Passphrase = types.StringValue(value.String())
} else {
data.Passphrase = types.StringNull()
}
+ if value := res.Get("username"); value.Exists() {
+ data.Username = types.StringValue(value.String())
+ } else {
+ data.Username = types.StringNull()
+ }
(*parent).Users = append((*parent).Users, data)
return true
})
@@ -174,16 +174,16 @@ func (data *NetworkSNMP) fromBodyPartial(ctx context.Context, res gjson.Result)
continue
}
- if value := res.Get("username"); value.Exists() && !data.Username.IsNull() {
- data.Username = types.StringValue(value.String())
- } else {
- data.Username = types.StringNull()
- }
if value := res.Get("passphrase"); value.Exists() && !data.Passphrase.IsNull() {
data.Passphrase = types.StringValue(value.String())
} else {
data.Passphrase = types.StringNull()
}
+ if value := res.Get("username"); value.Exists() && !data.Username.IsNull() {
+ data.Username = types.StringValue(value.String())
+ } else {
+ data.Username = types.StringNull()
+ }
(*parent).Users[i] = data
}
}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 7d8b8bff..7ac97117 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -220,6 +220,8 @@ func (p *MerakiProvider) Resources(ctx context.Context) []func() resource.Resour
NewAdminResource,
NewNetworkResource,
NewNetworkDeviceClaimResource,
+ NewNetworkGroupPolicyResource,
+ NewNetworkSettingsResource,
NewNetworkSNMPResource,
NewOrganizationResource,
NewOrganizationInventoryClaimResource,
@@ -230,6 +232,8 @@ func (p *MerakiProvider) DataSources(ctx context.Context) []func() datasource.Da
return []func() datasource.DataSource{
NewAdminDataSource,
NewNetworkDataSource,
+ NewNetworkGroupPolicyDataSource,
+ NewNetworkSettingsDataSource,
NewNetworkSNMPDataSource,
NewOrganizationDataSource,
}
diff --git a/internal/provider/resource_meraki_admin.go b/internal/provider/resource_meraki_admin.go
index 54f025bc..08c2f70c 100644
--- a/internal/provider/resource_meraki_admin.go
+++ b/internal/provider/resource_meraki_admin.go
@@ -80,6 +80,13 @@ func (r *AdminResource) Schema(ctx context.Context, req resource.SchemaRequest,
stringplanmodifier.RequiresReplace(),
},
},
+ "authentication_method": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("No longer used as of Cisco SecureX end-of-life. Can be one of `Email`. The default is Email authentication.").AddStringEnumDescription("Email").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("Email"),
+ },
+ },
"email": schema.StringAttribute{
MarkdownDescription: helpers.NewAttributeDescription("The email of the dashboard administrator. This attribute can not be updated.").String,
Required: true,
@@ -89,10 +96,10 @@ func (r *AdminResource) Schema(ctx context.Context, req resource.SchemaRequest,
Required: true,
},
"org_access": schema.StringAttribute{
- MarkdownDescription: helpers.NewAttributeDescription("The privilege of the dashboard administrator on the organization. Can be one of `full`, `read-only`, `enterprise` or `none`").AddStringEnumDescription("full", "read-only", "enterprise", "none").String,
+ MarkdownDescription: helpers.NewAttributeDescription("The privilege of the dashboard administrator on the organization. Can be one of `full`, `read-only`, `enterprise` or `none`").AddStringEnumDescription("enterprise", "full", "none", "read-only").String,
Required: true,
Validators: []validator.String{
- stringvalidator.OneOf("full", "read-only", "enterprise", "none"),
+ stringvalidator.OneOf("enterprise", "full", "none", "read-only"),
},
},
"networks": schema.ListNestedAttribute{
@@ -120,10 +127,10 @@ func (r *AdminResource) Schema(ctx context.Context, req resource.SchemaRequest,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"access": schema.StringAttribute{
- MarkdownDescription: helpers.NewAttributeDescription("The privilege of the dashboard administrator on the tag. Can be one of `full`, `read-only`, `guest-ambassador` or `monitor-only`").AddStringEnumDescription("full", "read-only", "guest-ambassador", "monitor-only").String,
+ MarkdownDescription: helpers.NewAttributeDescription("The privilege of the dashboard administrator on the tag. Can be one of `full`, `read-only`, `guest-ambassador` or `monitor-only`").AddStringEnumDescription("full", "guest-ambassador", "monitor-only", "read-only").String,
Required: true,
Validators: []validator.String{
- stringvalidator.OneOf("full", "read-only", "guest-ambassador", "monitor-only"),
+ stringvalidator.OneOf("full", "guest-ambassador", "monitor-only", "read-only"),
},
},
"tag": schema.StringAttribute{
diff --git a/internal/provider/resource_meraki_admin_test.go b/internal/provider/resource_meraki_admin_test.go
index 800333b6..ee2f3889 100644
--- a/internal/provider/resource_meraki_admin_test.go
+++ b/internal/provider/resource_meraki_admin_test.go
@@ -31,6 +31,7 @@ import (
func TestAccMerakiAdmin(t *testing.T) {
var checks []resource.TestCheckFunc
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_admin.test", "authentication_method", "Email"))
checks = append(checks, resource.TestCheckResourceAttr("meraki_admin.test", "email", "miles@meraki.com"))
checks = append(checks, resource.TestCheckResourceAttr("meraki_admin.test", "name", "Miles Meraki"))
checks = append(checks, resource.TestCheckResourceAttr("meraki_admin.test", "org_access", "none"))
@@ -93,6 +94,7 @@ func testAccMerakiAdminConfig_minimum() string {
func testAccMerakiAdminConfig_all() string {
config := `resource "meraki_admin" "test" {` + "\n"
config += ` organization_id = data.meraki_organization.test.id` + "\n"
+ config += ` authentication_method = "Email"` + "\n"
config += ` email = "miles@meraki.com"` + "\n"
config += ` name = "Miles Meraki"` + "\n"
config += ` org_access = "none"` + "\n"
diff --git a/internal/provider/resource_meraki_network_group_policy.go b/internal/provider/resource_meraki_network_group_policy.go
new file mode 100644
index 00000000..6cdccf1a
--- /dev/null
+++ b/internal/provider/resource_meraki_network_group_policy.go
@@ -0,0 +1,548 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/CiscoDevNet/terraform-provider-meraki/internal/provider/helpers"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "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/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/netascode/go-meraki"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin model
+
+// Ensure provider defined types fully satisfy framework interfaces
+var (
+ _ resource.Resource = &NetworkGroupPolicyResource{}
+ _ resource.ResourceWithImportState = &NetworkGroupPolicyResource{}
+)
+
+func NewNetworkGroupPolicyResource() resource.Resource {
+ return &NetworkGroupPolicyResource{}
+}
+
+type NetworkGroupPolicyResource struct {
+ client *meraki.Client
+}
+
+func (r *NetworkGroupPolicyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_network_group_policy"
+}
+
+func (r *NetworkGroupPolicyResource) 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 can manage the `Network Group Policy` configuration.").String,
+
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "The id of the object",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "network_id": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Network ID").String,
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The name for your group policy. Required.").String,
+ Required: true,
+ },
+ "splash_auth_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether clients bound to your policy will bypass splash authorization or behave according to the network`s rules. Can be one of `network default` or `bypass`. Only available if your network has a wireless configuration.").AddStringEnumDescription("bypass", "network default").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("bypass", "network default"),
+ },
+ },
+ "bandwidth_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How bandwidth limits are enforced. Can be `network default`, `ignore` or `custom`.").AddStringEnumDescription("custom", "ignore", "network default").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("custom", "ignore", "network default"),
+ },
+ },
+ "bandwidth_limit_down": schema.Int64Attribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The maximum download limit (integer, in Kbps). null indicates no limit").String,
+ Optional: true,
+ },
+ "bandwidth_limit_up": schema.Int64Attribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The maximum upload limit (integer, in Kbps). null indicates no limit").String,
+ Optional: true,
+ },
+ "bonjour_forwarding_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How Bonjour rules are applied. Can be `network default`, `ignore` or `custom`.").AddStringEnumDescription("custom", "ignore", "network default").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("custom", "ignore", "network default"),
+ },
+ },
+ "bonjour_forwarding_rules": schema.ListNestedAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A list of the Bonjour forwarding rules for your group policy. If `settings` is set to `custom`, at least one rule must be specified.").String,
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "description": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A description for your Bonjour forwarding rule. Optional.").String,
+ Optional: true,
+ },
+ "vlan_id": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The ID of the service VLAN. Required.").String,
+ Required: true,
+ },
+ "services": schema.ListAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A list of Bonjour services. At least one service must be specified. Available services are `All Services`, `AirPlay`, `AFP`, `BitTorrent`, `FTP`, `iChat`, `iTunes`, `Printers`, `Samba`, `Scanners` and `SSH`").String,
+ ElementType: types.StringType,
+ Optional: true,
+ },
+ },
+ },
+ },
+ "content_filtering_allowed_url_patterns_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How URL patterns are applied. Can be `network default`, `append` or `override`.").AddStringEnumDescription("append", "network default", "override").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("append", "network default", "override"),
+ },
+ },
+ "content_filtering_allowed_url_patterns": schema.ListAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A list of URL patterns that are allowed").String,
+ ElementType: types.StringType,
+ Optional: true,
+ },
+ "content_filtering_blocked_url_categories_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How URL categories are applied. Can be `network default`, `append` or `override`.").AddStringEnumDescription("append", "network default", "override").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("append", "network default", "override"),
+ },
+ },
+ "content_filtering_blocked_url_categories": schema.ListAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A list of URL categories to block").String,
+ ElementType: types.StringType,
+ Optional: true,
+ },
+ "content_filtering_blocked_url_patterns_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How URL patterns are applied. Can be `network default`, `append` or `override`.").AddStringEnumDescription("append", "network default", "override").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("append", "network default", "override"),
+ },
+ },
+ "content_filtering_blocked_url_patterns": schema.ListAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A list of URL patterns that are blocked").String,
+ ElementType: types.StringType,
+ Optional: true,
+ },
+ "firewall_and_traffic_shaping_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How firewall and traffic shaping rules are enforced. Can be `network default`, `ignore` or `custom`.").AddStringEnumDescription("custom", "ignore", "network default").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("custom", "ignore", "network default"),
+ },
+ },
+ "l3_firewall_rules": schema.ListNestedAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("An ordered array of the L3 firewall rules").String,
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "comment": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Description of the rule (optional)").String,
+ Optional: true,
+ },
+ "dest_cidr": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Destination IP address (in IP or CIDR notation), a fully-qualified domain name (FQDN, if your network supports it) or `any`.").String,
+ Required: true,
+ },
+ "dest_port": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Destination port (integer in the range 1-65535), a port range (e.g. 8080-9090), or `any`").String,
+ Optional: true,
+ },
+ "policy": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("`allow` or `deny` traffic specified by this rule").String,
+ Required: true,
+ },
+ "protocol": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The type of protocol (must be `tcp`, `udp`, `icmp`, `icmp6` or `any`)").String,
+ Required: true,
+ },
+ },
+ },
+ },
+ "l7_firewall_rules": schema.ListNestedAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("An ordered array of L7 firewall rules").String,
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "policy": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The policy applied to matching traffic. Must be `deny`.").AddStringEnumDescription("deny").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("deny"),
+ },
+ },
+ "type": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Type of the L7 Rule. Must be `application`, `applicationCategory`, `host`, `port` or `ipRange`").AddStringEnumDescription("application", "applicationCategory", "host", "ipRange", "port").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("application", "applicationCategory", "host", "ipRange", "port"),
+ },
+ },
+ "value": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The `value` of what you want to block. If `type` is `host`, `port` or `ipRange`, `value` must be a string matching either a hostname (e.g. somewhere.com), a port (e.g. 8080), or an IP range (e.g. 192.1.0.0/16). If `type` is `application` or `applicationCategory`, then `value` must be an object with an ID for the application.").String,
+ Optional: true,
+ },
+ },
+ },
+ },
+ "traffic_shaping_rules": schema.ListNestedAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("An array of traffic shaping rules. Rules are applied in the order that they are specified in. An empty list (or null) means no rules. Note that you are allowed a maximum of 8 rules.").String,
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "dscp_tag_value": schema.Int64Attribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The DSCP tag applied by your rule. null means `Do not change DSCP tag`. For a list of possible tag values, use the trafficShaping/dscpTaggingOptions endpoint.").String,
+ Optional: true,
+ },
+ "pcp_tag_value": schema.Int64Attribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The PCP tag applied by your rule. Can be 0 (lowest priority) through 7 (highest priority). null means `Do not set PCP tag`.").String,
+ Optional: true,
+ },
+ "priority": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A string, indicating the priority level for packets bound to your rule. Can be `low`, `normal` or `high`.").String,
+ Optional: true,
+ },
+ "per_client_bandwidth_limits_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How bandwidth limits are applied by your rule. Can be one of `network default`, `ignore` or `custom`.").String,
+ Optional: true,
+ },
+ "per_client_bandwidth_limits_bandwidth_limits_limit_down": schema.Int64Attribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The maximum download limit (integer, in Kbps).").String,
+ Optional: true,
+ },
+ "per_client_bandwidth_limits_bandwidth_limits_limit_up": schema.Int64Attribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The maximum upload limit (integer, in Kbps).").String,
+ Optional: true,
+ },
+ "definitions": schema.ListNestedAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("A list of objects describing the definitions of your traffic shaping rule. At least one definition is required.").String,
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The type of definition. Can be one of `application`, `applicationCategory`, `host`, `port`, `ipRange` or `localNet`.").AddStringEnumDescription("application", "applicationCategory", "host", "ipRange", "localNet", "port").String,
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("application", "applicationCategory", "host", "ipRange", "localNet", "port"),
+ },
+ },
+ "value": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("If 'type' is `host`, `port`, `ipRange` or `localNet`, then 'value' must be a string, matching either a hostname (e.g. 'somesite.com'), a port (e.g. 8080), or an IP range ('192.1.0.0', '192.1.0.0/16', or '10.1.0.0/16:80'). `localNet` also supports CIDR notation, excluding custom ports. If 'type' is `application` or `applicationCategory`, then 'value' must be an object with the structure { 'id': 'meraki:layer7/...' }, where 'id' is the application category or application ID (for a list of IDs for your network, use the trafficShaping/applicationCategories endpoint).").String,
+ Required: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "scheduling_enabled": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether scheduling is enabled (true) or disabled (false). Defaults to false. If true, the schedule objects for each day of the week (monday - sunday) are parsed.").String,
+ Optional: true,
+ },
+ "scheduling_friday_active": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.").String,
+ Optional: true,
+ },
+ "scheduling_friday_from": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_friday_to": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_monday_active": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.").String,
+ Optional: true,
+ },
+ "scheduling_monday_from": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_monday_to": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_saturday_active": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.").String,
+ Optional: true,
+ },
+ "scheduling_saturday_from": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_saturday_to": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_sunday_active": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.").String,
+ Optional: true,
+ },
+ "scheduling_sunday_from": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_sunday_to": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_thursday_active": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.").String,
+ Optional: true,
+ },
+ "scheduling_thursday_from": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_thursday_to": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_tuesday_active": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.").String,
+ Optional: true,
+ },
+ "scheduling_tuesday_from": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_tuesday_to": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_wednesday_active": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Whether the schedule is active (true) or inactive (false) during the time specified between `from` and `to`. Defaults to true.").String,
+ Optional: true,
+ },
+ "scheduling_wednesday_from": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be less than the time specified in `to`. Defaults to `00:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "scheduling_wednesday_to": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The time, from `00:00` to `24:00`. Must be greater than the time specified in `from`. Defaults to `24:00`. Only 30 minute increments are allowed.").String,
+ Optional: true,
+ },
+ "vlan_tagging_settings": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("How VLAN tagging is applied. Can be `network default`, `ignore` or `custom`.").AddStringEnumDescription("custom", "ignore", "network default").String,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("custom", "ignore", "network default"),
+ },
+ },
+ "vlan_tagging_vlan_id": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The ID of the vlan you want to tag. This only applies if `settings` is set to `custom`.").String,
+ Optional: true,
+ },
+ },
+ }
+}
+
+func (r *NetworkGroupPolicyResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ r.client = req.ProviderData.(*MerakiProviderData).Client
+}
+
+// End of section. //template:end model
+
+// Section below is generated&owned by "gen/generator.go". //template:begin create
+
+func (r *NetworkGroupPolicyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan NetworkGroupPolicy
+
+ // Read plan
+ diags := req.Plan.Get(ctx, &plan)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString()))
+
+ // Create object
+ body := plan.toBody(ctx, NetworkGroupPolicy{})
+ res, err := r.client.Post(plan.getPath(), body)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String()))
+ return
+ }
+ plan.Id = types.StringValue(res.Get("groupPolicyId").String())
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString()))
+
+ diags = resp.State.Set(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+
+ helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics)
+}
+
+// End of section. //template:end create
+
+// Section below is generated&owned by "gen/generator.go". //template:begin read
+
+func (r *NetworkGroupPolicyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state NetworkGroupPolicy
+
+ // Read state
+ diags := req.State.Get(ctx, &state)
+ if resp.Diagnostics.Append(diags...); 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
+ }
+
+ imp, diags := helpers.IsFlagImporting(ctx, req)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ // After `terraform import` we switch to a full read.
+ if imp {
+ state.fromBody(ctx, res)
+ } else {
+ state.fromBodyPartial(ctx, res)
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString()))
+
+ diags = resp.State.Set(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+
+ helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics)
+}
+
+// End of section. //template:end read
+
+// Section below is generated&owned by "gen/generator.go". //template:begin update
+
+func (r *NetworkGroupPolicyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan, state NetworkGroupPolicy
+
+ // Read plan
+ diags := req.Plan.Get(ctx, &plan)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Read state
+ diags = req.State.Get(ctx, &state)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString()))
+
+ body := plan.toBody(ctx, state)
+ 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...)
+}
+
+// End of section. //template:end update
+
+// Section below is generated&owned by "gen/generator.go". //template:begin delete
+
+func (r *NetworkGroupPolicyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state NetworkGroupPolicy
+
+ // Read state
+ diags := req.State.Get(ctx, &state)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString()))
+ res, err := r.client.Delete(state.getPath() + "/" + url.QueryEscape(state.Id.ValueString()))
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (DELETE), got error: %s, %s", err, res.String()))
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString()))
+
+ resp.State.RemoveResource(ctx)
+}
+
+// End of section. //template:end delete
+
+// Section below is generated&owned by "gen/generator.go". //template:begin import
+func (r *NetworkGroupPolicyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ idParts := strings.Split(req.ID, ",")
+
+ if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
+ resp.Diagnostics.AddError(
+ "Unexpected Import Identifier",
+ fmt.Sprintf("Expected import identifier with format: ,. Got: %q", req.ID),
+ )
+ return
+ }
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), idParts[0])...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...)
+
+ helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics)
+}
+
+// End of section. //template:end import
diff --git a/internal/provider/resource_meraki_network_group_policy_test.go b/internal/provider/resource_meraki_network_group_policy_test.go
new file mode 100644
index 00000000..faef87e8
--- /dev/null
+++ b/internal/provider/resource_meraki_network_group_policy_test.go
@@ -0,0 +1,201 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAcc
+
+func TestAccMerakiNetworkGroupPolicy(t *testing.T) {
+ var checks []resource.TestCheckFunc
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "name", "No video streaming"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "splash_auth_settings", "bypass"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "bandwidth_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "bandwidth_limit_down", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "bandwidth_limit_up", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "bonjour_forwarding_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "bonjour_forwarding_rules.0.description", "A simple bonjour rule"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "bonjour_forwarding_rules.0.vlan_id", "1"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "bonjour_forwarding_rules.0.services.0", "All Services"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "firewall_and_traffic_shaping_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l3_firewall_rules.0.comment", "Allow TCP traffic to subnet with HTTP servers."))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l3_firewall_rules.0.dest_cidr", "192.168.1.0/24"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l3_firewall_rules.0.dest_port", "443"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l3_firewall_rules.0.policy", "allow"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l3_firewall_rules.0.protocol", "tcp"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l7_firewall_rules.0.policy", "deny"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l7_firewall_rules.0.type", "host"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "l7_firewall_rules.0.value", "google.com"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "traffic_shaping_rules.0.dscp_tag_value", "0"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "traffic_shaping_rules.0.pcp_tag_value", "0"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "traffic_shaping_rules.0.per_client_bandwidth_limits_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "traffic_shaping_rules.0.per_client_bandwidth_limits_bandwidth_limits_limit_down", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "traffic_shaping_rules.0.per_client_bandwidth_limits_bandwidth_limits_limit_up", "1000000"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "traffic_shaping_rules.0.definitions.0.type", "host"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "traffic_shaping_rules.0.definitions.0.value", "google.com"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_friday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_friday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_friday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_monday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_monday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_monday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_saturday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_saturday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_saturday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_sunday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_sunday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_sunday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_thursday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_thursday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_thursday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_tuesday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_tuesday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_tuesday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_wednesday_active", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_wednesday_from", "09:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "scheduling_wednesday_to", "17:00"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "vlan_tagging_settings", "custom"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_group_policy.test", "vlan_tagging_vlan_id", "1"))
+
+ var steps []resource.TestStep
+ if os.Getenv("SKIP_MINIMUM_TEST") == "" {
+ steps = append(steps, resource.TestStep{
+ Config: testAccMerakiNetworkGroupPolicyPrerequisitesConfig + testAccMerakiNetworkGroupPolicyConfig_minimum(),
+ })
+ }
+ steps = append(steps, resource.TestStep{
+ Config: testAccMerakiNetworkGroupPolicyPrerequisitesConfig + testAccMerakiNetworkGroupPolicyConfig_all(),
+ Check: resource.ComposeTestCheckFunc(checks...),
+ })
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: steps,
+ })
+}
+
+// End of section. //template:end testAcc
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites
+
+const testAccMerakiNetworkGroupPolicyPrerequisitesConfig = `
+data "meraki_organization" "test" {
+ name = "Dev"
+}
+resource "meraki_network" "test" {
+ organization_id = data.meraki_organization.test.id
+ name = "Network1"
+ product_types = ["switch", "wireless"]
+}
+
+`
+
+// End of section. //template:end testPrerequisites
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal
+
+func testAccMerakiNetworkGroupPolicyConfig_minimum() string {
+ config := `resource "meraki_network_group_policy" "test" {` + "\n"
+ config += ` network_id = meraki_network.test.id` + "\n"
+ config += ` name = "No video streaming"` + "\n"
+ config += `}` + "\n"
+ return config
+}
+
+// End of section. //template:end testAccConfigMinimal
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll
+
+func testAccMerakiNetworkGroupPolicyConfig_all() string {
+ config := `resource "meraki_network_group_policy" "test" {` + "\n"
+ config += ` network_id = meraki_network.test.id` + "\n"
+ config += ` name = "No video streaming"` + "\n"
+ config += ` splash_auth_settings = "bypass"` + "\n"
+ config += ` bandwidth_settings = "custom"` + "\n"
+ config += ` bandwidth_limit_down = 1000000` + "\n"
+ config += ` bandwidth_limit_up = 1000000` + "\n"
+ config += ` bonjour_forwarding_settings = "custom"` + "\n"
+ config += ` bonjour_forwarding_rules = [{` + "\n"
+ config += ` description = "A simple bonjour rule"` + "\n"
+ config += ` vlan_id = "1"` + "\n"
+ config += ` services = ["All Services"]` + "\n"
+ config += ` }]` + "\n"
+ config += ` firewall_and_traffic_shaping_settings = "custom"` + "\n"
+ config += ` l3_firewall_rules = [{` + "\n"
+ config += ` comment = "Allow TCP traffic to subnet with HTTP servers."` + "\n"
+ config += ` dest_cidr = "192.168.1.0/24"` + "\n"
+ config += ` dest_port = "443"` + "\n"
+ config += ` policy = "allow"` + "\n"
+ config += ` protocol = "tcp"` + "\n"
+ config += ` }]` + "\n"
+ config += ` l7_firewall_rules = [{` + "\n"
+ config += ` policy = "deny"` + "\n"
+ config += ` type = "host"` + "\n"
+ config += ` value = "google.com"` + "\n"
+ config += ` }]` + "\n"
+ config += ` traffic_shaping_rules = [{` + "\n"
+ config += ` dscp_tag_value = 0` + "\n"
+ config += ` pcp_tag_value = 0` + "\n"
+ config += ` priority = "normal"` + "\n"
+ config += ` per_client_bandwidth_limits_settings = "custom"` + "\n"
+ config += ` per_client_bandwidth_limits_bandwidth_limits_limit_down = 1000000` + "\n"
+ config += ` per_client_bandwidth_limits_bandwidth_limits_limit_up = 1000000` + "\n"
+ config += ` definitions = [{` + "\n"
+ config += ` type = "host"` + "\n"
+ config += ` value = "google.com"` + "\n"
+ config += ` }]` + "\n"
+ config += ` }]` + "\n"
+ config += ` scheduling_enabled = true` + "\n"
+ config += ` scheduling_friday_active = true` + "\n"
+ config += ` scheduling_friday_from = "09:00"` + "\n"
+ config += ` scheduling_friday_to = "17:00"` + "\n"
+ config += ` scheduling_monday_active = true` + "\n"
+ config += ` scheduling_monday_from = "09:00"` + "\n"
+ config += ` scheduling_monday_to = "17:00"` + "\n"
+ config += ` scheduling_saturday_active = true` + "\n"
+ config += ` scheduling_saturday_from = "09:00"` + "\n"
+ config += ` scheduling_saturday_to = "17:00"` + "\n"
+ config += ` scheduling_sunday_active = true` + "\n"
+ config += ` scheduling_sunday_from = "09:00"` + "\n"
+ config += ` scheduling_sunday_to = "17:00"` + "\n"
+ config += ` scheduling_thursday_active = true` + "\n"
+ config += ` scheduling_thursday_from = "09:00"` + "\n"
+ config += ` scheduling_thursday_to = "17:00"` + "\n"
+ config += ` scheduling_tuesday_active = true` + "\n"
+ config += ` scheduling_tuesday_from = "09:00"` + "\n"
+ config += ` scheduling_tuesday_to = "17:00"` + "\n"
+ config += ` scheduling_wednesday_active = true` + "\n"
+ config += ` scheduling_wednesday_from = "09:00"` + "\n"
+ config += ` scheduling_wednesday_to = "17:00"` + "\n"
+ config += ` vlan_tagging_settings = "custom"` + "\n"
+ config += ` vlan_tagging_vlan_id = "1"` + "\n"
+ config += `}` + "\n"
+ return config
+}
+
+// End of section. //template:end testAccConfigAll
diff --git a/internal/provider/resource_meraki_network_settings.go b/internal/provider/resource_meraki_network_settings.go
new file mode 100644
index 00000000..3c4ea5e2
--- /dev/null
+++ b/internal/provider/resource_meraki_network_settings.go
@@ -0,0 +1,262 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/CiscoDevNet/terraform-provider-meraki/internal/provider/helpers"
+ "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/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/netascode/go-meraki"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin model
+
+// Ensure provider defined types fully satisfy framework interfaces
+var (
+ _ resource.Resource = &NetworkSettingsResource{}
+ _ resource.ResourceWithImportState = &NetworkSettingsResource{}
+)
+
+func NewNetworkSettingsResource() resource.Resource {
+ return &NetworkSettingsResource{}
+}
+
+type NetworkSettingsResource struct {
+ client *meraki.Client
+}
+
+func (r *NetworkSettingsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_network_settings"
+}
+
+func (r *NetworkSettingsResource) 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 can manage the `Network Settings` configuration.").String,
+
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "The id of the object",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "network_id": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Network ID").String,
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "local_status_page_enabled": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Enables / disables the local device status pages (my.meraki.com, ap.meraki.com, switch.meraki.com, wired.meraki.com). Optional (defaults to false)").String,
+ Optional: true,
+ },
+ "remote_status_page_enabled": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Enables / disables access to the device status page (http://[device`s LAN IP]). Optional. Can only be set if localStatusPageEnabled is set to true").String,
+ Optional: true,
+ },
+ "local_status_page_authentication_enabled": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Enables / disables the authentication on Local Status page(s).").String,
+ Optional: true,
+ },
+ "local_status_page_authentication_password": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The password used for Local Status Page(s). Set this to null to clear the password.").String,
+ Optional: true,
+ },
+ "named_vlans_enabled": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Enables / disables Named VLANs on the Network.").String,
+ Optional: true,
+ },
+ "secure_port_enabled": schema.BoolAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("Enables / disables SecureConnect on the network. Optional.").String,
+ Optional: true,
+ },
+ },
+ }
+}
+
+func (r *NetworkSettingsResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ r.client = req.ProviderData.(*MerakiProviderData).Client
+}
+
+// End of section. //template:end model
+
+// Section below is generated&owned by "gen/generator.go". //template:begin create
+
+func (r *NetworkSettingsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan NetworkSettings
+
+ // Read plan
+ diags := req.Plan.Get(ctx, &plan)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString()))
+
+ // Create object
+ body := plan.toBody(ctx, NetworkSettings{})
+ res, err := r.client.Put(plan.getPath(), body)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String()))
+ return
+ }
+ plan.Id = plan.NetworkId
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString()))
+
+ diags = resp.State.Set(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+
+ helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics)
+}
+
+// End of section. //template:end create
+
+// Section below is generated&owned by "gen/generator.go". //template:begin read
+
+func (r *NetworkSettingsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state NetworkSettings
+
+ // Read state
+ diags := req.State.Get(ctx, &state)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String()))
+ res, err := r.client.Get(state.getPath())
+ 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
+ }
+
+ imp, diags := helpers.IsFlagImporting(ctx, req)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ // After `terraform import` we switch to a full read.
+ if imp {
+ state.fromBody(ctx, res)
+ } else {
+ state.fromBodyPartial(ctx, res)
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString()))
+
+ diags = resp.State.Set(ctx, &state)
+ resp.Diagnostics.Append(diags...)
+
+ helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics)
+}
+
+// End of section. //template:end read
+
+// Section below is generated&owned by "gen/generator.go". //template:begin update
+
+func (r *NetworkSettingsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan, state NetworkSettings
+
+ // Read plan
+ diags := req.Plan.Get(ctx, &plan)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Read state
+ diags = req.State.Get(ctx, &state)
+ if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString()))
+
+ body := plan.toBody(ctx, state)
+ res, err := r.client.Put(plan.getPath(), 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...)
+}
+
+// End of section. //template:end update
+
+// Section below is generated&owned by "gen/generator.go". //template:begin delete
+
+func (r *NetworkSettingsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state NetworkSettings
+
+ // Read state
+ diags := req.State.Get(ctx, &state)
+ if resp.Diagnostics.Append(diags...); 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)
+}
+
+// End of section. //template:end delete
+
+// Section below is generated&owned by "gen/generator.go". //template:begin import
+func (r *NetworkSettingsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ idParts := strings.Split(req.ID, ",")
+
+ if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
+ resp.Diagnostics.AddError(
+ "Unexpected Import Identifier",
+ fmt.Sprintf("Expected import identifier with format: ,. Got: %q", req.ID),
+ )
+ return
+ }
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), idParts[0])...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...)
+
+ helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics)
+}
+
+// End of section. //template:end import
diff --git a/internal/provider/resource_meraki_network_settings_test.go b/internal/provider/resource_meraki_network_settings_test.go
new file mode 100644
index 00000000..2de53bd0
--- /dev/null
+++ b/internal/provider/resource_meraki_network_settings_test.go
@@ -0,0 +1,103 @@
+// Copyright © 2024 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
+
+package provider
+
+// Section below is generated&owned by "gen/generator.go". //template:begin imports
+import (
+ "os"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+// End of section. //template:end imports
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAcc
+
+func TestAccMerakiNetworkSettings(t *testing.T) {
+ var checks []resource.TestCheckFunc
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "local_status_page_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "remote_status_page_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "local_status_page_authentication_enabled", "false"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "named_vlans_enabled", "true"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_settings.test", "secure_port_enabled", "false"))
+
+ var steps []resource.TestStep
+ if os.Getenv("SKIP_MINIMUM_TEST") == "" {
+ steps = append(steps, resource.TestStep{
+ Config: testAccMerakiNetworkSettingsPrerequisitesConfig + testAccMerakiNetworkSettingsConfig_minimum(),
+ })
+ }
+ steps = append(steps, resource.TestStep{
+ Config: testAccMerakiNetworkSettingsPrerequisitesConfig + testAccMerakiNetworkSettingsConfig_all(),
+ Check: resource.ComposeTestCheckFunc(checks...),
+ })
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: steps,
+ })
+}
+
+// End of section. //template:end testAcc
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites
+
+const testAccMerakiNetworkSettingsPrerequisitesConfig = `
+data "meraki_organization" "test" {
+ name = "Dev"
+}
+resource "meraki_network" "test" {
+ organization_id = data.meraki_organization.test.id
+ name = "Network1"
+ product_types = ["switch", "wireless"]
+}
+
+`
+
+// End of section. //template:end testPrerequisites
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal
+
+func testAccMerakiNetworkSettingsConfig_minimum() string {
+ config := `resource "meraki_network_settings" "test" {` + "\n"
+ config += ` network_id = meraki_network.test.id` + "\n"
+ config += ` local_status_page_enabled = true` + "\n"
+ config += `}` + "\n"
+ return config
+}
+
+// End of section. //template:end testAccConfigMinimal
+
+// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll
+
+func testAccMerakiNetworkSettingsConfig_all() string {
+ config := `resource "meraki_network_settings" "test" {` + "\n"
+ config += ` network_id = meraki_network.test.id` + "\n"
+ config += ` local_status_page_enabled = true` + "\n"
+ config += ` remote_status_page_enabled = true` + "\n"
+ config += ` local_status_page_authentication_enabled = false` + "\n"
+ config += ` local_status_page_authentication_password = "miles123"` + "\n"
+ config += ` named_vlans_enabled = true` + "\n"
+ config += ` secure_port_enabled = false` + "\n"
+ config += `}` + "\n"
+ return config
+}
+
+// End of section. //template:end testAccConfigAll
diff --git a/internal/provider/resource_meraki_network_snmp.go b/internal/provider/resource_meraki_network_snmp.go
index d32b1f42..b6c79f0a 100644
--- a/internal/provider/resource_meraki_network_snmp.go
+++ b/internal/provider/resource_meraki_network_snmp.go
@@ -78,10 +78,10 @@ func (r *NetworkSNMPResource) Schema(ctx context.Context, req resource.SchemaReq
},
},
"access": schema.StringAttribute{
- MarkdownDescription: helpers.NewAttributeDescription("The type of SNMP access. Can be one of `none` (disabled), `community` (V1/V2c), or `users` (V3).").AddStringEnumDescription("none", "community", "users").String,
+ MarkdownDescription: helpers.NewAttributeDescription("The type of SNMP access. Can be one of `none` (disabled), `community` (V1/V2c), or `users` (V3).").AddStringEnumDescription("community", "none", "users").String,
Optional: true,
Validators: []validator.String{
- stringvalidator.OneOf("none", "community", "users"),
+ stringvalidator.OneOf("community", "none", "users"),
},
},
"community_string": schema.StringAttribute{
@@ -93,14 +93,14 @@ func (r *NetworkSNMPResource) Schema(ctx context.Context, req resource.SchemaReq
Optional: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
- "username": schema.StringAttribute{
- MarkdownDescription: helpers.NewAttributeDescription("The username for the SNMP user. Required.").String,
- Required: true,
- },
"passphrase": schema.StringAttribute{
MarkdownDescription: helpers.NewAttributeDescription("The passphrase for the SNMP user. Required.").String,
Required: true,
},
+ "username": schema.StringAttribute{
+ MarkdownDescription: helpers.NewAttributeDescription("The username for the SNMP user. Required.").String,
+ Required: true,
+ },
},
},
},
diff --git a/internal/provider/resource_meraki_network_snmp_test.go b/internal/provider/resource_meraki_network_snmp_test.go
index a9a1634b..d96e66ef 100644
--- a/internal/provider/resource_meraki_network_snmp_test.go
+++ b/internal/provider/resource_meraki_network_snmp_test.go
@@ -32,8 +32,8 @@ import (
func TestAccMerakiNetworkSNMP(t *testing.T) {
var checks []resource.TestCheckFunc
checks = append(checks, resource.TestCheckResourceAttr("meraki_network_snmp.test", "access", "users"))
- checks = append(checks, resource.TestCheckResourceAttr("meraki_network_snmp.test", "users.0.username", "User1"))
- checks = append(checks, resource.TestCheckResourceAttr("meraki_network_snmp.test", "users.0.passphrase", "Password123"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_snmp.test", "users.0.passphrase", "hunter2"))
+ checks = append(checks, resource.TestCheckResourceAttr("meraki_network_snmp.test", "users.0.username", "AzureDiamond"))
var steps []resource.TestStep
if os.Getenv("SKIP_MINIMUM_TEST") == "" {
@@ -77,7 +77,7 @@ func testAccMerakiNetworkSNMPConfig_minimum() string {
config := `resource "meraki_network_snmp" "test" {` + "\n"
config += ` network_id = meraki_network.test.id` + "\n"
config += ` access = "community"` + "\n"
- config += ` community_string = "MerakiCommunity"` + "\n"
+ config += ` community_string = "sample"` + "\n"
config += `}` + "\n"
return config
}
@@ -91,8 +91,8 @@ func testAccMerakiNetworkSNMPConfig_all() string {
config += ` network_id = meraki_network.test.id` + "\n"
config += ` access = "users"` + "\n"
config += ` users = [{` + "\n"
- config += ` username = "User1"` + "\n"
- config += ` passphrase = "Password123"` + "\n"
+ config += ` passphrase = "hunter2"` + "\n"
+ config += ` username = "AzureDiamond"` + "\n"
config += ` }]` + "\n"
config += `}` + "\n"
return config
diff --git a/tools/tools.go b/tools/tools.go
index dc5f8a86..81222d06 100644
--- a/tools/tools.go
+++ b/tools/tools.go
@@ -23,6 +23,7 @@ import (
// Documentation generation
_ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs"
// Code generation
+ _ "golang.org/x/exp/maps"
_ "golang.org/x/tools/cmd/goimports"
_ "gopkg.in/yaml.v3"
)