├── .gitattributes
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── acctest.yml
│ ├── ci.yaml
│ ├── dependabot.yml
│ └── release.yml
├── .gitignore
├── .golangci.yaml
├── .goreleaser.yml
├── .vscode
└── launch.json
├── LICENSE
├── Makefile
├── README.md
├── docker-compose.yaml
├── docs
├── data-sources
│ ├── account.md
│ ├── ap_group.md
│ ├── dns_record.md
│ ├── network.md
│ ├── port_profile.md
│ ├── radius_profile.md
│ ├── user.md
│ └── user_group.md
├── guides
│ ├── csv-users.md
│ └── multiple-site-firewall.md
├── index.md
└── resources
│ ├── account.md
│ ├── device.md
│ ├── dns_record.md
│ ├── dynamic_dns.md
│ ├── firewall_group.md
│ ├── firewall_rule.md
│ ├── network.md
│ ├── port_forward.md
│ ├── port_profile.md
│ ├── radius_profile.md
│ ├── setting_mgmt.md
│ ├── setting_radius.md
│ ├── setting_usg.md
│ ├── site.md
│ ├── static_route.md
│ ├── user.md
│ ├── user_group.md
│ └── wlan.md
├── examples
├── csv_users
│ ├── users.csv
│ └── users.tf
├── data-sources
│ ├── unifi_ap_group
│ │ └── data-source.tf
│ ├── unifi_network
│ │ └── data-source.tf
│ ├── unifi_port_profile
│ │ └── data-source.tf
│ └── unifi_user
│ │ └── data-source.tf
├── multiple_site_firewall
│ └── firewall.tf
├── provider
│ ├── provider.tf
│ ├── test.auto.tfvars
│ ├── test.tf
│ └── variables.tf
└── resources
│ ├── unifi_device
│ └── resource.tf
│ ├── unifi_dns_record
│ └── resource.tf
│ ├── unifi_dynamic_dns
│ └── resource.tf
│ ├── unifi_firewall_group
│ ├── resource.tf
│ └── test.auto.tfvars
│ ├── unifi_firewall_rule
│ ├── import.sh
│ ├── resource.tf
│ └── test.auto.tfvars
│ ├── unifi_network
│ ├── import.sh
│ ├── resource.tf
│ └── test.auto.tfvars
│ ├── unifi_port_profile
│ └── resource.tf
│ ├── unifi_setting_mgmt
│ └── resource.tf
│ ├── unifi_site
│ ├── import.sh
│ └── resource.tf
│ ├── unifi_static_route
│ └── resource.tf
│ ├── unifi_user
│ ├── resource.tf
│ ├── test.auto.tfvars
│ └── test.tf
│ ├── unifi_user_group
│ ├── import.sh
│ └── resource.tf
│ └── unifi_wlan
│ ├── import.sh
│ └── resource.tf
├── go.mod
├── go.sum
├── internal
└── provider
│ ├── cidr.go
│ ├── cidr_test.go
│ ├── controller_versions.go
│ ├── controller_versions_test.go
│ ├── data_account.go
│ ├── data_account_test.go
│ ├── data_ap_group.go
│ ├── data_ap_group_test.go
│ ├── data_dns_record.go
│ ├── data_dns_record_test.go
│ ├── data_network.go
│ ├── data_network_test.go
│ ├── data_port_profile.go
│ ├── data_port_profile_test.go
│ ├── data_radius_profile.go
│ ├── data_user.go
│ ├── data_user_group.go
│ ├── data_user_group_test.go
│ ├── data_user_test.go
│ ├── importer.go
│ ├── lazy_client.go
│ ├── mac.go
│ ├── markdown.go
│ ├── port_range.go
│ ├── provider.go
│ ├── provider_test.go
│ ├── resource_account.go
│ ├── resource_account_test.go
│ ├── resource_device.go
│ ├── resource_device_test.go
│ ├── resource_dns_record.go
│ ├── resource_dns_record_test.go
│ ├── resource_dynamic_dns.go
│ ├── resource_dynamic_dns_test.go
│ ├── resource_firewall_group.go
│ ├── resource_firewall_group_test.go
│ ├── resource_firewall_rule.go
│ ├── resource_firewall_rule_test.go
│ ├── resource_network.go
│ ├── resource_network_test.go
│ ├── resource_port_forward.go
│ ├── resource_port_forward_test.go
│ ├── resource_port_profile.go
│ ├── resource_port_profile_test.go
│ ├── resource_radius_profile.go
│ ├── resource_radius_profile_test.go
│ ├── resource_setting_mgmt.go
│ ├── resource_setting_mgmt_test.go
│ ├── resource_setting_radius.go
│ ├── resource_setting_radius_test.go
│ ├── resource_setting_usg.go
│ ├── resource_setting_usg_test.go
│ ├── resource_site.go
│ ├── resource_site_test.go
│ ├── resource_static_route.go
│ ├── resource_static_route_test.go
│ ├── resource_user.go
│ ├── resource_user_group.go
│ ├── resource_user_group_test.go
│ ├── resource_user_test.go
│ ├── resource_wlan.go
│ ├── resource_wlan_test.go
│ └── strings.go
├── main.go
├── scripts
└── init.d
│ └── demo-mode
├── templates
├── guides
│ ├── csv-users.md.tmpl
│ └── multiple-site-firewall.md.tmpl
└── index.md.tmpl
└── tools
└── tools.go
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | *.go diff=golang
4 |
5 | *.sh eol=lf
6 |
7 | *.jar binary
8 | *.wt binary
9 | *.bson binary
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: paultyng
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | # Check for updates to GitHub Actions every weekday
8 | interval: "daily"
9 |
10 | # Maintain dependencies for Go modules
11 | - package-ecosystem: "gomod"
12 | directory: "/"
13 | schedule:
14 | # Check for updates to Go modules every weekday
15 | interval: "daily"
16 |
--------------------------------------------------------------------------------
/.github/workflows/acctest.yml:
--------------------------------------------------------------------------------
1 | name: Acceptance Tests
2 | on:
3 | pull_request:
4 | paths-ignore:
5 | - "README.md"
6 | push:
7 | paths-ignore:
8 | - "README.md"
9 | schedule:
10 | - cron: "0 13 * * *"
11 | jobs:
12 | test:
13 | name: Matrix Test
14 | runs-on: ubuntu-latest
15 | timeout-minutes: 15
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | unifi_version:
20 | - "v6.5"
21 | - "v6"
22 | - "v7.0"
23 | - "v7.1"
24 | - "v7.2"
25 | - "v7.3"
26 | - "v7.4"
27 | - "v7"
28 | - "latest"
29 | steps:
30 | - uses: actions/checkout@v3
31 | - uses: actions/setup-go@v4
32 | with:
33 | go-version-file: "go.mod"
34 | check-latest: true
35 |
36 | # - uses: hoverkraft-tech/compose-action@v0.0.0
37 | # env:
38 | # UNIFI_VERSION: ${{ matrix.unifi_version }}
39 |
40 | # The acceptance tests sometimes timeout for some unknown reason.
41 | - name: TF acceptance tests
42 | uses: "nick-fields/retry@v3"
43 | with:
44 | timeout_minutes: 20
45 | max_attempts: 3
46 | command: make testacc TEST_TIMEOUT=1h UNIFI_STDOUT=true UNIFI_VERSION=${{ matrix.unifi_download_url && 'beta' || matrix.unifi_version }} UNIFI_DOWNLOAD_URL=${{ matrix.unifi_download_url }}
47 | retry_on: "timeout"
48 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | on:
3 | pull_request: {}
4 | push:
5 | branches:
6 | - "main"
7 | tags:
8 | - "v*"
9 |
10 | jobs:
11 | build:
12 | runs-on: "ubuntu-latest"
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-go@v4
16 | with:
17 | go-version-file: "go.mod"
18 | cache: true
19 | check-latest: true
20 |
21 | - run: "go build ./..."
22 |
23 | lint:
24 | runs-on: "ubuntu-latest"
25 | steps:
26 | - uses: actions/checkout@v3
27 | - uses: actions/setup-go@v4
28 | with:
29 | go-version-file: "go.mod"
30 | check-latest: true
31 |
32 | - uses: "golangci/golangci-lint-action@v3.7.1"
33 | with:
34 | skip-pkg-cache: true
35 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Autogenerated by https://github.com/appkins/ecu/github
2 |
3 | name: Dependabot auto-approve
4 | on:
5 | pull_request_target:
6 | types: [opened]
7 |
8 | permissions:
9 | pull-requests: write
10 | contents: write
11 |
12 | jobs:
13 | dependabot:
14 | runs-on: ubuntu-latest
15 | if: ${{ github.actor == 'dependabot[bot]' }}
16 | steps:
17 | - name: Dependabot metadata
18 | id: metadata
19 | uses: dependabot/fetch-metadata@v2.2.0
20 | with:
21 | github-token: "${{ secrets.GITHUB_TOKEN }}"
22 | - name: Approve a PR
23 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}}
24 | run: gh pr review --approve "$PR_URL"
25 | env:
26 | PR_URL: ${{github.event.pull_request.html_url}}
27 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
28 | - name: Enable auto-merge for Dependabot PRs
29 | run: gh pr merge --auto --merge "$PR_URL"
30 | env:
31 | PR_URL: ${{github.event.pull_request.html_url}}
32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
33 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 | workflow_dispatch:
8 | inputs:
9 | tag:
10 | description: "Tag to release"
11 | required: false
12 | default: "v0.41.1"
13 |
14 | # Releases need permissions to read and write the repository contents.
15 | # GitHub considers creating releases and uploading assets as writing contents.
16 | permissions:
17 | contents: write
18 |
19 | jobs:
20 | goreleaser:
21 | runs-on: ubuntu-latest
22 | concurrency: release
23 | steps:
24 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
25 | with:
26 | # Allow goreleaser to access older tag information.
27 | fetch-depth: 0
28 | ref: ${{ inputs.tag || github.ref }}
29 |
30 | - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
31 | with:
32 | go-version-file: "go.mod"
33 | cache: true
34 | - name: Import GPG key
35 | uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
36 | id: import_gpg
37 | with:
38 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
39 | passphrase: ${{ secrets.PASSPHRASE }}
40 | - name: Run GoReleaser
41 | uses: goreleaser/goreleaser-action@v4.6.0
42 | with:
43 | version: latest
44 | args: release --parallelism 2 --clean
45 | env:
46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 |
3 | .vscode
4 | dist
5 | terraform-provider-unifi
6 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | linters:
3 | disable-all: true
4 | enable:
5 | - "gofmt"
6 | - "gosimple"
7 | - "govet"
8 | - "ineffassign"
9 | - "makezero"
10 | - "misspell"
11 | - "nakedret"
12 | - "nilerr"
13 | - "staticcheck"
14 | - "structcheck"
15 | - "unconvert"
16 | - "unused"
17 |
18 | run:
19 | timeout: '5m'
20 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | builds:
3 | - env:
4 | - CGO_ENABLED=0
5 | mod_timestamp: "{{ .CommitTimestamp }}"
6 | flags:
7 | - -trimpath
8 | ldflags:
9 | - "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}"
10 | goos:
11 | - freebsd
12 | - windows
13 | - linux
14 | - darwin
15 | goarch:
16 | - amd64
17 | - "386"
18 | - arm
19 | - arm64
20 | ignore:
21 | - goos: darwin
22 | goarch: "386"
23 | binary: "{{ .ProjectName }}_v{{ .Version }}"
24 | archives:
25 | - format: zip
26 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
27 | checksum:
28 | name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS"
29 | algorithm: sha256
30 | signs:
31 | - artifacts: checksum
32 | args:
33 | - "--batch"
34 | - "--local-user"
35 | - "{{ .Env.GPG_FINGERPRINT }}"
36 | - "--output"
37 | - "${signature}"
38 | - "--detach-sign"
39 | - "${artifact}"
40 | changelog:
41 | disable: true
42 | use: github-native
43 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Acceptance Tests",
9 | "type": "go",
10 | "request": "launch",
11 | "mode": "test",
12 | // this assumes your workspace is the root of the repo
13 | "program": "${fileDirname}",
14 | "env": {
15 | "TF_ACC": "1",
16 | },
17 | "args": [],
18 | },
19 | // You could pair this configuration with an exec configuration that runs Terraform as
20 | // a compound launch configuration:
21 | // https://code.visualstudio.com/docs/editor/debugging#_compound-launch-configurations
22 | {
23 | "name": "Debug - Attach External CLI",
24 | "type": "go",
25 | "request": "launch",
26 | "mode": "debug",
27 | // this assumes your workspace is the root of the repo
28 | "program": "${workspaceFolder}",
29 | "env": {},
30 | "args": [
31 | // pass the debug flag for reattaching
32 | "-debug",
33 | ],
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | TEST ?= ./...
2 | TESTARGS ?=
3 | TEST_COUNT ?= 1
4 | TEST_TIMEOUT ?= 10m
5 |
6 | .PHONY: default
7 | default: build
8 |
9 | .PHONY: build
10 | build:
11 | go install
12 |
13 | .PHONY: testacc
14 | testacc:
15 | TF_ACC=1 go test $(TEST) -v -count $(TEST_COUNT) -timeout $(TEST_TIMEOUT) $(TESTARGS)
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Unifi Terraform Provider (terraform-provider-unifi)
4 |
5 | **Note** You can't (for obvious reasons) configure your network while connected to something that may disconnect (like the WiFi). Use a hard-wired connection to your controller to use this provider.
6 |
7 | Functionality first needs to be added to the [go-unifi](https://github.com/ubiquiti-community/go-unifi) SDK.
8 |
9 | ## Documentation
10 |
11 | You can browse documentation on the [Terraform provider registry](https://registry.terraform.io/providers/paultyng/unifi/latest/docs).
12 |
13 | ## Supported Unifi Controller Versions
14 |
15 | As of version [v0.34](https://github.com/ubiquiti-community/terraform-provider-unifi/releases/tag/v0.34.0), this provider only supports version 6 of the Unifi controller software. If you need v5 support, you can pin an older version of the provider.
16 |
17 | The docker, UDM, and UDM-Pro versions are slightly different (the API is proxied a little differently) but for the most part should all be supported. Individual patch versions of the controller are generally not tested for compatibility, just the latest stable versions.
18 |
19 | ## Using the Provider
20 |
21 | ### Terraform 1.0 and above
22 |
23 | You can use the provider via the [Terraform provider registry](https://registry.terraform.io/providers/paultyng/unifi).
24 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | services:
3 | unifi:
4 | image: "jacobalberty/unifi:${UNIFI_VERSION:-latest}"
5 | init: true
6 | restart: "always"
7 | environment:
8 | PKGURL: "${UNIFI_DOWNLOAD_URL:-}"
9 | UNIFI_STDOUT: "true"
10 | ports:
11 | - "${UNIFI_HTTP_PORT:-8080}:8080/tcp"
12 | - "${UNIFI_HTTPS_PORT:-8443}:8443/tcp"
13 | volumes:
14 | - "./scripts/init.d:/usr/local/unifi/init.d:ro"
15 |
--------------------------------------------------------------------------------
/docs/data-sources/account.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_account Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_account data source can be used to retrieve RADIUS user accounts
7 | ---
8 |
9 | # unifi_account (Data Source)
10 |
11 | `unifi_account` data source can be used to retrieve RADIUS user accounts
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Required
19 |
20 | - `name` (String) The name of the account to look up
21 |
22 | ### Optional
23 |
24 | - `site` (String) The name of the site the account is associated with.
25 |
26 | ### Read-Only
27 |
28 | - `id` (String) The ID of this account.
29 | - `network_id` (String) ID of the network for this account
30 | - `password` (String, Sensitive) The password of the account.
31 | - `tunnel_medium_type` (Number) See RFC2868 section 3.2
32 | - `tunnel_type` (Number) See RFC2868 section 3.1
33 |
--------------------------------------------------------------------------------
/docs/data-sources/ap_group.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_ap_group Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_ap_group data source can be used to retrieve the ID for an AP group by name.
7 | ---
8 |
9 | # unifi_ap_group (Data Source)
10 |
11 | `unifi_ap_group` data source can be used to retrieve the ID for an AP group by name.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | data "unifi_ap_group" "default" {
17 | }
18 | ```
19 |
20 |
21 | ## Schema
22 |
23 | ### Optional
24 |
25 | - `name` (String) The name of the AP group to look up, leave blank to look up the default AP group.
26 | - `site` (String) The name of the site the AP group is associated with.
27 |
28 | ### Read-Only
29 |
30 | - `id` (String) The ID of this AP group.
31 |
--------------------------------------------------------------------------------
/docs/data-sources/dns_record.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_dns_record Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_dns_record data source can be used to retrieve the ID for an DNS record by name.
7 | ---
8 |
9 | # unifi_dns_record (Data Source)
10 |
11 | `unifi_dns_record` data source can be used to retrieve the ID for an DNS record by name.
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Optional
19 |
20 | - `name` (String) The name of the DNS record to look up, leave blank to look up the default DNS record.
21 | - `port` (Number) The port of the DNS record.
22 | - `priority` (Number) The priority of the DNS record.
23 | - `record_type` (String) The type of the DNS record.
24 | - `site` (String) The name of the site the DNS record is associated with.
25 | - `ttl` (Number) The TTL of the DNS record.
26 | - `value` (String) The value of the DNS record.
27 | - `weight` (Number) The weight of the DNS record.
28 |
29 | ### Read-Only
30 |
31 | - `id` (String) The ID of this DNS record.
32 |
--------------------------------------------------------------------------------
/docs/data-sources/network.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_network Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_network data source can be used to retrieve settings for a network by name or ID.
7 | ---
8 |
9 | # unifi_network (Data Source)
10 |
11 | `unifi_network` data source can be used to retrieve settings for a network by name or ID.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | #retrieve network data by unifi network name
17 | data "unifi_network" "lan_network" {
18 | name = "Default"
19 | }
20 |
21 | #retrieve network data from user record
22 | data "unifi_user" "my_device" {
23 | mac = "01:23:45:67:89:ab"
24 | }
25 | data "unifi_network" "my_network" {
26 | id = data.unifi_user.my_device.network_id
27 | }
28 | ```
29 |
30 |
31 | ## Schema
32 |
33 | ### Optional
34 |
35 | - `id` (String) The ID of the network.
36 | - `name` (String) The name of the network.
37 | - `site` (String) The name of the site to associate the network with.
38 |
39 | ### Read-Only
40 |
41 | - `dhcp_dns` (List of String) IPv4 addresses for the DNS server to be returned from the DHCP server.
42 | - `dhcp_enabled` (Boolean) whether DHCP is enabled or not on this network.
43 | - `dhcp_lease` (Number) lease time for DHCP addresses.
44 | - `dhcp_start` (String) The IPv4 address where the DHCP range of addresses starts.
45 | - `dhcp_stop` (String) The IPv4 address where the DHCP range of addresses stops.
46 | - `dhcp_v6_dns` (List of String) Specifies the IPv6 addresses for the DNS server to be returned from the DHCP server. Used if `dhcp_v6_dns_auto` is set to `false`.
47 | - `dhcp_v6_dns_auto` (Boolean) Specifies DNS source to propagate. If set `false` the entries in `dhcp_v6_dns` are used, the upstream entries otherwise
48 | - `dhcp_v6_enabled` (Boolean) Enable stateful DHCPv6 for static configuration.
49 | - `dhcp_v6_lease` (Number) Specifies the lease time for DHCPv6 addresses.
50 | - `dhcp_v6_start` (String) Start address of the DHCPv6 range. Used in static DHCPv6 configuration.
51 | - `dhcp_v6_stop` (String) End address of the DHCPv6 range. Used in static DHCPv6 configuration.
52 | - `dhcpd_boot_enabled` (Boolean) Toggles on the DHCP boot options. will be set to true if you have dhcpd_boot_filename, and dhcpd_boot_server set.
53 | - `dhcpd_boot_filename` (String) the file to PXE boot from on the dhcpd_boot_server.
54 | - `dhcpd_boot_server` (String) IPv4 address of a TFTP server to network boot from.
55 | - `domain_name` (String) The domain name of this network.
56 | - `igmp_snooping` (Boolean) Specifies whether IGMP snooping is enabled or not.
57 | - `ipv6_interface_type` (String) Specifies which type of IPv6 connection to use. Must be one of either `static`, `pd`, or `none`.
58 | - `ipv6_pd_interface` (String) Specifies which WAN interface to use for IPv6 PD. Must be one of either `wan` or `wan2`.
59 | - `ipv6_pd_prefixid` (String) Specifies the IPv6 Prefix ID.
60 | - `ipv6_pd_start` (String) Start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.
61 | - `ipv6_pd_stop` (String) End address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.
62 | - `ipv6_ra_enable` (Boolean) Specifies whether to enable router advertisements or not.
63 | - `ipv6_ra_preferred_lifetime` (Number) Lifetime in which the address can be used. Address becomes deprecated afterwards. Must be lower than or equal to `ipv6_ra_valid_lifetime`
64 | - `ipv6_ra_priority` (String) IPv6 router advertisement priority. Must be one of either `high`, `medium`, or `low`
65 | - `ipv6_ra_valid_lifetime` (Number) Total lifetime in which the address can be used. Must be equal to or greater than `ipv6_ra_preferred_lifetime`.
66 | - `ipv6_static_subnet` (String) Specifies the static IPv6 subnet (when ipv6_interface_type is 'static').
67 | - `multicast_dns` (Boolean) Specifies whether Multicast DNS (mDNS) is enabled or not on the network (Controller >=v7).
68 | - `network_group` (String) The group of the network.
69 | - `purpose` (String) The purpose of the network. One of `corporate`, `guest`, `wan`, or `vlan-only`.
70 | - `subnet` (String) The subnet of the network (CIDR address).
71 | - `vlan_id` (Number) The VLAN ID of the network.
72 | - `wan_dhcp_v6_pd_size` (Number) Specifies the IPv6 prefix size to request from ISP. Must be a number between 48 and 64.
73 | - `wan_dns` (List of String) DNS servers IPs of the WAN.
74 | - `wan_egress_qos` (Number) Specifies the WAN egress quality of service.
75 | - `wan_gateway` (String) The IPv4 gateway of the WAN.
76 | - `wan_gateway_v6` (String) The IPv6 gateway of the WAN.
77 | - `wan_ip` (String) The IPv4 address of the WAN.
78 | - `wan_ipv6` (String) The IPv6 address of the WAN.
79 | - `wan_netmask` (String) The IPv4 netmask of the WAN.
80 | - `wan_networkgroup` (String) Specifies the WAN network group. One of either `WAN`, `WAN2` or `WAN_LTE_FAILOVER`.
81 | - `wan_prefixlen` (Number) The IPv6 prefix length of the WAN. Must be between 1 and 128.
82 | - `wan_type` (String) Specifies the IPV4 WAN connection type. One of either `disabled`, `static`, `dhcp`, or `pppoe`.
83 | - `wan_type_v6` (String) Specifies the IPV6 WAN connection type. Must be one of either `disabled`, `static`, or `dhcpv6`.
84 | - `wan_username` (String) Specifies the IPV4 WAN username.
85 | - `x_wan_password` (String) Specifies the IPV4 WAN password.
86 |
--------------------------------------------------------------------------------
/docs/data-sources/port_profile.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_port_profile Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_port_profile data source can be used to retrieve the ID for a port profile by name.
7 | ---
8 |
9 | # unifi_port_profile (Data Source)
10 |
11 | `unifi_port_profile` data source can be used to retrieve the ID for a port profile by name.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | data "unifi_port_profile" "all" {
17 | }
18 | ```
19 |
20 |
21 | ## Schema
22 |
23 | ### Optional
24 |
25 | - `name` (String) The name of the port profile to look up. Defaults to `All`.
26 | - `site` (String) The name of the site the port profile is associated with.
27 |
28 | ### Read-Only
29 |
30 | - `id` (String) The ID of this port profile.
31 |
--------------------------------------------------------------------------------
/docs/data-sources/radius_profile.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_radius_profile Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_radius_profile data source can be used to retrieve the ID for a RADIUS profile by name.
7 | ---
8 |
9 | # unifi_radius_profile (Data Source)
10 |
11 | `unifi_radius_profile` data source can be used to retrieve the ID for a RADIUS profile by name.
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Optional
19 |
20 | - `name` (String) The name of the RADIUS profile to look up. Defaults to `Default`.
21 | - `site` (String) The name of the site the RADIUS profile is associated with.
22 |
23 | ### Read-Only
24 |
25 | - `id` (String) The ID of this AP group.
26 |
--------------------------------------------------------------------------------
/docs/data-sources/user.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_user Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_user retrieves properties of a user (or "client" in the UI) of the network by MAC address.
7 | ---
8 |
9 | # unifi_user (Data Source)
10 |
11 | `unifi_user` retrieves properties of a user (or "client" in the UI) of the network by MAC address.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | data "unifi_user" "client" {
17 | mac = "01:23:45:67:89:ab"
18 | }
19 | ```
20 |
21 |
22 | ## Schema
23 |
24 | ### Required
25 |
26 | - `mac` (String) The MAC address of the user.
27 |
28 | ### Optional
29 |
30 | - `site` (String) The name of the site the user is associated with.
31 |
32 | ### Read-Only
33 |
34 | - `blocked` (Boolean) Specifies whether this user should be blocked from the network.
35 | - `dev_id_override` (Number) Override the device fingerprint.
36 | - `fixed_ip` (String) fixed IPv4 address set for this user.
37 | - `hostname` (String) The hostname of the user.
38 | - `id` (String) The ID of the user.
39 | - `ip` (String) The IP address of the user.
40 | - `local_dns_record` (String) The local DNS record for this user.
41 | - `name` (String) The name of the user.
42 | - `network_id` (String) The network ID for this user.
43 | - `note` (String) A note with additional information for the user.
44 | - `user_group_id` (String) The user group ID for the user.
45 |
--------------------------------------------------------------------------------
/docs/data-sources/user_group.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_user_group Data Source - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_user_group data source can be used to retrieve the ID for a user group by name.
7 | ---
8 |
9 | # unifi_user_group (Data Source)
10 |
11 | `unifi_user_group` data source can be used to retrieve the ID for a user group by name.
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Optional
19 |
20 | - `name` (String) The name of the user group to look up. Defaults to `Default`.
21 | - `site` (String) The name of the site the user group is associated with.
22 |
23 | ### Read-Only
24 |
25 | - `id` (String) The ID of this AP group.
26 | - `qos_rate_max_down` (Number)
27 | - `qos_rate_max_up` (Number)
28 |
--------------------------------------------------------------------------------
/docs/guides/csv-users.md:
--------------------------------------------------------------------------------
1 | ---
2 | subcategory: ""
3 | page_title: "Manage Users/Clients in a CSV - Unifi Provider"
4 | description: |-
5 | An example of using a CSV to manage all of your users of your network.
6 | ---
7 |
8 | # Manage Users in a CSV
9 |
10 | Given a CSV file with the following content:
11 |
12 | ```csv
13 | mac,name,note
14 | 01:23:45:67:89:AB,My Device,custom note
15 | ```
16 |
17 | You could create/manage a `unifi_user` for every row/MAC address in the CSV with the following config:
18 |
19 | ```terraform
20 | locals {
21 | userscsv = csvdecode(file("${path.module}/users.csv"))
22 | users = { for user in local.userscsv : user.mac => user }
23 | }
24 |
25 | resource "unifi_user" "user" {
26 | for_each = local.users
27 |
28 | mac = each.key
29 | name = each.value.name
30 | # append an optional additional note
31 | note = trimspace("${each.value.note}\n\nmanaged by TF")
32 |
33 | allow_existing = true
34 | skip_forget_on_destroy = true
35 | }
36 | ```
37 |
--------------------------------------------------------------------------------
/docs/guides/multiple-site-firewall.md:
--------------------------------------------------------------------------------
1 | ---
2 | subcategory: ""
3 | page_title: "Manage Firewall Rules for Multiple Sites - Unifi Provider"
4 | description: |-
5 | An example of applying firewall rules to multiple sites.
6 | ---
7 |
8 | # Manage Firewall Rules for Multiple Sites
9 |
10 | The provider takes a default site value but all resources in the provider should allow overriding of the
11 | site you are managing. In order to apply and manage a firewall rule across multiple sites, you simply
12 | need to provide different values for the `site` attribute to `unifi_firewall_rule`:
13 |
14 | ```terraform
15 | resource "unifi_firewall_rule" "rule" {
16 | # list of sites
17 | for_each = toset(["default", "vq98kwez", "bfa2l6i7"])
18 | # use the key of the list as the site value
19 | site = each.key
20 |
21 | name = "drop all"
22 | action = "drop"
23 | ruleset = "LAN_IN"
24 |
25 | rule_index = 2011
26 |
27 | protocol = "all"
28 |
29 | dst_address = var.ip_address
30 | }
31 | ```
32 |
33 | You could optionally load lists of sites from JSON/CSV, variables, or other sources.
34 |
35 | When you apply this configuration it will create the same firewall rule on every site in the list.
36 | If you need to update the rule, you simply make an update to the rule definition and Terraform will
37 | apply/update it across all the sites. If you add / or remove a site from the list, Terraform will also
38 | handle creating or removing the rule on the subsequent `terraform apply`.
39 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: ""
3 | page_title: "Provider: Unifi"
4 | description: |-
5 | The Unifi provider provides resources to interact with a Unifi controller API.
6 | ---
7 |
8 | # Unifi Provider
9 |
10 | The Unifi provider provides resources to interact with a Unifi controller API.
11 |
12 | It is not recommended to use your own account for management of your controller. A user specific to
13 | Terraform is recommended. You can create a **Limited Admin** with **Local Access Only** and
14 | provide that information for authentication. Two-factor authentication is not supported in the provider.
15 |
16 | ## Example Usage
17 |
18 | ```terraform
19 | provider "unifi" {
20 | username = var.username # optionally use UNIFI_USERNAME env var
21 | password = var.password # optionally use UNIFI_PASSWORD env var
22 | api_url = var.api_url # optionally use UNIFI_API env var
23 |
24 | # you may need to allow insecure TLS communications unless you have configured
25 | # certificates for your controller
26 | allow_insecure = var.insecure # optionally use UNIFI_INSECURE env var
27 |
28 | # if you are not configuring the default site, you can change the site
29 | # site = "foo" or optionally use UNIFI_SITE env var
30 | }
31 | ```
32 |
33 |
34 | ## Schema
35 |
36 | ### Optional
37 |
38 | - `allow_insecure` (Boolean) Skip verification of TLS certificates of API requests. You may need to set this to `true` if you are using your local API without setting up a signed certificate. Can be specified with the `UNIFI_INSECURE` environment variable.
39 | - `api_url` (String) URL of the controller API. Can be specified with the `UNIFI_API` environment variable. You should **NOT** supply the path (`/api`), the SDK will discover the appropriate paths. This is to support UDM Pro style API paths as well as more standard controller paths.
40 | - `password` (String) Password for the user accessing the API. Can be specified with the `UNIFI_PASSWORD` environment variable.
41 | - `site` (String) The site in the Unifi controller this provider will manage. Can be specified with the `UNIFI_SITE` environment variable. Default: `default`
42 | - `username` (String) Local user name for the Unifi controller API. Can be specified with the `UNIFI_USERNAME` environment variable.
43 |
--------------------------------------------------------------------------------
/docs/resources/account.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_account Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_account manages a RADIUS user account
7 | To authenticate devices based on MAC address, use the MAC address as the username and password under client creation.
8 | Convert lowercase letters to uppercase, and also remove colons or periods from the MAC address.
9 | ATTENTION: If the user profile does not include a VLAN, the client will fall back to the untagged VLAN.
10 | NOTE: MAC-based authentication accounts can only be used for wireless and wired clients. L2TP remote access does not apply.
11 | ---
12 |
13 | # unifi_account (Resource)
14 |
15 | `unifi_account` manages a RADIUS user account
16 |
17 | To authenticate devices based on MAC address, use the MAC address as the username and password under client creation.
18 | Convert lowercase letters to uppercase, and also remove colons or periods from the MAC address.
19 |
20 | ATTENTION: If the user profile does not include a VLAN, the client will fall back to the untagged VLAN.
21 |
22 | NOTE: MAC-based authentication accounts can only be used for wireless and wired clients. L2TP remote access does not apply.
23 |
24 |
25 |
26 |
27 | ## Schema
28 |
29 | ### Required
30 |
31 | - `name` (String) The name of the account.
32 | - `password` (String, Sensitive) The password of the account.
33 |
34 | ### Optional
35 |
36 | - `network_id` (String) ID of the network for this account
37 | - `site` (String) The name of the site to associate the account with.
38 | - `tunnel_medium_type` (Number) See [RFC 2868](https://www.rfc-editor.org/rfc/rfc2868) section 3.2 Defaults to `6`.
39 | - `tunnel_type` (Number) See [RFC 2868](https://www.rfc-editor.org/rfc/rfc2868) section 3.1 Defaults to `13`.
40 |
41 | ### Read-Only
42 |
43 | - `id` (String) The ID of the account.
44 |
--------------------------------------------------------------------------------
/docs/resources/device.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_device Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_device manages a device of the network.
7 | Devices are adopted by the controller, so it is not possible for this resource to be created through Terraform, the create operation instead will simply start managing the device specified by MAC address. It's safer to start this process with an explicit import of the device.
8 | ---
9 |
10 | # unifi_device (Resource)
11 |
12 | `unifi_device` manages a device of the network.
13 |
14 | Devices are adopted by the controller, so it is not possible for this resource to be created through Terraform, the create operation instead will simply start managing the device specified by MAC address. It's safer to start this process with an explicit import of the device.
15 |
16 | ## Example Usage
17 |
18 | ```terraform
19 | data "unifi_port_profile" "disabled" {
20 | # look up the built-in disabled port profile
21 | name = "Disabled"
22 | }
23 |
24 | resource "unifi_port_profile" "poe" {
25 | name = "poe"
26 | forward = "customize"
27 |
28 | native_networkconf_id = var.native_network_id
29 | tagged_networkconf_ids = [
30 | var.some_vlan_network_id,
31 | ]
32 |
33 | poe_mode = "auto"
34 | }
35 |
36 | resource "unifi_device" "us_24_poe" {
37 | # optionally specify MAC address to skip manually importing
38 | # manual import is the safest way to add a device
39 | mac = "01:23:45:67:89:AB"
40 |
41 | name = "Switch with POE"
42 |
43 | port_override {
44 | number = 1
45 | name = "port w/ poe"
46 | port_profile_id = unifi_port_profile.poe.id
47 | }
48 |
49 | port_override {
50 | number = 2
51 | name = "disabled"
52 | port_profile_id = data.unifi_port_profile.disabled.id
53 | }
54 |
55 | # port aggregation for ports 11 and 12
56 | port_override {
57 | number = 11
58 | op_mode = "aggregate"
59 | aggregate_num_ports = 2
60 | }
61 | }
62 | ```
63 |
64 |
65 | ## Schema
66 |
67 | ### Optional
68 |
69 | - `allow_adoption` (Boolean) Specifies whether this resource should tell the controller to adopt the device on create. Defaults to `true`.
70 | - `forget_on_destroy` (Boolean) Specifies whether this resource should tell the controller to forget the device on destroy. Defaults to `true`.
71 | - `mac` (String) The MAC address of the device. This can be specified so that the provider can take control of a device (since devices are created through adoption).
72 | - `name` (String) The name of the device.
73 | - `port_override` (Block Set) Settings overrides for specific switch ports. (see [below for nested schema](#nestedblock--port_override))
74 | - `site` (String) The name of the site to associate the device with.
75 |
76 | ### Read-Only
77 |
78 | - `disabled` (Boolean) Specifies whether this device should be disabled.
79 | - `id` (String) The ID of the device.
80 |
81 |
82 | ### Nested Schema for `port_override`
83 |
84 | Required:
85 |
86 | - `number` (Number) Switch port number.
87 |
88 | Optional:
89 |
90 | - `aggregate_num_ports` (Number) Number of ports in the aggregate.
91 | - `name` (String) Human-readable name of the port.
92 | - `op_mode` (String) Operating mode of the port, valid values are `switch`, `mirror`, and `aggregate`. Defaults to `switch`.
93 | - `poe_mode` (String) PoE mode of the port; valid values are `auto`, `pasv24`, `passthrough`, and `off`.
94 | - `port_profile_id` (String) ID of the Port Profile used on this port.
95 |
--------------------------------------------------------------------------------
/docs/resources/dns_record.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_dns_record Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_dns_record manages DNS record settings for different providers.
7 | ---
8 |
9 | # unifi_dns_record (Resource)
10 |
11 | `unifi_dns_record` manages DNS record settings for different providers.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | resource "unifi_dns_record" "test" {
17 | name = "my-network"
18 | enabled = true
19 | port = 0
20 | priority = 10
21 | record_type = "A"
22 | ttl = 300
23 | value = "my-network.example.com"
24 | }
25 | ```
26 |
27 |
28 | ## Schema
29 |
30 | ### Required
31 |
32 | - `name` (String) The key of the DNS record.
33 | - `port` (Number) The port of the DNS record.
34 | - `value` (String) The value of the DNS record.
35 |
36 | ### Optional
37 |
38 | - `enabled` (Boolean) Whether the DNS record is enabled. Defaults to `true`.
39 | - `priority` (Number) The priority of the DNS record.
40 | - `record_type` (String) The type of the DNS record.
41 | - `site` (String) The name of the site to associate the DNS record with.
42 | - `ttl` (Number) The TTL of the DNS record.
43 | - `weight` (Number) The weight of the DNS record.
44 |
45 | ### Read-Only
46 |
47 | - `id` (String) The ID of the DNS record.
48 |
--------------------------------------------------------------------------------
/docs/resources/dynamic_dns.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_dynamic_dns Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_dynamic_dns manages dynamic DNS settings for different providers.
7 | ---
8 |
9 | # unifi_dynamic_dns (Resource)
10 |
11 | `unifi_dynamic_dns` manages dynamic DNS settings for different providers.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | resource "unifi_dynamic_dns" "test" {
17 | service = "dyndns"
18 |
19 | host_name = "my-network.example.com"
20 |
21 | server = "domains.google.com"
22 | login = var.dns_login
23 | password = var.dns_password
24 | }
25 | ```
26 |
27 |
28 | ## Schema
29 |
30 | ### Required
31 |
32 | - `host_name` (String) The host name to update in the dynamic DNS service.
33 | - `service` (String) The Dynamic DNS service provider, various values are supported (for example `dyndns`, etc.).
34 |
35 | ### Optional
36 |
37 | - `interface` (String) The interface for the dynamic DNS. Can be `wan` or `wan2`. Defaults to `wan`.
38 | - `login` (String) The server for the dynamic DNS service.
39 | - `password` (String, Sensitive) The server for the dynamic DNS service.
40 | - `server` (String) The server for the dynamic DNS service.
41 | - `site` (String) The name of the site to associate the dynamic DNS with.
42 |
43 | ### Read-Only
44 |
45 | - `id` (String) The ID of the dynamic DNS.
46 |
--------------------------------------------------------------------------------
/docs/resources/firewall_group.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_firewall_group Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_firewall_group manages groups of addresses or ports for use in firewall rules (unifi_firewall_rule).
7 | ---
8 |
9 | # unifi_firewall_group (Resource)
10 |
11 | `unifi_firewall_group` manages groups of addresses or ports for use in firewall rules (`unifi_firewall_rule`).
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | variable "laptop_ips" {
17 | type = list(string)
18 | }
19 |
20 | resource "unifi_firewall_group" "can_print" {
21 | name = "can-print"
22 | type = "address-group"
23 |
24 | members = var.laptop_ips
25 | }
26 | ```
27 |
28 |
29 | ## Schema
30 |
31 | ### Required
32 |
33 | - `members` (Set of String) The members of the firewall group.
34 | - `name` (String) The name of the firewall group.
35 | - `type` (String) The type of the firewall group. Must be one of: `address-group`, `port-group`, or `ipv6-address-group`.
36 |
37 | ### Optional
38 |
39 | - `site` (String) The name of the site to associate the firewall group with.
40 |
41 | ### Read-Only
42 |
43 | - `id` (String) The ID of the firewall group.
44 |
--------------------------------------------------------------------------------
/docs/resources/firewall_rule.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_firewall_rule Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_firewall_rule manages an individual firewall rule on the gateway.
7 | ---
8 |
9 | # unifi_firewall_rule (Resource)
10 |
11 | `unifi_firewall_rule` manages an individual firewall rule on the gateway.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | variable "ip_address" {
17 | type = string
18 | }
19 |
20 | resource "unifi_firewall_rule" "drop_all" {
21 | name = "drop all"
22 | action = "drop"
23 | ruleset = "LAN_IN"
24 |
25 | rule_index = 2011
26 |
27 | protocol = "all"
28 |
29 | dst_address = var.ip_address
30 | }
31 | ```
32 |
33 |
34 | ## Schema
35 |
36 | ### Required
37 |
38 | - `action` (String) The action of the firewall rule. Must be one of `drop`, `accept`, or `reject`.
39 | - `name` (String) The name of the firewall rule.
40 | - `rule_index` (Number) The index of the rule. Must be >= 2000 < 3000 or >= 4000 < 5000.
41 | - `ruleset` (String) The ruleset for the rule. This is from the perspective of the security gateway. Must be one of `WAN_IN`, `WAN_OUT`, `WAN_LOCAL`, `LAN_IN`, `LAN_OUT`, `LAN_LOCAL`, `GUEST_IN`, `GUEST_OUT`, `GUEST_LOCAL`, `WANv6_IN`, `WANv6_OUT`, `WANv6_LOCAL`, `LANv6_IN`, `LANv6_OUT`, `LANv6_LOCAL`, `GUESTv6_IN`, `GUESTv6_OUT`, or `GUESTv6_LOCAL`.
42 |
43 | ### Optional
44 |
45 | - `dst_address` (String) The destination address of the firewall rule.
46 | - `dst_address_ipv6` (String) The IPv6 destination address of the firewall rule.
47 | - `dst_firewall_group_ids` (Set of String) The destination firewall group IDs of the firewall rule.
48 | - `dst_network_id` (String) The destination network ID of the firewall rule.
49 | - `dst_network_type` (String) The destination network type of the firewall rule. Can be one of `ADDRv4` or `NETv4`. Defaults to `NETv4`.
50 | - `dst_port` (String) The destination port of the firewall rule.
51 | - `enabled` (Boolean) Specifies whether the rule should be enabled. Defaults to `true`.
52 | - `icmp_typename` (String) ICMP type name.
53 | - `icmp_v6_typename` (String) ICMPv6 type name.
54 | - `ip_sec` (String) Specify whether the rule matches on IPsec packets. Can be one of `match-ipset` or `match-none`.
55 | - `logging` (Boolean) Enable logging for the firewall rule.
56 | - `protocol` (String) The protocol of the rule.
57 | - `protocol_v6` (String) The IPv6 protocol of the rule.
58 | - `site` (String) The name of the site to associate the firewall rule with.
59 | - `src_address` (String) The source address for the firewall rule.
60 | - `src_address_ipv6` (String) The IPv6 source address for the firewall rule.
61 | - `src_firewall_group_ids` (Set of String) The source firewall group IDs for the firewall rule.
62 | - `src_mac` (String) The source MAC address of the firewall rule.
63 | - `src_network_id` (String) The source network ID for the firewall rule.
64 | - `src_network_type` (String) The source network type of the firewall rule. Can be one of `ADDRv4` or `NETv4`. Defaults to `NETv4`.
65 | - `src_port` (String) The source port of the firewall rule.
66 | - `state_established` (Boolean) Match where the state is established.
67 | - `state_invalid` (Boolean) Match where the state is invalid.
68 | - `state_new` (Boolean) Match where the state is new.
69 | - `state_related` (Boolean) Match where the state is related.
70 |
71 | ### Read-Only
72 |
73 | - `id` (String) The ID of the firewall rule.
74 |
75 | ## Import
76 |
77 | Import is supported using the following syntax:
78 |
79 | ```shell
80 | # import using the ID from the controller API/UI
81 | terraform import unifi_firewall_rule.my_rule 5f7080eb6b8969064f80494f
82 | ```
83 |
--------------------------------------------------------------------------------
/docs/resources/port_forward.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_port_forward Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_port_forward manages a port forwarding rule on the gateway.
7 | ---
8 |
9 | # unifi_port_forward (Resource)
10 |
11 | `unifi_port_forward` manages a port forwarding rule on the gateway.
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Optional
19 |
20 | - `dst_port` (String) The destination port for the forwarding.
21 | - `enabled` (Boolean, Deprecated) Specifies whether the port forwarding rule is enabled or not. Defaults to `true`. This will attribute will be removed in a future release. Instead of disabling a port forwarding rule you can remove it from your configuration.
22 | - `fwd_ip` (String) The IPv4 address to forward traffic to.
23 | - `fwd_port` (String) The port to forward traffic to.
24 | - `log` (Boolean) Specifies whether to log forwarded traffic or not. Defaults to `false`.
25 | - `name` (String) The name of the port forwarding rule.
26 | - `port_forward_interface` (String) The port forwarding interface. Can be `wan`, `wan2`, or `both`.
27 | - `protocol` (String) The protocol for the port forwarding rule. Can be `tcp`, `udp`, or `tcp_udp`. Defaults to `tcp_udp`.
28 | - `site` (String) The name of the site to associate the port forwarding rule with.
29 | - `src_ip` (String) The source IPv4 address (or CIDR) of the port forwarding rule. For all traffic, specify `any`. Defaults to `any`.
30 |
31 | ### Read-Only
32 |
33 | - `id` (String) The ID of the port forwarding rule.
34 |
--------------------------------------------------------------------------------
/docs/resources/port_profile.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_port_profile Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_port_profile manages a port profile for use on network switches.
7 | ---
8 |
9 | # unifi_port_profile (Resource)
10 |
11 | `unifi_port_profile` manages a port profile for use on network switches.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | variable "vlan_id" {
17 | default = 10
18 | }
19 |
20 | resource "unifi_network" "vlan" {
21 | name = "wifi-vlan"
22 | purpose = "corporate"
23 |
24 | subnet = "10.0.0.1/24"
25 | vlan_id = var.vlan_id
26 | dhcp_start = "10.0.0.6"
27 | dhcp_stop = "10.0.0.254"
28 | dhcp_enabled = true
29 | }
30 |
31 | resource "unifi_port_profile" "poe_disabled" {
32 | name = "POE Disabled"
33 |
34 | native_networkconf_id = unifi_network.vlan.id
35 | poe_mode = "off"
36 | }
37 | ```
38 |
39 |
40 | ## Schema
41 |
42 | ### Optional
43 |
44 | - `autoneg` (Boolean) Enable link auto negotiation for the port profile. When set to `true` this overrides `speed`. Defaults to `true`.
45 | - `dot1x_ctrl` (String) The type of 802.1X control to use. Can be `auto`, `force_authorized`, `force_unauthorized`, `mac_based` or `multi_host`. Defaults to `force_authorized`.
46 | - `dot1x_idle_timeout` (Number) The timeout, in seconds, to use when using the MAC Based 802.1X control. Can be between 0 and 65535 Defaults to `300`.
47 | - `egress_rate_limit_kbps` (Number) The egress rate limit, in kpbs, for the port profile. Can be between `64` and `9999999`.
48 | - `egress_rate_limit_kbps_enabled` (Boolean) Enable egress rate limiting for the port profile. Defaults to `false`.
49 | - `forward` (String) The type forwarding to use for the port profile. Can be `all`, `native`, `customize` or `disabled`. Defaults to `native`.
50 | - `full_duplex` (Boolean) Enable full duplex for the port profile. Defaults to `false`.
51 | - `isolation` (Boolean) Enable port isolation for the port profile. Defaults to `false`.
52 | - `lldpmed_enabled` (Boolean) Enable LLDP-MED for the port profile. Defaults to `true`.
53 | - `lldpmed_notify_enabled` (Boolean) Enable LLDP-MED topology change notifications for the port profile.
54 | - `name` (String) The name of the port profile.
55 | - `native_networkconf_id` (String) The ID of network to use as the main network on the port profile.
56 | - `op_mode` (String) The operation mode for the port profile. Can only be `switch` Defaults to `switch`.
57 | - `poe_mode` (String) The POE mode for the port profile. Can be one of `auto`, `passv24`, `passthrough` or `off`.
58 | - `port_security_enabled` (Boolean) Enable port security for the port profile. Defaults to `false`.
59 | - `port_security_mac_address` (Set of String) The MAC addresses associated with the port security for the port profile.
60 | - `priority_queue1_level` (Number) The priority queue 1 level for the port profile. Can be between 0 and 100.
61 | - `priority_queue2_level` (Number) The priority queue 2 level for the port profile. Can be between 0 and 100.
62 | - `priority_queue3_level` (Number) The priority queue 3 level for the port profile. Can be between 0 and 100.
63 | - `priority_queue4_level` (Number) The priority queue 4 level for the port profile. Can be between 0 and 100.
64 | - `site` (String) The name of the site to associate the port profile with.
65 | - `speed` (Number) The link speed to set for the port profile. Can be one of `10`, `100`, `1000`, `2500`, `5000`, `10000`, `20000`, `25000`, `40000`, `50000` or `100000`
66 | - `stormctrl_bcast_enabled` (Boolean) Enable broadcast Storm Control for the port profile. Defaults to `false`.
67 | - `stormctrl_bcast_level` (Number) The broadcast Storm Control level for the port profile. Can be between 0 and 100.
68 | - `stormctrl_bcast_rate` (Number) The broadcast Storm Control rate for the port profile. Can be between 0 and 14880000.
69 | - `stormctrl_mcast_enabled` (Boolean) Enable multicast Storm Control for the port profile. Defaults to `false`.
70 | - `stormctrl_mcast_level` (Number) The multicast Storm Control level for the port profile. Can be between 0 and 100.
71 | - `stormctrl_mcast_rate` (Number) The multicast Storm Control rate for the port profile. Can be between 0 and 14880000.
72 | - `stormctrl_type` (String) The type of Storm Control to use for the port profile. Can be one of `level` or `rate`.
73 | - `stormctrl_ucast_enabled` (Boolean) Enable unknown unicast Storm Control for the port profile. Defaults to `false`.
74 | - `stormctrl_ucast_level` (Number) The unknown unicast Storm Control level for the port profile. Can be between 0 and 100.
75 | - `stormctrl_ucast_rate` (Number) The unknown unicast Storm Control rate for the port profile. Can be between 0 and 14880000.
76 | - `stp_port_mode` (Boolean) Enable spanning tree protocol on the port profile. Defaults to `true`.
77 | - `tagged_vlan_mgmt` (String) The IDs of networks to tag traffic with for the port profile.
78 | - `voice_networkconf_id` (String) The ID of network to use as the voice network on the port profile.
79 |
80 | ### Read-Only
81 |
82 | - `id` (String) The ID of the port profile.
83 |
--------------------------------------------------------------------------------
/docs/resources/radius_profile.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_radius_profile Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_radius_profile manages RADIUS profiles.
7 | ---
8 |
9 | # unifi_radius_profile (Resource)
10 |
11 | `unifi_radius_profile` manages RADIUS profiles.
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Required
19 |
20 | - `name` (String) The name of the profile.
21 |
22 | ### Optional
23 |
24 | - `accounting_enabled` (Boolean) Specifies whether to use RADIUS accounting. Defaults to `false`.
25 | - `acct_server` (Block List) RADIUS accounting servers. (see [below for nested schema](#nestedblock--acct_server))
26 | - `auth_server` (Block List) RADIUS authentication servers. (see [below for nested schema](#nestedblock--auth_server))
27 | - `interim_update_enabled` (Boolean) Specifies whether to use interim_update. Defaults to `false`.
28 | - `interim_update_interval` (Number) Specifies interim_update interval. Defaults to `3600`.
29 | - `site` (String) The name of the site to associate the settings with.
30 | - `use_usg_acct_server` (Boolean) Specifies whether to use usg as a RADIUS accounting server. Defaults to `false`.
31 | - `use_usg_auth_server` (Boolean) Specifies whether to use usg as a RADIUS authentication server. Defaults to `false`.
32 | - `vlan_enabled` (Boolean) Specifies whether to use vlan on wired connections. Defaults to `false`.
33 | - `vlan_wlan_mode` (String) Specifies whether to use vlan on wireless connections. Must be one of `disabled`, `optional`, or `required`. Defaults to ``.
34 |
35 | ### Read-Only
36 |
37 | - `id` (String) The ID of the settings.
38 |
39 |
40 | ### Nested Schema for `acct_server`
41 |
42 | Required:
43 |
44 | - `ip` (String) IP address of accounting service server.
45 | - `xsecret` (String, Sensitive) RADIUS secret.
46 |
47 | Optional:
48 |
49 | - `port` (Number) Port of accounting service. Defaults to `1813`.
50 |
51 |
52 |
53 | ### Nested Schema for `auth_server`
54 |
55 | Required:
56 |
57 | - `ip` (String) IP address of authentication service server.
58 | - `xsecret` (String, Sensitive) RADIUS secret.
59 |
60 | Optional:
61 |
62 | - `port` (Number) Port of authentication service. Defaults to `1812`.
63 |
--------------------------------------------------------------------------------
/docs/resources/setting_mgmt.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_setting_mgmt Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_setting_mgmt manages settings for a unifi site.
7 | ---
8 |
9 | # unifi_setting_mgmt (Resource)
10 |
11 | `unifi_setting_mgmt` manages settings for a unifi site.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | resource "unifi_site" "example" {
17 | description = "example"
18 | }
19 |
20 | resource "unifi_setting_mgmt" "example" {
21 | site = unifi_site.example.name
22 | auto_upgrade = true
23 | }
24 | ```
25 |
26 |
27 | ## Schema
28 |
29 | ### Optional
30 |
31 | - `auto_upgrade` (Boolean) Automatically upgrade device firmware.
32 | - `site` (String) The name of the site to associate the settings with.
33 | - `ssh_enabled` (Boolean) Enable SSH authentication.
34 | - `ssh_key` (Block Set) SSH key. (see [below for nested schema](#nestedblock--ssh_key))
35 |
36 | ### Read-Only
37 |
38 | - `id` (String) The ID of the settings.
39 |
40 |
41 | ### Nested Schema for `ssh_key`
42 |
43 | Required:
44 |
45 | - `name` (String) Name of SSH key.
46 | - `type` (String) Type of SSH key, e.g. ssh-rsa.
47 |
48 | Optional:
49 |
50 | - `comment` (String) Comment.
51 | - `key` (String) Public SSH key.
52 |
--------------------------------------------------------------------------------
/docs/resources/setting_radius.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_setting_radius Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_setting_radius manages settings for the built-in RADIUS server.
7 | ---
8 |
9 | # unifi_setting_radius (Resource)
10 |
11 | `unifi_setting_radius` manages settings for the built-in RADIUS server.
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Optional
19 |
20 | - `accounting_enabled` (Boolean) Enable RADIUS accounting Defaults to `false`.
21 | - `accounting_port` (Number) The port for accounting communications. Defaults to `1813`.
22 | - `auth_port` (Number) The port for authentication communications. Defaults to `1812`.
23 | - `enabled` (Boolean) RAIDUS server enabled. Defaults to `true`.
24 | - `interim_update_interval` (Number) Statistics will be collected from connected clients at this interval. Defaults to `3600`.
25 | - `secret` (String, Sensitive) RAIDUS secret passphrase. Defaults to ``.
26 | - `site` (String) The name of the site to associate the settings with.
27 | - `tunneled_reply` (Boolean) Encrypt communication between the server and the client. Defaults to `true`.
28 |
29 | ### Read-Only
30 |
31 | - `id` (String) The ID of the settings.
32 |
--------------------------------------------------------------------------------
/docs/resources/setting_usg.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_setting_usg Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_setting_usg manages settings for a Unifi Security Gateway.
7 | ---
8 |
9 | # unifi_setting_usg (Resource)
10 |
11 | `unifi_setting_usg` manages settings for a Unifi Security Gateway.
12 |
13 |
14 |
15 |
16 | ## Schema
17 |
18 | ### Optional
19 |
20 | - `dhcp_relay_servers` (List of String) The DHCP relay servers.
21 | - `firewall_guest_default_log` (Boolean) Whether the guest firewall log is enabled.
22 | - `firewall_lan_default_log` (Boolean) Whether the LAN firewall log is enabled.
23 | - `firewall_wan_default_log` (Boolean) Whether the WAN firewall log is enabled.
24 | - `multicast_dns_enabled` (Boolean) Whether multicast DNS is enabled.
25 | - `site` (String) The name of the site to associate the settings with.
26 |
27 | ### Read-Only
28 |
29 | - `id` (String) The ID of the settings.
30 |
--------------------------------------------------------------------------------
/docs/resources/site.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_site Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_site manages Unifi sites
7 | ---
8 |
9 | # unifi_site (Resource)
10 |
11 | `unifi_site` manages Unifi sites
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | resource "unifi_site" "mysite" {
17 | description = "mysite"
18 | }
19 | ```
20 |
21 |
22 | ## Schema
23 |
24 | ### Required
25 |
26 | - `description` (String) The description of the site.
27 |
28 | ### Read-Only
29 |
30 | - `id` (String) The ID of the site.
31 | - `name` (String) The name of the site.
32 |
33 | ## Import
34 |
35 | Import is supported using the following syntax:
36 |
37 | ```shell
38 | # import using the API/UI ID
39 | terraform import unifi_site.mysite 5fe6261995fe130013456a36
40 |
41 | # import using the name (short ID)
42 | terraform import unifi_site.mysite vq98kwez
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/resources/static_route.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_static_route Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_static_route manages a static route.
7 | ---
8 |
9 | # unifi_static_route (Resource)
10 |
11 | `unifi_static_route` manages a static route.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | resource "unifi_static_route" "nexthop" {
17 | type = "nexthop-route"
18 | network = "172.17.0.0/16"
19 | name = "basic nexthop"
20 | distance = 1
21 | next_hop = "172.16.0.1"
22 | }
23 |
24 | resource "unifi_static_route" "blackhole" {
25 | type = "blackhole"
26 | network = var.blackhole_cidr
27 | name = "blackhole traffice to cidr"
28 | distance = 1
29 | }
30 |
31 | resource "unifi_static_route" "interface" {
32 | type = "interface-route"
33 | network = var.wan2_cidr
34 | name = "send traffic over wan2"
35 | distance = 1
36 | interface = "WAN2"
37 | }
38 | ```
39 |
40 |
41 | ## Schema
42 |
43 | ### Required
44 |
45 | - `distance` (Number) The distance of the static route.
46 | - `name` (String) The name of the static route.
47 | - `network` (String) The network subnet address.
48 | - `type` (String) The type of static route. Can be `interface-route`, `nexthop-route`, or `blackhole`.
49 |
50 | ### Optional
51 |
52 | - `interface` (String) The interface of the static route (only valid for `interface-route` type). This can be `WAN1`, `WAN2`, or a network ID.
53 | - `next_hop` (String) The next hop of the static route (only valid for `nexthop-route` type).
54 | - `site` (String) The name of the site to associate the static route with.
55 |
56 | ### Read-Only
57 |
58 | - `id` (String) The ID of the static route.
59 |
--------------------------------------------------------------------------------
/docs/resources/user.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_user Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_user manages a user (or "client" in the UI) of the network, these are identified by unique MAC addresses.
7 | Users are created in the controller when observed on the network, so the resource defaults to allowing itself to just take over management of a MAC address, but this can be turned off.
8 | ---
9 |
10 | # unifi_user (Resource)
11 |
12 | `unifi_user` manages a user (or "client" in the UI) of the network, these are identified by unique MAC addresses.
13 |
14 | Users are created in the controller when observed on the network, so the resource defaults to allowing itself to just take over management of a MAC address, but this can be turned off.
15 |
16 | ## Example Usage
17 |
18 | ```terraform
19 | resource "unifi_user" "test" {
20 | mac = "01:23:45:67:89:AB"
21 | name = "some client"
22 | note = "my note"
23 |
24 | fixed_ip = "10.0.0.50"
25 | network_id = unifi_network.my_vlan.id
26 | }
27 | ```
28 |
29 |
30 | ## Schema
31 |
32 | ### Required
33 |
34 | - `mac` (String) The MAC address of the user.
35 | - `name` (String) The name of the user.
36 |
37 | ### Optional
38 |
39 | - `allow_existing` (Boolean) Specifies whether this resource should just take over control of an existing user. Defaults to `true`.
40 | - `blocked` (Boolean) Specifies whether this user should be blocked from the network.
41 | - `dev_id_override` (Number) Override the device fingerprint.
42 | - `fixed_ip` (String) A fixed IPv4 address for this user.
43 | - `local_dns_record` (String) Specifies the local DNS record for this user.
44 | - `network_id` (String) The network ID for this user.
45 | - `note` (String) A note with additional information for the user.
46 | - `site` (String) The name of the site to associate the user with.
47 | - `skip_forget_on_destroy` (Boolean) Specifies whether this resource should tell the controller to "forget" the user on destroy. Defaults to `false`.
48 | - `user_group_id` (String) The user group ID for the user.
49 |
50 | ### Read-Only
51 |
52 | - `hostname` (String) The hostname of the user.
53 | - `id` (String) The ID of the user.
54 | - `ip` (String) The IP address of the user.
55 |
--------------------------------------------------------------------------------
/docs/resources/user_group.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_user_group Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_user_group manages a user group (called "client group" in the UI), which can be used to limit bandwidth for groups of users.
7 | ---
8 |
9 | # unifi_user_group (Resource)
10 |
11 | `unifi_user_group` manages a user group (called "client group" in the UI), which can be used to limit bandwidth for groups of users.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | resource "unifi_user_group" "wifi" {
17 | name = "wifi"
18 |
19 | qos_rate_max_down = 2000 # 2mbps
20 | qos_rate_max_up = 10 # 10kbps
21 | }
22 | ```
23 |
24 |
25 | ## Schema
26 |
27 | ### Required
28 |
29 | - `name` (String) The name of the user group.
30 |
31 | ### Optional
32 |
33 | - `qos_rate_max_down` (Number) The QOS maximum download rate. Defaults to `-1`.
34 | - `qos_rate_max_up` (Number) The QOS maximum upload rate. Defaults to `-1`.
35 | - `site` (String) The name of the site to associate the user group with.
36 |
37 | ### Read-Only
38 |
39 | - `id` (String) The ID of the user group.
40 |
41 | ## Import
42 |
43 | Import is supported using the following syntax:
44 |
45 | ```shell
46 | # import using the ID
47 | terraform import unifi_user_group.wifi 5fe6261995fe130013456a36
48 | ```
49 |
--------------------------------------------------------------------------------
/docs/resources/wlan.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "unifi_wlan Resource - terraform-provider-unifi"
4 | subcategory: ""
5 | description: |-
6 | unifi_wlan manages a WiFi network / SSID.
7 | ---
8 |
9 | # unifi_wlan (Resource)
10 |
11 | `unifi_wlan` manages a WiFi network / SSID.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | variable "vlan_id" {
17 | default = 10
18 | }
19 |
20 | data "unifi_ap_group" "default" {
21 | }
22 |
23 | data "unifi_user_group" "default" {
24 | }
25 |
26 | resource "unifi_network" "vlan" {
27 | name = "wifi-vlan"
28 | purpose = "corporate"
29 |
30 | subnet = "10.0.0.1/24"
31 | vlan_id = var.vlan_id
32 | dhcp_start = "10.0.0.6"
33 | dhcp_stop = "10.0.0.254"
34 | dhcp_enabled = true
35 | }
36 |
37 | resource "unifi_wlan" "wifi" {
38 | name = "myssid"
39 | passphrase = "12345678"
40 | security = "wpapsk"
41 |
42 | # enable WPA2/WPA3 support
43 | wpa3_support = true
44 | wpa3_transition = true
45 | pmf_mode = "optional"
46 |
47 | network_id = unifi_network.vlan.id
48 | ap_group_ids = [data.unifi_ap_group.default.id]
49 | user_group_id = data.unifi_user_group.default.id
50 | }
51 | ```
52 |
53 |
54 | ## Schema
55 |
56 | ### Required
57 |
58 | - `name` (String) The SSID of the network.
59 | - `security` (String) The type of WiFi security for this network. Valid values are: `wpapsk`, `wpaeap`, and `open`.
60 | - `user_group_id` (String) ID of the user group to use for this network.
61 |
62 | ### Optional
63 |
64 | - `ap_group_ids` (Set of String) IDs of the AP groups to use for this network.
65 | - `bss_transition` (Boolean) Improves client transitions between APs when they have a weak signal. Defaults to `true`.
66 | - `fast_roaming_enabled` (Boolean) Enables 802.11r fast roaming. Defaults to `false`.
67 | - `hide_ssid` (Boolean) Indicates whether or not to hide the SSID from broadcast.
68 | - `is_guest` (Boolean) Indicates that this is a guest WLAN and should use guest behaviors.
69 | - `l2_isolation` (Boolean) Isolates stations on layer 2 (ethernet) level. Defaults to `false`.
70 | - `mac_filter_enabled` (Boolean) Indicates whether or not the MAC filter is turned of for the network.
71 | - `mac_filter_list` (Set of String) List of MAC addresses to filter (only valid if `mac_filter_enabled` is `true`).
72 | - `mac_filter_policy` (String) MAC address filter policy (only valid if `mac_filter_enabled` is `true`). Defaults to `deny`.
73 | - `minimum_data_rate_2g_kbps` (Number) Set minimum data rate control for 2G devices, in Kbps. Use `0` to disable minimum data rates. Valid values are: `1000`, `2000`, `5500`, `6000`, `9000`, `11000`, `12000`, `18000`, `24000`, `36000`, `48000`, and `54000`.
74 | - `minimum_data_rate_5g_kbps` (Number) Set minimum data rate control for 5G devices, in Kbps. Use `0` to disable minimum data rates. Valid values are: `6000`, `9000`, `12000`, `18000`, `24000`, `36000`, `48000`, and `54000`.
75 | - `multicast_enhance` (Boolean) Indicates whether or not Multicast Enhance is turned of for the network.
76 | - `network_id` (String) ID of the network for this SSID
77 | - `no2ghz_oui` (Boolean) Connect high performance clients to 5 GHz only. Defaults to `true`.
78 | - `passphrase` (String, Sensitive) The passphrase for the network, this is only required if `security` is not set to `open`.
79 | - `pmf_mode` (String) Enable Protected Management Frames. This cannot be disabled if using WPA 3. Valid values are `required`, `optional` and `disabled`. Defaults to `disabled`.
80 | - `proxy_arp` (Boolean) Reduces airtime usage by allowing APs to "proxy" common broadcast frames as unicast. Defaults to `false`.
81 | - `radius_profile_id` (String) ID of the RADIUS profile to use when security `wpaeap`. You can query this via the `unifi_radius_profile` data source.
82 | - `schedule` (Block List) Start and stop schedules for the WLAN (see [below for nested schema](#nestedblock--schedule))
83 | - `site` (String) The name of the site to associate the wlan with.
84 | - `uapsd` (Boolean) Enable Unscheduled Automatic Power Save Delivery. Defaults to `false`.
85 | - `wlan_band` (String) Radio band your WiFi network will use. Defaults to `both`.
86 | - `wpa3_support` (Boolean) Enable WPA 3 support (security must be `wpapsk` and PMF must be turned on).
87 | - `wpa3_transition` (Boolean) Enable WPA 3 and WPA 2 support (security must be `wpapsk` and `wpa3_support` must be true).
88 |
89 | ### Read-Only
90 |
91 | - `id` (String) The ID of the network.
92 |
93 |
94 | ### Nested Schema for `schedule`
95 |
96 | Required:
97 |
98 | - `day_of_week` (String) Day of week for the block. Valid values are `sun`, `mon`, `tue`, `wed`, `thu`, `fri`, `sat`.
99 | - `duration` (Number) Length of the block in minutes.
100 | - `start_hour` (Number) Start hour for the block (0-23).
101 |
102 | Optional:
103 |
104 | - `name` (String) Name of the block.
105 | - `start_minute` (Number) Start minute for the block (0-59). Defaults to `0`.
106 |
107 | ## Import
108 |
109 | Import is supported using the following syntax:
110 |
111 | ```shell
112 | # import from provider configured site
113 | terraform import unifi_wlan.mywlan 5dc28e5e9106d105bdc87217
114 |
115 | # import from another site
116 | terraform import unifi_wlan.mywlan bfa2l6i7:5dc28e5e9106d105bdc87217
117 | ```
118 |
--------------------------------------------------------------------------------
/examples/csv_users/users.csv:
--------------------------------------------------------------------------------
1 | mac,name,note
2 | 01:23:45:67:89:AB,My Device,custom note
3 |
--------------------------------------------------------------------------------
/examples/csv_users/users.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | userscsv = csvdecode(file("${path.module}/users.csv"))
3 | users = { for user in local.userscsv : user.mac => user }
4 | }
5 |
6 | resource "unifi_user" "user" {
7 | for_each = local.users
8 |
9 | mac = each.key
10 | name = each.value.name
11 | # append an optional additional note
12 | note = trimspace("${each.value.note}\n\nmanaged by TF")
13 |
14 | allow_existing = true
15 | skip_forget_on_destroy = true
16 | }
--------------------------------------------------------------------------------
/examples/data-sources/unifi_ap_group/data-source.tf:
--------------------------------------------------------------------------------
1 | data "unifi_ap_group" "default" {
2 | }
--------------------------------------------------------------------------------
/examples/data-sources/unifi_network/data-source.tf:
--------------------------------------------------------------------------------
1 | #retrieve network data by unifi network name
2 | data "unifi_network" "lan_network" {
3 | name = "Default"
4 | }
5 |
6 | #retrieve network data from user record
7 | data "unifi_user" "my_device" {
8 | mac = "01:23:45:67:89:ab"
9 | }
10 | data "unifi_network" "my_network" {
11 | id = data.unifi_user.my_device.network_id
12 | }
13 |
--------------------------------------------------------------------------------
/examples/data-sources/unifi_port_profile/data-source.tf:
--------------------------------------------------------------------------------
1 | data "unifi_port_profile" "all" {
2 | }
3 |
--------------------------------------------------------------------------------
/examples/data-sources/unifi_user/data-source.tf:
--------------------------------------------------------------------------------
1 | data "unifi_user" "client" {
2 | mac = "01:23:45:67:89:ab"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/multiple_site_firewall/firewall.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_firewall_rule" "rule" {
2 | # list of sites
3 | for_each = toset(["default", "vq98kwez", "bfa2l6i7"])
4 | # use the key of the list as the site value
5 | site = each.key
6 |
7 | name = "drop all"
8 | action = "drop"
9 | ruleset = "LAN_IN"
10 |
11 | rule_index = 2011
12 |
13 | protocol = "all"
14 |
15 | dst_address = var.ip_address
16 | }
17 |
--------------------------------------------------------------------------------
/examples/provider/provider.tf:
--------------------------------------------------------------------------------
1 | provider "unifi" {
2 | username = var.username # optionally use UNIFI_USERNAME env var
3 | password = var.password # optionally use UNIFI_PASSWORD env var
4 | api_url = var.api_url # optionally use UNIFI_API env var
5 |
6 | # you may need to allow insecure TLS communications unless you have configured
7 | # certificates for your controller
8 | allow_insecure = var.insecure # optionally use UNIFI_INSECURE env var
9 |
10 | # if you are not configuring the default site, you can change the site
11 | # site = "foo" or optionally use UNIFI_SITE env var
12 | }
--------------------------------------------------------------------------------
/examples/provider/test.auto.tfvars:
--------------------------------------------------------------------------------
1 | username = "tfacctest"
2 | password = "tfacctest1234"
3 |
4 | # this assumes the default port for acc testing
5 | api_url = "https://localhost:8443/api/"
6 | insecure = true
--------------------------------------------------------------------------------
/examples/provider/test.tf:
--------------------------------------------------------------------------------
1 | data "unifi_ap_group" "default" {
2 | }
3 |
--------------------------------------------------------------------------------
/examples/provider/variables.tf:
--------------------------------------------------------------------------------
1 | variable "username" {
2 | }
3 |
4 | variable "password" {
5 | }
6 |
7 | variable "api_url" {
8 | }
9 |
10 | variable "insecure" {
11 | default = false
12 | }
--------------------------------------------------------------------------------
/examples/resources/unifi_device/resource.tf:
--------------------------------------------------------------------------------
1 | data "unifi_port_profile" "disabled" {
2 | # look up the built-in disabled port profile
3 | name = "Disabled"
4 | }
5 |
6 | resource "unifi_port_profile" "poe" {
7 | name = "poe"
8 | forward = "customize"
9 |
10 | native_networkconf_id = var.native_network_id
11 | tagged_networkconf_ids = [
12 | var.some_vlan_network_id,
13 | ]
14 |
15 | poe_mode = "auto"
16 | }
17 |
18 | resource "unifi_device" "us_24_poe" {
19 | # optionally specify MAC address to skip manually importing
20 | # manual import is the safest way to add a device
21 | mac = "01:23:45:67:89:AB"
22 |
23 | name = "Switch with POE"
24 |
25 | port_override {
26 | number = 1
27 | name = "port w/ poe"
28 | port_profile_id = unifi_port_profile.poe.id
29 | }
30 |
31 | port_override {
32 | number = 2
33 | name = "disabled"
34 | port_profile_id = data.unifi_port_profile.disabled.id
35 | }
36 |
37 | # port aggregation for ports 11 and 12
38 | port_override {
39 | number = 11
40 | op_mode = "aggregate"
41 | aggregate_num_ports = 2
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/resources/unifi_dns_record/resource.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_dns_record" "test" {
2 | name = "my-network"
3 | enabled = true
4 | port = 0
5 | priority = 10
6 | record_type = "A"
7 | ttl = 300
8 | value = "my-network.example.com"
9 | }
10 |
--------------------------------------------------------------------------------
/examples/resources/unifi_dynamic_dns/resource.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_dynamic_dns" "test" {
2 | service = "dyndns"
3 |
4 | host_name = "my-network.example.com"
5 |
6 | server = "domains.google.com"
7 | login = var.dns_login
8 | password = var.dns_password
9 | }
10 |
--------------------------------------------------------------------------------
/examples/resources/unifi_firewall_group/resource.tf:
--------------------------------------------------------------------------------
1 | variable "laptop_ips" {
2 | type = list(string)
3 | }
4 |
5 | resource "unifi_firewall_group" "can_print" {
6 | name = "can-print"
7 | type = "address-group"
8 |
9 | members = var.laptop_ips
10 | }
--------------------------------------------------------------------------------
/examples/resources/unifi_firewall_group/test.auto.tfvars:
--------------------------------------------------------------------------------
1 | # these values are used in tests
2 | laptop_ips = ["192.168.1.25"]
--------------------------------------------------------------------------------
/examples/resources/unifi_firewall_rule/import.sh:
--------------------------------------------------------------------------------
1 | # import using the ID from the controller API/UI
2 | terraform import unifi_firewall_rule.my_rule 5f7080eb6b8969064f80494f
3 |
--------------------------------------------------------------------------------
/examples/resources/unifi_firewall_rule/resource.tf:
--------------------------------------------------------------------------------
1 | variable "ip_address" {
2 | type = string
3 | }
4 |
5 | resource "unifi_firewall_rule" "drop_all" {
6 | name = "drop all"
7 | action = "drop"
8 | ruleset = "LAN_IN"
9 |
10 | rule_index = 2011
11 |
12 | protocol = "all"
13 |
14 | dst_address = var.ip_address
15 | }
--------------------------------------------------------------------------------
/examples/resources/unifi_firewall_rule/test.auto.tfvars:
--------------------------------------------------------------------------------
1 | # these values are used in tests
2 | ip_address = "192.168.1.1"
--------------------------------------------------------------------------------
/examples/resources/unifi_network/import.sh:
--------------------------------------------------------------------------------
1 | # import from provider configured site
2 | terraform import unifi_network.mynetwork 5dc28e5e9106d105bdc87217
3 |
4 | # import from another site
5 | terraform import unifi_network.mynetwork bfa2l6i7:5dc28e5e9106d105bdc87217
6 |
7 | # import network by name
8 | terraform import unifi_network.mynetwork name=LAN
9 |
--------------------------------------------------------------------------------
/examples/resources/unifi_network/resource.tf:
--------------------------------------------------------------------------------
1 | variable "vlan_id" {
2 | default = 10
3 | }
4 |
5 | resource "unifi_network" "vlan" {
6 | name = "wifi-vlan"
7 | purpose = "corporate"
8 |
9 | subnet = "10.0.0.1/24"
10 | vlan_id = var.vlan_id
11 | dhcp_start = "10.0.0.6"
12 | dhcp_stop = "10.0.0.254"
13 | dhcp_enabled = true
14 | }
15 |
16 | resource "unifi_network" "wan" {
17 | name = "wan"
18 | purpose = "wan"
19 |
20 | wan_networkgroup = "WAN"
21 | wan_type = "pppoe"
22 | wan_ip = "192.168.1.1"
23 | wan_egress_qos = 1
24 | wan_username = "username"
25 | x_wan_password = "password"
26 | }
27 |
--------------------------------------------------------------------------------
/examples/resources/unifi_network/test.auto.tfvars:
--------------------------------------------------------------------------------
1 | vlan_id = 41
--------------------------------------------------------------------------------
/examples/resources/unifi_port_profile/resource.tf:
--------------------------------------------------------------------------------
1 | variable "vlan_id" {
2 | default = 10
3 | }
4 |
5 | resource "unifi_network" "vlan" {
6 | name = "wifi-vlan"
7 | purpose = "corporate"
8 |
9 | subnet = "10.0.0.1/24"
10 | vlan_id = var.vlan_id
11 | dhcp_start = "10.0.0.6"
12 | dhcp_stop = "10.0.0.254"
13 | dhcp_enabled = true
14 | }
15 |
16 | resource "unifi_port_profile" "poe_disabled" {
17 | name = "POE Disabled"
18 |
19 | native_networkconf_id = unifi_network.vlan.id
20 | poe_mode = "off"
21 | }
22 |
--------------------------------------------------------------------------------
/examples/resources/unifi_setting_mgmt/resource.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_site" "example" {
2 | description = "example"
3 | }
4 |
5 | resource "unifi_setting_mgmt" "example" {
6 | site = unifi_site.example.name
7 | auto_upgrade = true
8 | }
9 |
--------------------------------------------------------------------------------
/examples/resources/unifi_site/import.sh:
--------------------------------------------------------------------------------
1 | # import using the API/UI ID
2 | terraform import unifi_site.mysite 5fe6261995fe130013456a36
3 |
4 | # import using the name (short ID)
5 | terraform import unifi_site.mysite vq98kwez
6 |
--------------------------------------------------------------------------------
/examples/resources/unifi_site/resource.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_site" "mysite" {
2 | description = "mysite"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/resources/unifi_static_route/resource.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_static_route" "nexthop" {
2 | type = "nexthop-route"
3 | network = "172.17.0.0/16"
4 | name = "basic nexthop"
5 | distance = 1
6 | next_hop = "172.16.0.1"
7 | }
8 |
9 | resource "unifi_static_route" "blackhole" {
10 | type = "blackhole"
11 | network = var.blackhole_cidr
12 | name = "blackhole traffice to cidr"
13 | distance = 1
14 | }
15 |
16 | resource "unifi_static_route" "interface" {
17 | type = "interface-route"
18 | network = var.wan2_cidr
19 | name = "send traffic over wan2"
20 | distance = 1
21 | interface = "WAN2"
22 | }
23 |
--------------------------------------------------------------------------------
/examples/resources/unifi_user/resource.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_user" "test" {
2 | mac = "01:23:45:67:89:AB"
3 | name = "some client"
4 | note = "my note"
5 |
6 | fixed_ip = "10.0.0.50"
7 | network_id = unifi_network.my_vlan.id
8 | }
--------------------------------------------------------------------------------
/examples/resources/unifi_user/test.auto.tfvars:
--------------------------------------------------------------------------------
1 | vlan_id = 42
--------------------------------------------------------------------------------
/examples/resources/unifi_user/test.tf:
--------------------------------------------------------------------------------
1 | variable "vlan_id" {
2 | default = 10
3 | }
4 |
5 | resource "unifi_network" "my_vlan" {
6 | name = "wifi-vlan"
7 | purpose = "corporate"
8 |
9 | subnet = "10.0.0.1/24"
10 | vlan_id = var.vlan_id
11 | dhcp_start = "10.0.0.6"
12 | dhcp_stop = "10.0.0.254"
13 | dhcp_enabled = true
14 | }
--------------------------------------------------------------------------------
/examples/resources/unifi_user_group/import.sh:
--------------------------------------------------------------------------------
1 | # import using the ID
2 | terraform import unifi_user_group.wifi 5fe6261995fe130013456a36
--------------------------------------------------------------------------------
/examples/resources/unifi_user_group/resource.tf:
--------------------------------------------------------------------------------
1 | resource "unifi_user_group" "wifi" {
2 | name = "wifi"
3 |
4 | qos_rate_max_down = 2000 # 2mbps
5 | qos_rate_max_up = 10 # 10kbps
6 | }
--------------------------------------------------------------------------------
/examples/resources/unifi_wlan/import.sh:
--------------------------------------------------------------------------------
1 | # import from provider configured site
2 | terraform import unifi_wlan.mywlan 5dc28e5e9106d105bdc87217
3 |
4 | # import from another site
5 | terraform import unifi_wlan.mywlan bfa2l6i7:5dc28e5e9106d105bdc87217
6 |
--------------------------------------------------------------------------------
/examples/resources/unifi_wlan/resource.tf:
--------------------------------------------------------------------------------
1 | variable "vlan_id" {
2 | default = 10
3 | }
4 |
5 | data "unifi_ap_group" "default" {
6 | }
7 |
8 | data "unifi_user_group" "default" {
9 | }
10 |
11 | resource "unifi_network" "vlan" {
12 | name = "wifi-vlan"
13 | purpose = "corporate"
14 |
15 | subnet = "10.0.0.1/24"
16 | vlan_id = var.vlan_id
17 | dhcp_start = "10.0.0.6"
18 | dhcp_stop = "10.0.0.254"
19 | dhcp_enabled = true
20 | }
21 |
22 | resource "unifi_wlan" "wifi" {
23 | name = "myssid"
24 | passphrase = "12345678"
25 | security = "wpapsk"
26 |
27 | # enable WPA2/WPA3 support
28 | wpa3_support = true
29 | wpa3_transition = true
30 | pmf_mode = "optional"
31 |
32 | network_id = unifi_network.vlan.id
33 | ap_group_ids = [data.unifi_ap_group.default.id]
34 | user_group_id = data.unifi_user_group.default.id
35 | }
36 |
--------------------------------------------------------------------------------
/internal/provider/cidr.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "net"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func cidrValidate(raw any, key string) ([]string, []error) {
11 | v, ok := raw.(string)
12 | if !ok {
13 | return nil, []error{fmt.Errorf("expected string, got %T", raw)}
14 | }
15 |
16 | _, _, err := net.ParseCIDR(v)
17 | if err != nil {
18 | return nil, []error{err}
19 | }
20 |
21 | return nil, nil
22 | }
23 |
24 | func cidrDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
25 | _, oldNet, err := net.ParseCIDR(old)
26 | if err != nil {
27 | return false
28 | }
29 |
30 | _, newNet, err := net.ParseCIDR(new)
31 | if err != nil {
32 | return false
33 | }
34 |
35 | return oldNet.String() == newNet.String()
36 | }
37 |
38 | func cidrZeroBased(cidr string) string {
39 | _, cidrNet, err := net.ParseCIDR(cidr)
40 | if err != nil {
41 | return ""
42 | }
43 |
44 | return cidrNet.String()
45 | }
46 |
47 | func cidrOneBased(cidr string) string {
48 | _, cidrNet, err := net.ParseCIDR(cidr)
49 | if err != nil {
50 | return ""
51 | }
52 |
53 | cidrNet.IP[3]++
54 |
55 | return cidrNet.String()
56 | }
57 |
--------------------------------------------------------------------------------
/internal/provider/cidr_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestCIDRValidate(t *testing.T) {
8 | for _, c := range []struct {
9 | expectedError string
10 | cidr string
11 | }{
12 | {"invalid CIDR address: ", ""},
13 | {"invalid CIDR address: abc", "abc"},
14 | {"invalid CIDR address: 192.1.2.3", "192.1.2.3"},
15 | {"invalid CIDR address: 500.1.2.3/20", "500.1.2.3/20"},
16 | {"invalid CIDR address: 192.1.2.3/500", "192.1.2.3/500"},
17 |
18 | {"", "192.1.2.1/20"},
19 | } {
20 | t.Run(c.cidr, func(t *testing.T) {
21 | _, actualErrs := cidrValidate(c.cidr, "key")
22 | switch len(actualErrs) {
23 | case 0:
24 | if c.expectedError != "" {
25 | t.Fatalf("expected no error, got %d: %#v", len(actualErrs), actualErrs)
26 | }
27 | case 1:
28 | actualErr := actualErrs[0].Error()
29 | if actualErr != c.expectedError {
30 | t.Fatalf("expected %q, got %q", c.expectedError, actualErr)
31 | }
32 | default:
33 | t.Fatalf("expected 0 or 1 errors, got %d: %#v", len(actualErrs), actualErrs)
34 | }
35 | })
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/internal/provider/controller_versions.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/hashicorp/go-version"
7 | )
8 |
9 | var (
10 | controllerV6 = version.Must(version.NewVersion("6.0.0"))
11 | controllerV7 = version.Must(version.NewVersion("7.0.0"))
12 |
13 | // https://community.ui.com/releases/UniFi-Network-Controller-6-1-61/62f1ad38-1ac5-430c-94b0-becbb8f71d7d
14 | controllerVersionWPA3 = version.Must(version.NewVersion("6.1.61"))
15 | )
16 |
17 | func (c *client) ControllerVersion() *version.Version {
18 | return version.Must(version.NewVersion(c.c.Version()))
19 | }
20 |
21 | func checkMinimumControllerVersion(versionString string) error {
22 | v, err := version.NewVersion(versionString)
23 | if err != nil {
24 | return err
25 | }
26 | if v.LessThan(controllerV6) {
27 | return fmt.Errorf("Controller version %q or greater is required to use the provider, found %q.", controllerV6, v)
28 | }
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/internal/provider/controller_versions_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/go-version"
7 | )
8 |
9 | func preCheckMinVersion(t *testing.T, min *version.Version) {
10 | v, err := version.NewVersion(testClient.Version())
11 | if err != nil {
12 | t.Fatalf("error parsing version: %s", err)
13 | }
14 | if v.LessThan(min) {
15 | t.Skipf("skipping test on controller version %q (need at least %q)", v, min)
16 | }
17 | }
18 |
19 | func preCheckVersionConstraint(t *testing.T, cs string) {
20 | v, err := version.NewVersion(testClient.Version())
21 | if err != nil {
22 | t.Fatalf("Error parsing version: %s", err)
23 | }
24 |
25 | c, err := version.NewConstraint(cs)
26 | if err != nil {
27 | t.Fatalf("Error parsing version constriant: %s", err)
28 | }
29 |
30 | if !c.Check(v) {
31 | t.Skipf("Skipping test on controller version %q (constrained to %q)", v, c)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/internal/provider/data_account.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func dataAccount() *schema.Resource {
11 | return &schema.Resource{
12 | Description: "`unifi_account` data source can be used to retrieve RADIUS user accounts",
13 |
14 | ReadContext: dataAccountRead,
15 |
16 | Schema: map[string]*schema.Schema{
17 | "id": {
18 | Description: "The ID of this account.",
19 | Type: schema.TypeString,
20 | Computed: true,
21 | },
22 | "site": {
23 | Description: "The name of the site the account is associated with.",
24 | Type: schema.TypeString,
25 | Computed: true,
26 | Optional: true,
27 | },
28 | "name": {
29 | Description: "The name of the account to look up",
30 | Type: schema.TypeString,
31 | Required: true,
32 | },
33 |
34 | "password": {
35 | Description: "The password of the account.",
36 | Type: schema.TypeString,
37 | Computed: true,
38 | Sensitive: true,
39 | },
40 | "tunnel_type": {
41 | Description: "See RFC2868 section 3.1", // @TODO: better documentation https://help.ui.com/hc/en-us/articles/360015268353-UniFi-USG-UDM-Configuring-RADIUS-Server#6
42 | Type: schema.TypeInt,
43 | Computed: true,
44 | },
45 | "tunnel_medium_type": {
46 | Description: "See RFC2868 section 3.2", // @TODO: better documentation https://help.ui.com/hc/en-us/articles/360015268353-UniFi-USG-UDM-Configuring-RADIUS-Server#6
47 | Type: schema.TypeInt,
48 | Computed: true,
49 | },
50 | "network_id": {
51 | Description: "ID of the network for this account",
52 | Type: schema.TypeString,
53 | Computed: true,
54 | },
55 | },
56 | }
57 | }
58 |
59 | func dataAccountRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
60 | c := meta.(*client)
61 |
62 | name := d.Get("name").(string)
63 | site := d.Get("site").(string)
64 | if site == "" {
65 | site = c.site
66 | }
67 |
68 | accounts, err := c.c.ListAccounts(ctx, site)
69 | if err != nil {
70 | return diag.FromErr(err)
71 | }
72 | for _, account := range accounts {
73 | if account.Name == name {
74 | d.SetId(account.ID)
75 | d.Set("name", account.Name)
76 | d.Set("password", account.XPassword)
77 | d.Set("tunnel_type", account.TunnelType)
78 | d.Set("tunnel_medium_type", account.TunnelMediumType)
79 | d.Set("network_id", account.NetworkID)
80 | d.Set("site", site)
81 | return nil
82 | }
83 | }
84 |
85 | return diag.Errorf("Account not found with name %s", name)
86 | }
87 |
--------------------------------------------------------------------------------
/internal/provider/data_account_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
6 | "testing"
7 | )
8 |
9 | func TestAccDataAccount_default(t *testing.T) {
10 | resource.ParallelTest(t, resource.TestCase{
11 | PreCheck: func() {
12 | preCheck(t)
13 | },
14 | ProviderFactories: providerFactories,
15 | // TODO: CheckDestroy: ,
16 | Steps: []resource.TestStep{
17 | {
18 | Config: testAccDataAccountConfig("tfusertest", "secure_1234"),
19 | Check: resource.ComposeTestCheckFunc(),
20 | },
21 | },
22 | })
23 | }
24 |
25 | func TestAccDataAccount_mac(t *testing.T) {
26 | resource.ParallelTest(t, resource.TestCase{
27 | PreCheck: func() {
28 | preCheck(t)
29 | },
30 | ProviderFactories: providerFactories,
31 | // TODO: CheckDestroy: ,
32 | Steps: []resource.TestStep{
33 | {
34 | Config: testAccDataMacAccountConfig("00B0D06FC226"),
35 | Check: resource.ComposeTestCheckFunc(),
36 | },
37 | },
38 | })
39 | }
40 |
41 | func testAccDataAccountConfig(name, password string) string {
42 | return fmt.Sprintf(`
43 | resource "unifi_account" "test" {
44 | name = "%s"
45 | password = "%s"
46 | }
47 |
48 | data "unifi_account" "test" {
49 | name = "%s"
50 | depends_on = [
51 | unifi_account.test
52 | ]
53 | }
54 | `, name, password, name)
55 | }
56 |
57 | func testAccDataMacAccountConfig(mac string) string {
58 | return fmt.Sprintf(`
59 | resource "unifi_account" "test" {
60 | name = "%s"
61 | password = "%s"
62 | }
63 |
64 | data "unifi_account" "test" {
65 | name = "%s"
66 | depends_on = [
67 | unifi_account.test
68 | ]
69 | }
70 | `, mac, mac, mac)
71 | }
72 |
--------------------------------------------------------------------------------
/internal/provider/data_ap_group.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func dataAPGroup() *schema.Resource {
11 | return &schema.Resource{
12 | Description: "`unifi_ap_group` data source can be used to retrieve the ID for an AP group by name.",
13 |
14 | ReadContext: dataAPGroupRead,
15 |
16 | Schema: map[string]*schema.Schema{
17 | "id": {
18 | Description: "The ID of this AP group.",
19 | Type: schema.TypeString,
20 | Computed: true,
21 | },
22 | "site": {
23 | Description: "The name of the site the AP group is associated with.",
24 | Type: schema.TypeString,
25 | Computed: true,
26 | Optional: true,
27 | },
28 | "name": {
29 | Description: "The name of the AP group to look up, leave blank to look up the default AP group.",
30 | Type: schema.TypeString,
31 | Optional: true,
32 | },
33 | },
34 | }
35 | }
36 |
37 | func dataAPGroupRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
38 | c := meta.(*client)
39 |
40 | name := d.Get("name").(string)
41 | site := d.Get("site").(string)
42 | if site == "" {
43 | site = c.site
44 | }
45 |
46 | groups, err := c.c.ListAPGroup(ctx, site)
47 | if err != nil {
48 | return diag.FromErr(err)
49 | }
50 | for _, g := range groups {
51 | if (name == "" && g.HiddenID == "default") || g.Name == name {
52 | d.SetId(g.ID)
53 | d.Set("site", site)
54 | return nil
55 | }
56 | }
57 |
58 | return diag.Errorf("AP group not found with name %s", name)
59 | }
60 |
--------------------------------------------------------------------------------
/internal/provider/data_ap_group_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
7 | )
8 |
9 | func TestAccDataAPGroup_default(t *testing.T) {
10 | resource.ParallelTest(t, resource.TestCase{
11 | PreCheck: func() {
12 | preCheck(t)
13 | },
14 | ProviderFactories: providerFactories,
15 | // TODO: CheckDestroy: ,
16 | Steps: []resource.TestStep{
17 | {
18 | Config: testAccDataAPGroupConfig_default,
19 | Check: resource.ComposeTestCheckFunc(
20 | // testCheckNetworkExists(t, "name"),
21 | ),
22 | },
23 | },
24 | })
25 | }
26 |
27 | const testAccDataAPGroupConfig_default = `
28 | data "unifi_ap_group" "default" {
29 | }
30 | `
31 |
--------------------------------------------------------------------------------
/internal/provider/data_dns_record.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func dataDNSRecord() *schema.Resource {
11 | return &schema.Resource{
12 | Description: "`unifi_dns_record` data source can be used to retrieve the ID for an DNS record by name.",
13 |
14 | ReadContext: dataDNSRecordRead,
15 |
16 | Schema: map[string]*schema.Schema{
17 | "id": {
18 | Description: "The ID of this DNS record.",
19 | Type: schema.TypeString,
20 | Computed: true,
21 | },
22 | "site": {
23 | Description: "The name of the site the DNS record is associated with.",
24 | Type: schema.TypeString,
25 | Computed: true,
26 | Optional: true,
27 | },
28 | "name": {
29 | Description: "The name of the DNS record to look up, leave blank to look up the default DNS record.",
30 | Type: schema.TypeString,
31 | Optional: true,
32 | },
33 | "port": {
34 | Description: "The port of the DNS record.",
35 | Type: schema.TypeInt,
36 | Optional: true,
37 | },
38 | "priority": {
39 | Description: "The priority of the DNS record.",
40 | Type: schema.TypeInt,
41 | Optional: true,
42 | },
43 | "record_type": {
44 | Description: "The type of the DNS record.",
45 | Type: schema.TypeString,
46 | Optional: true,
47 | },
48 | "ttl": {
49 | Description: "The TTL of the DNS record.",
50 | Type: schema.TypeInt,
51 | Optional: true,
52 | },
53 | "value": {
54 | Description: "The value of the DNS record.",
55 | Type: schema.TypeString,
56 | Optional: true,
57 | },
58 | "weight": {
59 | Description: "The weight of the DNS record.",
60 | Type: schema.TypeInt,
61 | Optional: true,
62 | },
63 | },
64 | }
65 | }
66 |
67 | func dataDNSRecordRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
68 | c := meta.(*client)
69 |
70 | name := d.Get("name").(string)
71 | site := d.Get("site").(string)
72 | if site == "" {
73 | site = c.site
74 | }
75 |
76 | groups, err := c.c.ListDNSRecord(ctx, site)
77 | if err != nil {
78 | return diag.FromErr(err)
79 | }
80 | for _, g := range groups {
81 | if (name == "" && g.HiddenID == "default") || g.Key == name {
82 | d.SetId(g.ID)
83 | d.Set("site", site)
84 | d.Set("port", g.Port)
85 | d.Set("priority", g.Priority)
86 | d.Set("record_type", g.RecordType)
87 | d.Set("ttl", g.Ttl)
88 | d.Set("value", g.Value)
89 | d.Set("weight", g.Weight)
90 |
91 | return nil
92 | }
93 | }
94 |
95 | return diag.Errorf("DNS record not found with name %s", name)
96 | }
97 |
--------------------------------------------------------------------------------
/internal/provider/data_dns_record_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
7 | )
8 |
9 | func TestAccDataDNSRecord_default(t *testing.T) {
10 | resource.ParallelTest(t, resource.TestCase{
11 | PreCheck: func() {
12 | preCheck(t)
13 | },
14 | ProviderFactories: providerFactories,
15 | // TODO: CheckDestroy: ,
16 | Steps: []resource.TestStep{
17 | {
18 | Config: testAccDataDNSRecordConfig_default,
19 | Check: resource.ComposeTestCheckFunc(
20 | // testCheckNetworkExists(t, "name"),
21 | ),
22 | },
23 | },
24 | })
25 | }
26 |
27 | const testAccDataDNSRecordConfig_default = `
28 | data "unifi_dns_record" "default" {
29 | }
30 | `
31 |
--------------------------------------------------------------------------------
/internal/provider/data_network_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/hashicorp/go-version"
8 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
9 | )
10 |
11 | func TestAccDataNetwork_byName(t *testing.T) {
12 | defaultName := "Default"
13 | v, err := version.NewVersion(testClient.Version())
14 | if err != nil {
15 | t.Fatalf("error parsing version: %s", err)
16 | }
17 | if v.LessThan(controllerV7) {
18 | defaultName = "LAN"
19 | }
20 |
21 | resource.ParallelTest(t, resource.TestCase{
22 | PreCheck: func() {
23 | preCheck(t)
24 | },
25 | ProviderFactories: providerFactories,
26 | // TODO: CheckDestroy: ,
27 | Steps: []resource.TestStep{
28 | {
29 | Config: testAccDataNetworkConfig_byName(defaultName),
30 | Check: resource.ComposeTestCheckFunc(
31 | // testCheckNetworkExists(t, "name"),
32 | ),
33 | },
34 | },
35 | })
36 | }
37 |
38 | func TestAccDataNetwork_byID(t *testing.T) {
39 | defaultName := "Default"
40 | v, err := version.NewVersion(testClient.Version())
41 | if err != nil {
42 | t.Fatalf("error parsing version: %s", err)
43 | }
44 | if v.LessThan(controllerV7) {
45 | defaultName = "LAN"
46 | }
47 |
48 | resource.ParallelTest(t, resource.TestCase{
49 | PreCheck: func() {
50 | preCheck(t)
51 | },
52 | ProviderFactories: providerFactories,
53 | // TODO: CheckDestroy: ,
54 | Steps: []resource.TestStep{
55 | {
56 | Config: testAccDataNetworkConfig_byID(defaultName),
57 | Check: resource.ComposeTestCheckFunc(
58 | // testCheckNetworkExists(t, "name"),
59 | ),
60 | },
61 | },
62 | })
63 | }
64 |
65 | func testAccDataNetworkConfig_byName(name string) string {
66 | return fmt.Sprintf(`
67 | data "unifi_network" "lan" {
68 | name = %q
69 | }
70 | `, name)
71 | }
72 |
73 | func testAccDataNetworkConfig_byID(name string) string {
74 | return fmt.Sprintf(`
75 | data "unifi_network" "lan" {
76 | name = %q
77 | }
78 |
79 | data "unifi_network" "lan_id" {
80 | id = data.unifi_network.lan.id
81 | }
82 | `, name)
83 | }
84 |
--------------------------------------------------------------------------------
/internal/provider/data_port_profile.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func dataPortProfile() *schema.Resource {
11 | return &schema.Resource{
12 | Description: "`unifi_port_profile` data source can be used to retrieve the ID for a port profile by name.",
13 |
14 | ReadContext: dataPortProfileRead,
15 |
16 | Schema: map[string]*schema.Schema{
17 | "id": {
18 | Description: "The ID of this port profile.",
19 | Type: schema.TypeString,
20 | Computed: true,
21 | },
22 | "site": {
23 | Description: "The name of the site the port profile is associated with.",
24 | Type: schema.TypeString,
25 | Computed: true,
26 | Optional: true,
27 | },
28 | "name": {
29 | Description: "The name of the port profile to look up.",
30 | Type: schema.TypeString,
31 | Optional: true,
32 | Default: "All",
33 | },
34 | },
35 | }
36 | }
37 |
38 | func dataPortProfileRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
39 | c := meta.(*client)
40 |
41 | name := d.Get("name").(string)
42 | site := d.Get("site").(string)
43 | if site == "" {
44 | site = c.site
45 | }
46 |
47 | groups, err := c.c.ListPortProfile(ctx, site)
48 | if err != nil {
49 | return diag.FromErr(err)
50 | }
51 | for _, g := range groups {
52 | if g.Name == name {
53 | d.SetId(g.ID)
54 |
55 | d.Set("site", site)
56 |
57 | return nil
58 | }
59 | }
60 |
61 | return diag.Errorf("port profile not found with name %s", name)
62 | }
63 |
--------------------------------------------------------------------------------
/internal/provider/data_port_profile_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
8 | )
9 |
10 | func TestAccDataPortProfile_default(t *testing.T) {
11 | resource.ParallelTest(t, resource.TestCase{
12 | PreCheck: func() {
13 | preCheck(t)
14 | preCheckVersionConstraint(t, "< 7.4")
15 | },
16 | ProviderFactories: providerFactories,
17 | // TODO: CheckDestroy: ,
18 | Steps: []resource.TestStep{
19 | {
20 | Config: testAccDataPortProfileConfig_default,
21 | Check: resource.ComposeTestCheckFunc(),
22 | },
23 | },
24 | })
25 | }
26 |
27 | func TestAccDataPortProfile_multiple_providers(t *testing.T) {
28 | resource.ParallelTest(t, resource.TestCase{
29 | PreCheck: func() {
30 | preCheck(t)
31 | preCheckVersionConstraint(t, "< 7.4")
32 | },
33 | ProviderFactories: map[string]func() (*schema.Provider, error){
34 | "unifi2": func() (*schema.Provider, error) {
35 | return New("acctest")(), nil
36 | },
37 | "unifi3": func() (*schema.Provider, error) {
38 | return New("acctest")(), nil
39 | },
40 | },
41 | // TODO: CheckDestroy: ,
42 | Steps: []resource.TestStep{
43 | {
44 | Config: `
45 | data "unifi_port_profile" "unifi2" {
46 | provider = "unifi2"
47 | }
48 | data "unifi_port_profile" "unifi3" {
49 | provider = "unifi3"
50 | }
51 | `,
52 | Check: resource.ComposeTestCheckFunc(
53 | // testCheckNetworkExists(t, "name"),
54 | ),
55 | },
56 | },
57 | })
58 | }
59 |
60 | const testAccDataPortProfileConfig_default = `
61 | data "unifi_port_profile" "default" {
62 | }
63 | `
64 |
--------------------------------------------------------------------------------
/internal/provider/data_radius_profile.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func dataRADIUSProfile() *schema.Resource {
11 | return &schema.Resource{
12 | Description: "`unifi_radius_profile` data source can be used to retrieve the ID for a RADIUS profile by name.",
13 |
14 | ReadContext: dataRADIUSProfileRead,
15 |
16 | Schema: map[string]*schema.Schema{
17 | "id": {
18 | Description: "The ID of this AP group.",
19 | Type: schema.TypeString,
20 | Computed: true,
21 | },
22 | "site": {
23 | Description: "The name of the site the RADIUS profile is associated with.",
24 | Type: schema.TypeString,
25 | Computed: true,
26 | Optional: true,
27 | },
28 | "name": {
29 | Description: "The name of the RADIUS profile to look up.",
30 | Type: schema.TypeString,
31 | Optional: true,
32 | Default: "Default",
33 | },
34 | },
35 | }
36 | }
37 |
38 | func dataRADIUSProfileRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
39 | c := meta.(*client)
40 |
41 | name := d.Get("name").(string)
42 | site := d.Get("site").(string)
43 | if site == "" {
44 | site = c.site
45 | }
46 |
47 | profiles, err := c.c.ListRADIUSProfile(ctx, site)
48 | if err != nil {
49 | return diag.FromErr(err)
50 | }
51 | for _, g := range profiles {
52 | if g.Name == name {
53 | d.SetId(g.ID)
54 | d.Set("site", site)
55 | return nil
56 | }
57 | }
58 |
59 | return diag.Errorf("RADIUS profile not found with name %s", name)
60 | }
61 |
--------------------------------------------------------------------------------
/internal/provider/data_user.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
10 | )
11 |
12 | func dataUser() *schema.Resource {
13 | return &schema.Resource{
14 | Description: "`unifi_user` retrieves properties of a user (or \"client\" in the UI) of the network by MAC address.",
15 |
16 | ReadContext: dataUserRead,
17 |
18 | Schema: map[string]*schema.Schema{
19 | "site": {
20 | Description: "The name of the site the user is associated with.",
21 | Type: schema.TypeString,
22 | Computed: true,
23 | Optional: true,
24 | },
25 | "mac": {
26 | Description: "The MAC address of the user.",
27 | Type: schema.TypeString,
28 | Required: true,
29 | DiffSuppressFunc: macDiffSuppressFunc,
30 | ValidateFunc: validation.StringMatch(macAddressRegexp, "Mac address is invalid"),
31 | },
32 |
33 | // read-only / computed
34 | "id": {
35 | Description: "The ID of the user.",
36 | Type: schema.TypeString,
37 | Computed: true,
38 | },
39 | "name": {
40 | Description: "The name of the user.",
41 | Type: schema.TypeString,
42 | Computed: true,
43 | },
44 | "user_group_id": {
45 | Description: "The user group ID for the user.",
46 | Type: schema.TypeString,
47 | Computed: true,
48 | },
49 | "note": {
50 | Description: "A note with additional information for the user.",
51 | Type: schema.TypeString,
52 | Computed: true,
53 | },
54 | "fixed_ip": {
55 | Description: "fixed IPv4 address set for this user.",
56 | Type: schema.TypeString,
57 | Computed: true,
58 | },
59 | "network_id": {
60 | Description: "The network ID for this user.",
61 | Type: schema.TypeString,
62 | Computed: true,
63 | },
64 | "blocked": {
65 | Description: "Specifies whether this user should be blocked from the network.",
66 | Type: schema.TypeBool,
67 | Computed: true,
68 | },
69 | "dev_id_override": {
70 | Description: "Override the device fingerprint.",
71 | Type: schema.TypeInt,
72 | Computed: true,
73 | },
74 | "hostname": {
75 | Description: "The hostname of the user.",
76 | Type: schema.TypeString,
77 | Computed: true,
78 | },
79 | "ip": {
80 | Description: "The IP address of the user.",
81 | Type: schema.TypeString,
82 | Computed: true,
83 | },
84 | "local_dns_record": {
85 | Description: "The local DNS record for this user.",
86 | Type: schema.TypeString,
87 | Computed: true,
88 | },
89 | },
90 | }
91 | }
92 |
93 | func dataUserRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
94 | c := meta.(*client)
95 |
96 | site := d.Get("site").(string)
97 | if site == "" {
98 | site = c.site
99 | }
100 | mac := d.Get("mac").(string)
101 |
102 | macResp, err := c.c.GetUserByMAC(ctx, site, strings.ToLower(mac))
103 | if err != nil {
104 | return diag.FromErr(err)
105 | }
106 |
107 | resp, err := c.c.GetUser(ctx, site, macResp.ID)
108 | if err != nil {
109 | return diag.FromErr(err)
110 | }
111 |
112 | // for some reason the IP address is only on this endpoint, so issue another request
113 |
114 | resp.IP = macResp.IP
115 | fixedIP := ""
116 | if resp.UseFixedIP {
117 | fixedIP = resp.FixedIP
118 | }
119 | localDnsRecord := ""
120 | if resp.LocalDNSRecordEnabled {
121 | localDnsRecord = resp.LocalDNSRecord
122 | }
123 | d.SetId(resp.ID)
124 | d.Set("site", site)
125 | d.Set("mac", resp.MAC)
126 | d.Set("name", resp.Name)
127 | d.Set("user_group_id", resp.UserGroupID)
128 | d.Set("note", resp.Note)
129 | d.Set("fixed_ip", fixedIP)
130 | d.Set("network_id", resp.NetworkID)
131 | d.Set("blocked", resp.Blocked)
132 | d.Set("dev_id_override", resp.DevIdOverride)
133 | d.Set("hostname", resp.Hostname)
134 | d.Set("ip", resp.IP)
135 | d.Set("ip", localDnsRecord)
136 |
137 | return nil
138 | }
139 |
--------------------------------------------------------------------------------
/internal/provider/data_user_group.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func dataUserGroup() *schema.Resource {
11 | return &schema.Resource{
12 | Description: "`unifi_user_group` data source can be used to retrieve the ID for a user group by name.",
13 |
14 | ReadContext: dataUserGroupRead,
15 |
16 | Schema: map[string]*schema.Schema{
17 | "id": {
18 | Description: "The ID of this AP group.",
19 | Type: schema.TypeString,
20 | Computed: true,
21 | },
22 | "site": {
23 | Description: "The name of the site the user group is associated with.",
24 | Type: schema.TypeString,
25 | Computed: true,
26 | Optional: true,
27 | },
28 | "name": {
29 | Description: "The name of the user group to look up.",
30 | Type: schema.TypeString,
31 | Optional: true,
32 | Default: "Default",
33 | },
34 |
35 | "qos_rate_max_down": {
36 | Type: schema.TypeInt,
37 | Computed: true,
38 | },
39 | "qos_rate_max_up": {
40 | Type: schema.TypeInt,
41 | Computed: true,
42 | },
43 | },
44 | }
45 | }
46 |
47 | func dataUserGroupRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
48 | c := meta.(*client)
49 |
50 | name := d.Get("name").(string)
51 | site := d.Get("site").(string)
52 | if site == "" {
53 | site = c.site
54 | }
55 |
56 | groups, err := c.c.ListUserGroup(ctx, site)
57 | if err != nil {
58 | return diag.FromErr(err)
59 | }
60 | for _, g := range groups {
61 | if g.Name == name {
62 | d.SetId(g.ID)
63 |
64 | d.Set("site", site)
65 | d.Set("qos_rate_max_down", g.QOSRateMaxDown)
66 | d.Set("qos_rate_max_up", g.QOSRateMaxUp)
67 |
68 | return nil
69 | }
70 | }
71 |
72 | return diag.Errorf("user group not found with name %s", name)
73 | }
74 |
--------------------------------------------------------------------------------
/internal/provider/data_user_group_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
8 | )
9 |
10 | func TestAccDataUserGroup_default(t *testing.T) {
11 | resource.ParallelTest(t, resource.TestCase{
12 | PreCheck: func() { preCheck(t) },
13 | ProviderFactories: providerFactories,
14 | // TODO: CheckDestroy: ,
15 | Steps: []resource.TestStep{
16 | {
17 | Config: testAccDataUserGroupConfig_default,
18 | Check: resource.ComposeTestCheckFunc(
19 | // testCheckNetworkExists(t, "name"),
20 | ),
21 | },
22 | },
23 | })
24 | }
25 |
26 | func TestAccDataUserGroup_multiple_providers(t *testing.T) {
27 | resource.ParallelTest(t, resource.TestCase{
28 | PreCheck: func() { preCheck(t) },
29 | ProviderFactories: map[string]func() (*schema.Provider, error){
30 | "unifi2": func() (*schema.Provider, error) {
31 | return New("acctest")(), nil
32 | },
33 | "unifi3": func() (*schema.Provider, error) {
34 | return New("acctest")(), nil
35 | },
36 | },
37 | // TODO: CheckDestroy: ,
38 | Steps: []resource.TestStep{
39 | {
40 | Config: `
41 | data "unifi_user_group" "unifi2" {
42 | provider = "unifi2"
43 | }
44 | data "unifi_user_group" "unifi3" {
45 | provider = "unifi3"
46 | }
47 | `,
48 | Check: resource.ComposeTestCheckFunc(
49 | // testCheckNetworkExists(t, "name"),
50 | ),
51 | },
52 | },
53 | })
54 | }
55 |
56 | const testAccDataUserGroupConfig_default = `
57 | data "unifi_user_group" "default" {
58 | }
59 | `
60 |
--------------------------------------------------------------------------------
/internal/provider/data_user_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
9 | "github.com/ubiquiti-community/go-unifi/unifi"
10 | )
11 |
12 | func TestAccDataUser_default(t *testing.T) {
13 | mac, unallocateTestMac := allocateTestMac(t)
14 | defer unallocateTestMac()
15 |
16 | resource.ParallelTest(t, resource.TestCase{
17 | PreCheck: func() {
18 | //preCheck(t)
19 |
20 | _, err := testClient.CreateUser(context.Background(), "default", &unifi.User{
21 | MAC: mac,
22 | Name: "tfacc-User-Data",
23 | Note: "tfacc-User-Data",
24 | })
25 | if err != nil {
26 | t.Fatal(err)
27 | }
28 | },
29 | //PreCheck: func() { preCheck(t) },
30 | ProviderFactories: providerFactories,
31 | Steps: []resource.TestStep{
32 | {
33 | Config: testAccDataUserConfig_default(mac),
34 | Check: resource.ComposeTestCheckFunc(),
35 | },
36 | },
37 | })
38 | }
39 |
40 | func testAccDataUserConfig_default(mac string) string {
41 | return fmt.Sprintf(`
42 | data "unifi_user" "test" {
43 | mac = "%s"
44 | }
45 | `, mac)
46 | }
47 |
--------------------------------------------------------------------------------
/internal/provider/importer.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | func importSiteAndID(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
11 | if id := d.Id(); strings.Contains(id, ":") {
12 | importParts := strings.SplitN(id, ":", 2)
13 | d.SetId(importParts[1])
14 | d.Set("site", importParts[0])
15 | }
16 | return []*schema.ResourceData{d}, nil
17 | }
18 |
--------------------------------------------------------------------------------
/internal/provider/mac.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | )
9 |
10 | var macAddressRegexp = regexp.MustCompile("^([0-9a-fA-F][0-9a-fA-F][-:]){5}([0-9a-fA-F][0-9a-fA-F])$")
11 |
12 | func cleanMAC(mac string) string {
13 | return strings.TrimSpace(strings.ReplaceAll(strings.ToLower(mac), "-", ":"))
14 | }
15 |
16 | func macDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool {
17 | old = cleanMAC(old)
18 | new = cleanMAC(new)
19 | return old == new
20 | }
21 |
--------------------------------------------------------------------------------
/internal/provider/markdown.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import "strconv"
4 |
5 | func markdownValueListInt(values []int) string {
6 | switch {
7 | case len(values) == 0:
8 | return ""
9 | case len(values) == 1:
10 | return "`" + strconv.Itoa(values[0]) + "`"
11 | default:
12 | s := ""
13 | for i := 0; i < len(values)-1; i++ {
14 | s += "`" + strconv.Itoa(values[i]) + "`, "
15 | }
16 | s += " and `" + strconv.Itoa(values[len(values)-1]) + "`"
17 | return s
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/internal/provider/port_range.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "regexp"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
7 | )
8 |
9 | var (
10 | portRangeRegexp = regexp.MustCompile("(([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5]))+(,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])){0,14}")
11 | validatePortRange = validation.StringMatch(portRangeRegexp, "invalid port range")
12 | )
13 |
--------------------------------------------------------------------------------
/internal/provider/provider_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "fmt"
7 | "math"
8 | "net"
9 | "os"
10 | "sync"
11 | "testing"
12 |
13 | "github.com/apparentlymart/go-cidr/cidr"
14 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
15 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
16 | "github.com/hashicorp/terraform-plugin-testing/terraform"
17 | "github.com/testcontainers/testcontainers-go"
18 | "github.com/testcontainers/testcontainers-go/modules/compose"
19 | "github.com/ubiquiti-community/go-unifi/unifi"
20 | )
21 |
22 | var providerFactories = map[string]func() (*schema.Provider, error){
23 | "unifi": func() (*schema.Provider, error) {
24 | return New("acctest")(), nil
25 | },
26 | }
27 |
28 | var testClient *unifi.Client
29 |
30 | func TestMain(m *testing.M) {
31 | if os.Getenv("TF_ACC") == "" {
32 | // short circuit non acceptance test runs
33 | os.Exit(m.Run())
34 | }
35 |
36 | os.Exit(runAcceptanceTests(m))
37 | }
38 |
39 | func runAcceptanceTests(m *testing.M) int {
40 | dc, err := compose.NewDockerCompose("../../docker-compose.yaml")
41 | if err != nil {
42 | panic(err)
43 | }
44 |
45 | ctx, cancel := context.WithCancel(context.Background())
46 | defer cancel()
47 |
48 | if err = dc.WithOsEnv().Up(ctx, compose.Wait(true)); err != nil {
49 | panic(err)
50 | }
51 |
52 | defer func() {
53 | if err := dc.Down(context.Background(), compose.RemoveOrphans(true), compose.RemoveImagesLocal); err != nil {
54 | panic(err)
55 | }
56 | }()
57 |
58 | container, err := dc.ServiceContainer(ctx, "unifi")
59 | if err != nil {
60 | panic(err)
61 | }
62 |
63 | // Dump the container logs on exit.
64 | //
65 | // TODO: Use https://pkg.go.dev/github.com/testcontainers/testcontainers-go#LogConsumer instead.
66 | defer func() {
67 | if os.Getenv("UNIFI_STDOUT") == "" {
68 | return
69 | }
70 |
71 | stream, err := container.Logs(ctx)
72 | if err != nil {
73 | panic(err)
74 | }
75 |
76 | buffer := new(bytes.Buffer)
77 | buffer.ReadFrom(stream)
78 | testcontainers.Logger.Printf("%s", buffer)
79 | }()
80 |
81 | endpoint, err := container.PortEndpoint(ctx, "8443/tcp", "https")
82 | if err != nil {
83 | panic(err)
84 | }
85 |
86 | const user = "admin"
87 | const password = "admin"
88 |
89 | if err = os.Setenv("UNIFI_USERNAME", user); err != nil {
90 | panic(err)
91 | }
92 |
93 | if err = os.Setenv("UNIFI_PASSWORD", password); err != nil {
94 | panic(err)
95 | }
96 |
97 | if err = os.Setenv("UNIFI_INSECURE", "true"); err != nil {
98 | panic(err)
99 | }
100 |
101 | if err = os.Setenv("UNIFI_API", endpoint); err != nil {
102 | panic(err)
103 | }
104 |
105 | testClient = &unifi.Client{}
106 | setHTTPClient(testClient, true, "unifi")
107 | testClient.SetBaseURL(endpoint)
108 | if err = testClient.Login(ctx, user, password); err != nil {
109 | panic(err)
110 | }
111 |
112 | return m.Run()
113 | }
114 |
115 | func importStep(name string, ignore ...string) resource.TestStep {
116 | step := resource.TestStep{
117 | ResourceName: name,
118 | ImportState: true,
119 | ImportStateVerify: true,
120 | }
121 |
122 | if len(ignore) > 0 {
123 | step.ImportStateVerifyIgnore = ignore
124 | }
125 |
126 | return step
127 | }
128 |
129 | func siteAndIDImportStateIDFunc(resourceName string) func(*terraform.State) (string, error) {
130 | return func(s *terraform.State) (string, error) {
131 | rs, ok := s.RootModule().Resources[resourceName]
132 | if !ok {
133 | return "", fmt.Errorf("not found: %s", resourceName)
134 | }
135 | networkID := rs.Primary.Attributes["id"]
136 | site := rs.Primary.Attributes["site"]
137 | return site + ":" + networkID, nil
138 | }
139 | }
140 |
141 | func preCheck(t *testing.T) {
142 | variables := []string{
143 | "UNIFI_USERNAME",
144 | "UNIFI_PASSWORD",
145 | "UNIFI_API",
146 | }
147 |
148 | for _, variable := range variables {
149 | value := os.Getenv(variable)
150 | if value == "" {
151 | t.Fatalf("`%s` must be set for acceptance tests!", variable)
152 | }
153 | }
154 | }
155 |
156 | const (
157 | vlanMin = 2
158 | vlanMax = 4095
159 | )
160 |
161 | var (
162 | network = &net.IPNet{
163 | IP: net.IPv4(10, 0, 0, 0).To4(),
164 | Mask: net.IPv4Mask(255, 0, 0, 0),
165 | }
166 |
167 | vlanLock sync.Mutex
168 | vlanNext = vlanMin
169 | )
170 |
171 | func getTestVLAN(t *testing.T) (*net.IPNet, int) {
172 | vlanLock.Lock()
173 | defer vlanLock.Unlock()
174 |
175 | vlan := vlanNext
176 | vlanNext++
177 |
178 | subnet, err := cidr.Subnet(network, int(math.Ceil(math.Log2(vlanMax))), vlan)
179 | if err != nil {
180 | t.Error(err)
181 | }
182 |
183 | return subnet, vlan
184 | }
185 |
--------------------------------------------------------------------------------
/internal/provider/resource_account.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
9 | "github.com/ubiquiti-community/go-unifi/unifi"
10 | )
11 |
12 | func resourceAccount() *schema.Resource {
13 | return &schema.Resource{
14 | Description: "`unifi_account` manages a RADIUS user account\n\n" +
15 | "To authenticate devices based on MAC address, use the MAC address as the username and password under client creation. \n" +
16 | "Convert lowercase letters to uppercase, and also remove colons or periods from the MAC address. \n\n" +
17 | "ATTENTION: If the user profile does not include a VLAN, the client will fall back to the untagged VLAN. \n\n" +
18 | "NOTE: MAC-based authentication accounts can only be used for wireless and wired clients. L2TP remote access does not apply.",
19 |
20 | CreateContext: resourceAccountCreate,
21 | ReadContext: resourceAccountRead,
22 | UpdateContext: resourceAccountUpdate,
23 | DeleteContext: resourceAccountDelete,
24 | Importer: &schema.ResourceImporter{
25 | StateContext: importSiteAndID,
26 | },
27 |
28 | Schema: map[string]*schema.Schema{
29 | "id": {
30 | Description: "The ID of the account.",
31 | Type: schema.TypeString,
32 | Computed: true,
33 | },
34 | "site": {
35 | Description: "The name of the site to associate the account with.",
36 | Type: schema.TypeString,
37 | Computed: true,
38 | Optional: true,
39 | ForceNew: true,
40 | },
41 | "name": {
42 | Description: "The name of the account.",
43 | Type: schema.TypeString,
44 | Required: true,
45 | },
46 | "password": {
47 | Description: "The password of the account.",
48 | Type: schema.TypeString,
49 | Required: true,
50 | Sensitive: true,
51 | },
52 | "tunnel_type": {
53 | Description: "See [RFC 2868](https://www.rfc-editor.org/rfc/rfc2868) section 3.1", // @TODO: better documentation https://help.ui.com/hc/en-us/articles/360015268353-UniFi-USG-UDM-Configuring-RADIUS-Server#6
54 | Type: schema.TypeInt,
55 | Optional: true,
56 | Default: 13,
57 | ValidateFunc: validation.IntBetween(1, 13),
58 | },
59 | "tunnel_medium_type": {
60 | Description: "See [RFC 2868](https://www.rfc-editor.org/rfc/rfc2868) section 3.2", // @TODO: better documentation https://help.ui.com/hc/en-us/articles/360015268353-UniFi-USG-UDM-Configuring-RADIUS-Server#6
61 | Type: schema.TypeInt,
62 | Optional: true,
63 | Default: 6,
64 | ValidateFunc: validation.IntBetween(1, 15),
65 | },
66 | "network_id": {
67 | Description: "ID of the network for this account",
68 | Type: schema.TypeString,
69 | Optional: true,
70 | },
71 | },
72 | }
73 | }
74 |
75 | func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
76 | c := meta.(*client)
77 |
78 | req, err := resourceAccountGetResourceData(d)
79 | if err != nil {
80 | return diag.FromErr(err)
81 | }
82 |
83 | site := d.Get("site").(string)
84 | if site == "" {
85 | site = c.site
86 | }
87 |
88 | resp, err := c.c.CreateAccount(ctx, site, req)
89 | if err != nil {
90 | return diag.FromErr(err)
91 | }
92 |
93 | d.SetId(resp.ID)
94 |
95 | return resourceAccountSetResourceData(resp, d, site)
96 | }
97 |
98 | func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
99 | c := meta.(*client)
100 |
101 | site := d.Get("site").(string)
102 | if site == "" {
103 | site = c.site
104 | }
105 |
106 | req, err := resourceAccountGetResourceData(d)
107 | if err != nil {
108 | return diag.FromErr(err)
109 | }
110 |
111 | req.ID = d.Id()
112 | req.SiteID = site
113 |
114 | resp, err := c.c.UpdateAccount(ctx, site, req)
115 | if err != nil {
116 | return diag.FromErr(err)
117 | }
118 |
119 | return resourceAccountSetResourceData(resp, d, site)
120 | }
121 |
122 | func resourceAccountDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
123 | c := meta.(*client)
124 |
125 | //name := d.Get("name").(string)
126 | site := d.Get("site").(string)
127 | if site == "" {
128 | site = c.site
129 | }
130 |
131 | id := d.Id()
132 | err := c.c.DeleteAccount(ctx, site, id)
133 | if _, ok := err.(*unifi.NotFoundError); ok {
134 | return nil
135 | }
136 | return diag.FromErr(err)
137 | }
138 |
139 | func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
140 | c := meta.(*client)
141 |
142 | id := d.Id()
143 |
144 | site := d.Get("site").(string)
145 | if site == "" {
146 | site = c.site
147 | }
148 |
149 | resp, err := c.c.GetAccount(ctx, site, id)
150 | if _, ok := err.(*unifi.NotFoundError); ok {
151 | d.SetId("")
152 | return nil
153 | }
154 | if err != nil {
155 | return diag.FromErr(err)
156 | }
157 |
158 | return resourceAccountSetResourceData(resp, d, site)
159 | }
160 |
161 | func resourceAccountSetResourceData(resp *unifi.Account, d *schema.ResourceData, site string) diag.Diagnostics {
162 | d.Set("site", site)
163 | d.Set("name", resp.Name)
164 | d.Set("password", resp.XPassword)
165 | d.Set("tunnel_type", resp.TunnelType)
166 | d.Set("tunnel_medium_type", resp.TunnelMediumType)
167 | d.Set("network_id", resp.NetworkID)
168 | return nil
169 | }
170 |
171 | func resourceAccountGetResourceData(d *schema.ResourceData) (*unifi.Account, error) {
172 | return &unifi.Account{
173 | Name: d.Get("name").(string),
174 | XPassword: d.Get("password").(string),
175 | TunnelType: d.Get("tunnel_type").(int),
176 | TunnelMediumType: d.Get("tunnel_medium_type").(int),
177 | NetworkID: d.Get("network_id").(string),
178 | }, nil
179 | }
180 |
--------------------------------------------------------------------------------
/internal/provider/resource_account_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
8 | )
9 |
10 | func TestAccAccount_basic(t *testing.T) {
11 | resource.ParallelTest(t, resource.TestCase{
12 | PreCheck: func() { preCheck(t) },
13 | ProviderFactories: providerFactories,
14 | // TODO: CheckDestroy: ,
15 | Steps: []resource.TestStep{
16 | {
17 | Config: testAccAccountConfig("tfacc", "secure"),
18 | Check: resource.ComposeTestCheckFunc(
19 | // testCheckNetworkExists(t, "name"),
20 | resource.TestCheckResourceAttr("unifi_account.test", "name", "tfacc"),
21 | ),
22 | },
23 | importStep("unifi_account.test"),
24 | },
25 | })
26 | }
27 |
28 | func TestAccAccount_mac(t *testing.T) {
29 | resource.ParallelTest(t, resource.TestCase{
30 | PreCheck: func() { preCheck(t) },
31 | ProviderFactories: providerFactories,
32 | // TODO: CheckDestroy: ,
33 | Steps: []resource.TestStep{
34 | {
35 | Config: testAccAccountConfig("00B0D06FC226", "00B0D06FC226"),
36 | Check: resource.ComposeTestCheckFunc(
37 | // testCheckNetworkExists(t, "name"),
38 | resource.TestCheckResourceAttr("unifi_account.test", "name", "00B0D06FC226"),
39 | resource.TestCheckResourceAttr("unifi_account.test", "password", "00B0D06FC226"),
40 | ),
41 | },
42 | importStep("unifi_account.test"),
43 | },
44 | })
45 | }
46 |
47 | func testAccAccountConfig(name, password string) string {
48 | return fmt.Sprintf(`
49 | resource "unifi_account" "test" {
50 | name = "%s"
51 | password = "%s"
52 | }
53 | `, name, password)
54 | }
55 |
--------------------------------------------------------------------------------
/internal/provider/resource_dns_record.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "slices"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9 | "github.com/ubiquiti-community/go-unifi/unifi"
10 | )
11 |
12 | func resourceDNSRecord() *schema.Resource {
13 | return &schema.Resource{
14 | Description: "`unifi_dns_record` manages DNS record settings for different providers.",
15 |
16 | CreateContext: resourceDNSRecordCreate,
17 | ReadContext: resourceDNSRecordRead,
18 | UpdateContext: resourceDNSRecordUpdate,
19 | DeleteContext: resourceDNSRecordDelete,
20 | Importer: &schema.ResourceImporter{
21 | StateContext: importSiteAndID,
22 | },
23 |
24 | Schema: map[string]*schema.Schema{
25 | "id": {
26 | Description: "The ID of the DNS record.",
27 | Type: schema.TypeString,
28 | Computed: true,
29 | },
30 | "site": {
31 | Description: "The name of the site to associate the DNS record with.",
32 | Type: schema.TypeString,
33 | Computed: true,
34 | Optional: true,
35 | ForceNew: true,
36 | },
37 | "name": {
38 | Description: "The key of the DNS record.",
39 | Type: schema.TypeString,
40 | Required: true,
41 | ForceNew: true,
42 | },
43 | "enabled": {
44 | Description: "Whether the DNS record is enabled.",
45 | Type: schema.TypeBool,
46 | Optional: true,
47 | Default: true,
48 | ForceNew: false,
49 | },
50 | "port": {
51 | Description: "The port of the DNS record.",
52 | Type: schema.TypeInt,
53 | Required: true,
54 | },
55 | "priority": {
56 | Description: "The priority of the DNS record.",
57 | Type: schema.TypeInt,
58 | Optional: true,
59 | },
60 | "record_type": {
61 | Description: "The type of the DNS record.",
62 | Type: schema.TypeString,
63 | Optional: true,
64 | },
65 | "ttl": {
66 | Description: "The TTL of the DNS record.",
67 | Type: schema.TypeInt,
68 | Optional: true,
69 | },
70 | "value": {
71 | Description: "The value of the DNS record.",
72 | Type: schema.TypeString,
73 | Required: true,
74 | },
75 | "weight": {
76 | Description: "The weight of the DNS record.",
77 | Type: schema.TypeInt,
78 | Optional: true,
79 | },
80 | },
81 | }
82 | }
83 |
84 | func resourceDNSRecordCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
85 | c := meta.(*client)
86 |
87 | req, err := resourceDNSRecordGetResourceData(d)
88 | if err != nil {
89 | return diag.FromErr(err)
90 | }
91 |
92 | site := d.Get("site").(string)
93 | if site == "" {
94 | site = c.site
95 | }
96 |
97 | resp, err := c.c.CreateDNSRecord(ctx, site, req)
98 | if err != nil {
99 | return diag.FromErr(err)
100 | }
101 |
102 | d.SetId(resp.ID)
103 |
104 | return resourceDNSRecordSetResourceData(resp, d, site)
105 | }
106 |
107 | func resourceDNSRecordGetResourceData(d *schema.ResourceData) (*unifi.DNSRecord, error) {
108 | r := &unifi.DNSRecord{
109 | Enabled: d.Get("enabled").(bool),
110 | Key: d.Get("name").(string),
111 | Port: d.Get("port").(int),
112 | Priority: d.Get("priority").(int),
113 | RecordType: d.Get("record_type").(string),
114 | Ttl: d.Get("ttl").(int),
115 | Value: d.Get("value").(string),
116 | Weight: d.Get("weight").(int),
117 | }
118 |
119 | return r, nil
120 | }
121 |
122 | func resourceDNSRecordSetResourceData(resp *unifi.DNSRecord, d *schema.ResourceData, site string) diag.Diagnostics {
123 | d.Set("enabled", resp.Enabled)
124 | d.Set("name", resp.Key)
125 | d.Set("port", resp.Port)
126 | d.Set("priority", resp.Priority)
127 | d.Set("record_type", resp.RecordType)
128 | d.Set("ttl", resp.Ttl)
129 | d.Set("value", resp.Value)
130 | d.Set("weight", resp.Weight)
131 | d.Set("site", site)
132 |
133 | return nil
134 | }
135 |
136 | func resourceDNSRecordRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
137 | c := meta.(*client)
138 |
139 | id := d.Id()
140 |
141 | site := d.Get("site").(string)
142 | if site == "" {
143 | site = c.site
144 | }
145 |
146 | resp, err := c.c.ListDNSRecord(ctx, site)
147 |
148 | if err != nil {
149 | return diag.FromErr(err)
150 | }
151 |
152 | i := slices.IndexFunc(resp, func(r unifi.DNSRecord) bool {
153 | return r.ID == id
154 | })
155 |
156 | if i == -1 {
157 | d.SetId("")
158 | return nil
159 | }
160 |
161 | rec := resp[i]
162 |
163 | return resourceDNSRecordSetResourceData(&rec, d, site)
164 | }
165 |
166 | func resourceDNSRecordUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
167 | c := meta.(*client)
168 |
169 | req, err := resourceDNSRecordGetResourceData(d)
170 | if err != nil {
171 | return diag.FromErr(err)
172 | }
173 |
174 | req.ID = d.Id()
175 |
176 | site := d.Get("site").(string)
177 | if site == "" {
178 | site = c.site
179 | }
180 | req.SiteID = site
181 |
182 | resp, err := c.c.UpdateDNSRecord(ctx, site, req)
183 | if err != nil {
184 | return diag.FromErr(err)
185 | }
186 |
187 | return resourceDNSRecordSetResourceData(resp, d, site)
188 | }
189 |
190 | func resourceDNSRecordDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
191 | c := meta.(*client)
192 |
193 | id := d.Id()
194 |
195 | site := d.Get("site").(string)
196 | if site == "" {
197 | site = c.site
198 | }
199 | err := c.c.DeleteDNSRecord(ctx, site, id)
200 | if _, ok := err.(*unifi.NotFoundError); ok {
201 | return nil
202 | }
203 | return diag.FromErr(err)
204 | }
205 |
--------------------------------------------------------------------------------
/internal/provider/resource_dns_record_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
7 | )
8 |
9 | func TestAccDNSRecord_default(t *testing.T) {
10 | resource.ParallelTest(t, resource.TestCase{
11 | PreCheck: func() { preCheck(t) },
12 | ProviderFactories: providerFactories,
13 | // TODO: CheckDestroy: ,
14 | Steps: []resource.TestStep{
15 | {
16 | Config: testAccDNSRecordConfig,
17 | // Check: resource.ComposeTestCheckFunc(
18 | // // testCheckFirewallGroupExists(t, "name"),
19 | // ),
20 | },
21 | importStep("unifi_dns_record.test"),
22 | },
23 | })
24 | }
25 |
26 | const testAccDNSRecordConfig = `
27 | resource "unifi_dns_record" "test" {
28 | service = "default"
29 |
30 | host_name = "test.example.com"
31 |
32 | server = "default.example.com"
33 | login = "testuser"
34 | password = "password"
35 | }
36 | `
37 |
--------------------------------------------------------------------------------
/internal/provider/resource_dynamic_dns.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | "github.com/ubiquiti-community/go-unifi/unifi"
9 | )
10 |
11 | func resourceDynamicDNS() *schema.Resource {
12 | return &schema.Resource{
13 | Description: "`unifi_dynamic_dns` manages dynamic DNS settings for different providers.",
14 |
15 | CreateContext: resourceDynamicDNSCreate,
16 | ReadContext: resourceDynamicDNSRead,
17 | UpdateContext: resourceDynamicDNSUpdate,
18 | DeleteContext: resourceDynamicDNSDelete,
19 | Importer: &schema.ResourceImporter{
20 | StateContext: importSiteAndID,
21 | },
22 |
23 | Schema: map[string]*schema.Schema{
24 | "id": {
25 | Description: "The ID of the dynamic DNS.",
26 | Type: schema.TypeString,
27 | Computed: true,
28 | },
29 | "site": {
30 | Description: "The name of the site to associate the dynamic DNS with.",
31 | Type: schema.TypeString,
32 | Computed: true,
33 | Optional: true,
34 | ForceNew: true,
35 | },
36 | "interface": {
37 | Description: "The interface for the dynamic DNS. Can be `wan` or `wan2`.",
38 | Type: schema.TypeString,
39 | Optional: true,
40 | Default: "wan",
41 | ForceNew: true,
42 | },
43 | "service": {
44 | Description: "The Dynamic DNS service provider, various values are supported (for example `dyndns`, etc.).",
45 | Type: schema.TypeString,
46 | Required: true,
47 | ForceNew: true,
48 | },
49 | "host_name": {
50 | Description: "The host name to update in the dynamic DNS service.",
51 | Type: schema.TypeString,
52 | Required: true,
53 | },
54 | "server": {
55 | Description: "The server for the dynamic DNS service.",
56 | Type: schema.TypeString,
57 | Optional: true,
58 | },
59 | "login": {
60 | Description: "The server for the dynamic DNS service.",
61 | Type: schema.TypeString,
62 | Optional: true,
63 | },
64 | "password": {
65 | Description: "The server for the dynamic DNS service.",
66 | Type: schema.TypeString,
67 | Optional: true,
68 | Sensitive: true,
69 | },
70 |
71 | //TODO: options support?
72 | },
73 | }
74 | }
75 |
76 | func resourceDynamicDNSCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
77 | c := meta.(*client)
78 |
79 | req, err := resourceDynamicDNSGetResourceData(d)
80 | if err != nil {
81 | return diag.FromErr(err)
82 | }
83 |
84 | site := d.Get("site").(string)
85 | if site == "" {
86 | site = c.site
87 | }
88 |
89 | resp, err := c.c.CreateDynamicDNS(ctx, site, req)
90 | if err != nil {
91 | return diag.FromErr(err)
92 | }
93 |
94 | d.SetId(resp.ID)
95 |
96 | return resourceDynamicDNSSetResourceData(resp, d, site)
97 | }
98 |
99 | func resourceDynamicDNSGetResourceData(d *schema.ResourceData) (*unifi.DynamicDNS, error) {
100 | r := &unifi.DynamicDNS{
101 | Interface: d.Get("interface").(string),
102 | Service: d.Get("service").(string),
103 |
104 | HostName: d.Get("host_name").(string),
105 |
106 | Server: d.Get("server").(string),
107 | Login: d.Get("login").(string),
108 | XPassword: d.Get("password").(string),
109 | }
110 |
111 | return r, nil
112 | }
113 |
114 | func resourceDynamicDNSSetResourceData(resp *unifi.DynamicDNS, d *schema.ResourceData, site string) diag.Diagnostics {
115 | d.Set("interface", resp.Interface)
116 | d.Set("service", resp.Service)
117 |
118 | d.Set("host_name", resp.HostName)
119 |
120 | d.Set("server", resp.Server)
121 | d.Set("login", resp.Login)
122 | d.Set("password", resp.XPassword)
123 |
124 | return nil
125 | }
126 |
127 | func resourceDynamicDNSRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
128 | c := meta.(*client)
129 |
130 | id := d.Id()
131 |
132 | site := d.Get("site").(string)
133 | if site == "" {
134 | site = c.site
135 | }
136 |
137 | resp, err := c.c.GetDynamicDNS(ctx, site, id)
138 | if _, ok := err.(*unifi.NotFoundError); ok {
139 | d.SetId("")
140 | return nil
141 | }
142 | if err != nil {
143 | return diag.FromErr(err)
144 | }
145 |
146 | return resourceDynamicDNSSetResourceData(resp, d, site)
147 | }
148 |
149 | func resourceDynamicDNSUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
150 | c := meta.(*client)
151 |
152 | req, err := resourceDynamicDNSGetResourceData(d)
153 | if err != nil {
154 | return diag.FromErr(err)
155 | }
156 |
157 | req.ID = d.Id()
158 |
159 | site := d.Get("site").(string)
160 | if site == "" {
161 | site = c.site
162 | }
163 | req.SiteID = site
164 |
165 | resp, err := c.c.UpdateDynamicDNS(ctx, site, req)
166 | if err != nil {
167 | return diag.FromErr(err)
168 | }
169 |
170 | return resourceDynamicDNSSetResourceData(resp, d, site)
171 | }
172 |
173 | func resourceDynamicDNSDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
174 | c := meta.(*client)
175 |
176 | id := d.Id()
177 |
178 | site := d.Get("site").(string)
179 | if site == "" {
180 | site = c.site
181 | }
182 | err := c.c.DeleteDynamicDNS(ctx, site, id)
183 | if _, ok := err.(*unifi.NotFoundError); ok {
184 | return nil
185 | }
186 | return diag.FromErr(err)
187 | }
188 |
--------------------------------------------------------------------------------
/internal/provider/resource_dynamic_dns_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
7 | )
8 |
9 | func TestAccDynamicDNS_dyndns(t *testing.T) {
10 | resource.ParallelTest(t, resource.TestCase{
11 | PreCheck: func() { preCheck(t) },
12 | ProviderFactories: providerFactories,
13 | // TODO: CheckDestroy: ,
14 | Steps: []resource.TestStep{
15 | {
16 | Config: testAccDynamicDNSConfig,
17 | // Check: resource.ComposeTestCheckFunc(
18 | // // testCheckFirewallGroupExists(t, "name"),
19 | // ),
20 | },
21 | importStep("unifi_dynamic_dns.test"),
22 | },
23 | })
24 | }
25 |
26 | const testAccDynamicDNSConfig = `
27 | resource "unifi_dynamic_dns" "test" {
28 | service = "dyndns"
29 |
30 | host_name = "test.example.com"
31 |
32 | server = "dyndns.example.com"
33 | login = "testuser"
34 | password = "password"
35 | }
36 | `
37 |
--------------------------------------------------------------------------------
/internal/provider/resource_firewall_group.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
10 | "github.com/ubiquiti-community/go-unifi/unifi"
11 | )
12 |
13 | func resourceFirewallGroup() *schema.Resource {
14 | return &schema.Resource{
15 | Description: "`unifi_firewall_group` manages groups of addresses or ports for use in firewall rules (`unifi_firewall_rule`).",
16 |
17 | CreateContext: resourceFirewallGroupCreate,
18 | ReadContext: resourceFirewallGroupRead,
19 | UpdateContext: resourceFirewallGroupUpdate,
20 | DeleteContext: resourceFirewallGroupDelete,
21 | Importer: &schema.ResourceImporter{
22 | StateContext: importSiteAndID,
23 | },
24 |
25 | Schema: map[string]*schema.Schema{
26 | "id": {
27 | Description: "The ID of the firewall group.",
28 | Type: schema.TypeString,
29 | Computed: true,
30 | },
31 | "site": {
32 | Description: "The name of the site to associate the firewall group with.",
33 | Type: schema.TypeString,
34 | Computed: true,
35 | Optional: true,
36 | ForceNew: true,
37 | },
38 | "name": {
39 | Description: "The name of the firewall group.",
40 | Type: schema.TypeString,
41 | Required: true,
42 | },
43 | "type": {
44 | Description: "The type of the firewall group. Must be one of: `address-group`, `port-group`, or `ipv6-address-group`.",
45 | Type: schema.TypeString,
46 | Required: true,
47 | ValidateFunc: validation.StringInSlice([]string{"address-group", "port-group", "ipv6-address-group"}, false),
48 | },
49 | "members": {
50 | Description: "The members of the firewall group.",
51 | Type: schema.TypeSet,
52 | Required: true,
53 | Elem: &schema.Schema{Type: schema.TypeString},
54 | },
55 | },
56 | }
57 | }
58 |
59 | func resourceFirewallGroupCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
60 | c := meta.(*client)
61 |
62 | req, err := resourceFirewallGroupGetResourceData(d)
63 | if err != nil {
64 | return diag.FromErr(err)
65 | }
66 |
67 | site := d.Get("site").(string)
68 | if site == "" {
69 | site = c.site
70 | }
71 |
72 | resp, err := c.c.CreateFirewallGroup(ctx, site, req)
73 | if err != nil {
74 | var apiErr *unifi.APIError
75 | if errors.As(err, &apiErr) && apiErr.Message == "api.err.FirewallGroupExisted" {
76 | return diag.Errorf("firewall groups must have unique names: %s", err)
77 | }
78 |
79 | return diag.FromErr(err)
80 | }
81 |
82 | d.SetId(resp.ID)
83 |
84 | return resourceFirewallGroupSetResourceData(resp, d, site)
85 | }
86 |
87 | func resourceFirewallGroupGetResourceData(d *schema.ResourceData) (*unifi.FirewallGroup, error) {
88 | members, err := setToStringSlice(d.Get("members").(*schema.Set))
89 | if err != nil {
90 | return nil, err
91 | }
92 |
93 | return &unifi.FirewallGroup{
94 | Name: d.Get("name").(string),
95 | GroupType: d.Get("type").(string),
96 | GroupMembers: members,
97 | }, nil
98 | }
99 |
100 | func resourceFirewallGroupSetResourceData(resp *unifi.FirewallGroup, d *schema.ResourceData, site string) diag.Diagnostics {
101 | d.Set("site", site)
102 | d.Set("name", resp.Name)
103 | d.Set("type", resp.GroupType)
104 | d.Set("members", stringSliceToSet(resp.GroupMembers))
105 |
106 | return nil
107 | }
108 |
109 | func resourceFirewallGroupRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
110 | c := meta.(*client)
111 |
112 | id := d.Id()
113 |
114 | site := d.Get("site").(string)
115 | if site == "" {
116 | site = c.site
117 | }
118 |
119 | resp, err := c.c.GetFirewallGroup(ctx, site, id)
120 | if _, ok := err.(*unifi.NotFoundError); ok {
121 | d.SetId("")
122 | return nil
123 | }
124 | if err != nil {
125 | return diag.FromErr(err)
126 | }
127 |
128 | return resourceFirewallGroupSetResourceData(resp, d, site)
129 | }
130 |
131 | func resourceFirewallGroupUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
132 | c := meta.(*client)
133 |
134 | req, err := resourceFirewallGroupGetResourceData(d)
135 | if err != nil {
136 | return diag.FromErr(err)
137 | }
138 |
139 | req.ID = d.Id()
140 |
141 | site := d.Get("site").(string)
142 | if site == "" {
143 | site = c.site
144 | }
145 | req.SiteID = site
146 |
147 | resp, err := c.c.UpdateFirewallGroup(ctx, site, req)
148 | if err != nil {
149 | return diag.FromErr(err)
150 | }
151 |
152 | return resourceFirewallGroupSetResourceData(resp, d, site)
153 | }
154 |
155 | func resourceFirewallGroupDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
156 | c := meta.(*client)
157 |
158 | id := d.Id()
159 |
160 | site := d.Get("site").(string)
161 | if site == "" {
162 | site = c.site
163 | }
164 |
165 | err := c.c.DeleteFirewallGroup(ctx, site, id)
166 | if _, ok := err.(*unifi.NotFoundError); ok {
167 | return nil
168 | }
169 | return diag.FromErr(err)
170 | }
171 |
--------------------------------------------------------------------------------
/internal/provider/resource_firewall_group_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
10 | )
11 |
12 | func TestAccFirewallGroup_port_group(t *testing.T) {
13 | resource.ParallelTest(t, resource.TestCase{
14 | PreCheck: func() { preCheck(t) },
15 | ProviderFactories: providerFactories,
16 | // TODO: CheckDestroy: ,
17 | Steps: []resource.TestStep{
18 | {
19 | Config: testAccFirewallGroupConfig("testpg", "port-group", nil),
20 | // Check: resource.ComposeTestCheckFunc(
21 | // // testCheckFirewallGroupExists(t, "name"),
22 | // ),
23 | },
24 | importStep("unifi_firewall_group.test"),
25 | {
26 | Config: testAccFirewallGroupConfig("testpg", "port-group", []string{"80", "443"}),
27 | },
28 | importStep("unifi_firewall_group.test"),
29 | },
30 | })
31 | }
32 |
33 | func TestAccFirewallGroup_address_group(t *testing.T) {
34 | resource.ParallelTest(t, resource.TestCase{
35 | PreCheck: func() { preCheck(t) },
36 | ProviderFactories: providerFactories,
37 | // TODO: CheckDestroy: ,
38 | Steps: []resource.TestStep{
39 | {
40 | Config: testAccFirewallGroupConfig("testag", "address-group", nil),
41 | // Check: resource.ComposeTestCheckFunc(
42 | // // testCheckFirewallGroupExists(t, "name"),
43 | // ),
44 | },
45 | importStep("unifi_firewall_group.test"),
46 | {
47 | Config: testAccFirewallGroupConfig("testag", "address-group", []string{"10.0.0.1", "10.0.0.2"}),
48 | },
49 | importStep("unifi_firewall_group.test"),
50 | {
51 | Config: testAccFirewallGroupConfig("testag", "address-group", []string{"10.0.0.0/24"}),
52 | },
53 | importStep("unifi_firewall_group.test"),
54 | },
55 | })
56 | }
57 |
58 | func TestAccFirewallGroup_same_name(t *testing.T) {
59 | resource.ParallelTest(t, resource.TestCase{
60 | PreCheck: func() { preCheck(t) },
61 | ProviderFactories: providerFactories,
62 | // TODO: CheckDestroy: ,
63 | Steps: []resource.TestStep{
64 | {
65 | Config: testAccFirewallGroupConfig_same_name,
66 | ExpectError: regexp.MustCompile("firewall groups must have unique names"),
67 | },
68 | },
69 | })
70 | }
71 |
72 | func testAccFirewallGroupConfig(name, ty string, members []string) string {
73 | joined := strings.Join(members, "\",\"")
74 | if len(joined) > 0 {
75 | joined = "\"" + joined + "\""
76 | }
77 |
78 | return fmt.Sprintf(`
79 | resource "unifi_firewall_group" "test" {
80 | name = "%s"
81 | type = "%s"
82 |
83 | members = [%s]
84 | }
85 | `, name, ty, joined)
86 | }
87 |
88 | const testAccFirewallGroupConfig_same_name = `
89 | resource "unifi_firewall_group" "test_a" {
90 | name = "tf-acc fg"
91 | type = "address-group"
92 |
93 | members = []
94 | }
95 |
96 | resource "unifi_firewall_group" "test_b" {
97 | name = "tf-acc fg"
98 | type = "address-group"
99 |
100 | members = []
101 | }
102 | `
103 |
--------------------------------------------------------------------------------
/internal/provider/resource_port_forward.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
9 | "github.com/ubiquiti-community/go-unifi/unifi"
10 | )
11 |
12 | func resourcePortForward() *schema.Resource {
13 | return &schema.Resource{
14 | Description: "`unifi_port_forward` manages a port forwarding rule on the gateway.",
15 |
16 | CreateContext: resourcePortForwardCreate,
17 | ReadContext: resourcePortForwardRead,
18 | UpdateContext: resourcePortForwardUpdate,
19 | DeleteContext: resourcePortForwardDelete,
20 | Importer: &schema.ResourceImporter{
21 | StateContext: importSiteAndID,
22 | },
23 |
24 | Schema: map[string]*schema.Schema{
25 | "id": {
26 | Description: "The ID of the port forwarding rule.",
27 | Type: schema.TypeString,
28 | Computed: true,
29 | },
30 | "site": {
31 | Description: "The name of the site to associate the port forwarding rule with.",
32 | Type: schema.TypeString,
33 | Computed: true,
34 | Optional: true,
35 | ForceNew: true,
36 | },
37 | "dst_port": {
38 | Description: "The destination port for the forwarding.",
39 | Type: schema.TypeString,
40 | Optional: true,
41 | ValidateFunc: validatePortRange,
42 | },
43 | // TODO: remove this, disabled rules should just be deleted.
44 | "enabled": {
45 | Description: "Specifies whether the port forwarding rule is enabled or not.",
46 | Type: schema.TypeBool,
47 | Default: true,
48 | Optional: true,
49 | Deprecated: "This will attribute will be removed in a future release. Instead of disabling a " +
50 | "port forwarding rule you can remove it from your configuration.",
51 | },
52 | "fwd_ip": {
53 | Description: "The IPv4 address to forward traffic to.",
54 | Type: schema.TypeString,
55 | Optional: true,
56 | ValidateFunc: validation.IsIPv4Address,
57 | },
58 | "fwd_port": {
59 | Description: "The port to forward traffic to.",
60 | Type: schema.TypeString,
61 | Optional: true,
62 | ValidateFunc: validatePortRange,
63 | },
64 | "log": {
65 | Description: "Specifies whether to log forwarded traffic or not.",
66 | Type: schema.TypeBool,
67 | Default: false,
68 | Optional: true,
69 | },
70 | "name": {
71 | Description: "The name of the port forwarding rule.",
72 | Type: schema.TypeString,
73 | Optional: true,
74 | },
75 | "port_forward_interface": {
76 | Description: "The port forwarding interface. Can be `wan`, `wan2`, or `both`.",
77 | Type: schema.TypeString,
78 | Optional: true,
79 | ValidateFunc: validation.StringInSlice([]string{"wan", "wan2", "both"}, false),
80 | },
81 | "protocol": {
82 | Description: "The protocol for the port forwarding rule. Can be `tcp`, `udp`, or `tcp_udp`.",
83 | Type: schema.TypeString,
84 | Optional: true,
85 | Default: "tcp_udp",
86 | ValidateFunc: validation.StringInSlice([]string{"tcp_udp", "tcp", "udp"}, false),
87 | },
88 | "src_ip": {
89 | Description: "The source IPv4 address (or CIDR) of the port forwarding rule. For all traffic, specify `any`.",
90 | Type: schema.TypeString,
91 | Optional: true,
92 | Default: "any",
93 | ValidateFunc: validation.Any(
94 | validation.StringInSlice([]string{"any"}, false),
95 | validation.IsIPv4Address,
96 | cidrValidate,
97 | ),
98 | },
99 | },
100 | }
101 | }
102 |
103 | func resourcePortForwardCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
104 | c := meta.(*client)
105 |
106 | req, err := resourcePortForwardGetResourceData(d)
107 | if err != nil {
108 | return diag.FromErr(err)
109 | }
110 |
111 | site := d.Get("site").(string)
112 | if site == "" {
113 | site = c.site
114 | }
115 | resp, err := c.c.CreatePortForward(ctx, site, req)
116 | if err != nil {
117 | return diag.FromErr(err)
118 | }
119 |
120 | d.SetId(resp.ID)
121 |
122 | return resourcePortForwardSetResourceData(resp, d, site)
123 | }
124 |
125 | func resourcePortForwardGetResourceData(d *schema.ResourceData) (*unifi.PortForward, error) {
126 | return &unifi.PortForward{
127 | DstPort: d.Get("dst_port").(string),
128 | Enabled: d.Get("enabled").(bool),
129 | Fwd: d.Get("fwd_ip").(string),
130 | FwdPort: d.Get("fwd_port").(string),
131 | Log: d.Get("log").(bool),
132 | Name: d.Get("name").(string),
133 | PfwdInterface: d.Get("port_forward_interface").(string),
134 | Proto: d.Get("protocol").(string),
135 | Src: d.Get("src_ip").(string),
136 | }, nil
137 | }
138 |
139 | func resourcePortForwardSetResourceData(resp *unifi.PortForward, d *schema.ResourceData, site string) diag.Diagnostics {
140 | d.Set("site", site)
141 | d.Set("dst_port", resp.DstPort)
142 | d.Set("enabled", resp.Enabled)
143 | d.Set("fwd_ip", resp.Fwd)
144 | d.Set("fwd_port", resp.FwdPort)
145 | d.Set("log", resp.Log)
146 | d.Set("name", resp.Name)
147 | d.Set("port_forward_interface", resp.PfwdInterface)
148 | d.Set("protocol", resp.Proto)
149 | d.Set("src_ip", resp.Src)
150 |
151 | return nil
152 | }
153 |
154 | func resourcePortForwardRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
155 | c := meta.(*client)
156 |
157 | id := d.Id()
158 |
159 | site := d.Get("site").(string)
160 | if site == "" {
161 | site = c.site
162 | }
163 | resp, err := c.c.GetPortForward(ctx, site, id)
164 | if _, ok := err.(*unifi.NotFoundError); ok {
165 | d.SetId("")
166 | return nil
167 | }
168 | if err != nil {
169 | return diag.FromErr(err)
170 | }
171 |
172 | return resourcePortForwardSetResourceData(resp, d, site)
173 | }
174 |
175 | func resourcePortForwardUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
176 | c := meta.(*client)
177 |
178 | req, err := resourcePortForwardGetResourceData(d)
179 | if err != nil {
180 | return diag.FromErr(err)
181 | }
182 |
183 | req.ID = d.Id()
184 |
185 | site := d.Get("site").(string)
186 | if site == "" {
187 | site = c.site
188 | }
189 | req.SiteID = site
190 |
191 | resp, err := c.c.UpdatePortForward(ctx, site, req)
192 | if err != nil {
193 | return diag.FromErr(err)
194 | }
195 |
196 | return resourcePortForwardSetResourceData(resp, d, site)
197 | }
198 |
199 | func resourcePortForwardDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
200 | c := meta.(*client)
201 |
202 | id := d.Id()
203 |
204 | site := d.Get("site").(string)
205 | if site == "" {
206 | site = c.site
207 | }
208 |
209 | err := c.c.DeletePortForward(ctx, site, id)
210 | return diag.FromErr(err)
211 | }
212 |
--------------------------------------------------------------------------------
/internal/provider/resource_port_forward_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
8 | )
9 |
10 | func TestAccPortForward_basic(t *testing.T) {
11 | resource.ParallelTest(t, resource.TestCase{
12 | PreCheck: func() { preCheck(t) },
13 | ProviderFactories: providerFactories,
14 | // TODO: CheckDestroy: ,
15 | Steps: []resource.TestStep{
16 | {
17 | Config: testAccPortForwardConfig("22", false, "10.1.1.1", "22", "fwd name"),
18 | Check: resource.ComposeTestCheckFunc(
19 | // testCheckNetworkExists(t, "name"),
20 | resource.TestCheckResourceAttr("unifi_port_forward.test", "dst_port", "22"),
21 | ),
22 | },
23 | importStep("unifi_port_forward.test"),
24 | {
25 | Config: testAccPortForwardConfig("22", false, "10.1.1.2", "8022", "fwd name"),
26 | Check: resource.ComposeTestCheckFunc(
27 | resource.TestCheckResourceAttr("unifi_port_forward.test", "fwd_port", "8022"),
28 | resource.TestCheckResourceAttr("unifi_port_forward.test", "fwd_ip", "10.1.1.2"),
29 | ),
30 | },
31 | importStep("unifi_port_forward.test"),
32 | {
33 | Config: testAccPortForwardConfig("22", false, "10.1.1.1", "22", "fwd name 2"),
34 | Check: resource.ComposeTestCheckFunc(
35 | resource.TestCheckResourceAttr("unifi_port_forward.test", "name", "fwd name 2"),
36 | ),
37 | },
38 | importStep("unifi_port_forward.test"),
39 | },
40 | })
41 | }
42 |
43 | func TestAccPortForward_src_ip(t *testing.T) {
44 | resource.ParallelTest(t, resource.TestCase{
45 | PreCheck: func() { preCheck(t) },
46 | ProviderFactories: providerFactories,
47 | // TODO: CheckDestroy: ,
48 | Steps: []resource.TestStep{
49 | {
50 | Config: testAccPortForwardConfigSrc("22", false, "10.1.1.1", "22", "fwd name", "192.168.1.0"),
51 | Check: resource.ComposeTestCheckFunc(
52 | // testCheckNetworkExists(t, "name"),
53 | resource.TestCheckResourceAttr("unifi_port_forward.test", "dst_port", "22"),
54 | ),
55 | },
56 | importStep("unifi_port_forward.test"),
57 | },
58 | })
59 | }
60 |
61 | func TestAccPortForward_src_cidr(t *testing.T) {
62 | resource.ParallelTest(t, resource.TestCase{
63 | PreCheck: func() { preCheck(t) },
64 | ProviderFactories: providerFactories,
65 | // TODO: CheckDestroy: ,
66 | Steps: []resource.TestStep{
67 | {
68 | Config: testAccPortForwardConfigSrc("22", false, "10.1.1.1", "22", "fwd name", "192.168.1.0/20"),
69 | Check: resource.ComposeTestCheckFunc(
70 | // testCheckNetworkExists(t, "name"),
71 | resource.TestCheckResourceAttr("unifi_port_forward.test", "dst_port", "22"),
72 | ),
73 | },
74 | importStep("unifi_port_forward.test"),
75 | },
76 | })
77 | }
78 |
79 | func testAccPortForwardConfig(dstPort string, enabled bool, fwdIP, fwdPort, name string) string {
80 | return fmt.Sprintf(`
81 | resource "unifi_port_forward" "test" {
82 | dst_port = %q
83 | enabled = %t
84 | fwd_ip = %q
85 | fwd_port = %q
86 | name = %q
87 | }
88 | `, dstPort, enabled, fwdIP, fwdPort, name)
89 | }
90 |
91 | func testAccPortForwardConfigSrc(dstPort string, enabled bool, fwdIP, fwdPort, name, src string) string {
92 | return fmt.Sprintf(`
93 | resource "unifi_port_forward" "test" {
94 | dst_port = %q
95 | enabled = %t
96 | fwd_ip = %q
97 | fwd_port = %q
98 | name = %q
99 | src_ip = %q
100 | }
101 | `, dstPort, enabled, fwdIP, fwdPort, name, src)
102 | }
103 |
--------------------------------------------------------------------------------
/internal/provider/resource_port_profile_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
7 | )
8 |
9 | func TestAccPortProfile_basic(t *testing.T) {
10 | resource.ParallelTest(t, resource.TestCase{
11 | PreCheck: func() { preCheck(t) },
12 | ProviderFactories: providerFactories,
13 | // TODO: CheckDestroy: ,
14 | Steps: []resource.TestStep{
15 | {
16 | Config: testAccPortProfileConfig,
17 | Check: resource.ComposeTestCheckFunc(
18 | resource.TestCheckResourceAttr("unifi_port_profile.test", "poe_mode", "off"),
19 | ),
20 | },
21 | importStep("unifi_port_profile.test"),
22 | },
23 | })
24 | }
25 |
26 | const testAccPortProfileConfig = `
27 | resource "unifi_port_profile" "test" {
28 | name = "provider created"
29 |
30 | poe_mode = "off"
31 | speed = 1000
32 | stp_port_mode = false
33 | }
34 | `
35 |
--------------------------------------------------------------------------------
/internal/provider/resource_radius_profile_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
8 | )
9 |
10 | func TestAccRadiusProfile_basic(t *testing.T) {
11 | resource.ParallelTest(t, resource.TestCase{
12 | PreCheck: func() { preCheck(t) },
13 | ProviderFactories: providerFactories,
14 | // TODO: CheckDestroy: ,
15 | Steps: []resource.TestStep{
16 | {
17 | Config: testAccRadiusProfileConfig("test"),
18 | Check: resource.ComposeTestCheckFunc(
19 | resource.TestCheckResourceAttr("unifi_radius_profile.test", "name", "test"),
20 | ),
21 | },
22 | importStep("unifi_radius_profile.test"),
23 | },
24 | })
25 | }
26 |
27 | func TestAccRadiusProfile_servers(t *testing.T) {
28 | resource.ParallelTest(t, resource.TestCase{
29 | PreCheck: func() { preCheck(t) },
30 | ProviderFactories: providerFactories,
31 | // TODO: CheckDestroy: ,
32 | Steps: []resource.TestStep{
33 | {
34 | Config: testAccRadiusProfileConfigServer(),
35 | Check: resource.ComposeTestCheckFunc(
36 | resource.TestCheckResourceAttr("unifi_radius_profile.test", "name", "test"),
37 | ),
38 | },
39 | importStep("unifi_radius_profile.test"),
40 | },
41 | })
42 | }
43 |
44 | func TestAccRadiusProfile_importByName(t *testing.T) {
45 | resource.ParallelTest(t, resource.TestCase{
46 | PreCheck: func() { preCheck(t) },
47 | ProviderFactories: providerFactories,
48 | Steps: []resource.TestStep{
49 | // Apply and import network by name.
50 | {
51 | Config: testAccRadiusProfileImport(),
52 | },
53 | {
54 | Config: testAccRadiusProfileImport(),
55 | ResourceName: "unifi_radius_profile.test",
56 | ImportState: true,
57 | ImportStateVerify: true,
58 | ImportStateId: "name=imported",
59 | },
60 | },
61 | })
62 | }
63 |
64 | func testAccRadiusProfileConfigServer() string {
65 | return `
66 | resource "unifi_radius_profile" "test" {
67 | name = "test"
68 | auth_server {
69 | ip = "192.168.1.1"
70 | xsecret = "securepw1"
71 | }
72 | auth_server {
73 | ip = "192.168.10.1"
74 | port = 8888
75 | xsecret = "securepw2"
76 | }
77 | acct_server {
78 | ip = "192.168.1.1"
79 | xsecret = "securepw1"
80 | }
81 | acct_server {
82 | ip = "192.168.10.1"
83 | port = 9999
84 | xsecret = "securepw2"
85 | }
86 | use_usg_acct_server = false
87 | use_usg_auth_server = false
88 | }
89 | `
90 | }
91 |
92 | func testAccRadiusProfileConfig(name string) string {
93 | return fmt.Sprintf(`
94 | resource "unifi_radius_profile" "test" {
95 | name = "%[1]s"
96 | }
97 | `, name)
98 | }
99 |
100 | func testAccRadiusProfileImport() string {
101 | return `
102 | resource "unifi_radius_profile" "test" {
103 | name = "imported"
104 | auth_server {
105 | ip = "192.168.1.1"
106 | port = 1812
107 | xsecret = "securepw"
108 | }
109 | use_usg_auth_server = true
110 | vlan_enabled = true
111 | vlan_wlan_mode = "required"
112 | }
113 | `
114 | }
115 |
--------------------------------------------------------------------------------
/internal/provider/resource_setting_mgmt.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9 | "github.com/ubiquiti-community/go-unifi/unifi"
10 | )
11 |
12 | // TODO: probably need to update this to be more like setting_usg,
13 | // using locking, and upsert, more computed, etc.
14 |
15 | func resourceSettingMgmt() *schema.Resource {
16 | return &schema.Resource{
17 | Description: "`unifi_setting_mgmt` manages settings for a unifi site.",
18 |
19 | CreateContext: resourceSettingMgmtCreate,
20 | ReadContext: resourceSettingMgmtRead,
21 | UpdateContext: resourceSettingMgmtUpdate,
22 | DeleteContext: resourceSettingMgmtDelete,
23 | Importer: &schema.ResourceImporter{
24 | StateContext: importSiteAndID,
25 | },
26 |
27 | Schema: map[string]*schema.Schema{
28 | "id": {
29 | Description: "The ID of the settings.",
30 | Type: schema.TypeString,
31 | Computed: true,
32 | },
33 | "site": {
34 | Description: "The name of the site to associate the settings with.",
35 | Type: schema.TypeString,
36 | Computed: true,
37 | Optional: true,
38 | ForceNew: true,
39 | },
40 | "auto_upgrade": {
41 | Description: "Automatically upgrade device firmware.",
42 | Type: schema.TypeBool,
43 | Optional: true,
44 | },
45 | "ssh_enabled": {
46 | Description: "Enable SSH authentication.",
47 | Type: schema.TypeBool,
48 | Optional: true,
49 | },
50 | "ssh_key": {
51 | Description: "SSH key.",
52 | Type: schema.TypeSet,
53 | Optional: true,
54 | Elem: &schema.Resource{
55 | Schema: map[string]*schema.Schema{
56 | "name": {
57 | Description: "Name of SSH key.",
58 | Type: schema.TypeString,
59 | Required: true,
60 | },
61 | "type": {
62 | Description: "Type of SSH key, e.g. ssh-rsa.",
63 | Type: schema.TypeString,
64 | Required: true,
65 | },
66 | "key": {
67 | Description: "Public SSH key.",
68 | Type: schema.TypeString,
69 | Optional: true,
70 | },
71 | "comment": {
72 | Description: "Comment.",
73 | Type: schema.TypeString,
74 | Optional: true,
75 | },
76 | },
77 | },
78 | },
79 | },
80 | }
81 | }
82 |
83 | func setToSshKeys(set *schema.Set) ([]unifi.SettingMgmtXSshKeys, error) {
84 | var sshKeys []unifi.SettingMgmtXSshKeys
85 | for _, item := range set.List() {
86 | data, ok := item.(map[string]any)
87 | if !ok {
88 | return nil, fmt.Errorf("unexpected data in block")
89 | }
90 | sshKey, err := toSshKey(data)
91 | if err != nil {
92 | return nil, fmt.Errorf("unable to create port override: %w", err)
93 | }
94 | sshKeys = append(sshKeys, sshKey)
95 | }
96 | return sshKeys, nil
97 | }
98 |
99 | func toSshKey(data map[string]any) (unifi.SettingMgmtXSshKeys, error) {
100 | return unifi.SettingMgmtXSshKeys{
101 | Name: data["name"].(string),
102 | KeyType: data["type"].(string),
103 | Key: data["key"].(string),
104 | Comment: data["comment"].(string),
105 | }, nil
106 | }
107 |
108 | func setFromSshKeys(sshKeys []unifi.SettingMgmtXSshKeys) ([]map[string]any, error) {
109 | list := make([]map[string]any, 0, len(sshKeys))
110 | for _, sshKey := range sshKeys {
111 | v, err := fromSshKey(sshKey)
112 | if err != nil {
113 | return nil, fmt.Errorf("unable to parse ssh key: %w", err)
114 | }
115 | list = append(list, v)
116 | }
117 | return list, nil
118 | }
119 |
120 | func fromSshKey(sshKey unifi.SettingMgmtXSshKeys) (map[string]any, error) {
121 | return map[string]any{
122 | "name": sshKey.Name,
123 | "type": sshKey.KeyType,
124 | "key": sshKey.Key,
125 | "comment": sshKey.Comment,
126 | }, nil
127 | }
128 |
129 | func resourceSettingMgmtGetResourceData(d *schema.ResourceData, meta any) (*unifi.SettingMgmt, error) {
130 | sshKeys, err := setToSshKeys(d.Get("ssh_key").(*schema.Set))
131 | if err != nil {
132 | return nil, fmt.Errorf("unable to process ssh_key block: %w", err)
133 | }
134 |
135 | return &unifi.SettingMgmt{
136 | AutoUpgrade: d.Get("auto_upgrade").(bool),
137 | XSshEnabled: d.Get("ssh_enabled").(bool),
138 | XSshKeys: sshKeys,
139 | }, nil
140 | }
141 |
142 | func resourceSettingMgmtCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
143 | c := meta.(*client)
144 |
145 | req, err := resourceSettingMgmtGetResourceData(d, meta)
146 | if err != nil {
147 | return diag.FromErr(err)
148 | }
149 |
150 | site := d.Get("site").(string)
151 | if site == "" {
152 | site = c.site
153 | }
154 |
155 | resp, err := c.c.UpdateSettingMgmt(ctx, site, req)
156 | if err != nil {
157 | return diag.FromErr(err)
158 | }
159 |
160 | d.SetId(resp.ID)
161 |
162 | return resourceSettingMgmtSetResourceData(resp, d, meta, site)
163 | }
164 |
165 | func resourceSettingMgmtSetResourceData(resp *unifi.SettingMgmt, d *schema.ResourceData, meta any, site string) diag.Diagnostics {
166 | sshKeys, err := setFromSshKeys(resp.XSshKeys)
167 | if err != nil {
168 | return diag.FromErr(err)
169 | }
170 |
171 | d.Set("site", site)
172 | d.Set("auto_upgrade", resp.AutoUpgrade)
173 | d.Set("ssh_enabled", resp.XSshEnabled)
174 | d.Set("ssh_key", sshKeys)
175 | return nil
176 | }
177 |
178 | func resourceSettingMgmtRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
179 | c := meta.(*client)
180 |
181 | site := d.Get("site").(string)
182 | if site == "" {
183 | site = c.site
184 | }
185 |
186 | resp, err := c.c.GetSettingMgmt(ctx, site)
187 | if _, ok := err.(*unifi.NotFoundError); ok {
188 | d.SetId("")
189 | return nil
190 | }
191 | if err != nil {
192 | return diag.FromErr(err)
193 | }
194 |
195 | return resourceSettingMgmtSetResourceData(resp, d, meta, site)
196 | }
197 |
198 | func resourceSettingMgmtUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
199 | c := meta.(*client)
200 |
201 | req, err := resourceSettingMgmtGetResourceData(d, meta)
202 | if err != nil {
203 | return diag.FromErr(err)
204 | }
205 |
206 | req.ID = d.Id()
207 | site := d.Get("site").(string)
208 | if site == "" {
209 | site = c.site
210 | }
211 |
212 | resp, err := c.c.UpdateSettingMgmt(ctx, site, req)
213 | if err != nil {
214 | return diag.FromErr(err)
215 | }
216 |
217 | return resourceSettingMgmtSetResourceData(resp, d, meta, site)
218 | }
219 |
220 | func resourceSettingMgmtDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
221 | return nil
222 | }
223 |
--------------------------------------------------------------------------------
/internal/provider/resource_setting_mgmt_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "sync"
5 | "testing"
6 |
7 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
8 | )
9 |
10 | var settingMgmtLock = sync.Mutex{}
11 |
12 | func TestAccSettingMgmt_basic(t *testing.T) {
13 | resource.ParallelTest(t, resource.TestCase{
14 | PreCheck: func() {
15 | preCheck(t)
16 | settingMgmtLock.Lock()
17 | t.Cleanup(func() {
18 | settingMgmtLock.Unlock()
19 | })
20 | },
21 | ProviderFactories: providerFactories,
22 | Steps: []resource.TestStep{
23 | {
24 | Config: testAccSettingMgmtConfig_basic(),
25 | Check: resource.ComposeTestCheckFunc(),
26 | },
27 | importStep("unifi_setting_mgmt.test"),
28 | },
29 | })
30 | }
31 |
32 | func TestAccSettingMgmt_site(t *testing.T) {
33 | resource.ParallelTest(t, resource.TestCase{
34 | PreCheck: func() {
35 | preCheck(t)
36 | settingMgmtLock.Lock()
37 | t.Cleanup(func() {
38 | settingMgmtLock.Unlock()
39 | })
40 | },
41 | ProviderFactories: providerFactories,
42 | Steps: []resource.TestStep{
43 | {
44 | Config: testAccSettingMgmtConfig_site(),
45 | Check: resource.ComposeTestCheckFunc(),
46 | },
47 | {
48 | ResourceName: "unifi_setting_mgmt.test",
49 | ImportState: true,
50 | ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_mgmt.test"),
51 | ImportStateVerify: true,
52 | },
53 | },
54 | })
55 | }
56 |
57 | func TestAccSettingMgmt_sshKeys(t *testing.T) {
58 | resource.ParallelTest(t, resource.TestCase{
59 | PreCheck: func() {
60 | preCheck(t)
61 | settingMgmtLock.Lock()
62 | t.Cleanup(func() {
63 | settingMgmtLock.Unlock()
64 | })
65 | },
66 | ProviderFactories: providerFactories,
67 | Steps: []resource.TestStep{
68 | {
69 | Config: testAccSettingMgmtConfig_sshKeys(),
70 | Check: resource.ComposeTestCheckFunc(),
71 | },
72 | {
73 | ResourceName: "unifi_setting_mgmt.test",
74 | ImportState: true,
75 | ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_mgmt.test"),
76 | ImportStateVerify: true,
77 | },
78 | },
79 | })
80 | }
81 |
82 | func testAccSettingMgmtConfig_basic() string {
83 | return `
84 | resource "unifi_setting_mgmt" "test" {
85 | auto_upgrade = true
86 | }
87 | `
88 | }
89 |
90 | func testAccSettingMgmtConfig_site() string {
91 | return `
92 | resource "unifi_site" "test" {
93 | description = "test"
94 | }
95 |
96 | resource "unifi_setting_mgmt" "test" {
97 | site = unifi_site.test.name
98 | auto_upgrade = true
99 | }
100 | `
101 | }
102 |
103 | func testAccSettingMgmtConfig_sshKeys() string {
104 | return `
105 | resource "unifi_site" "test" {
106 | description = "test"
107 | }
108 |
109 | resource "unifi_setting_mgmt" "test" {
110 | site = unifi_site.test.name
111 | ssh_enabled = true
112 | ssh_key {
113 | name = "Test key"
114 | type = "ssh-rsa"
115 | key = "AAAAB3NzaC1yc2EAAAADAQABAAACAQDNWqT8zvVtmaks7sLlP+hmWmJVmruyNU9uk8JpLTX0oE+r9hjePsXCThTrft7s+vlaj+bLr8Yf5//TT8KS7LB/YIp2O3jPomOz9A4hIsG5R6FLfSggzQP4a7QSlNLCm/6WjKHP9DhRb7trnFz+KkCNmCVKLZgiyeUm2LydVKJ2QncHopA5yomtSpmb6x66zaKr+DbwzHC13WIEms5Ros0N9pEOcAghsSEVL42bfGBfSH37R+Kaw0nhWei4Y25jO66xsbtyZKoiF1+XXXBuEi77Tv7iQGHHOFRqNKKfGI1QhYvwlcjdzh9wu7Gtzeyh/+jpF8mwCLtFKle+W/zSs+lHCuCihvQEQtCIpZL5FapvxfxPZQJWL5RgsL9jieUaoF8EsWAOM83BCSZa/FB1RyfKdy4f7BQtDCKIm3nD5paCJSfS6DSw1TMvaFPeJLG3PuyHRbNvbVLmHRl9lK03na6/R9JX06nBUuPdP+FLjIZsyZz1DOUSDjCWHFk0+Ne2uEinV7SkOoxC6E2NxqlY/SyMnWZS+p95Zx6yOlNqB9sQ+Q4/YLGY5mUmqJrHPlH6LjXfudybKHMZUuVRF1NX3ESue8NSKc0SlJDQUXtJ9wkjjX1wAWvXCDwI72jtC86r/wzw+mcIfpks3jHQrOhpwCRmQL4vAs5DztA3hKxkgElYaw=="
116 | comment = "test@example.com"
117 | }
118 | }
119 | `
120 | }
121 |
--------------------------------------------------------------------------------
/internal/provider/resource_setting_radius.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
9 | "github.com/ubiquiti-community/go-unifi/unifi"
10 | )
11 |
12 | func resourceSettingRadius() *schema.Resource {
13 | return &schema.Resource{
14 | Description: "`unifi_setting_radius` manages settings for the built-in RADIUS server.",
15 |
16 | CreateContext: resourceSettingRadiusCreate,
17 | ReadContext: resourceSettingRadiusRead,
18 | UpdateContext: resourceSettingRadiusUpdate,
19 | DeleteContext: schema.NoopContext,
20 | Importer: &schema.ResourceImporter{
21 | StateContext: importSiteAndID,
22 | },
23 |
24 | Schema: map[string]*schema.Schema{
25 | "id": {
26 | Description: "The ID of the settings.",
27 | Type: schema.TypeString,
28 | Computed: true,
29 | },
30 | "site": {
31 | Description: "The name of the site to associate the settings with.",
32 | Type: schema.TypeString,
33 | Computed: true,
34 | Optional: true,
35 | ForceNew: true,
36 | },
37 | "accounting_enabled": {
38 | Description: "Enable RADIUS accounting",
39 | Type: schema.TypeBool,
40 | Optional: true,
41 | Default: false,
42 | },
43 | "accounting_port": {
44 | Description: "The port for accounting communications.",
45 | Type: schema.TypeInt,
46 | Optional: true,
47 | Default: 1813,
48 | ValidateFunc: validation.IsPortNumber,
49 | },
50 | "auth_port": {
51 | Description: "The port for authentication communications.",
52 | Type: schema.TypeInt,
53 | Optional: true,
54 | Default: 1812,
55 | ValidateFunc: validation.IsPortNumber,
56 | },
57 | "interim_update_interval": {
58 | Description: "Statistics will be collected from connected clients at this interval.",
59 | Type: schema.TypeInt,
60 | Optional: true,
61 | Default: 3600,
62 | },
63 | "tunneled_reply": {
64 | Description: "Encrypt communication between the server and the client.",
65 | Type: schema.TypeBool,
66 | Optional: true,
67 | Default: true,
68 | },
69 | "secret": {
70 | Description: "RAIDUS secret passphrase.",
71 | Type: schema.TypeString,
72 | Sensitive: true,
73 | Optional: true,
74 | Default: "",
75 | },
76 | "enabled": {
77 | Description: "RAIDUS server enabled.",
78 | Type: schema.TypeBool,
79 | Default: true,
80 | Optional: true,
81 | },
82 | },
83 | }
84 | }
85 |
86 | func resourceSettingRadiusGetResourceData(d *schema.ResourceData, meta any) (*unifi.SettingRadius, error) {
87 | return &unifi.SettingRadius{
88 | AccountingEnabled: d.Get("accounting_enabled").(bool),
89 | Enabled: d.Get("enabled").(bool),
90 | AcctPort: d.Get("accounting_port").(int),
91 | AuthPort: d.Get("auth_port").(int),
92 | ConfigureWholeNetwork: true,
93 | TunneledReply: d.Get("tunneled_reply").(bool),
94 | XSecret: d.Get("secret").(string),
95 | InterimUpdateInterval: d.Get("interim_update_interval").(int),
96 | }, nil
97 | }
98 |
99 | func resourceSettingRadiusCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
100 | c := meta.(*client)
101 |
102 | req, err := resourceSettingRadiusGetResourceData(d, meta)
103 | if err != nil {
104 | return diag.FromErr(err)
105 | }
106 |
107 | site := d.Get("site").(string)
108 | if site == "" {
109 | site = c.site
110 | }
111 |
112 | resp, err := c.c.UpdateSettingRadius(ctx, site, req)
113 | if err != nil {
114 | return diag.FromErr(err)
115 | }
116 |
117 | d.SetId(resp.ID)
118 |
119 | return resourceSettingRadiusSetResourceData(resp, d, meta, site)
120 | }
121 |
122 | func resourceSettingRadiusSetResourceData(resp *unifi.SettingRadius, d *schema.ResourceData, meta any, site string) diag.Diagnostics {
123 | d.Set("site", site)
124 | d.Set("enabled", resp.Enabled)
125 | d.Set("accounting_enabled", resp.AccountingEnabled)
126 | d.Set("accounting_port", resp.AcctPort)
127 | d.Set("auth_port", resp.AuthPort)
128 | d.Set("tunneled_reply", resp.TunneledReply)
129 | d.Set("secret", resp.XSecret)
130 | d.Set("interim_update_interval", resp.InterimUpdateInterval)
131 | return nil
132 | }
133 |
134 | func resourceSettingRadiusRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
135 | c := meta.(*client)
136 |
137 | site := d.Get("site").(string)
138 | if site == "" {
139 | site = c.site
140 | }
141 |
142 | resp, err := c.c.GetSettingRadius(ctx, site)
143 | if _, ok := err.(*unifi.NotFoundError); ok {
144 | d.SetId("")
145 | return nil
146 | }
147 | if err != nil {
148 | return diag.FromErr(err)
149 | }
150 |
151 | return resourceSettingRadiusSetResourceData(resp, d, meta, site)
152 | }
153 |
154 | func resourceSettingRadiusUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
155 | c := meta.(*client)
156 |
157 | req, err := resourceSettingRadiusGetResourceData(d, meta)
158 | if err != nil {
159 | return diag.FromErr(err)
160 | }
161 |
162 | req.ID = d.Id()
163 | site := d.Get("site").(string)
164 | if site == "" {
165 | site = c.site
166 | }
167 |
168 | resp, err := c.c.UpdateSettingRadius(ctx, site, req)
169 | if err != nil {
170 | return diag.FromErr(err)
171 | }
172 |
173 | return resourceSettingRadiusSetResourceData(resp, d, meta, site)
174 | }
175 |
--------------------------------------------------------------------------------
/internal/provider/resource_setting_radius_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "sync"
5 | "testing"
6 |
7 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
8 | )
9 |
10 | var settingRadiusLock = sync.Mutex{}
11 |
12 | func TestAccSettingRadius_basic(t *testing.T) {
13 | resource.ParallelTest(t, resource.TestCase{
14 | PreCheck: func() {
15 | preCheck(t)
16 | settingRadiusLock.Lock()
17 | t.Cleanup(func() {
18 | settingRadiusLock.Unlock()
19 | })
20 | },
21 | ProviderFactories: providerFactories,
22 | Steps: []resource.TestStep{
23 | {
24 | Config: testAccSettingRadiusConfig_basic(),
25 | Check: resource.ComposeTestCheckFunc(),
26 | },
27 | importStep("unifi_setting_radius.test"),
28 | },
29 | })
30 | }
31 |
32 | func TestAccSettingRadius_site(t *testing.T) {
33 | resource.ParallelTest(t, resource.TestCase{
34 | PreCheck: func() {
35 | preCheck(t)
36 | settingRadiusLock.Lock()
37 | t.Cleanup(func() {
38 | settingRadiusLock.Unlock()
39 | })
40 | },
41 | ProviderFactories: providerFactories,
42 | Steps: []resource.TestStep{
43 | {
44 | Config: testAccSettingRadiusConfig_site(),
45 | Check: resource.ComposeTestCheckFunc(),
46 | },
47 | {
48 | ResourceName: "unifi_setting_radius.test",
49 | ImportState: true,
50 | ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_radius.test"),
51 | ImportStateVerify: true,
52 | },
53 | },
54 | })
55 | }
56 |
57 | func TestAccSettingRadius_full(t *testing.T) {
58 | resource.ParallelTest(t, resource.TestCase{
59 | PreCheck: func() {
60 | preCheck(t)
61 | settingRadiusLock.Lock()
62 | t.Cleanup(func() {
63 | settingRadiusLock.Unlock()
64 | })
65 | },
66 | ProviderFactories: providerFactories,
67 | Steps: []resource.TestStep{
68 | {
69 | Config: testAccSettingRadiusConfig_full(),
70 | Check: resource.ComposeTestCheckFunc(),
71 | },
72 | {
73 | ResourceName: "unifi_setting_radius.test",
74 | ImportState: true,
75 | ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_radius.test"),
76 | ImportStateVerify: true,
77 | },
78 | },
79 | })
80 | }
81 |
82 | func TestAccSettingRadius_vlan(t *testing.T) {
83 | resource.ParallelTest(t, resource.TestCase{
84 | PreCheck: func() {
85 | preCheck(t)
86 | settingRadiusLock.Lock()
87 | t.Cleanup(func() {
88 | settingRadiusLock.Unlock()
89 | })
90 | },
91 | ProviderFactories: providerFactories,
92 | Steps: []resource.TestStep{
93 | {
94 | Config: testAccSettingRadiusConfig_vlan(),
95 | Check: resource.ComposeTestCheckFunc(),
96 | },
97 | importStep("unifi_setting_radius.test"),
98 | },
99 | })
100 | }
101 |
102 | func testAccSettingRadiusConfig_basic() string {
103 | return `
104 | resource "unifi_setting_radius" "test" {
105 | enabled = true
106 | secret = "securepw"
107 | }
108 | `
109 | }
110 |
111 | func testAccSettingRadiusConfig_site() string {
112 | return `
113 | resource "unifi_site" "test" {
114 | description = "test"
115 | }
116 |
117 | resource "unifi_setting_radius" "test" {
118 | site = unifi_site.test.name
119 | enabled = true
120 | secret = "securepw"
121 | }
122 | `
123 | }
124 |
125 | func testAccSettingRadiusConfig_full() string {
126 | return `
127 | resource "unifi_setting_radius" "test" {
128 | enabled = true
129 | secret = "securepw"
130 | accounting_port = "9999"
131 | auth_port = "8888"
132 | }
133 | `
134 | }
135 |
136 | func testAccSettingRadiusConfig_vlan() string {
137 | return `
138 | resource "unifi_setting_radius" "test" {
139 | enabled = true
140 | secret = "securepw"
141 | accounting_enabled = true
142 | }
143 | `
144 | }
145 |
--------------------------------------------------------------------------------
/internal/provider/resource_setting_usg.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 |
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
11 | "github.com/ubiquiti-community/go-unifi/unifi"
12 | )
13 |
14 | var resourceSettingUsgLock = sync.Mutex{}
15 |
16 | func resourceSettingUsgLocker(f func(context.Context, *schema.ResourceData, any) diag.Diagnostics) func(context.Context, *schema.ResourceData, any) diag.Diagnostics {
17 | return func(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
18 | resourceSettingUsgLock.Lock()
19 | defer resourceSettingUsgLock.Unlock()
20 | return f(ctx, d, meta)
21 | }
22 | }
23 |
24 | func resourceSettingUsg() *schema.Resource {
25 | return &schema.Resource{
26 | Description: "`unifi_setting_usg` manages settings for a Unifi Security Gateway.",
27 |
28 | CreateContext: resourceSettingUsgLocker(resourceSettingUsgUpsert),
29 | ReadContext: resourceSettingUsgLocker(resourceSettingUsgRead),
30 | UpdateContext: resourceSettingUsgLocker(resourceSettingUsgUpsert),
31 | DeleteContext: schema.NoopContext,
32 | Importer: &schema.ResourceImporter{
33 | StateContext: importSiteAndID,
34 | },
35 |
36 | Schema: map[string]*schema.Schema{
37 | "id": {
38 | Description: "The ID of the settings.",
39 | Type: schema.TypeString,
40 | Computed: true,
41 | },
42 | "site": {
43 | Description: "The name of the site to associate the settings with.",
44 | Type: schema.TypeString,
45 | Computed: true,
46 | Optional: true,
47 | ForceNew: true,
48 | },
49 | "multicast_dns_enabled": {
50 | Description: "Whether multicast DNS is enabled.",
51 | Type: schema.TypeBool,
52 | Optional: true,
53 | Computed: true,
54 | },
55 | "firewall_guest_default_log": {
56 | Description: "Whether the guest firewall log is enabled.",
57 | Type: schema.TypeBool,
58 | Optional: true,
59 | Computed: true,
60 | },
61 | "firewall_lan_default_log": {
62 | Description: "Whether the LAN firewall log is enabled.",
63 | Type: schema.TypeBool,
64 | Optional: true,
65 | Computed: true,
66 | },
67 | "firewall_wan_default_log": {
68 | Description: "Whether the WAN firewall log is enabled.",
69 | Type: schema.TypeBool,
70 | Optional: true,
71 | Computed: true,
72 | },
73 | "dhcp_relay_servers": {
74 | Description: "The DHCP relay servers.",
75 | Type: schema.TypeList,
76 | Optional: true,
77 | Computed: true,
78 | MaxItems: 5,
79 | Elem: &schema.Schema{
80 | Type: schema.TypeString,
81 | ValidateFunc: validation.All(
82 | validation.IsIPv4Address,
83 | // this doesn't let blank through
84 | validation.StringLenBetween(1, 50),
85 | ),
86 | },
87 | },
88 | },
89 | }
90 | }
91 |
92 | func resourceSettingUsgUpdateResourceData(d *schema.ResourceData, meta any, setting *unifi.SettingUsg) error {
93 | c := meta.(*client)
94 |
95 | //nolint // GetOkExists is deprecated, but using here:
96 | if mdns, hasMdns := d.GetOkExists("multicast_dns_enabled"); hasMdns {
97 | if v := c.ControllerVersion(); v.GreaterThanOrEqual(controllerV7) {
98 | return fmt.Errorf("multicast_dns_enabled is not supported on controller version %v", c.ControllerVersion())
99 | }
100 |
101 | setting.MdnsEnabled = mdns.(bool)
102 | }
103 |
104 | setting.FirewallGuestDefaultLog = d.Get("firewall_guest_default_log").(bool)
105 | setting.FirewallLanDefaultLog = d.Get("firewall_lan_default_log").(bool)
106 | setting.FirewallWANDefaultLog = d.Get("firewall_wan_default_log").(bool)
107 |
108 | dhcpRelay, err := listToStringSlice(d.Get("dhcp_relay_servers").([]any))
109 | if err != nil {
110 | return fmt.Errorf("unable to convert dhcp_relay_servers to string slice: %w", err)
111 | }
112 | setting.DHCPRelayServer1 = append(dhcpRelay, "")[0]
113 | setting.DHCPRelayServer2 = append(dhcpRelay, "", "")[1]
114 | setting.DHCPRelayServer3 = append(dhcpRelay, "", "", "")[2]
115 | setting.DHCPRelayServer4 = append(dhcpRelay, "", "", "", "")[3]
116 | setting.DHCPRelayServer5 = append(dhcpRelay, "", "", "", "", "")[4]
117 |
118 | return nil
119 | }
120 |
121 | func resourceSettingUsgUpsert(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
122 | c := meta.(*client)
123 |
124 | site := d.Get("site").(string)
125 | if site == "" {
126 | site = c.site
127 | }
128 |
129 | req, err := c.c.GetSettingUsg(ctx, c.site)
130 | if err != nil {
131 | return diag.FromErr(err)
132 | }
133 |
134 | err = resourceSettingUsgUpdateResourceData(d, meta, req)
135 | if err != nil {
136 | return diag.FromErr(err)
137 | }
138 |
139 | resp, err := c.c.UpdateSettingUsg(ctx, site, req)
140 | if err != nil {
141 | return diag.FromErr(err)
142 | }
143 |
144 | d.SetId(resp.ID)
145 | return resourceSettingUsgSetResourceData(resp, d, meta, site)
146 | }
147 |
148 | func resourceSettingUsgSetResourceData(resp *unifi.SettingUsg, d *schema.ResourceData, meta any, site string) diag.Diagnostics {
149 | d.Set("site", site)
150 | d.Set("multicast_dns_enabled", resp.MdnsEnabled)
151 | d.Set("firewall_guest_default_log", resp.FirewallGuestDefaultLog)
152 | d.Set("firewall_lan_default_log", resp.FirewallLanDefaultLog)
153 | d.Set("firewall_wan_default_log", resp.FirewallWANDefaultLog)
154 |
155 | dhcpRelay := []string{}
156 | for _, s := range []string{
157 | resp.DHCPRelayServer1,
158 | resp.DHCPRelayServer2,
159 | resp.DHCPRelayServer3,
160 | resp.DHCPRelayServer4,
161 | resp.DHCPRelayServer5,
162 | } {
163 | if s == "" {
164 | continue
165 | }
166 | dhcpRelay = append(dhcpRelay, s)
167 | }
168 | d.Set("dhcp_relay_servers", dhcpRelay)
169 |
170 | return nil
171 | }
172 |
173 | func resourceSettingUsgRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
174 | c := meta.(*client)
175 |
176 | site := d.Get("site").(string)
177 | if site == "" {
178 | site = c.site
179 | }
180 |
181 | resp, err := c.c.GetSettingUsg(ctx, site)
182 | if _, ok := err.(*unifi.NotFoundError); ok {
183 | d.SetId("")
184 | return nil
185 | }
186 | if err != nil {
187 | return diag.FromErr(err)
188 | }
189 |
190 | return resourceSettingUsgSetResourceData(resp, d, meta, site)
191 | }
192 |
--------------------------------------------------------------------------------
/internal/provider/resource_setting_usg_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | "sync"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
10 | )
11 |
12 | // using an additional lock to the one around the resource to avoid deadlocking accidentally
13 | var settingUsgLock = sync.Mutex{}
14 |
15 | func TestAccSettingUsg_mdns_v6(t *testing.T) {
16 | resource.ParallelTest(t, resource.TestCase{
17 | PreCheck: func() {
18 | preCheck(t)
19 | preCheckVersionConstraint(t, "< 7")
20 | settingUsgLock.Lock()
21 | t.Cleanup(func() {
22 | settingUsgLock.Unlock()
23 | })
24 | },
25 | ProviderFactories: providerFactories,
26 | Steps: []resource.TestStep{
27 | {
28 | Config: testAccSettingUsgConfig_mdns(true),
29 | Check: resource.ComposeTestCheckFunc(),
30 | },
31 | importStep("unifi_setting_usg.test"),
32 | {
33 | Config: testAccSettingUsgConfig_mdns(false),
34 | Check: resource.ComposeTestCheckFunc(),
35 | },
36 | importStep("unifi_setting_usg.test"),
37 | {
38 | Config: testAccSettingUsgConfig_mdns(true),
39 | Check: resource.ComposeTestCheckFunc(),
40 | },
41 | importStep("unifi_setting_usg.test"),
42 | },
43 | })
44 | }
45 |
46 | func TestAccSettingUsg_mdns_v7(t *testing.T) {
47 | resource.ParallelTest(t, resource.TestCase{
48 | PreCheck: func() {
49 | preCheck(t)
50 | preCheckVersionConstraint(t, ">= 7")
51 | settingUsgLock.Lock()
52 | t.Cleanup(func() {
53 | settingUsgLock.Unlock()
54 | })
55 | },
56 | ProviderFactories: providerFactories,
57 | Steps: []resource.TestStep{
58 | {
59 | Config: testAccSettingUsgConfig_mdns(true),
60 | ExpectError: regexp.MustCompile("multicast_dns_enabled is not supported"),
61 | },
62 | },
63 | })
64 | }
65 |
66 | func TestAccSettingUsg_dhcpRelay(t *testing.T) {
67 | resource.ParallelTest(t, resource.TestCase{
68 | PreCheck: func() {
69 | preCheck(t)
70 | settingUsgLock.Lock()
71 | t.Cleanup(func() {
72 | settingUsgLock.Unlock()
73 | })
74 | },
75 | ProviderFactories: providerFactories,
76 | Steps: []resource.TestStep{
77 | {
78 | Config: testAccSettingUsgConfig_dhcpRelay(),
79 | Check: resource.ComposeTestCheckFunc(),
80 | },
81 | importStep("unifi_setting_usg.test"),
82 | },
83 | })
84 | }
85 |
86 | func TestAccSettingUsg_site(t *testing.T) {
87 | resource.ParallelTest(t, resource.TestCase{
88 | PreCheck: func() {
89 | preCheck(t)
90 | settingUsgLock.Lock()
91 | t.Cleanup(func() {
92 | settingUsgLock.Unlock()
93 | })
94 | },
95 | ProviderFactories: providerFactories,
96 | Steps: []resource.TestStep{
97 | {
98 | Config: testAccSettingUsgConfig_site(),
99 | Check: resource.ComposeTestCheckFunc(),
100 | },
101 | {
102 | ResourceName: "unifi_setting_usg.test",
103 | ImportState: true,
104 | ImportStateIdFunc: siteAndIDImportStateIDFunc("unifi_setting_usg.test"),
105 | ImportStateVerify: true,
106 | },
107 | },
108 | })
109 | }
110 |
111 | func testAccSettingUsgConfig_mdns(mdns bool) string {
112 | return fmt.Sprintf(`
113 | resource "unifi_setting_usg" "test" {
114 | multicast_dns_enabled = %t
115 | }
116 | `, mdns)
117 | }
118 |
119 | func testAccSettingUsgConfig_dhcpRelay() string {
120 | return `
121 | resource "unifi_setting_usg" "test" {
122 | dhcp_relay_servers = [
123 | "10.1.2.3",
124 | "10.1.2.4",
125 | ]
126 | }
127 | `
128 | }
129 |
130 | func testAccSettingUsgConfig_site() string {
131 | return `
132 | resource "unifi_site" "test" {
133 | description = "test"
134 | }
135 |
136 | resource "unifi_setting_usg" "test" {
137 | site = unifi_site.test.name
138 | }
139 | `
140 | }
141 |
--------------------------------------------------------------------------------
/internal/provider/resource_site.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 |
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10 | "github.com/ubiquiti-community/go-unifi/unifi"
11 | )
12 |
13 | func resourceSite() *schema.Resource {
14 | return &schema.Resource{
15 | Description: "`unifi_site` manages Unifi sites",
16 |
17 | CreateContext: resourceSiteCreate,
18 | ReadContext: resourceSiteRead,
19 | UpdateContext: resourceSiteUpdate,
20 | DeleteContext: resourceSiteDelete,
21 | Importer: &schema.ResourceImporter{
22 | StateContext: resourceSiteImport,
23 | },
24 |
25 | Schema: map[string]*schema.Schema{
26 | "id": {
27 | Description: "The ID of the site.",
28 | Type: schema.TypeString,
29 | Computed: true,
30 | },
31 | "description": {
32 | Description: "The description of the site.",
33 | Type: schema.TypeString,
34 | Required: true,
35 | },
36 | "name": {
37 | Description: "The name of the site.",
38 | Type: schema.TypeString,
39 | Computed: true,
40 | },
41 | },
42 | }
43 | }
44 |
45 | func resourceSiteImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
46 | c := meta.(*client)
47 |
48 | id := d.Id()
49 | _, err := c.c.GetSite(ctx, id)
50 | if err != nil {
51 | var nf *unifi.NotFoundError
52 | if !errors.As(err, &nf) {
53 | return nil, err
54 | }
55 | } else {
56 | // id is a valid site
57 | return []*schema.ResourceData{d}, nil
58 | }
59 |
60 | // lookup site by name
61 | sites, err := c.c.ListSites(ctx)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | for _, s := range sites {
67 | if s.Name == id {
68 | d.SetId(s.ID)
69 | return []*schema.ResourceData{d}, nil
70 | }
71 | }
72 |
73 | return nil, fmt.Errorf("unable to find site %q on controller", id)
74 | }
75 |
76 | func resourceSiteCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
77 | c := meta.(*client)
78 |
79 | description := d.Get("description").(string)
80 |
81 | resp, err := c.c.CreateSite(ctx, description)
82 | if err != nil {
83 | return diag.FromErr(err)
84 | }
85 |
86 | site := resp[0]
87 | d.SetId(site.ID)
88 |
89 | return resourceSiteSetResourceData(&site, d)
90 | }
91 |
92 | func resourceSiteSetResourceData(resp *unifi.Site, d *schema.ResourceData) diag.Diagnostics {
93 | d.Set("name", resp.Name)
94 | d.Set("description", resp.Description)
95 | return nil
96 | }
97 |
98 | func resourceSiteRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
99 | c := meta.(*client)
100 |
101 | id := d.Id()
102 |
103 | site, err := c.c.GetSite(ctx, id)
104 | if _, ok := err.(*unifi.NotFoundError); ok {
105 | d.SetId("")
106 | return nil
107 | }
108 | if err != nil {
109 | return diag.FromErr(err)
110 | }
111 |
112 | return resourceSiteSetResourceData(site, d)
113 | }
114 |
115 | func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
116 | c := meta.(*client)
117 |
118 | site := &unifi.Site{
119 | ID: d.Id(),
120 | Name: d.Get("name").(string),
121 | Description: d.Get("description").(string),
122 | }
123 |
124 | resp, err := c.c.UpdateSite(ctx, site.Name, site.Description)
125 | if err != nil {
126 | return diag.FromErr(err)
127 | }
128 |
129 | return resourceSiteSetResourceData(&resp[0], d)
130 | }
131 |
132 | func resourceSiteDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
133 | c := meta.(*client)
134 | id := d.Id()
135 | _, err := c.c.DeleteSite(ctx, id)
136 | return diag.FromErr(err)
137 | }
138 |
--------------------------------------------------------------------------------
/internal/provider/resource_site_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
10 | "github.com/hashicorp/terraform-plugin-testing/terraform"
11 | )
12 |
13 | func TestAccSite_basic(t *testing.T) {
14 | var siteName string
15 |
16 | resource.ParallelTest(t, resource.TestCase{
17 | PreCheck: func() { preCheck(t) },
18 | ProviderFactories: providerFactories,
19 | CheckDestroy: testAccCheckSiteResourceDestroy,
20 | Steps: []resource.TestStep{
21 | {
22 | Config: testAccSiteConfig("tfacc-desc1"),
23 | Check: resource.ComposeTestCheckFunc(
24 | resource.TestCheckResourceAttr("unifi_site.test", "description", "tfacc-desc1"),
25 |
26 | // extract siteName for future use
27 | func(s *terraform.State) error {
28 | siteName = s.RootModule().Resources["unifi_site.test"].Primary.Attributes["name"]
29 | return nil
30 | },
31 | ),
32 | },
33 | importStep("unifi_site.test"),
34 | {
35 | Config: testAccSiteConfig("tfacc-desc2"),
36 | Check: resource.ComposeTestCheckFunc(
37 | resource.TestCheckResourceAttr("unifi_site.test", "description", "tfacc-desc2"),
38 | ),
39 | },
40 | importStep("unifi_site.test"),
41 |
42 | // test importing from name, not id
43 | {
44 | ResourceName: "unifi_site.test",
45 | ImportStateIdFunc: func(*terraform.State) (string, error) {
46 | return siteName, nil
47 | },
48 | ImportState: true,
49 | ImportStateVerify: true,
50 | },
51 | },
52 | })
53 | }
54 |
55 | func testAccCheckSiteResourceDestroy(s *terraform.State) error {
56 | sites, err := testClient.ListSites(context.Background())
57 | if err != nil {
58 | return err
59 | }
60 | for _, site := range sites {
61 | if strings.HasPrefix(site.Description, "tfacc-") {
62 | return fmt.Errorf("site not destroyed")
63 | }
64 | }
65 | return nil
66 | }
67 |
68 | func testAccSiteConfig(desc string) string {
69 | return fmt.Sprintf(`
70 | resource "unifi_site" "test" {
71 | description = %q
72 | }
73 | `, desc)
74 | }
75 |
--------------------------------------------------------------------------------
/internal/provider/resource_static_route.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
10 | "github.com/ubiquiti-community/go-unifi/unifi"
11 | )
12 |
13 | func resourceStaticRoute() *schema.Resource {
14 | return &schema.Resource{
15 | Description: "`unifi_static_route` manages a static route.",
16 |
17 | CreateContext: resourceStaticRouteCreate,
18 | ReadContext: resourceStaticRouteRead,
19 | UpdateContext: resourceStaticRouteUpdate,
20 | DeleteContext: resourceStaticRouteDelete,
21 | Importer: &schema.ResourceImporter{
22 | StateContext: importSiteAndID,
23 | },
24 |
25 | Schema: map[string]*schema.Schema{
26 | "id": {
27 | Description: "The ID of the static route.",
28 | Type: schema.TypeString,
29 | Computed: true,
30 | },
31 | "site": {
32 | Description: "The name of the site to associate the static route with.",
33 | Type: schema.TypeString,
34 | Computed: true,
35 | Optional: true,
36 | ForceNew: true,
37 | },
38 | "name": {
39 | Description: "The name of the static route.",
40 | Type: schema.TypeString,
41 | Required: true,
42 | },
43 |
44 | "network": {
45 | Description: "The network subnet address.",
46 | Type: schema.TypeString,
47 | Required: true,
48 | ValidateFunc: cidrValidate,
49 | DiffSuppressFunc: cidrDiffSuppress,
50 | },
51 | "type": {
52 | Description: "The type of static route. Can be `interface-route`, `nexthop-route`, or `blackhole`.",
53 | Type: schema.TypeString,
54 | Required: true,
55 | ValidateFunc: validation.StringInSlice([]string{"interface-route", "nexthop-route", "blackhole"}, false),
56 | },
57 | "distance": {
58 | Description: "The distance of the static route.",
59 | Type: schema.TypeInt,
60 | Required: true,
61 | },
62 |
63 | "next_hop": {
64 | Description: "The next hop of the static route (only valid for `nexthop-route` type).",
65 | Type: schema.TypeString,
66 | Optional: true,
67 | ValidateFunc: validation.IsIPAddress,
68 | },
69 | "interface": {
70 | Description: "The interface of the static route (only valid for `interface-route` type). This can be `WAN1`, `WAN2`, or a network ID.",
71 | Type: schema.TypeString,
72 | Optional: true,
73 | },
74 | },
75 | }
76 | }
77 |
78 | func resourceStaticRouteCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
79 | c := meta.(*client)
80 |
81 | req, err := resourceStaticRouteGetResourceData(d)
82 | if err != nil {
83 | return diag.FromErr(err)
84 | }
85 |
86 | site := d.Get("site").(string)
87 | if site == "" {
88 | site = c.site
89 | }
90 |
91 | resp, err := c.c.CreateRouting(ctx, site, req)
92 | if err != nil {
93 | return diag.FromErr(err)
94 | }
95 |
96 | d.SetId(resp.ID)
97 |
98 | return resourceStaticRouteSetResourceData(resp, d, site)
99 | }
100 |
101 | func resourceStaticRouteGetResourceData(d *schema.ResourceData) (*unifi.Routing, error) {
102 | t := d.Get("type").(string)
103 |
104 | r := &unifi.Routing{
105 | Enabled: true,
106 | Type: "static-route",
107 |
108 | Name: d.Get("name").(string),
109 | StaticRouteNetwork: cidrZeroBased(d.Get("network").(string)),
110 | StaticRouteDistance: d.Get("distance").(int),
111 | StaticRouteType: t,
112 | }
113 |
114 | switch t {
115 | case "interface-route":
116 | r.StaticRouteInterface = d.Get("interface").(string)
117 | case "nexthop-route":
118 | r.StaticRouteNexthop = d.Get("next_hop").(string)
119 | case "blackhole":
120 | default:
121 | return nil, fmt.Errorf("unexpected route type: %q", t)
122 | }
123 |
124 | return r, nil
125 | }
126 |
127 | func resourceStaticRouteSetResourceData(resp *unifi.Routing, d *schema.ResourceData, site string) diag.Diagnostics {
128 | d.Set("site", site)
129 | d.Set("name", resp.Name)
130 | d.Set("network", cidrZeroBased(resp.StaticRouteNetwork))
131 | d.Set("distance", resp.StaticRouteDistance)
132 |
133 | t := resp.StaticRouteType
134 | d.Set("type", t)
135 |
136 | d.Set("next_hop", "")
137 | d.Set("interface", "")
138 |
139 | switch t {
140 | case "interface-route":
141 | d.Set("interface", resp.StaticRouteInterface)
142 | case "nexthop-route":
143 | d.Set("next_hop", resp.StaticRouteNexthop)
144 | case "blackhole":
145 | // no additional attributes
146 | default:
147 | return diag.Errorf("unexpected static route type: %q", t)
148 | }
149 |
150 | return nil
151 | }
152 |
153 | func resourceStaticRouteRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
154 | c := meta.(*client)
155 |
156 | id := d.Id()
157 |
158 | site := d.Get("site").(string)
159 | if site == "" {
160 | site = c.site
161 | }
162 |
163 | resp, err := c.c.GetRouting(ctx, site, id)
164 | if _, ok := err.(*unifi.NotFoundError); ok {
165 | d.SetId("")
166 | return nil
167 | }
168 | if err != nil {
169 | return diag.FromErr(err)
170 | }
171 |
172 | return resourceStaticRouteSetResourceData(resp, d, site)
173 | }
174 |
175 | func resourceStaticRouteUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
176 | c := meta.(*client)
177 |
178 | req, err := resourceStaticRouteGetResourceData(d)
179 | if err != nil {
180 | return diag.FromErr(err)
181 | }
182 |
183 | req.ID = d.Id()
184 |
185 | site := d.Get("site").(string)
186 | if site == "" {
187 | site = c.site
188 | }
189 | req.SiteID = site
190 |
191 | resp, err := c.c.UpdateRouting(ctx, site, req)
192 | if err != nil {
193 | return diag.FromErr(err)
194 | }
195 |
196 | return resourceStaticRouteSetResourceData(resp, d, site)
197 | }
198 |
199 | func resourceStaticRouteDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
200 | c := meta.(*client)
201 |
202 | id := d.Id()
203 |
204 | site := d.Get("site").(string)
205 | if site == "" {
206 | site = c.site
207 | }
208 | err := c.c.DeleteRouting(ctx, site, id)
209 | if _, ok := err.(*unifi.NotFoundError); ok {
210 | return nil
211 | }
212 | return diag.FromErr(err)
213 | }
214 |
--------------------------------------------------------------------------------
/internal/provider/resource_user_group.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8 | "github.com/ubiquiti-community/go-unifi/unifi"
9 | )
10 |
11 | func resourceUserGroup() *schema.Resource {
12 | return &schema.Resource{
13 | Description: "`unifi_user_group` manages a user group (called \"client group\" in the UI), which can be used " +
14 | "to limit bandwidth for groups of users.",
15 |
16 | CreateContext: resourceUserGroupCreate,
17 | ReadContext: resourceUserGroupRead,
18 | UpdateContext: resourceUserGroupUpdate,
19 | DeleteContext: resourceUserGroupDelete,
20 | Importer: &schema.ResourceImporter{
21 | StateContext: importSiteAndID,
22 | },
23 |
24 | Schema: map[string]*schema.Schema{
25 | "id": {
26 | Description: "The ID of the user group.",
27 | Type: schema.TypeString,
28 | Computed: true,
29 | },
30 | "site": {
31 | Description: "The name of the site to associate the user group with.",
32 | Type: schema.TypeString,
33 | Computed: true,
34 | Optional: true,
35 | ForceNew: true,
36 | },
37 | "name": {
38 | Description: "The name of the user group.",
39 | Type: schema.TypeString,
40 | Required: true,
41 | },
42 | "qos_rate_max_down": {
43 | Description: "The QOS maximum download rate.",
44 | Type: schema.TypeInt,
45 | Optional: true,
46 | Default: -1,
47 | // TODO: validate does not equal 0,1
48 | },
49 | "qos_rate_max_up": {
50 | Description: "The QOS maximum upload rate.",
51 | Type: schema.TypeInt,
52 | Optional: true,
53 | Default: -1,
54 | // TODO: validate does not equal 0,1
55 | },
56 | },
57 | }
58 | }
59 |
60 | func resourceUserGroupCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
61 | c := meta.(*client)
62 |
63 | req, err := resourceUserGroupGetResourceData(d)
64 | if err != nil {
65 | return diag.FromErr(err)
66 | }
67 |
68 | site := d.Get("site").(string)
69 | if site == "" {
70 | site = c.site
71 | }
72 |
73 | resp, err := c.c.CreateUserGroup(context.TODO(), site, req)
74 | if err != nil {
75 | return diag.FromErr(err)
76 | }
77 |
78 | d.SetId(resp.ID)
79 |
80 | return resourceUserGroupSetResourceData(resp, d)
81 | }
82 |
83 | func resourceUserGroupGetResourceData(d *schema.ResourceData) (*unifi.UserGroup, error) {
84 | return &unifi.UserGroup{
85 | Name: d.Get("name").(string),
86 | QOSRateMaxDown: d.Get("qos_rate_max_down").(int),
87 | QOSRateMaxUp: d.Get("qos_rate_max_up").(int),
88 | }, nil
89 | }
90 |
91 | func resourceUserGroupSetResourceData(resp *unifi.UserGroup, d *schema.ResourceData) diag.Diagnostics {
92 | d.Set("name", resp.Name)
93 | d.Set("qos_rate_max_down", resp.QOSRateMaxDown)
94 | d.Set("qos_rate_max_up", resp.QOSRateMaxUp)
95 |
96 | return nil
97 | }
98 |
99 | func resourceUserGroupRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
100 | c := meta.(*client)
101 |
102 | id := d.Id()
103 |
104 | site := d.Get("site").(string)
105 | if site == "" {
106 | site = c.site
107 | }
108 |
109 | resp, err := c.c.GetUserGroup(context.TODO(), site, id)
110 | if _, ok := err.(*unifi.NotFoundError); ok {
111 | d.SetId("")
112 | return nil
113 | }
114 | if err != nil {
115 | return diag.FromErr(err)
116 | }
117 |
118 | return resourceUserGroupSetResourceData(resp, d)
119 | }
120 |
121 | func resourceUserGroupUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
122 | c := meta.(*client)
123 |
124 | req, err := resourceUserGroupGetResourceData(d)
125 | if err != nil {
126 | return diag.FromErr(err)
127 | }
128 |
129 | req.ID = d.Id()
130 |
131 | site := d.Get("site").(string)
132 | if site == "" {
133 | site = c.site
134 | }
135 | req.SiteID = site
136 |
137 | resp, err := c.c.UpdateUserGroup(context.TODO(), site, req)
138 | if err != nil {
139 | return diag.FromErr(err)
140 | }
141 |
142 | return resourceUserGroupSetResourceData(resp, d)
143 | }
144 |
145 | func resourceUserGroupDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
146 | c := meta.(*client)
147 |
148 | id := d.Id()
149 |
150 | site := d.Get("site").(string)
151 | if site == "" {
152 | site = c.site
153 | }
154 | err := c.c.DeleteUserGroup(context.TODO(), site, id)
155 | if _, ok := err.(*unifi.NotFoundError); ok {
156 | return nil
157 | }
158 | return diag.FromErr(err)
159 | }
160 |
--------------------------------------------------------------------------------
/internal/provider/resource_user_group_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-testing/helper/resource"
7 | )
8 |
9 | func TestAccUserGroup_basic(t *testing.T) {
10 | resource.ParallelTest(t, resource.TestCase{
11 | PreCheck: func() { preCheck(t) },
12 | ProviderFactories: providerFactories,
13 | // TODO: CheckDestroy: ,
14 | Steps: []resource.TestStep{
15 | {
16 | Config: testAccUserGroupConfig,
17 | // Check: resource.ComposeTestCheckFunc(
18 | // // testCheckUserGroupExists(t, "name"),
19 | // ),
20 | },
21 | {
22 | Config: testAccUserGroupConfig_qos,
23 | },
24 | importStep("unifi_user_group.test"),
25 | {
26 | Config: testAccUserGroupConfig,
27 | },
28 | importStep("unifi_user_group.test"),
29 | },
30 | })
31 | }
32 |
33 | const testAccUserGroupConfig = `
34 | resource "unifi_user_group" "test" {
35 | name = "tfacc"
36 | }
37 | `
38 |
39 | const testAccUserGroupConfig_qos = `
40 | resource "unifi_user_group" "test" {
41 | name = "tfacc"
42 |
43 | qos_rate_max_up = 2000
44 | qos_rate_max_down = 50
45 | }
46 | `
47 |
--------------------------------------------------------------------------------
/internal/provider/strings.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7 | )
8 |
9 | func listToStringSlice(src []any) ([]string, error) {
10 | dst := make([]string, 0, len(src))
11 | for _, s := range src {
12 | d, ok := s.(string)
13 | if !ok {
14 | return nil, fmt.Errorf("unale to convert %v (%T) to string", s, s)
15 | }
16 | dst = append(dst, d)
17 | }
18 | return dst, nil
19 | }
20 |
21 | func setToStringSlice(src *schema.Set) ([]string, error) {
22 | return listToStringSlice(src.List())
23 | }
24 |
25 | func stringSliceToList(list []string) []any {
26 | vs := make([]any, 0, len(list))
27 | for _, v := range list {
28 | vs = append(vs, v)
29 | }
30 | return vs
31 | }
32 |
33 | func stringSliceToSet(src []string) *schema.Set {
34 | return schema.NewSet(schema.HashString, stringSliceToList(src))
35 | }
36 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main // import "github.com/ubiquiti-community/terraform-provider-unifi"
2 |
3 | import (
4 | "flag"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
7 |
8 | "github.com/ubiquiti-community/terraform-provider-unifi/internal/provider"
9 | )
10 |
11 | // Generate docs for website
12 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
13 |
14 | var (
15 | // these will be set by the goreleaser configuration
16 | // to appropriate values for the compiled binary
17 | version string = "dev"
18 |
19 | // goreleaser can also pass the specific commit if you want
20 | // commit string = ""
21 | )
22 |
23 | func main() {
24 | var debugMode bool
25 |
26 | flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve")
27 | flag.Parse()
28 |
29 | opts := &plugin.ServeOpts{ProviderFunc: provider.New(version)}
30 |
31 | if debugMode {
32 | opts.Debug = true
33 | opts.ProviderAddr = "registry.terraform.io/paultyng/unifi"
34 | }
35 |
36 | plugin.Serve(opts)
37 | }
38 |
--------------------------------------------------------------------------------
/scripts/init.d/demo-mode:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | write_config() {
4 | echo "${1}=${2}" >> /usr/lib/unifi/data/system.properties
5 | }
6 |
7 | write_config is_simulation true
8 |
9 | # Increase the number of demo devices to allow more concurrent tests to be executed simultaneously.
10 | write_config demo.num_uap 0
11 | write_config demo.num_ugw 1
12 | write_config demo.num_usw 20
13 |
--------------------------------------------------------------------------------
/templates/guides/csv-users.md.tmpl:
--------------------------------------------------------------------------------
1 | ---
2 | subcategory: ""
3 | page_title: "Manage Users/Clients in a CSV - Unifi Provider"
4 | description: |-
5 | An example of using a CSV to manage all of your users of your network.
6 | ---
7 |
8 | # Manage Users in a CSV
9 |
10 | Given a CSV file with the following content:
11 |
12 | {{ codefile "csv" "examples/csv_users/users.csv" }}
13 |
14 | You could create/manage a `unifi_user` for every row/MAC address in the CSV with the following config:
15 |
16 | {{ tffile "examples/csv_users/users.tf" }}
17 |
--------------------------------------------------------------------------------
/templates/guides/multiple-site-firewall.md.tmpl:
--------------------------------------------------------------------------------
1 | ---
2 | subcategory: ""
3 | page_title: "Manage Firewall Rules for Multiple Sites - Unifi Provider"
4 | description: |-
5 | An example of applying firewall rules to multiple sites.
6 | ---
7 |
8 | # Manage Firewall Rules for Multiple Sites
9 |
10 | The provider takes a default site value but all resources in the provider should allow overriding of the
11 | site you are managing. In order to apply and manage a firewall rule across multiple sites, you simply
12 | need to provide different values for the `site` attribute to `unifi_firewall_rule`:
13 |
14 | {{ tffile "examples/multiple_site_firewall/firewall.tf" }}
15 |
16 | You could optionally load lists of sites from JSON/CSV, variables, or other sources.
17 |
18 | When you apply this configuration it will create the same firewall rule on every site in the list.
19 | If you need to update the rule, you simply make an update to the rule definition and Terraform will
20 | apply/update it across all the sites. If you add / or remove a site from the list, Terraform will also
21 | handle creating or removing the rule on the subsequent `terraform apply`.
22 |
--------------------------------------------------------------------------------
/templates/index.md.tmpl:
--------------------------------------------------------------------------------
1 | ---
2 | layout: ""
3 | page_title: "Provider: Unifi"
4 | description: |-
5 | The Unifi provider provides resources to interact with a Unifi controller API.
6 | ---
7 |
8 | # Unifi Provider
9 |
10 | The Unifi provider provides resources to interact with a Unifi controller API.
11 |
12 | It is not recommended to use your own account for management of your controller. A user specific to
13 | Terraform is recommended. You can create a **Limited Admin** with **Local Access Only** and
14 | provide that information for authentication. Two-factor authentication is not supported in the provider.
15 |
16 | ## Example Usage
17 |
18 | {{tffile "examples/provider/provider.tf"}}
19 |
20 | {{ .SchemaMarkdown | trimspace }}
21 |
--------------------------------------------------------------------------------
/tools/tools.go:
--------------------------------------------------------------------------------
1 | //go:build tools
2 | // +build tools
3 |
4 | package tools
5 |
6 | import (
7 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint"
8 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs"
9 | )
10 |
--------------------------------------------------------------------------------