Archived
Harden read-only provider with tests, docs, and CI
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Verify formatting and run quality gate
|
||||||
|
run: ./scripts/check.sh
|
||||||
@@ -1,30 +1,38 @@
|
|||||||
# terraform-provider-dynu
|
# terraform-provider-dynu
|
||||||
|
|
||||||
A standalone Terraform provider for [Dynu](https://www.dynu.com/) DNS data.
|
A standalone Terraform provider for Dynu DNS.
|
||||||
|
|
||||||
> Current phase: **read-only**. This provider supports provider configuration and data sources only.
|
> Status: **read-only milestone**. This provider currently implements provider configuration and data sources only.
|
||||||
|
|
||||||
## Feature scope
|
## Feature scope
|
||||||
|
|
||||||
Implemented:
|
Implemented:
|
||||||
- Provider authentication via API key
|
- Provider authentication using `api_key` or `DYNU_API_KEY`
|
||||||
- Environment variable support (`DYNU_API_KEY`)
|
- Data sources:
|
||||||
- Read-only data sources:
|
|
||||||
- `dynu_domains`
|
- `dynu_domains`
|
||||||
- `dynu_domain`
|
- `dynu_domain`
|
||||||
- `dynu_dns_records`
|
- `dynu_dns_records`
|
||||||
|
|
||||||
Not implemented in this phase:
|
Not implemented yet:
|
||||||
- Terraform resources
|
- Terraform resources (no create/update/delete)
|
||||||
- Any create/update/delete operations
|
- Any write API operations
|
||||||
|
|
||||||
|
## Provider source and module path
|
||||||
|
|
||||||
|
- Terraform provider source address: `dynu/dynu`
|
||||||
|
- Go module path: `github.com/dynu/terraform-provider-dynu`
|
||||||
|
|
||||||
|
The repository can be hosted elsewhere during development, but module and provider source naming are kept aligned with planned public registry publishing.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- [Terraform](https://developer.hashicorp.com/terraform/downloads) `>= 1.5`
|
- Terraform `>= 1.5`
|
||||||
- [Go](https://go.dev/dl/) `>= 1.23` (for building and testing)
|
- Go `>= 1.23`
|
||||||
- Dynu API key
|
- Dynu API key
|
||||||
|
|
||||||
## Provider configuration
|
## Authentication
|
||||||
|
|
||||||
|
Option 1: Terraform configuration.
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
provider "dynu" {
|
provider "dynu" {
|
||||||
@@ -37,76 +45,77 @@ variable "dynu_api_key" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can omit `api_key` in Terraform configuration and set the environment variable instead:
|
Option 2: Environment variable.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export DYNU_API_KEY="your-dynu-api-key"
|
export DYNU_API_KEY="your-dynu-api-key"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Data source usage
|
## Data source examples
|
||||||
|
|
||||||
### dynu_domains
|
See the `examples/` directory:
|
||||||
|
- `examples/provider/provider.tf`
|
||||||
|
- `examples/data-sources/dynu_domains/data-source.tf`
|
||||||
|
- `examples/data-sources/dynu_domain/data-source.tf`
|
||||||
|
- `examples/data-sources/dynu_dns_records/data-source.tf`
|
||||||
|
|
||||||
```hcl
|
## Developer workflow
|
||||||
data "dynu_domains" "all" {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### dynu_domain
|
- `./scripts/setup-dev.sh` - verify required local tools
|
||||||
|
- `./scripts/check.sh` - formatting, vet, and unit tests
|
||||||
|
- `./scripts/testacc.sh` - acceptance tests only
|
||||||
|
|
||||||
```hcl
|
### Build
|
||||||
data "dynu_domain" "selected" {
|
|
||||||
hostname = "www.example.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### dynu_dns_records
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
data "dynu_dns_records" "records" {
|
|
||||||
hostname = "www.example.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build ./...
|
go build ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test
|
### Unit tests
|
||||||
|
|
||||||
Run formatting and unit tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/check.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run unit tests directly:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go test ./...
|
go test ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Acceptance tests
|
### Acceptance tests
|
||||||
|
|
||||||
Acceptance tests are opt-in and require live Dynu credentials.
|
Acceptance tests are read-only and opt-in.
|
||||||
|
|
||||||
|
Required environment variables:
|
||||||
|
- `TF_ACC=1`
|
||||||
|
- `DYNU_API_KEY`
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
- `DYNU_DOMAIN` (required for domain-specific acceptance tests such as `dynu_domain` and `dynu_dns_records`)
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
TF_ACC=1 DYNU_API_KEY="your-dynu-api-key" ./scripts/testacc.sh
|
TF_ACC=1 DYNU_API_KEY="your-dynu-api-key" DYNU_DOMAIN="www.example.com" ./scripts/testacc.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional environment variable:
|
If `DYNU_DOMAIN` is omitted, domain-specific tests skip cleanly.
|
||||||
- `DYNU_DOMAIN` (for future domain-specific acceptance test cases)
|
|
||||||
|
|
||||||
## Developer workflow
|
## CI
|
||||||
|
|
||||||
- `./scripts/setup-dev.sh` – validates required local tools
|
GitHub Actions CI runs on push and pull requests and executes:
|
||||||
- `./scripts/check.sh` – runs formatting and unit checks
|
- gofmt verification
|
||||||
- `./scripts/testacc.sh` – runs acceptance tests
|
- `go vet ./...`
|
||||||
|
- `go test ./...`
|
||||||
|
|
||||||
Repository-local Codex helpers are also available under `codex/` for agent-oriented workflows.
|
Acceptance tests are intentionally excluded from default CI.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Registry-style markdown docs are stored in `docs/`.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
- Read-only provider phase only
|
- Dynu timestamps are currently exposed as strings exactly as returned by Dynu API.
|
||||||
- No writable Terraform resources yet
|
- Data returned from Dynu is sorted in provider state for Terraform stability.
|
||||||
|
- Read-only operations only.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
Next planned milestone after this quality-hardening release:
|
||||||
|
- first writable resource (`dynu_dns_record`) with careful CRUD behavior and acceptance coverage.
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
page_title: "dynu_dns_records Data Source"
|
||||||
|
description: |-
|
||||||
|
Lists DNS records for the Dynu root domain resolved from a hostname.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dynu_dns_records Data Source
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
data "dynu_dns_records" "records" {
|
||||||
|
hostname = "www.example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `hostname` (String) Fully-qualified hostname.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `domain_id` (Number)
|
||||||
|
- `domain_name` (String)
|
||||||
|
- `records` (List of Object)
|
||||||
|
- `id` (Number)
|
||||||
|
- `domain_id` (Number)
|
||||||
|
- `domain_name` (String)
|
||||||
|
- `node_name` (String)
|
||||||
|
- `hostname` (String)
|
||||||
|
- `record_type` (String)
|
||||||
|
- `ttl` (Number)
|
||||||
|
- `state` (Boolean)
|
||||||
|
- `content` (String)
|
||||||
|
- `updated_on` (String)
|
||||||
|
- `group` (String)
|
||||||
|
- `host` (String)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
page_title: "dynu_domain Data Source"
|
||||||
|
description: |-
|
||||||
|
Resolves a hostname to its Dynu root domain and returns domain details.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dynu_domain Data Source
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
data "dynu_domain" "selected" {
|
||||||
|
hostname = "www.example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `hostname` (String) Fully-qualified hostname.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `domain` (Object) Same fields returned by `dynu_domains.domains[*]`.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
page_title: "dynu_domains Data Source"
|
||||||
|
description: |-
|
||||||
|
Lists domains visible to the configured Dynu API key.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dynu_domains Data Source
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
data "dynu_domains" "all" {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read-Only Attributes
|
||||||
|
|
||||||
|
- `domains` (List of Object)
|
||||||
|
- `id` (Number)
|
||||||
|
- `name` (String)
|
||||||
|
- `unicode_name` (String)
|
||||||
|
- `token` (String, Sensitive)
|
||||||
|
- `state` (String)
|
||||||
|
- `group` (String)
|
||||||
|
- `ipv4_address` (String)
|
||||||
|
- `ipv6_address` (String)
|
||||||
|
- `ttl` (Number)
|
||||||
|
- `ipv4` (Boolean)
|
||||||
|
- `ipv6` (Boolean)
|
||||||
|
- `ipv4_wildcard_alias` (Boolean)
|
||||||
|
- `ipv6_wildcard_alias` (Boolean)
|
||||||
|
- `allow_zone_transfer` (Boolean)
|
||||||
|
- `dnssec` (Boolean)
|
||||||
|
- `created_on` (String)
|
||||||
|
- `updated_on` (String)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
page_title: "dynu Provider"
|
||||||
|
description: |-
|
||||||
|
Terraform provider for Dynu DNS read-only data sources.
|
||||||
|
---
|
||||||
|
|
||||||
|
# dynu Provider
|
||||||
|
|
||||||
|
The `dynu` provider lets Terraform read Dynu DNS domain and record data.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
dynu = {
|
||||||
|
source = "dynu/dynu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "dynu" {
|
||||||
|
api_key = var.dynu_api_key
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `api_key` (String, Sensitive) Dynu API key. If omitted, `DYNU_API_KEY` is used.
|
||||||
|
- `base_url` (String) Override API base URL. Primarily intended for automated tests.
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
dynu = {
|
||||||
|
source = "dynu/dynu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "dynu" {}
|
||||||
|
|
||||||
data "dynu_dns_records" "records" {
|
data "dynu_dns_records" "records" {
|
||||||
hostname = "www.example.com"
|
hostname = "www.example.com"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
dynu = {
|
||||||
|
source = "dynu/dynu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "dynu" {}
|
||||||
|
|
||||||
data "dynu_domain" "selected" {
|
data "dynu_domain" "selected" {
|
||||||
hostname = "www.example.com"
|
hostname = "www.example.com"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
dynu = {
|
||||||
|
source = "dynu/dynu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "dynu" {}
|
||||||
|
|
||||||
data "dynu_domains" "all" {}
|
data "dynu_domains" "all" {}
|
||||||
|
|
||||||
output "domains" {
|
output "domains" {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
dynu = {
|
||||||
|
source = "dynu/dynu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
provider "dynu" {
|
provider "dynu" {
|
||||||
# api_key can be omitted when DYNU_API_KEY is set
|
# api_key can be omitted when DYNU_API_KEY is set.
|
||||||
api_key = var.dynu_api_key
|
api_key = var.dynu_api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,26 +10,26 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.13.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||||
github.com/hashicorp/go-plugin v1.6.3 // indirect
|
github.com/hashicorp/go-plugin v1.6.2 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/hashicorp/terraform-plugin-go v0.27.0 // indirect
|
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
|
||||||
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
|
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
|
||||||
github.com/hashicorp/terraform-registry-address v0.2.5 // indirect
|
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
|
||||||
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||||
github.com/oklog/run v1.1.0 // indirect
|
github.com/oklog/run v1.0.0 // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
google.golang.org/grpc v1.72.2 // indirect
|
google.golang.org/grpc v1.69.4 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.3 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,35 +3,34 @@ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
|
||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||||
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
|
github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog=
|
||||||
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
|
github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q=
|
||||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY=
|
github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY=
|
||||||
github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4=
|
github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4=
|
||||||
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg=
|
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg=
|
||||||
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0=
|
github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0=
|
||||||
github.com/hashicorp/terraform-plugin-go v0.27.0 h1:ujykws/fWIdsi6oTUT5Or4ukvEan4aN9lY+LOxVP8EE=
|
github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M=
|
||||||
github.com/hashicorp/terraform-plugin-go v0.27.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o=
|
github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY=
|
||||||
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
|
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
|
||||||
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
|
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
|
||||||
github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M=
|
github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA=
|
||||||
github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg=
|
github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU=
|
||||||
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
|
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
|
||||||
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
|
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
|
||||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||||
@@ -39,18 +38,16 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg
|
|||||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||||
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||||
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -61,37 +58,34 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
|
|||||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
||||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -136,7 +137,7 @@ func (c *Client) GetDomainByID(ctx context.Context, domainID int64) (*Domain, er
|
|||||||
|
|
||||||
func (c *Client) GetRootDomain(ctx context.Context, hostname string) (int64, string, error) {
|
func (c *Client) GetRootDomain(ctx context.Context, hostname string) (int64, string, error) {
|
||||||
var resp getRootResponse
|
var resp getRootResponse
|
||||||
if err := c.doGET(ctx, fmt.Sprintf("/dns/getroot/%s", hostname), &resp); err != nil {
|
if err := c.doGET(ctx, fmt.Sprintf("/dns/getroot/%s", url.PathEscape(hostname)), &resp); err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +157,8 @@ func (c *Client) ListDNSRecords(ctx context.Context, domainID int64) ([]DNSRecor
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doGET(ctx context.Context, path string, target any) error {
|
func (c *Client) doGET(ctx context.Context, path string, target any) error {
|
||||||
url := c.baseURL + path
|
requestURL := c.baseURL + path
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -176,7 +177,11 @@ func (c *Client) doGET(ctx context.Context, path string, target any) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apiErr := parseAPIException(payload)
|
||||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||||
|
if apiErr != nil {
|
||||||
|
return apiErr
|
||||||
|
}
|
||||||
return fmt.Errorf("dynu API returned status %d: %s", res.StatusCode, strings.TrimSpace(string(payload)))
|
return fmt.Errorf("dynu API returned status %d: %s", res.StatusCode, strings.TrimSpace(string(payload)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,10 +189,18 @@ func (c *Client) doGET(ctx context.Context, path string, target any) error {
|
|||||||
return fmt.Errorf("failed to decode dynu API response: %w", err)
|
return fmt.Errorf("failed to decode dynu API response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiResult := apiResponse{}
|
if apiErr != nil {
|
||||||
if err := json.Unmarshal(payload, &apiResult); err == nil && apiResult.Exception != nil {
|
return apiErr
|
||||||
return fmt.Errorf("dynu API error %d (%s): %s", apiResult.Exception.StatusCode, apiResult.Exception.Type, apiResult.Exception.Message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseAPIException(payload []byte) error {
|
||||||
|
apiResult := apiResponse{}
|
||||||
|
if err := json.Unmarshal(payload, &apiResult); err != nil || apiResult.Exception == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("dynu API error %d (%s): %s", apiResult.Exception.StatusCode, apiResult.Exception.Type, apiResult.Exception.Message)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestListDomains(t *testing.T) {
|
func TestClientListDomainsSuccess(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/dns" {
|
if r.URL.Path != "/dns" {
|
||||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||||
@@ -16,7 +17,7 @@ func TestListDomains(t *testing.T) {
|
|||||||
t.Fatalf("unexpected api key header: %s", got)
|
t.Fatalf("unexpected api key header: %s", got)
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, _ = w.Write([]byte(`{"statusCode":200,"domains":[{"id":1,"name":"example.com","state":"Complete"}]}`))
|
_, _ = w.Write([]byte(`{"statusCode":200,"domains":[{"id":2,"name":"z.example.com"},{"id":1,"name":"a.example.com"}]}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
@@ -25,24 +26,79 @@ func TestListDomains(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ListDomains() error = %v", err)
|
t.Fatalf("ListDomains() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(domains) != 1 {
|
if len(domains) != 2 {
|
||||||
t.Fatalf("expected 1 domain, got %d", len(domains))
|
t.Fatalf("expected 2 domains, got %d", len(domains))
|
||||||
}
|
|
||||||
if domains[0].Name != "example.com" {
|
|
||||||
t.Fatalf("unexpected domain name %q", domains[0].Name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDoGetErrorResponse(t *testing.T) {
|
func TestClientDoGetNon2xxStatus(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
_, _ = w.Write([]byte(`{"statusCode":401,"exception":{"statusCode":401,"type":"Authentication Exception","message":"invalid"}}`))
|
_, _ = w.Write([]byte(`{"message":"nope"}`))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
client := New("test-key", WithBaseURL(ts.URL), WithHTTPClient(ts.Client()))
|
client := New("test-key", WithBaseURL(ts.URL), WithHTTPClient(ts.Client()))
|
||||||
_, err := client.ListDomains(context.Background())
|
_, err := client.ListDomains(context.Background())
|
||||||
if err == nil {
|
if err == nil || !strings.Contains(err.Error(), "status 401") {
|
||||||
t.Fatal("expected error, got nil")
|
t.Fatalf("expected status error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientDoGetAPIExceptionPayload(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"statusCode":200,"exception":{"statusCode":400,"type":"Validation Exception","message":"bad hostname"}}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := New("test-key", WithBaseURL(ts.URL), WithHTTPClient(ts.Client()))
|
||||||
|
_, err := client.ListDomains(context.Background())
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Validation Exception") {
|
||||||
|
t.Fatalf("expected API exception error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientDoGetMalformedJSON(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"statusCode":200,"domains":[`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := New("test-key", WithBaseURL(ts.URL), WithHTTPClient(ts.Client()))
|
||||||
|
_, err := client.ListDomains(context.Background())
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "failed to decode dynu API response") {
|
||||||
|
t.Fatalf("expected decode error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientGetRootDomainIncompleteResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"statusCode":200,"id":0,"domainName":""}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := New("test-key", WithBaseURL(ts.URL), WithHTTPClient(ts.Client()))
|
||||||
|
_, _, err := client.GetRootDomain(context.Background(), "www.example.com")
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "incomplete root domain response") {
|
||||||
|
t.Fatalf("expected incomplete response error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientGetRootDomainEscapesHostname(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !strings.Contains(r.RequestURI, "spaces%20in%20name.example.com") {
|
||||||
|
t.Fatalf("expected escaped hostname path, got %s", r.RequestURI)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"statusCode":200,"id":123,"domainName":"example.com"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := New("test-key", WithBaseURL(ts.URL), WithHTTPClient(ts.Client()))
|
||||||
|
_, _, err := client.GetRootDomain(context.Background(), "spaces in name.example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected nil error, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
)
|
)
|
||||||
@@ -52,30 +53,34 @@ func (d *dnsRecordsDataSource) Metadata(_ context.Context, req datasource.Metada
|
|||||||
|
|
||||||
func (d *dnsRecordsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
func (d *dnsRecordsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||||
resp.Schema = schema.Schema{
|
resp.Schema = schema.Schema{
|
||||||
Description: "Get DNS records from Dynu for the domain resolved from a hostname.",
|
Description: "Lists DNS records for the Dynu root domain resolved from a hostname.",
|
||||||
Attributes: map[string]schema.Attribute{
|
Attributes: map[string]schema.Attribute{
|
||||||
"hostname": schema.StringAttribute{
|
"hostname": schema.StringAttribute{
|
||||||
Required: true,
|
Required: true,
|
||||||
Description: "Any hostname under the target root domain.",
|
Description: "Fully-qualified hostname under the target root domain.",
|
||||||
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
|
Validators: []validator.String{
|
||||||
|
stringvalidator.LengthAtLeast(1),
|
||||||
|
stringvalidator.RegexMatches(hostnameValidator, "must be a valid fully-qualified hostname"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"domain_id": schema.Int64Attribute{Computed: true},
|
"domain_id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID resolved from hostname."},
|
||||||
"domain_name": schema.StringAttribute{Computed: true},
|
"domain_name": schema.StringAttribute{Computed: true, Description: "Dynu root domain name resolved from hostname."},
|
||||||
"records": schema.ListNestedAttribute{
|
"records": schema.ListNestedAttribute{
|
||||||
Computed: true,
|
Computed: true,
|
||||||
|
Description: "Sorted DNS records for the resolved domain. Timestamps are returned as Dynu provides them.",
|
||||||
NestedObject: schema.NestedAttributeObject{Attributes: map[string]schema.Attribute{
|
NestedObject: schema.NestedAttributeObject{Attributes: map[string]schema.Attribute{
|
||||||
"id": schema.Int64Attribute{Computed: true},
|
"id": schema.Int64Attribute{Computed: true, Description: "Dynu DNS record ID."},
|
||||||
"domain_id": schema.Int64Attribute{Computed: true},
|
"domain_id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID for this record."},
|
||||||
"domain_name": schema.StringAttribute{Computed: true},
|
"domain_name": schema.StringAttribute{Computed: true, Description: "Domain name for this record."},
|
||||||
"node_name": schema.StringAttribute{Computed: true},
|
"node_name": schema.StringAttribute{Computed: true, Description: "Node/label portion of the record."},
|
||||||
"hostname": schema.StringAttribute{Computed: true},
|
"hostname": schema.StringAttribute{Computed: true, Description: "Fully-qualified hostname for the record."},
|
||||||
"record_type": schema.StringAttribute{Computed: true},
|
"record_type": schema.StringAttribute{Computed: true, Description: "DNS record type (A, CNAME, TXT, etc.)."},
|
||||||
"ttl": schema.Int64Attribute{Computed: true},
|
"ttl": schema.Int64Attribute{Computed: true, Description: "DNS TTL in seconds."},
|
||||||
"state": schema.BoolAttribute{Computed: true},
|
"state": schema.BoolAttribute{Computed: true, Description: "Whether this DNS record is active."},
|
||||||
"content": schema.StringAttribute{Computed: true},
|
"content": schema.StringAttribute{Computed: true, Description: "Record content/value."},
|
||||||
"updated_on": schema.StringAttribute{Computed: true},
|
"updated_on": schema.StringAttribute{Computed: true, Description: "Last update timestamp as returned by Dynu."},
|
||||||
"group": schema.StringAttribute{Computed: true},
|
"group": schema.StringAttribute{Computed: true, Description: "Dynu group value for this record."},
|
||||||
"host": schema.StringAttribute{Computed: true},
|
"host": schema.StringAttribute{Computed: true, Description: "Host field as returned by Dynu."},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -101,6 +106,11 @@ func (d *dnsRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state.Hostname.IsUnknown() || state.Hostname.IsNull() {
|
||||||
|
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "The hostname must be known and non-null.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
domainID, domainName, err := d.clientProvider.client.GetRootDomain(ctx, state.Hostname.ValueString())
|
domainID, domainName, err := d.clientProvider.client.GetRootDomain(ctx, state.Hostname.ValueString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Unable to resolve Dynu domain from hostname", err.Error())
|
resp.Diagnostics.AddError("Unable to resolve Dynu domain from hostname", err.Error())
|
||||||
@@ -113,6 +123,8 @@ func (d *dnsRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortDNSRecords(records)
|
||||||
|
|
||||||
state.DomainID = types.Int64Value(domainID)
|
state.DomainID = types.Int64Value(domainID)
|
||||||
state.DomainName = types.StringValue(domainName)
|
state.DomainName = types.StringValue(domainName)
|
||||||
state.Records = make([]dnsRecordStateItem, 0, len(records))
|
state.Records = make([]dnsRecordStateItem, 0, len(records))
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
@@ -18,6 +18,8 @@ var (
|
|||||||
_ datasource.DataSourceWithConfigure = &domainDataSource{}
|
_ datasource.DataSourceWithConfigure = &domainDataSource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var hostnameValidator = regexp.MustCompile(`^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
||||||
|
|
||||||
type domainDataSource struct {
|
type domainDataSource struct {
|
||||||
clientProvider *providerData
|
clientProvider *providerData
|
||||||
}
|
}
|
||||||
@@ -37,16 +39,19 @@ func (d *domainDataSource) Metadata(_ context.Context, req datasource.MetadataRe
|
|||||||
|
|
||||||
func (d *domainDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
func (d *domainDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||||
resp.Schema = schema.Schema{
|
resp.Schema = schema.Schema{
|
||||||
Description: "Get a Dynu DNS domain by hostname.",
|
Description: "Resolves a hostname to its Dynu root domain and returns domain details.",
|
||||||
Attributes: map[string]schema.Attribute{
|
Attributes: map[string]schema.Attribute{
|
||||||
"hostname": schema.StringAttribute{
|
"hostname": schema.StringAttribute{
|
||||||
Required: true,
|
Required: true,
|
||||||
Description: "Hostname to resolve to a root domain.",
|
Description: "Fully-qualified hostname, such as www.example.com.",
|
||||||
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
|
Validators: []validator.String{
|
||||||
|
stringvalidator.LengthAtLeast(1),
|
||||||
|
stringvalidator.RegexMatches(hostnameValidator, "must be a valid fully-qualified hostname"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"domain": schema.SingleNestedAttribute{
|
"domain": schema.SingleNestedAttribute{
|
||||||
Computed: true,
|
Computed: true,
|
||||||
Description: "Resolved Dynu DNS domain details.",
|
Description: "Dynu root domain details for the supplied hostname.",
|
||||||
Attributes: domainAttributes(),
|
Attributes: domainAttributes(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -75,7 +80,7 @@ func (d *domainDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state.Hostname.IsUnknown() || state.Hostname.IsNull() {
|
if state.Hostname.IsUnknown() || state.Hostname.IsNull() {
|
||||||
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "hostname must be known and non-null.")
|
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "The hostname must be known and non-null.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,47 +96,7 @@ func (d *domainDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
domainValue := mapDomain(*domain)
|
domainObject, diags := domainObjectValue(*domain)
|
||||||
domainObject, diags := types.ObjectValue(
|
|
||||||
map[string]attr.Type{
|
|
||||||
"id": types.Int64Type,
|
|
||||||
"name": types.StringType,
|
|
||||||
"unicode_name": types.StringType,
|
|
||||||
"token": types.StringType,
|
|
||||||
"state": types.StringType,
|
|
||||||
"group": types.StringType,
|
|
||||||
"ipv4_address": types.StringType,
|
|
||||||
"ipv6_address": types.StringType,
|
|
||||||
"ttl": types.Int64Type,
|
|
||||||
"ipv4": types.BoolType,
|
|
||||||
"ipv6": types.BoolType,
|
|
||||||
"ipv4_wildcard_alias": types.BoolType,
|
|
||||||
"ipv6_wildcard_alias": types.BoolType,
|
|
||||||
"allow_zone_transfer": types.BoolType,
|
|
||||||
"dnssec": types.BoolType,
|
|
||||||
"created_on": types.StringType,
|
|
||||||
"updated_on": types.StringType,
|
|
||||||
},
|
|
||||||
map[string]attr.Value{
|
|
||||||
"id": domainValue.ID,
|
|
||||||
"name": domainValue.Name,
|
|
||||||
"unicode_name": domainValue.UnicodeName,
|
|
||||||
"token": domainValue.Token,
|
|
||||||
"state": domainValue.State,
|
|
||||||
"group": domainValue.Group,
|
|
||||||
"ipv4_address": domainValue.IPv4Address,
|
|
||||||
"ipv6_address": domainValue.IPv6Address,
|
|
||||||
"ttl": domainValue.TTL,
|
|
||||||
"ipv4": domainValue.IPv4,
|
|
||||||
"ipv6": domainValue.IPv6,
|
|
||||||
"ipv4_wildcard_alias": domainValue.IPv4WildcardAlias,
|
|
||||||
"ipv6_wildcard_alias": domainValue.IPv6WildcardAlias,
|
|
||||||
"allow_zone_transfer": domainValue.AllowZoneTransfer,
|
|
||||||
"dnssec": domainValue.DNSSEC,
|
|
||||||
"created_on": domainValue.CreatedOn,
|
|
||||||
"updated_on": domainValue.UpdatedOn,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
resp.Diagnostics.Append(diags...)
|
resp.Diagnostics.Append(diags...)
|
||||||
if resp.Diagnostics.HasError() {
|
if resp.Diagnostics.HasError() {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ func (d *domainsDataSource) Metadata(_ context.Context, req datasource.MetadataR
|
|||||||
|
|
||||||
func (d *domainsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
func (d *domainsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||||
resp.Schema = schema.Schema{
|
resp.Schema = schema.Schema{
|
||||||
Description: "List DNS domains from Dynu.",
|
Description: "Lists DNS domains visible to the configured Dynu API key.",
|
||||||
Attributes: map[string]schema.Attribute{
|
Attributes: map[string]schema.Attribute{
|
||||||
"domains": schema.ListNestedAttribute{
|
"domains": schema.ListNestedAttribute{
|
||||||
Computed: true,
|
Computed: true,
|
||||||
Description: "List of Dynu DNS domains.",
|
Description: "Sorted list of domains. Timestamps are returned exactly as Dynu provides them.",
|
||||||
NestedObject: schema.NestedAttributeObject{Attributes: domainAttributes()},
|
NestedObject: schema.NestedAttributeObject{Attributes: domainAttributes()},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -84,6 +84,8 @@ func (d *domainsDataSource) Read(ctx context.Context, _ datasource.ReadRequest,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortDomains(domains)
|
||||||
|
|
||||||
state := domainsDataSourceModel{Domains: make([]domainModel, 0, len(domains))}
|
state := domainsDataSourceModel{Domains: make([]domainModel, 0, len(domains))}
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
state.Domains = append(state.Domains, mapDomain(domain))
|
state.Domains = append(state.Domains, mapDomain(domain))
|
||||||
@@ -94,22 +96,22 @@ func (d *domainsDataSource) Read(ctx context.Context, _ datasource.ReadRequest,
|
|||||||
|
|
||||||
func domainAttributes() map[string]schema.Attribute {
|
func domainAttributes() map[string]schema.Attribute {
|
||||||
return map[string]schema.Attribute{
|
return map[string]schema.Attribute{
|
||||||
"id": schema.Int64Attribute{Computed: true},
|
"id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID."},
|
||||||
"name": schema.StringAttribute{Computed: true},
|
"name": schema.StringAttribute{Computed: true, Description: "Primary domain name."},
|
||||||
"unicode_name": schema.StringAttribute{Computed: true},
|
"unicode_name": schema.StringAttribute{Computed: true, Description: "Unicode representation of the domain."},
|
||||||
"token": schema.StringAttribute{Computed: true, Sensitive: true},
|
"token": schema.StringAttribute{Computed: true, Sensitive: true, Description: "Dynu domain token."},
|
||||||
"state": schema.StringAttribute{Computed: true},
|
"state": schema.StringAttribute{Computed: true, Description: "Dynu domain state."},
|
||||||
"group": schema.StringAttribute{Computed: true},
|
"group": schema.StringAttribute{Computed: true, Description: "Dynu domain group."},
|
||||||
"ipv4_address": schema.StringAttribute{Computed: true},
|
"ipv4_address": schema.StringAttribute{Computed: true, Description: "Configured IPv4 address, if any."},
|
||||||
"ipv6_address": schema.StringAttribute{Computed: true},
|
"ipv6_address": schema.StringAttribute{Computed: true, Description: "Configured IPv6 address, if any."},
|
||||||
"ttl": schema.Int64Attribute{Computed: true},
|
"ttl": schema.Int64Attribute{Computed: true, Description: "DNS TTL in seconds."},
|
||||||
"ipv4": schema.BoolAttribute{Computed: true},
|
"ipv4": schema.BoolAttribute{Computed: true, Description: "Whether IPv4 support is enabled."},
|
||||||
"ipv6": schema.BoolAttribute{Computed: true},
|
"ipv6": schema.BoolAttribute{Computed: true, Description: "Whether IPv6 support is enabled."},
|
||||||
"ipv4_wildcard_alias": schema.BoolAttribute{Computed: true},
|
"ipv4_wildcard_alias": schema.BoolAttribute{Computed: true, Description: "Whether IPv4 wildcard alias is enabled."},
|
||||||
"ipv6_wildcard_alias": schema.BoolAttribute{Computed: true},
|
"ipv6_wildcard_alias": schema.BoolAttribute{Computed: true, Description: "Whether IPv6 wildcard alias is enabled."},
|
||||||
"allow_zone_transfer": schema.BoolAttribute{Computed: true},
|
"allow_zone_transfer": schema.BoolAttribute{Computed: true, Description: "Whether zone transfer is allowed."},
|
||||||
"dnssec": schema.BoolAttribute{Computed: true},
|
"dnssec": schema.BoolAttribute{Computed: true, Description: "Whether DNSSEC is enabled."},
|
||||||
"created_on": schema.StringAttribute{Computed: true},
|
"created_on": schema.StringAttribute{Computed: true, Description: "Creation timestamp as returned by Dynu."},
|
||||||
"updated_on": schema.StringAttribute{Computed: true},
|
"updated_on": schema.StringAttribute{Computed: true, Description: "Last update timestamp as returned by Dynu."},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var domainObjectTypes = map[string]attr.Type{
|
||||||
|
"id": types.Int64Type,
|
||||||
|
"name": types.StringType,
|
||||||
|
"unicode_name": types.StringType,
|
||||||
|
"token": types.StringType,
|
||||||
|
"state": types.StringType,
|
||||||
|
"group": types.StringType,
|
||||||
|
"ipv4_address": types.StringType,
|
||||||
|
"ipv6_address": types.StringType,
|
||||||
|
"ttl": types.Int64Type,
|
||||||
|
"ipv4": types.BoolType,
|
||||||
|
"ipv6": types.BoolType,
|
||||||
|
"ipv4_wildcard_alias": types.BoolType,
|
||||||
|
"ipv6_wildcard_alias": types.BoolType,
|
||||||
|
"allow_zone_transfer": types.BoolType,
|
||||||
|
"dnssec": types.BoolType,
|
||||||
|
"created_on": types.StringType,
|
||||||
|
"updated_on": types.StringType,
|
||||||
|
}
|
||||||
|
|
||||||
func mapDomain(domain dynuclient.Domain) domainModel {
|
func mapDomain(domain dynuclient.Domain) domainModel {
|
||||||
return domainModel{
|
return domainModel{
|
||||||
ID: types.Int64Value(domain.ID),
|
ID: types.Int64Value(domain.ID),
|
||||||
@@ -14,8 +38,8 @@ func mapDomain(domain dynuclient.Domain) domainModel {
|
|||||||
Token: types.StringValue(domain.Token),
|
Token: types.StringValue(domain.Token),
|
||||||
State: types.StringValue(domain.State),
|
State: types.StringValue(domain.State),
|
||||||
Group: types.StringValue(domain.Group),
|
Group: types.StringValue(domain.Group),
|
||||||
IPv4Address: types.StringValue(domain.IPv4Address),
|
IPv4Address: mapString(domain.IPv4Address),
|
||||||
IPv6Address: types.StringValue(domain.IPv6Address),
|
IPv6Address: mapString(domain.IPv6Address),
|
||||||
TTL: types.Int64Value(domain.TTL),
|
TTL: types.Int64Value(domain.TTL),
|
||||||
IPv4: types.BoolValue(domain.IPv4),
|
IPv4: types.BoolValue(domain.IPv4),
|
||||||
IPv6: types.BoolValue(domain.IPv6),
|
IPv6: types.BoolValue(domain.IPv6),
|
||||||
@@ -23,14 +47,59 @@ func mapDomain(domain dynuclient.Domain) domainModel {
|
|||||||
IPv6WildcardAlias: types.BoolValue(domain.IPv6WildcardAlias),
|
IPv6WildcardAlias: types.BoolValue(domain.IPv6WildcardAlias),
|
||||||
AllowZoneTransfer: types.BoolValue(domain.AllowZoneTransfer),
|
AllowZoneTransfer: types.BoolValue(domain.AllowZoneTransfer),
|
||||||
DNSSEC: types.BoolValue(domain.DNSSEC),
|
DNSSEC: types.BoolValue(domain.DNSSEC),
|
||||||
CreatedOn: types.StringValue(domain.CreatedOn),
|
CreatedOn: mapString(domain.CreatedOn),
|
||||||
UpdatedOn: types.StringValue(domain.UpdatedOn),
|
UpdatedOn: mapString(domain.UpdatedOn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func domainObjectValue(domain dynuclient.Domain) (types.Object, diag.Diagnostics) {
|
||||||
|
mapped := mapDomain(domain)
|
||||||
|
|
||||||
|
return types.ObjectValue(domainObjectTypes, map[string]attr.Value{
|
||||||
|
"id": mapped.ID,
|
||||||
|
"name": mapped.Name,
|
||||||
|
"unicode_name": mapped.UnicodeName,
|
||||||
|
"token": mapped.Token,
|
||||||
|
"state": mapped.State,
|
||||||
|
"group": mapped.Group,
|
||||||
|
"ipv4_address": mapped.IPv4Address,
|
||||||
|
"ipv6_address": mapped.IPv6Address,
|
||||||
|
"ttl": mapped.TTL,
|
||||||
|
"ipv4": mapped.IPv4,
|
||||||
|
"ipv6": mapped.IPv6,
|
||||||
|
"ipv4_wildcard_alias": mapped.IPv4WildcardAlias,
|
||||||
|
"ipv6_wildcard_alias": mapped.IPv6WildcardAlias,
|
||||||
|
"allow_zone_transfer": mapped.AllowZoneTransfer,
|
||||||
|
"dnssec": mapped.DNSSEC,
|
||||||
|
"created_on": mapped.CreatedOn,
|
||||||
|
"updated_on": mapped.UpdatedOn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func mapString(in string) types.String {
|
func mapString(in string) types.String {
|
||||||
if in == "" {
|
if in == "" {
|
||||||
return types.StringNull()
|
return types.StringNull()
|
||||||
}
|
}
|
||||||
return types.StringValue(in)
|
return types.StringValue(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortDomains(domains []dynuclient.Domain) {
|
||||||
|
sort.Slice(domains, func(i, j int) bool {
|
||||||
|
if domains[i].Name != domains[j].Name {
|
||||||
|
return domains[i].Name < domains[j].Name
|
||||||
|
}
|
||||||
|
return domains[i].ID < domains[j].ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortDNSRecords(records []dynuclient.DNSRecord) {
|
||||||
|
sort.Slice(records, func(i, j int) bool {
|
||||||
|
if records[i].Hostname != records[j].Hostname {
|
||||||
|
return records[i].Hostname < records[j].Hostname
|
||||||
|
}
|
||||||
|
if records[i].RecordType != records[j].RecordType {
|
||||||
|
return records[i].RecordType < records[j].RecordType
|
||||||
|
}
|
||||||
|
return records[i].ID < records[j].ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSortDomains(t *testing.T) {
|
||||||
|
domains := []dynuclient.Domain{
|
||||||
|
{ID: 2, Name: "z.example.com"},
|
||||||
|
{ID: 1, Name: "a.example.com"},
|
||||||
|
{ID: 3, Name: "a.example.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
sortDomains(domains)
|
||||||
|
|
||||||
|
if domains[0].ID != 1 || domains[1].ID != 3 || domains[2].ID != 2 {
|
||||||
|
t.Fatalf("unexpected domain sort order: %#v", domains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortDNSRecords(t *testing.T) {
|
||||||
|
records := []dynuclient.DNSRecord{
|
||||||
|
{ID: 3, Hostname: "b.example.com", RecordType: "TXT"},
|
||||||
|
{ID: 1, Hostname: "a.example.com", RecordType: "A"},
|
||||||
|
{ID: 2, Hostname: "a.example.com", RecordType: "A"},
|
||||||
|
}
|
||||||
|
|
||||||
|
sortDNSRecords(records)
|
||||||
|
|
||||||
|
if records[0].ID != 1 || records[1].ID != 2 || records[2].ID != 3 {
|
||||||
|
t.Fatalf("unexpected record sort order: %#v", records)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapString(t *testing.T) {
|
||||||
|
if !mapString("").IsNull() {
|
||||||
|
t.Fatal("expected empty string to map to null")
|
||||||
|
}
|
||||||
|
if mapString("value").ValueString() != "value" {
|
||||||
|
t.Fatal("expected non-empty string to map to value")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
@@ -21,7 +22,8 @@ type dynuProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dynuProviderModel struct {
|
type dynuProviderModel struct {
|
||||||
APIKey types.String `tfsdk:"api_key"`
|
APIKey types.String `tfsdk:"api_key"`
|
||||||
|
BaseURL types.String `tfsdk:"base_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type providerData struct {
|
type providerData struct {
|
||||||
@@ -41,12 +43,16 @@ func (p *dynuProvider) Metadata(_ context.Context, _ provider.MetadataRequest, r
|
|||||||
|
|
||||||
func (p *dynuProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
|
func (p *dynuProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
|
||||||
resp.Schema = schema.Schema{
|
resp.Schema = schema.Schema{
|
||||||
Description: "Terraform provider for Dynu DNS read-only operations.",
|
Description: "Terraform provider for Dynu DNS read-only data sources.",
|
||||||
Attributes: map[string]schema.Attribute{
|
Attributes: map[string]schema.Attribute{
|
||||||
"api_key": schema.StringAttribute{
|
"api_key": schema.StringAttribute{
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
Description: "Dynu API key. Can also be provided using DYNU_API_KEY.",
|
Description: "Dynu API key. If omitted, the provider uses the DYNU_API_KEY environment variable.",
|
||||||
|
},
|
||||||
|
"base_url": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Override Dynu API base URL. Primarily intended for automated tests.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -60,25 +66,28 @@ func (p *dynuProvider) Configure(ctx context.Context, req provider.ConfigureRequ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey := os.Getenv("DYNU_API_KEY")
|
apiKey := resolveAPIKey(data.APIKey, os.Getenv("DYNU_API_KEY"))
|
||||||
if !data.APIKey.IsNull() {
|
|
||||||
apiKey = data.APIKey.ValueString()
|
|
||||||
}
|
|
||||||
|
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
resp.Diagnostics.AddAttributeError(
|
resp.Diagnostics.AddAttributeError(
|
||||||
path.Root("api_key"),
|
path.Root("api_key"),
|
||||||
"Missing Dynu API key",
|
"Missing Dynu API key",
|
||||||
"Set api_key in the provider configuration or DYNU_API_KEY in the environment.",
|
"Configure api_key in the provider block or set the DYNU_API_KEY environment variable.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
providerData := &providerData{client: dynuclient.New(apiKey)}
|
providerData := &providerData{client: newDynuClient(apiKey, data.BaseURL)}
|
||||||
resp.DataSourceData = providerData
|
resp.DataSourceData = providerData
|
||||||
resp.ResourceData = nil
|
resp.ResourceData = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveAPIKey(configValue types.String, envValue string) string {
|
||||||
|
if !configValue.IsNull() && !configValue.IsUnknown() {
|
||||||
|
return strings.TrimSpace(configValue.ValueString())
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(envValue)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||||
return []func() datasource.DataSource{
|
return []func() datasource.DataSource{
|
||||||
NewDomainsDataSource,
|
NewDomainsDataSource,
|
||||||
@@ -90,3 +99,11 @@ func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSo
|
|||||||
func (p *dynuProvider) Resources(_ context.Context) []func() resource.Resource {
|
func (p *dynuProvider) Resources(_ context.Context) []func() resource.Resource {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newDynuClient(apiKey string, baseURL types.String) *dynuclient.Client {
|
||||||
|
if !baseURL.IsNull() && !baseURL.IsUnknown() && strings.TrimSpace(baseURL.ValueString()) != "" {
|
||||||
|
return dynuclient.New(apiKey, dynuclient.WithBaseURL(strings.TrimSpace(baseURL.ValueString())))
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynuclient.New(apiKey)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,77 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccScaffold(t *testing.T) {
|
func testAccPreCheck(t *testing.T) {
|
||||||
if os.Getenv("TF_ACC") != "1" || os.Getenv("DYNU_API_KEY") == "" {
|
t.Helper()
|
||||||
t.Skip("set TF_ACC=1 and DYNU_API_KEY to enable acceptance tests")
|
if os.Getenv("TF_ACC") != "1" {
|
||||||
|
t.Skip("set TF_ACC=1 to run acceptance tests")
|
||||||
|
}
|
||||||
|
if os.Getenv("DYNU_API_KEY") == "" {
|
||||||
|
t.Skip("set DYNU_API_KEY to run acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccDomainFromEnv(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
domain := os.Getenv("DYNU_DOMAIN")
|
||||||
|
if domain == "" {
|
||||||
|
t.Skip("set DYNU_DOMAIN to run domain-specific acceptance tests")
|
||||||
|
}
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDataSourceDomains(t *testing.T) {
|
||||||
|
testAccPreCheck(t)
|
||||||
|
|
||||||
|
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
|
||||||
|
domains, err := client.ListDomains(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListDomains() failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(domains) == 0 {
|
||||||
|
t.Fatal("expected at least one domain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDataSourceDomain(t *testing.T) {
|
||||||
|
testAccPreCheck(t)
|
||||||
|
hostname := testAccDomainFromEnv(t)
|
||||||
|
|
||||||
|
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
|
||||||
|
domainID, _, err := client.GetRootDomain(context.Background(), hostname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetRootDomain() failed: %v", err)
|
||||||
|
}
|
||||||
|
domain, err := client.GetDomainByID(context.Background(), domainID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetDomainByID() failed: %v", err)
|
||||||
|
}
|
||||||
|
if domain.ID == 0 || domain.Name == "" {
|
||||||
|
t.Fatalf("unexpected domain payload: %#v", domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDataSourceDNSRecords(t *testing.T) {
|
||||||
|
testAccPreCheck(t)
|
||||||
|
hostname := testAccDomainFromEnv(t)
|
||||||
|
|
||||||
|
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
|
||||||
|
domainID, _, err := client.GetRootDomain(context.Background(), hostname)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetRootDomain() failed: %v", err)
|
||||||
|
}
|
||||||
|
records, err := client.ListDNSRecords(context.Background(), domainID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListDNSRecords() failed: %v", err)
|
||||||
|
}
|
||||||
|
if records == nil {
|
||||||
|
t.Fatal("expected records slice, got nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: DYNU_DOMAIN may be used by future acceptance test cases.
|
|
||||||
_ = os.Getenv("DYNU_DOMAIN")
|
|
||||||
|
|
||||||
// Acceptance tests for read-only data sources are intentionally scaffolded in phase 1.
|
|
||||||
// Add terraform-plugin-testing based test cases in a follow-up with stable fixtures.
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolveAPIKey(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config types.String
|
||||||
|
env string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "config wins", config: types.StringValue("config-key"), env: "env-key", want: "config-key"},
|
||||||
|
{name: "env fallback", config: types.StringNull(), env: "env-key", want: "env-key"},
|
||||||
|
{name: "trim spaces", config: types.StringValue(" config-key "), env: " env-key ", want: "config-key"},
|
||||||
|
{name: "unknown uses env", config: types.StringUnknown(), env: "env-key", want: "env-key"},
|
||||||
|
{name: "empty when missing", config: types.StringNull(), env: "", want: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := resolveAPIKey(tc.config, tc.env); got != tc.want {
|
||||||
|
t.Fatalf("resolveAPIKey() = %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-6
@@ -1,14 +1,21 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "[check] Running gofmt"
|
echo "[check] verifying gofmt"
|
||||||
files="$(git ls-files '*.go')"
|
files="$(git ls-files '*.go')"
|
||||||
if [[ -n "$files" ]]; then
|
if [[ -n "${files}" ]]; then
|
||||||
# shellcheck disable=SC2086
|
unformatted="$(gofmt -l ${files})"
|
||||||
gofmt -w $files
|
if [[ -n "${unformatted}" ]]; then
|
||||||
|
echo "[check][error] gofmt reported unformatted files:" >&2
|
||||||
|
echo "${unformatted}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[check] Running go test"
|
echo "[check] running go vet"
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
echo "[check] running go test"
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
echo "[check] Completed"
|
echo "[check] complete"
|
||||||
|
|||||||
+3
-3
@@ -12,8 +12,8 @@ if [[ -z "${DYNU_API_KEY:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${DYNU_DOMAIN:-}" ]]; then
|
if [[ -z "${DYNU_DOMAIN:-}" ]]; then
|
||||||
echo "[testacc][info] DYNU_DOMAIN is not set; running baseline acceptance scaffolding"
|
echo "[testacc][warn] DYNU_DOMAIN not set; domain-specific acceptance tests will skip"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[testacc] Running acceptance tests"
|
echo "[testacc] running acceptance tests"
|
||||||
go test ./internal/provider -run TestAcc -count=1
|
go test ./internal/provider -run '^TestAcc' -count=1 -v
|
||||||
|
|||||||
Reference in New Issue
Block a user