diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2755e44..72e9c9f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,37 +1,36 @@
name: release
on:
- push:
- tags:
- - 'v*'
+ push:
+ tags:
+ - "v*"
permissions:
- contents: write
+ contents: write
jobs:
- goreleaser:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Unshallow
- run: git fetch --prune --unshallow
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: 1.22.4
- - name: Import GPG Key
- id: import_gpg
- uses: crazy-max/ghaction-import-gpg@v6.2.0
- with:
- gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
- - name: Run GoReleaser
- uses: goreleaser/goreleaser-action@v6.0.0
- with:
- version: latest
- args: release --clean
- env:
- GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
- # GitHub sets this automatically
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
+ goreleaser:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - name: Unshallow
+ run: git fetch --prune --unshallow
+ - name: Setup Go
+ uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
+ with:
+ go-version: "1.22"
+ - name: Import GPG Key
+ id: import_gpg
+ uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
+ with:
+ gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
+ - name: Run GoReleaser
+ uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
+ with:
+ version: latest
+ args: release --clean
+ env:
+ GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
+ # GitHub sets this automatically
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b348a86..4e7a7ca 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -24,31 +24,42 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
with:
go-version-file: "go.mod"
cache: true
+
- run: go mod download
+
- run: go build -v .
+
- name: Run linters
- uses: golangci/golangci-lint-action@v6
+ uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 # v6.5.0
with:
version: latest
generate:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
with:
go-version-file: "go.mod"
cache: true
+
+ - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
+
- run: go generate ./...
+
- name: git diff
run: |
- git diff --compact-summary --exit-code || \
- (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1)
+ if [[ -n $(git ls-files --other --modified --exclude-standard) ]]; then
+ echo "Unexpected difference in directories after code generation. Run 'make gen' and include the output in the commit."
+ exit 1
+ fi
# Run acceptance tests in a matrix with Terraform CLI versions
test:
@@ -72,20 +83,24 @@ jobs:
- "1.8.*"
- "1.9.*"
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
with:
go-version-file: "go.mod"
cache: true
- - uses: hashicorp/setup-terraform@v3
+
+ - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
terraform_version: ${{ matrix.terraform }}
terraform_wrapper: false
+
- run: go mod download
- - env:
+
+ - run: go test -v -cover ./internal/provider/
+ env:
TF_ACC: "1"
CODER_ENTERPRISE_LICENSE: ${{ secrets.CODER_ENTERPRISE_LICENSE }}
- run: go test -v -cover ./internal/provider/
timeout-minutes: 10
lint:
@@ -93,20 +108,18 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- - name: Set up Go
- uses: actions/setup-go@v5
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
with:
go-version: "1.22"
id: go
- - uses: hashicorp/setup-terraform@v3
+ - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
terraform_version: "1.9.*"
terraform_wrapper: false
- - name: Check out code into the Go module directory
- uses: actions/checkout@v4
-
- name: Get dependencies
run: |
go mod download
diff --git a/.golangci.yml b/.golangci.yml
index 223cf95..7efde44 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,7 +1,7 @@
-# Visit https://golangci-lint.run/ for usage documentation
-# and information on other useful linters
+# Visit https://golangci-lint.run/ for usage documentation and information on
+# other useful linters
issues:
- max-per-linter: 0
+ max-issues-per-linter: 0
max-same-issues: 0
linters:
@@ -9,19 +9,25 @@ linters:
enable:
- durationcheck
- errcheck
- - exportloopref
- forcetypeassert
- godot
- gofmt
- gosimple
+ - govet
- ineffassign
- makezero
- misspell
- nilerr
+ - paralleltest
- predeclared
- staticcheck
- - tenv
- unconvert
- unparam
- unused
- - vet
\ No newline at end of file
+ - usetesting
+
+linters-settings:
+ paralleltest:
+ # Terraform acceptance subtests all share a Coder instance, and cannot run
+ # in parallel.
+ ignore-missing-subtests: true
diff --git a/Makefile b/Makefile
index 54a7a12..faf8f49 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,12 @@
default: testacc
fmt:
+ go fmt ./...
terraform fmt -recursive
+lint:
+ golangci-lint run ./...
+
gen:
go generate ./...
@@ -11,7 +15,10 @@ build: terraform-provider-coderd
terraform-provider-coderd: internal/provider/*.go main.go
CGO_ENABLED=0 go build .
+test: testacc
+.PHONY: test
+
# Run acceptance tests
-.PHONY: testacc
testacc:
TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m
+.PHONY: testacc
diff --git a/README.md b/README.md
index 8eeafa0..e79f8eb 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ The provider currently supports resources and data sources for:
- Templates + Template Versions
- Groups
- Workspace Proxies
-- Organizations (Data Source only)
+- Organizations
## Requirements
diff --git a/docs/data-sources/group.md b/docs/data-sources/group.md
index 90dabe5..f06c55b 100644
--- a/docs/data-sources/group.md
+++ b/docs/data-sources/group.md
@@ -71,5 +71,4 @@ Read-Only:
- `last_seen_at` (Number) Unix timestamp of when the member was last seen.
- `login_type` (String) The login type of the member. Can be `oidc`, `token`, `password`, `github` or `none`.
- `status` (String) The status of the member. Can be `active`, `dormant` or `suspended`.
-- `theme_preference` (String)
- `username` (String)
diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md
index 969a26a..252db9e 100644
--- a/docs/data-sources/user.md
+++ b/docs/data-sources/user.md
@@ -53,4 +53,3 @@ resource "coderd_group" "bosses" {
- `organization_ids` (Set of String) IDs of organizations the user is associated with.
- `roles` (Set of String) Roles assigned to the user. Valid roles are `owner`, `template-admin`, `user-admin`, and `auditor`.
- `suspended` (Boolean) Whether the user is suspended.
-- `theme_preference` (String) The user's preferred theme.
diff --git a/docs/index.md b/docs/index.md
index 3d9be49..79df017 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -27,7 +27,7 @@ terraform {
}
provider "coderd" {
- url = "coder.example.com"
+ url = "https://coder.example.com"
token = "****"
}
diff --git a/docs/resources/license.md b/docs/resources/license.md
index 58c773c..ec29706 100644
--- a/docs/resources/license.md
+++ b/docs/resources/license.md
@@ -4,7 +4,7 @@ page_title: "coderd_license Resource - terraform-provider-coderd"
subcategory: ""
description: |-
A license for a Coder deployment.
- It's recommended to create multiple instances of this resource when updating a license. Modifying an existing license will cause the resource to be replaced, which may result in a brief unlicensed period.
+ It's recommended to set create_before_destroy https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy on license resources. Without setting this, Terraform will remove the old license before adding the updated license. This will result in a temporary disruption to your users; during which they may be unable to use features that require a license.
Terraform does not guarantee this resource will be created before other resources or attributes that require a licensed deployment. The depends_on meta-argument is instead recommended.
---
@@ -12,11 +12,21 @@ description: |-
A license for a Coder deployment.
-It's recommended to create multiple instances of this resource when updating a license. Modifying an existing license will cause the resource to be replaced, which may result in a brief unlicensed period.
+It's recommended to set [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy) on license resources. Without setting this, Terraform will remove the old license before adding the updated license. This will result in a temporary disruption to your users; during which they may be unable to use features that require a license.
Terraform does not guarantee this resource will be created before other resources or attributes that require a licensed deployment. The `depends_on` meta-argument is instead recommended.
+## Example Usage
+```terraform
+resource "coderd_license" "license" {
+ license = "<…>"
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+```
## Schema
diff --git a/docs/resources/organization.md b/docs/resources/organization.md
new file mode 100644
index 0000000..88246dd
--- /dev/null
+++ b/docs/resources/organization.md
@@ -0,0 +1,103 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "coderd_organization Resource - terraform-provider-coderd"
+subcategory: ""
+description: |-
+ An organization on the Coder deployment.
+ ~> Warning
+ This resource is only compatible with Coder version 2.16.0 https://github.com/coder/coder/releases/tag/v2.16.0 and later.
+---
+
+# coderd_organization (Resource)
+
+An organization on the Coder deployment.
+
+~> **Warning**
+This resource is only compatible with Coder version [2.16.0](https://github.com/coder/coder/releases/tag/v2.16.0) and later.
+
+## Example Usage
+
+```terraform
+resource "coderd_organization" "blueberry" {
+ name = "blueberry"
+ display_name = "Blueberry"
+ description = "The organization for blueberries"
+ icon = "/emojis/1fad0.png"
+
+ org_sync_idp_groups = [
+ "wibble",
+ "wobble",
+ ]
+
+ group_sync {
+ field = "coder_groups"
+ mapping = {
+ toast = [coderd_group.bread.id]
+ }
+ }
+
+ role_sync {
+ field = "coder_roles"
+ mapping = {
+ manager = ["organization-user-admin"]
+ }
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) Name of the organization.
+
+### Optional
+
+- `description` (String)
+- `display_name` (String) Display name of the organization. Defaults to name.
+- `group_sync` (Block, Optional) Group sync settings to sync groups from an IdP. (see [below for nested schema](#nestedblock--group_sync))
+- `icon` (String)
+- `org_sync_idp_groups` (Set of String) Claims from the IdP provider that will give users access to this organization.
+- `role_sync` (Block, Optional) Role sync settings to sync organization roles from an IdP. (see [below for nested schema](#nestedblock--role_sync))
+
+### Read-Only
+
+- `id` (String) Organization ID
+
+
+### Nested Schema for `group_sync`
+
+Optional:
+
+- `auto_create_missing` (Boolean) Controls whether groups will be created if they are missing.
+- `field` (String) The claim field that specifies what groups a user should be in.
+- `mapping` (Map of List of String) A map from OIDC group name to Coder group ID.
+- `regex_filter` (String) A regular expression that will be used to filter the groups returned by the OIDC provider. Any group not matched will be ignored.
+
+
+
+### Nested Schema for `role_sync`
+
+Optional:
+
+- `field` (String) The claim field that specifies what organization roles a user should be given.
+- `mapping` (Map of List of String) A map from OIDC group name to Coder organization role.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+# The ID supplied can be either a organization UUID retrieved via the API
+# or the name of the organization.
+$ terraform import coderd_organization.our_org our-org
+```
+Alternatively, in Terraform v1.5.0 and later, an [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used:
+
+```terraform
+import {
+ to = coderd_organization.our_org
+ id = "our-org"
+}
+```
diff --git a/docs/resources/organization_sync_settings.md b/docs/resources/organization_sync_settings.md
new file mode 100644
index 0000000..efb6985
--- /dev/null
+++ b/docs/resources/organization_sync_settings.md
@@ -0,0 +1,47 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "coderd_organization_sync_settings Resource - terraform-provider-coderd"
+subcategory: ""
+description: |-
+ IdP sync settings for organizations.
+ This resource can only be created once. Attempts to create multiple will fail.
+ ~> Warning
+ This resource is only compatible with Coder version 2.19.0 https://github.com/coder/coder/releases/tag/v2.19.0 and later.
+---
+
+# coderd_organization_sync_settings (Resource)
+
+IdP sync settings for organizations.
+
+This resource can only be created once. Attempts to create multiple will fail.
+
+~> **Warning**
+This resource is only compatible with Coder version [2.19.0](https://github.com/coder/coder/releases/tag/v2.19.0) and later.
+
+## Example Usage
+
+```terraform
+// Important note: You can only have one resource of this type!
+resource "coderd_organization_sync_settings" "org_sync" {
+ field = "wibble"
+ assign_default = false
+
+ mapping = {
+ wobble = [
+ coderd_organization.my_organization.id,
+ ]
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `assign_default` (Boolean) When true, every user will be added to the default organization, regardless of claims.
+- `field` (String) The claim field that specifies what organizations a user should be in.
+
+### Optional
+
+- `mapping` (Map of List of String) A map from OIDC group name to Coder organization ID.
diff --git a/docs/resources/provisioner_key.md b/docs/resources/provisioner_key.md
new file mode 100644
index 0000000..d64b724
--- /dev/null
+++ b/docs/resources/provisioner_key.md
@@ -0,0 +1,29 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "coderd_provisioner_key Resource - terraform-provider-coderd"
+subcategory: ""
+description: |-
+ A provisioner key for a Coder deployment.
+---
+
+# coderd_provisioner_key (Resource)
+
+A provisioner key for a Coder deployment.
+
+
+
+
+## Schema
+
+### Required
+
+- `name` (String) The name of the key.
+- `organization_id` (String) The organization that provisioners connected with this key will be connected to.
+
+### Optional
+
+- `tags` (Map of String) The tags that provisioners connected with this key will accept jobs for.
+
+### Read-Only
+
+- `key` (String, Sensitive) The acquired provisioner key
diff --git a/docs/resources/template.md b/docs/resources/template.md
index c0efc4b..0b15d2f 100644
--- a/docs/resources/template.md
+++ b/docs/resources/template.md
@@ -77,7 +77,7 @@ resource "coderd_template" "ubuntu-main" {
- `description` (String) A description of the template.
- `display_name` (String) The display name of the template. Defaults to the template name.
- `failure_ttl_ms` (Number) (Enterprise) The max lifetime before Coder stops all resources for failed workspaces created from this template, in milliseconds.
-- `icon` (String) Relative path or external URL that specifes an icon to be displayed in the dashboard.
+- `icon` (String) Relative path or external URL that specifies an icon to be displayed in the dashboard.
- `max_port_share_level` (String) (Enterprise) The maximum port share level for workspaces created from this template. Defaults to `owner` on an Enterprise deployment, or `public` otherwise.
- `organization_id` (String) The ID of the organization. Defaults to the provider's default organization
- `require_active_version` (Boolean) (Enterprise) Whether workspaces must be created from the active version of this template. Defaults to false.
diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf
index fe3b9dc..d50d5e7 100644
--- a/examples/provider/provider.tf
+++ b/examples/provider/provider.tf
@@ -7,7 +7,7 @@ terraform {
}
provider "coderd" {
- url = "coder.example.com"
+ url = "https://coder.example.com"
token = "****"
}
diff --git a/examples/resources/coderd_license/resource.tf b/examples/resources/coderd_license/resource.tf
new file mode 100644
index 0000000..3a25e9c
--- /dev/null
+++ b/examples/resources/coderd_license/resource.tf
@@ -0,0 +1,7 @@
+resource "coderd_license" "license" {
+ license = "<…>"
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
diff --git a/examples/resources/coderd_organization/import.sh b/examples/resources/coderd_organization/import.sh
new file mode 100644
index 0000000..45bb87a
--- /dev/null
+++ b/examples/resources/coderd_organization/import.sh
@@ -0,0 +1,11 @@
+# The ID supplied can be either a organization UUID retrieved via the API
+# or the name of the organization.
+$ terraform import coderd_organization.our_org our-org
+```
+Alternatively, in Terraform v1.5.0 and later, an [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used:
+
+```terraform
+import {
+ to = coderd_organization.our_org
+ id = "our-org"
+}
diff --git a/examples/resources/coderd_organization/resource.tf b/examples/resources/coderd_organization/resource.tf
new file mode 100644
index 0000000..cb26a86
--- /dev/null
+++ b/examples/resources/coderd_organization/resource.tf
@@ -0,0 +1,25 @@
+resource "coderd_organization" "blueberry" {
+ name = "blueberry"
+ display_name = "Blueberry"
+ description = "The organization for blueberries"
+ icon = "/emojis/1fad0.png"
+
+ org_sync_idp_groups = [
+ "wibble",
+ "wobble",
+ ]
+
+ group_sync {
+ field = "coder_groups"
+ mapping = {
+ toast = [coderd_group.bread.id]
+ }
+ }
+
+ role_sync {
+ field = "coder_roles"
+ mapping = {
+ manager = ["organization-user-admin"]
+ }
+ }
+}
diff --git a/examples/resources/coderd_organization_sync_settings/resource.tf b/examples/resources/coderd_organization_sync_settings/resource.tf
new file mode 100644
index 0000000..f1da134
--- /dev/null
+++ b/examples/resources/coderd_organization_sync_settings/resource.tf
@@ -0,0 +1,11 @@
+// Important note: You can only have one resource of this type!
+resource "coderd_organization_sync_settings" "org_sync" {
+ field = "wibble"
+ assign_default = false
+
+ mapping = {
+ wobble = [
+ coderd_organization.my_organization.id,
+ ]
+ }
+}
diff --git a/go.mod b/go.mod
index c62b2b8..897c9e0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,75 +1,85 @@
module github.com/coder/terraform-provider-coderd
-go 1.22.8
+go 1.24.1
require (
- cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6
- github.com/coder/coder/v2 v2.17.0
- github.com/docker/docker v27.2.1+incompatible
+ cdr.dev/slog v1.6.2-0.20241112041820-0ec81e6e67bb
+ github.com/coder/coder/v2 v2.21.0
+ github.com/docker/docker v28.1.1+incompatible
github.com/docker/go-connections v0.5.0
github.com/google/uuid v1.6.0
- github.com/hashicorp/terraform-plugin-docs v0.19.4
- github.com/hashicorp/terraform-plugin-framework v1.13.0
- github.com/hashicorp/terraform-plugin-framework-validators v0.15.0
- github.com/hashicorp/terraform-plugin-go v0.25.0
+ github.com/hashicorp/terraform-plugin-docs v0.21.0
+ github.com/hashicorp/terraform-plugin-framework v1.14.1
+ github.com/hashicorp/terraform-plugin-framework-validators v0.17.0
+ github.com/hashicorp/terraform-plugin-go v0.26.0
github.com/hashicorp/terraform-plugin-log v0.9.0
- github.com/hashicorp/terraform-plugin-testing v1.10.0
- github.com/otiai10/copy v1.14.0
- github.com/stretchr/testify v1.9.0
+ github.com/hashicorp/terraform-plugin-testing v1.12.0
+ github.com/otiai10/copy v1.14.1
+ github.com/stretchr/testify v1.10.0
)
require (
github.com/BurntSushi/toml v1.2.1 // indirect
- github.com/DataDog/appsec-internal-go v1.8.0 // indirect
- github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
- github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 // indirect
- github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
- github.com/DataDog/go-libddwaf/v3 v3.4.0 // indirect
+ github.com/DataDog/appsec-internal-go v1.9.0 // indirect
+ github.com/DataDog/datadog-agent/pkg/obfuscate v0.58.0 // indirect
+ github.com/DataDog/datadog-agent/pkg/proto v0.58.0 // indirect
+ github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.58.0 // indirect
+ github.com/DataDog/datadog-agent/pkg/trace v0.58.0 // indirect
+ github.com/DataDog/datadog-agent/pkg/util/log v0.58.0 // indirect
+ github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0 // indirect
+ github.com/DataDog/datadog-go/v5 v5.5.0 // indirect
+ github.com/DataDog/go-libddwaf/v3 v3.5.1 // indirect
+ github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6 // indirect
+ github.com/DataDog/go-sqllexer v0.0.14 // indirect
github.com/DataDog/go-tuf v1.1.0-0.5.2 // indirect
github.com/DataDog/gostackparse v0.7.0 // indirect
+ github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 // indirect
github.com/DataDog/sketches-go v1.4.5 // indirect
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
+ github.com/ProtonMail/go-crypto v1.1.3 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
- github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
+ github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 // indirect
- github.com/coder/serpent v0.8.0 // indirect
- github.com/coder/terraform-provider-coder v1.0.2 // indirect
- github.com/coreos/go-oidc/v3 v3.11.0 // indirect
+ github.com/coder/serpent v0.10.0 // indirect
+ github.com/coder/terraform-provider-coder/v2 v2.1.3 // indirect
+ github.com/coder/websocket v1.8.12 // indirect
+ github.com/coreos/go-oidc/v3 v3.13.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 // indirect
- github.com/ebitengine/purego v0.6.0-alpha.5 // indirect
+ github.com/ebitengine/purego v0.8.2 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
- github.com/go-jose/go-jose/v4 v4.0.2 // indirect
+ github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
- github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
- github.com/hashicorp/cli v1.1.6 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
+ github.com/hashicorp/cli v1.1.7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
- github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
+ github.com/hashicorp/go-cty v1.5.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.2 // indirect
@@ -79,20 +89,21 @@ require (
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
- github.com/hashicorp/hc-install v0.9.0 // indirect
- github.com/hashicorp/hcl/v2 v2.22.0 // indirect
+ github.com/hashicorp/hc-install v0.9.1 // indirect
+ github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
- github.com/hashicorp/terraform-exec v0.21.0 // indirect
- github.com/hashicorp/terraform-json v0.22.1 // indirect
- github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect
- github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
+ github.com/hashicorp/terraform-exec v0.22.0 // indirect
+ github.com/hashicorp/terraform-json v0.24.0 // indirect
+ github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 // indirect
+ github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
- github.com/hashicorp/yamux v0.1.1 // indirect
+ github.com/hashicorp/yamux v0.1.2 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
- github.com/imdario/mergo v0.3.15 // indirect
- github.com/klauspost/compress v1.17.9 // indirect
+ github.com/imdario/mergo v0.3.16 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
@@ -101,12 +112,16 @@ require (
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
- github.com/moby/moby v27.3.1+incompatible // indirect
- github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
+ github.com/moby/moby v28.0.0+incompatible // indirect
+ github.com/moby/sys/atomicwriter v0.1.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
+ github.com/otiai10/mint v1.6.3 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect
github.com/pion/transport/v2 v2.2.10 // indirect
@@ -114,60 +129,74 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/posener/complete v1.2.3 // indirect
- github.com/prometheus/client_golang v1.20.4 // indirect
+ github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
+ github.com/prometheus/client_golang v1.21.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
- github.com/prometheus/common v0.60.0 // indirect
+ github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
+ github.com/shirou/gopsutil/v3 v3.24.4 // indirect
+ github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
- github.com/spf13/afero v1.11.0 // indirect
- github.com/spf13/cast v1.7.0 // indirect
+ github.com/spf13/afero v1.14.0 // indirect
+ github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tinylib/msgp v1.2.1 // indirect
- github.com/valyala/fasthttp v1.56.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.12 // indirect
+ github.com/tklauser/numcpus v0.6.1 // indirect
+ github.com/valyala/fasthttp v1.59.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
- github.com/yuin/goldmark v1.7.4 // indirect
+ github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
- github.com/zclconf/go-cty v1.15.0 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ github.com/zclconf/go-cty v1.16.2 // indirect
github.com/zeebo/errs v1.3.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
- go.nhat.io/otelsql v0.14.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
- go.opentelemetry.io/otel v1.30.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
- go.opentelemetry.io/otel/metric v1.30.0 // indirect
- go.opentelemetry.io/otel/sdk v1.30.0 // indirect
- go.opentelemetry.io/otel/trace v1.30.0 // indirect
- go.opentelemetry.io/proto/otlp v1.3.1 // indirect
+ go.nhat.io/otelsql v0.15.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/collector/component v0.104.0 // indirect
+ go.opentelemetry.io/collector/config/configtelemetry v0.104.0 // indirect
+ go.opentelemetry.io/collector/pdata v1.11.0 // indirect
+ go.opentelemetry.io/collector/pdata/pprofile v0.104.0 // indirect
+ go.opentelemetry.io/collector/semconv v0.104.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
+ go.opentelemetry.io/otel v1.34.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
+ go.opentelemetry.io/otel/metric v1.34.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.34.0 // indirect
+ go.opentelemetry.io/otel/trace v1.34.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
- golang.org/x/crypto v0.28.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/zap v1.27.0 // indirect
+ golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
- golang.org/x/mod v0.21.0 // indirect
- golang.org/x/net v0.30.0 // indirect
- golang.org/x/oauth2 v0.23.0 // indirect
- golang.org/x/sync v0.8.0 // indirect
- golang.org/x/sys v0.26.0 // indirect
- golang.org/x/term v0.25.0 // indirect
- golang.org/x/text v0.19.0 // indirect
- golang.org/x/time v0.7.0 // indirect
- golang.org/x/tools v0.26.0 // indirect
+ golang.org/x/mod v0.24.0 // indirect
+ golang.org/x/net v0.38.0 // indirect
+ golang.org/x/oauth2 v0.28.0 // indirect
+ golang.org/x/sync v0.12.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/term v0.30.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
+ golang.org/x/time v0.11.0 // indirect
+ golang.org/x/tools v0.31.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
- google.golang.org/grpc v1.67.1 // indirect
- google.golang.org/protobuf v1.35.1 // indirect
- gopkg.in/DataDog/dd-trace-go.v1 v1.69.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
+ google.golang.org/grpc v1.71.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+ gopkg.in/DataDog/dd-trace-go.v1 v1.72.1 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
- nhooyr.io/websocket v1.8.7 // indirect
storj.io/drpc v0.0.33 // indirect
)
diff --git a/go.sum b/go.sum
index 55c9312..781e76f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,13 @@
-cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 h1:KHblWIE/KHOwQ6lEbMZt6YpcGve2FEZ1sDtrW1Am5UI=
-cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ=
-cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
-cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
-cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
-cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
-cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs=
-cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A=
-cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc=
-cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0=
+cdr.dev/slog v1.6.2-0.20241112041820-0ec81e6e67bb h1:4MKA8lBQLnCqj2myJCb5Lzoa65y0tABO4gHrxuMdsCQ=
+cdr.dev/slog v1.6.2-0.20241112041820-0ec81e6e67bb/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ=
+cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
+cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
+cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
+cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
+cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
+cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
+cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
+cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
@@ -16,20 +16,34 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
-github.com/DataDog/appsec-internal-go v1.8.0 h1:1Tfn3LEogntRqZtf88twSApOCAAO3V+NILYhuQIo4J4=
-github.com/DataDog/appsec-internal-go v1.8.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g=
-github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
-github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo=
-github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0 h1:LplNAmMgZvGU7kKA0+4c1xWOjz828xweW5TCi8Mw9Q0=
-github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.57.0/go.mod h1:4Vo3SJ24uzfKHUHLoFa8t8o+LH+7TCQ7sPcZDtOpSP4=
-github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8=
-github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q=
-github.com/DataDog/go-libddwaf/v3 v3.4.0 h1:NJ2W2vhYaOm1OWr1LJCbdgp7ezG/XLJcQKBmjFwhSuM=
-github.com/DataDog/go-libddwaf/v3 v3.4.0/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4=
+github.com/DataDog/appsec-internal-go v1.9.0 h1:cGOneFsg0JTRzWl5U2+og5dbtyW3N8XaYwc5nXe39Vw=
+github.com/DataDog/appsec-internal-go v1.9.0/go.mod h1:wW0cRfWBo4C044jHGwYiyh5moQV2x0AhnwqMuiX7O/g=
+github.com/DataDog/datadog-agent/pkg/obfuscate v0.58.0 h1:nOrRNCHyriM/EjptMrttFOQhRSmvfagESdpyknb5VPg=
+github.com/DataDog/datadog-agent/pkg/obfuscate v0.58.0/go.mod h1:MfDvphBMmEMwE3a30h27AtPO7OzmvdoVTiGY1alEmo4=
+github.com/DataDog/datadog-agent/pkg/proto v0.58.0 h1:JX2Q0C5QnKcYqnYHWUcP0z7R0WB8iiQz3aWn+kT5DEc=
+github.com/DataDog/datadog-agent/pkg/proto v0.58.0/go.mod h1:0wLYojGxRZZFQ+SBbFjay9Igg0zbP88l03TfZaVZ6Dc=
+github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.58.0 h1:5hGO0Z8ih0bRojuq+1ZwLFtdgsfO3TqIjbwJAH12sOQ=
+github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.58.0/go.mod h1:jN5BsZI+VilHJV1Wac/efGxS4TPtXa1Lh9SiUyv93F4=
+github.com/DataDog/datadog-agent/pkg/trace v0.58.0 h1:4AjohoBWWN0nNaeD/0SDZ8lRTYmnJ48CqREevUfSets=
+github.com/DataDog/datadog-agent/pkg/trace v0.58.0/go.mod h1:MFnhDW22V5M78MxR7nv7abWaGc/B4L42uHH1KcIKxZs=
+github.com/DataDog/datadog-agent/pkg/util/log v0.58.0 h1:2MENBnHNw2Vx/ebKRyOPMqvzWOUps2Ol2o/j8uMvN4U=
+github.com/DataDog/datadog-agent/pkg/util/log v0.58.0/go.mod h1:1KdlfcwhqtYHS1szAunsgSfvgoiVsf3mAJc+WvNTnIE=
+github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0 h1:Jkf91q3tuIer4Hv9CLJIYjlmcelAsoJRMmkHyz+p1Dc=
+github.com/DataDog/datadog-agent/pkg/util/scrubber v0.58.0/go.mod h1:krOxbYZc4KKE7bdEDu10lLSQBjdeSFS/XDSclsaSf1Y=
+github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI6LDrKU=
+github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw=
+github.com/DataDog/go-libddwaf/v3 v3.5.1 h1:GWA4ln4DlLxiXm+X7HA/oj0ZLcdCwOS81KQitegRTyY=
+github.com/DataDog/go-libddwaf/v3 v3.5.1/go.mod h1:n98d9nZ1gzenRSk53wz8l6d34ikxS+hs62A31Fqmyi4=
+github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6 h1:bpitH5JbjBhfcTG+H2RkkiUXpYa8xSuIPnyNtTaSPog=
+github.com/DataDog/go-runtime-metrics-internal v0.0.4-0.20241206090539-a14610dc22b6/go.mod h1:quaQJ+wPN41xEC458FCpTwyROZm3MzmTZ8q8XOXQiPs=
+github.com/DataDog/go-sqllexer v0.0.14 h1:xUQh2tLr/95LGxDzLmttLgTo/1gzFeOyuwrQa/Iig4Q=
+github.com/DataDog/go-sqllexer v0.0.14/go.mod h1:KwkYhpFEVIq+BfobkTC1vfqm4gTi65skV/DpDBXtexc=
github.com/DataDog/go-tuf v1.1.0-0.5.2 h1:4CagiIekonLSfL8GMHRHcHudo1fQnxELS9g4tiAupQ4=
github.com/DataDog/go-tuf v1.1.0-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0=
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
+github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0 h1:fKv05WFWHCXQmUTehW1eEZvXJP65Qv00W4V01B1EqSA=
+github.com/DataDog/opentelemetry-mapping-go/pkg/otlp/attributes v0.20.0/go.mod h1:dvIWN9pA2zWNTw5rhDWZgzZnhcfpH++d+8d1SWW6xkY=
github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE=
github.com/DataDog/sketches-go v1.4.5/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg=
github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
@@ -43,12 +57,12 @@ github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBa
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
-github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
+github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
-github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
-github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
+github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
@@ -62,53 +76,58 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
-github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
+github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
-github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk=
-github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
-github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
-github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
-github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
-github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
-github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
-github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
-github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
+github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
+github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
+github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
+github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
+github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
+github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
+github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs=
+github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
-github.com/coder/coder/v2 v2.17.0 h1:U59FfsE++IitesBaCUzAX6/9A+VpnXQKO81RzefWVww=
-github.com/coder/coder/v2 v2.17.0/go.mod h1:5Fu0xCkdXPuFLwYq6ThyI4DAv4v//Sx5saM/M5CAU2w=
+github.com/coder/coder/v2 v2.21.0 h1:2MfGpgJ1Pd1szkUK1xQ+JYcQEIBSRf2U1Eo1yV8dX5Q=
+github.com/coder/coder/v2 v2.21.0/go.mod h1:QLX7sMgj72WylhO0Pyw/ld1W4hfXwIEbPMQXLbgnE3E=
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs=
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
-github.com/coder/serpent v0.8.0 h1:6OR+k6fekhSeEDmwwzBgnSjaa7FfGGrMlc3GoAEH9dg=
-github.com/coder/serpent v0.8.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q=
-github.com/coder/terraform-provider-coder v1.0.2 h1:xKbnJF/XUxcUJlZoC3ZkNOj4PZvk5Stdkel2TCZluDQ=
-github.com/coder/terraform-provider-coder v1.0.2/go.mod h1:1f3EjO+DA9QcIbM7sBSk/Ffw3u7kh6vXNBIQfV59yUk=
+github.com/coder/serpent v0.10.0 h1:ofVk9FJXSek+SmL3yVE3GoArP83M+1tX+H7S4t8BSuM=
+github.com/coder/serpent v0.10.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q=
+github.com/coder/terraform-provider-coder/v2 v2.1.3 h1:zB7ObGsiOGBHcJUUMmcSauEPlTWRIYmMYieF05LxHSc=
+github.com/coder/terraform-provider-coder/v2 v2.1.3/go.mod h1:RHGyb+ghiy8UpDAMJM8duRFuzd+1VqA3AtkRLh2P3Ug=
+github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
+github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
-github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
-github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
-github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
+github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8=
+github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
+github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
+github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
+github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI=
-github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
+github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -118,8 +137,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg=
github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds=
-github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY=
-github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
+github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
+github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -130,70 +149,44 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
-github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
-github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
-github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
-github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
-github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
-github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
-github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
-github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
-github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
-github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
+github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
+github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
+github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
+github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
+github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
+github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
-github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
-github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
-github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
-github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
-github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
-github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
-github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
-github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
-github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
-github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
-github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
-github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
-github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
-github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
-github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
-github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
-github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
-github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
+github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -203,13 +196,10 @@ github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0Z
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
-github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
-github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8=
-github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
+github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
+github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -218,8 +208,8 @@ github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuD
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
-github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI=
-github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs=
+github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0=
+github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
@@ -241,36 +231,36 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE=
-github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg=
-github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
-github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
+github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ=
+github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0=
+github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
+github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
-github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
-github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
-github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
-github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c=
-github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA=
-github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw=
-github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU=
-github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y=
-github.com/hashicorp/terraform-plugin-framework-validators v0.15.0/go.mod h1:Bh89/hNmqsEWug4/XWKYBwtnw3tbz5BAy1L1OgvbIaY=
-github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks=
-github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw=
+github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64=
+github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ=
+github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q=
+github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow=
+github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbKP/nteVNBaI3hW7iQ8=
+github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s=
+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-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-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M=
+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/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
-github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
-github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
-github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
-github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
-github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
-github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
+github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw=
+github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4=
+github.com/hashicorp/terraform-plugin-testing v1.12.0 h1:tpIe+T5KBkA1EO6aT704SPLedHUo55RenguLHcaSBdI=
+github.com/hashicorp/terraform-plugin-testing v1.12.0/go.mod h1:jbDQUkT9XRjAh1Bvyufq+PEH1Xs4RqIdpOQumSgSXBM=
+github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA=
+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/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/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
+github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 h1:AgcIVYPa6XJnU3phs104wLj8l5GEththEw6+F79YsIY=
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
@@ -278,13 +268,12 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
-github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
+github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
+github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
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/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
@@ -293,11 +282,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
-github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
-github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
-github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -305,20 +291,19 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
-github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY=
+github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
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-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
@@ -342,20 +327,23 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
-github.com/moby/moby v27.3.1+incompatible h1:KQbXBjo7PavKpzIl7UkHT31y9lw/e71Uvrqhr4X+zMA=
-github.com/moby/moby v27.3.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
+github.com/moby/moby v28.0.0+incompatible h1:D+F1Z56b/DS8J5pUkTG/stemqrvHBQ006hUqJxjV9P0=
+github.com/moby/moby v28.0.0+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
+github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
+github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
+github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
+github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
-github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
-github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
+github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
+github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
@@ -368,14 +356,12 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
-github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
-github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
-github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
+github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
+github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
+github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
+github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac=
-github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
-github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 h1:jYi87L8j62qkXzaYHAQAhEapgukhenIMZRBKTNRLHJ4=
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
@@ -396,12 +382,15 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
-github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
-github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI=
+github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
+github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
-github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA=
-github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
+github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
+github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -413,8 +402,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
@@ -422,21 +411,27 @@ github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAj
github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
+github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
+github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
+github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
+github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
+github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
+github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
-github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
+github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
+github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
-github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
+github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
-github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
+github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -454,33 +449,37 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
github.com/tinylib/msgp v1.2.1 h1:6ypy2qcCznxpP4hpORzhtXyTqrBs7cfM9MCCWY8zsmU=
github.com/tinylib/msgp v1.2.1/go.mod h1:2vIGs3lcUo8izAATNobrCHevYZC/LMsJtw4JPiYPHro=
-github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
-github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
-github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
-github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
-github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
+github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
+github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
+github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U=
-github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
+github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
+github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
+github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U=
+github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
+github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
+github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
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/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
+github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
@@ -489,12 +488,14 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
-github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
+github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
-github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
-github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
+github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
@@ -503,47 +504,63 @@ github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
-go.nhat.io/otelsql v0.14.0 h1:Mz4xo+WVQLAOPZy6abxjVzZzNe8xoOUh/tOMJoxo3oo=
-go.nhat.io/otelsql v0.14.0/go.mod h1:iO9KfDBZO2WI6O7n+ippHe5OHdXQ5iiA2aIa3Kzywo8=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
-go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
-go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
+go.nhat.io/otelsql v0.15.0 h1:e2lpIaFPe62Pa1fXZoOWXTvMzcN4SwHwHdCz1wDUG6c=
+go.nhat.io/otelsql v0.15.0/go.mod h1:IYUaWCLf7c883mzhfVpHXTBn0jxF4TRMkQjX6fqhXJ8=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/collector/component v0.104.0 h1:jqu/X9rnv8ha0RNZ1a9+x7OU49KwSMsPbOuIEykHuQE=
+go.opentelemetry.io/collector/component v0.104.0/go.mod h1:1C7C0hMVSbXyY1ycCmaMUAR9fVwpgyiNQqxXtEWhVpw=
+go.opentelemetry.io/collector/config/configtelemetry v0.104.0 h1:eHv98XIhapZA8MgTiipvi+FDOXoFhCYOwyKReOt+E4E=
+go.opentelemetry.io/collector/config/configtelemetry v0.104.0/go.mod h1:WxWKNVAQJg/Io1nA3xLgn/DWLE/W1QOB2+/Js3ACi40=
+go.opentelemetry.io/collector/pdata v1.11.0 h1:rzYyV1zfTQQz1DI9hCiaKyyaczqawN75XO9mdXmR/hE=
+go.opentelemetry.io/collector/pdata v1.11.0/go.mod h1:IHxHsp+Jq/xfjORQMDJjSH6jvedOSTOyu3nbxqhWSYE=
+go.opentelemetry.io/collector/pdata/pprofile v0.104.0 h1:MYOIHvPlKEJbWLiBKFQWGD0xd2u22xGVLt4jPbdxP4Y=
+go.opentelemetry.io/collector/pdata/pprofile v0.104.0/go.mod h1:7WpyHk2wJZRx70CGkBio8klrYTTXASbyIhf+rH4FKnA=
+go.opentelemetry.io/collector/semconv v0.104.0 h1:dUvajnh+AYJLEW/XOPk0T0BlwltSdi3vrjO7nSOos3k=
+go.opentelemetry.io/collector/semconv v0.104.0/go.mod h1:yMVUCNoQPZVq/IPfrHrnntZTWsLf5YGZ7qwKulIl5hw=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
+go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
+go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0 h1:IyFlqNsi8VT/nwYlLJfdM0y1gavxGpEvnf6FtVfZ6X4=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0/go.mod h1:bxiX8eUeKoAEQmbq/ecUT8UqZwCjZW52yJrXJUSozsk=
-go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM=
-go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0=
-go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
-go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
-go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
-go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
-go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM=
-go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y=
-go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
-go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
-go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
-go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
+go.opentelemetry.io/otel/exporters/prometheus v0.49.0 h1:Er5I1g/YhfYv9Affk9nJLfH/+qCCVVg1f2R9AbJfqDQ=
+go.opentelemetry.io/otel/exporters/prometheus v0.49.0/go.mod h1:KfQ1wpjf3zsHjzP149P4LyAwWRupc6c7t1ZJ9eXpKQM=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.33.0 h1:FiOTYABOX4tdzi8A0+mtzcsTmi6WBOxk66u0f1Mj9Gs=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.33.0/go.mod h1:xyo5rS8DgzV0Jtsht+LCEMwyiDbjpsxBpWETwFRF0/4=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 h1:W5AWUn/IVe8RFb5pZx1Uh9Laf/4+Qmm4kJL5zPuvR+0=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0/go.mod h1:mzKxJywMNBdEX8TSJais3NnsVZUaJ+bAy6UxPTng2vk=
+go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
+go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
+go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
+go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
+go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
+go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
+go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
+go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
+go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
+go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29 h1:w0QrHuh0hhUZ++UTQaBM2DMdrWQghZ/UsUb+Wb1+8YE=
go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
-golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
-golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
-golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -551,8 +568,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
-golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
+golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -565,10 +582,10 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
-golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
-golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
-golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
-golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
+golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -576,16 +593,18 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -596,15 +615,15 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -612,10 +631,9 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
-golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
-golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
@@ -623,11 +641,10 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
-golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
-golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -635,8 +652,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
-golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
+golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -646,29 +663,30 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6f
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
-google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE=
-google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE=
-google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
-google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
-google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
-google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
+google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
+google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
+google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
+google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
+google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
+google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
-google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
-google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
-gopkg.in/DataDog/dd-trace-go.v1 v1.69.0 h1:zSY6DDsFRMQDNQYKWCv/AEwJXoPpDf1FfMyw7I1B7M8=
-gopkg.in/DataDog/dd-trace-go.v1 v1.69.0/go.mod h1:U9AOeBHNAL95JXcd/SPf4a7O5GNeF/yD13sJtli/yaU=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/DataDog/dd-trace-go.v1 v1.72.1 h1:QG2HNpxe9H4WnztDYbdGQJL/5YIiiZ6xY1+wMuQ2c1w=
+gopkg.in/DataDog/dd-trace-go.v1 v1.72.1/go.mod h1:XqDhDqsLpThFnJc4z0FvAEItISIAUka+RHwmQ6EfN1U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -699,7 +717,5 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
-nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
storj.io/drpc v0.0.33 h1:yCGZ26r66ZdMP0IcTYsj7WDAUIIjzXk6DJhbhvt9FHI=
storj.io/drpc v0.0.33/go.mod h1:vR804UNzhBa49NOJ6HeLjd2H3MakC1j5Gv8bsOQT6N4=
diff --git a/integration/integration.go b/integration/integration.go
index f2ed0dd..9f7120f 100644
--- a/integration/integration.go
+++ b/integration/integration.go
@@ -51,9 +51,10 @@ func StartCoder(ctx context.Context, t *testing.T, name string, useLicense bool)
ctr, err := cli.ContainerCreate(ctx, &container.Config{
Image: coderImg + ":" + coderVersion,
Env: []string{
- "CODER_HTTP_ADDRESS=0.0.0.0:3000", // Listen on all interfaces inside the container
- "CODER_ACCESS_URL=http://localhost:3000", // Set explicitly to avoid creating try.coder.app URLs.
- "CODER_TELEMETRY_ENABLE=false", // Avoid creating noise.
+ "CODER_HTTP_ADDRESS=0.0.0.0:3000", // Listen on all interfaces inside the container
+ "CODER_ACCESS_URL=http://localhost:3000", // Set explicitly to avoid creating try.coder.app URLs.
+ "CODER_TELEMETRY_ENABLE=false", // Avoid creating noise.
+ "CODER_DANGEROUS_DISABLE_RATE_LIMITS=true", // Avoid hitting file rate limit in tests.
},
Labels: map[string]string{},
ExposedPorts: map[nat.Port]struct{}{nat.Port("3000/tcp"): {}},
@@ -95,7 +96,7 @@ func StartCoder(ctx context.Context, t *testing.T, name string, useLicense bool)
t.Logf("not ready yet: %s", err.Error())
}
return err == nil
- }, 15*time.Second, time.Second, "coder failed to become ready in time")
+ }, 20*time.Second, time.Second, "coder failed to become ready in time")
_, err = client.CreateFirstUser(ctx, codersdk.CreateFirstUserRequest{
Email: testEmail,
Username: testUsername,
diff --git a/integration/integration_test.go b/integration/integration_test.go
index 3bfdae4..79834a8 100644
--- a/integration/integration_test.go
+++ b/integration/integration_test.go
@@ -22,6 +22,7 @@ import (
// - Runs the `main.tf` specified in the given test directory against the Coder deployment
// - Asserts the state of the deployment via `codersdk`.
func TestIntegration(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "1" {
t.Skip("Skipping integration tests during tf acceptance tests")
}
@@ -35,7 +36,7 @@ func TestIntegration(t *testing.T) {
}
timeoutMins, err := strconv.Atoi(timeoutStr)
require.NoError(t, err, "invalid value specified for timeout")
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMins)*time.Minute)
+ ctx, cancel := context.WithTimeout(t.Context(), time.Duration(timeoutMins)*time.Minute)
t.Cleanup(cancel)
tfrcPath := setupProvider(t)
@@ -145,6 +146,8 @@ func TestIntegration(t *testing.T) {
},
} {
t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
client := StartCoder(ctx, t, tt.name, true)
wd, err := os.Getwd()
require.NoError(t, err)
@@ -166,7 +169,7 @@ func TestIntegration(t *testing.T) {
tfCmd.Stderr = &buf
tt.preF(t, client)
if err := tfCmd.Run(); !assert.NoError(t, err) {
- t.Logf("%s", buf.String())
+ t.Log(buf.String())
t.FailNow()
}
tt.assertF(t, client)
diff --git a/internal/codersdkvalidator/display_name.go b/internal/codersdkvalidator/display_name.go
new file mode 100644
index 0000000..1000c32
--- /dev/null
+++ b/internal/codersdkvalidator/display_name.go
@@ -0,0 +1,10 @@
+package codersdkvalidator
+
+import (
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func DisplayName() validator.String {
+ return validatorFromFunc(codersdk.DisplayNameValid, "value must be a valid display name")
+}
diff --git a/internal/codersdkvalidator/group_name.go b/internal/codersdkvalidator/group_name.go
new file mode 100644
index 0000000..20313db
--- /dev/null
+++ b/internal/codersdkvalidator/group_name.go
@@ -0,0 +1,10 @@
+package codersdkvalidator
+
+import (
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func GroupName() validator.String {
+ return validatorFromFunc(codersdk.GroupNameValid, "value must be a valid group name")
+}
diff --git a/internal/codersdkvalidator/name.go b/internal/codersdkvalidator/name.go
new file mode 100644
index 0000000..14adb25
--- /dev/null
+++ b/internal/codersdkvalidator/name.go
@@ -0,0 +1,10 @@
+package codersdkvalidator
+
+import (
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func Name() validator.String {
+ return validatorFromFunc(codersdk.NameValid, "value must be a valid name")
+}
diff --git a/internal/codersdkvalidator/regex.go b/internal/codersdkvalidator/regex.go
new file mode 100644
index 0000000..0077616
--- /dev/null
+++ b/internal/codersdkvalidator/regex.go
@@ -0,0 +1,16 @@
+package codersdkvalidator
+
+import (
+ "regexp"
+
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func checkRegexp(it string) error {
+ _, err := regexp.Compile("")
+ return err
+}
+
+func Regexp() validator.String {
+ return validatorFromFunc(checkRegexp, "value must be a valid regexp")
+}
diff --git a/internal/codersdkvalidator/template_version_name.go b/internal/codersdkvalidator/template_version_name.go
new file mode 100644
index 0000000..32c69d6
--- /dev/null
+++ b/internal/codersdkvalidator/template_version_name.go
@@ -0,0 +1,10 @@
+package codersdkvalidator
+
+import (
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func TemplateVersionName() validator.String {
+ return validatorFromFunc(codersdk.TemplateVersionNameValid, "value must be a valid template version name")
+}
diff --git a/internal/codersdkvalidator/user_real_name.go b/internal/codersdkvalidator/user_real_name.go
new file mode 100644
index 0000000..5bf9686
--- /dev/null
+++ b/internal/codersdkvalidator/user_real_name.go
@@ -0,0 +1,10 @@
+package codersdkvalidator
+
+import (
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func UserRealName() validator.String {
+ return validatorFromFunc(codersdk.UserRealNameValid, "value must be a valid name for a user")
+}
diff --git a/internal/codersdkvalidator/validator_from_func.go b/internal/codersdkvalidator/validator_from_func.go
new file mode 100644
index 0000000..9d5e631
--- /dev/null
+++ b/internal/codersdkvalidator/validator_from_func.go
@@ -0,0 +1,51 @@
+package codersdkvalidator
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+type functionValidator struct {
+ check func(string) error
+ defaultMessage string
+ err error
+}
+
+func validatorFromFunc(check func(string) error, defaultMessage string) functionValidator {
+ return functionValidator{
+ check: check,
+ defaultMessage: defaultMessage,
+ }
+}
+
+var _ validator.String = functionValidator{}
+
+func (v functionValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
+ if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
+ return
+ }
+
+ name := req.ConfigValue.ValueString()
+ if v.err = v.check(name); v.err != nil {
+ resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
+ req.Path,
+ v.Description(ctx),
+ name,
+ ))
+ }
+}
+
+var _ validator.Describer = functionValidator{}
+
+func (v functionValidator) Description(_ context.Context) string {
+ if v.err != nil {
+ return v.err.Error()
+ }
+ return v.defaultMessage
+}
+
+func (v functionValidator) MarkdownDescription(ctx context.Context) string {
+ return v.Description(ctx)
+}
diff --git a/internal/provider/group_data_source.go b/internal/provider/group_data_source.go
index 007abaa..9e3639f 100644
--- a/internal/provider/group_data_source.go
+++ b/internal/provider/group_data_source.go
@@ -40,14 +40,13 @@ type GroupDataSourceModel struct {
}
type Member struct {
- ID UUID `tfsdk:"id"`
- Username types.String `tfsdk:"username"`
- Email types.String `tfsdk:"email"`
- CreatedAt types.Int64 `tfsdk:"created_at"`
- LastSeenAt types.Int64 `tfsdk:"last_seen_at"`
- Status types.String `tfsdk:"status"`
- LoginType types.String `tfsdk:"login_type"`
- ThemePreference types.String `tfsdk:"theme_preference"`
+ ID UUID `tfsdk:"id"`
+ Username types.String `tfsdk:"username"`
+ Email types.String `tfsdk:"email"`
+ CreatedAt types.Int64 `tfsdk:"created_at"`
+ LastSeenAt types.Int64 `tfsdk:"last_seen_at"`
+ Status types.String `tfsdk:"status"`
+ LoginType types.String `tfsdk:"login_type"`
}
func (d *GroupDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
@@ -127,9 +126,6 @@ func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaReque
MarkdownDescription: "The login type of the member. Can be `oidc`, `token`, `password`, `github` or `none`.",
Computed: true,
},
- "theme_preference": schema.StringAttribute{
- Computed: true,
- },
// TODO: Upgrade requested user type if required
},
},
@@ -217,14 +213,13 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest,
members := make([]Member, 0, len(group.Members))
for _, member := range group.Members {
members = append(members, Member{
- ID: UUIDValue(member.ID),
- Username: types.StringValue(member.Username),
- Email: types.StringValue(member.Email),
- CreatedAt: types.Int64Value(member.CreatedAt.Unix()),
- LastSeenAt: types.Int64Value(member.LastSeenAt.Unix()),
- Status: types.StringValue(string(member.Status)),
- LoginType: types.StringValue(string(member.LoginType)),
- ThemePreference: types.StringValue(member.ThemePreference),
+ ID: UUIDValue(member.ID),
+ Username: types.StringValue(member.Username),
+ Email: types.StringValue(member.Email),
+ CreatedAt: types.Int64Value(member.CreatedAt.Unix()),
+ LastSeenAt: types.Int64Value(member.LastSeenAt.Unix()),
+ Status: types.StringValue(string(member.Status)),
+ LoginType: types.StringValue(string(member.LoginType)),
})
}
data.Members = members
diff --git a/internal/provider/group_data_source_test.go b/internal/provider/group_data_source_test.go
index 349e855..3cd43f2 100644
--- a/internal/provider/group_data_source_test.go
+++ b/internal/provider/group_data_source_test.go
@@ -1,13 +1,13 @@
package provider
import (
- "context"
"os"
"regexp"
"strings"
"testing"
"text/template"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/terraform-provider-coderd/integration"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -15,10 +15,11 @@ import (
)
func TestAccGroupDataSource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "group_data_acc", true)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -74,7 +75,7 @@ func TestAccGroupDataSource(t *testing.T) {
cfg := testAccGroupDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- ID: PtrTo(group.ID.String()),
+ ID: ptr.Ref(group.ID.String()),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -92,8 +93,8 @@ func TestAccGroupDataSource(t *testing.T) {
cfg := testAccGroupDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- OrganizationID: PtrTo(firstUser.OrganizationIDs[0].String()),
- Name: PtrTo("example-group"),
+ OrganizationID: ptr.Ref(firstUser.OrganizationIDs[0].String()),
+ Name: ptr.Ref("example-group"),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -111,7 +112,7 @@ func TestAccGroupDataSource(t *testing.T) {
cfg := testAccGroupDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example-group"),
+ Name: ptr.Ref("example-group"),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -129,7 +130,7 @@ func TestAccGroupDataSource(t *testing.T) {
cfg := testAccGroupDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- OrganizationID: PtrTo(firstUser.OrganizationIDs[0].String()),
+ OrganizationID: ptr.Ref(firstUser.OrganizationIDs[0].String()),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
diff --git a/internal/provider/group_resource.go b/internal/provider/group_resource.go
index a7dcd6a..fa370a0 100644
--- a/internal/provider/group_resource.go
+++ b/internal/provider/group_resource.go
@@ -6,8 +6,8 @@ import (
"strings"
"github.com/coder/coder/v2/codersdk"
+ "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator"
"github.com/google/uuid"
- "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -77,8 +77,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest,
MarkdownDescription: "The unique name of the group.",
Required: true,
Validators: []validator.String{
- stringvalidator.LengthBetween(1, 36),
- stringvalidator.RegexMatches(nameValidRegex, "Group names must be alpahnumeric with hyphens."),
+ codersdkvalidator.GroupName(),
},
},
"display_name": schema.StringAttribute{
@@ -86,8 +85,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest,
Computed: true,
Optional: true,
Validators: []validator.String{
- stringvalidator.LengthBetween(1, 64),
- stringvalidator.RegexMatches(displayNameRegex, "Group display names must be alphanumeric with spaces"),
+ codersdkvalidator.DisplayName(),
},
Default: stringdefault.StaticString(""),
},
diff --git a/internal/provider/group_resource_test.go b/internal/provider/group_resource_test.go
index 159856f..3c1b589 100644
--- a/internal/provider/group_resource_test.go
+++ b/internal/provider/group_resource_test.go
@@ -1,13 +1,13 @@
package provider
import (
- "context"
"os"
"regexp"
"strings"
"testing"
"text/template"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/terraform-provider-coderd/integration"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -15,10 +15,11 @@ import (
)
func TestAccGroupResource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "group_acc", true)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -44,17 +45,17 @@ func TestAccGroupResource(t *testing.T) {
cfg1 := testAccGroupResourceconfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example-group"),
- DisplayName: PtrTo("Example Group"),
- AvatarUrl: PtrTo("https://google.com"),
- QuotaAllowance: PtrTo(int32(100)),
- Members: PtrTo([]string{user1.ID.String()}),
+ Name: ptr.Ref("example-group"),
+ DisplayName: ptr.Ref("Example Group"),
+ AvatarUrl: ptr.Ref("https://google.com"),
+ QuotaAllowance: ptr.Ref(int32(100)),
+ Members: ptr.Ref([]string{user1.ID.String()}),
}
cfg2 := cfg1
- cfg2.Name = PtrTo("example-group-new")
- cfg2.DisplayName = PtrTo("Example Group New")
- cfg2.Members = PtrTo([]string{user2.ID.String()})
+ cfg2.Name = ptr.Ref("example-group-new")
+ cfg2.DisplayName = ptr.Ref("Example Group New")
+ cfg2.Members = ptr.Ref([]string{user2.ID.String()})
cfg3 := cfg2
cfg3.Members = nil
@@ -132,10 +133,11 @@ func TestAccGroupResource(t *testing.T) {
}
func TestAccGroupResourceAGPL(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "group_acc_agpl", false)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -143,11 +145,11 @@ func TestAccGroupResourceAGPL(t *testing.T) {
cfg1 := testAccGroupResourceconfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example-group"),
- DisplayName: PtrTo("Example Group"),
- AvatarUrl: PtrTo("https://google.com"),
- QuotaAllowance: PtrTo(int32(100)),
- Members: PtrTo([]string{firstUser.ID.String()}),
+ Name: ptr.Ref("example-group"),
+ DisplayName: ptr.Ref("Example Group"),
+ AvatarUrl: ptr.Ref("https://google.com"),
+ QuotaAllowance: ptr.Ref(int32(100)),
+ Members: ptr.Ref([]string{firstUser.ID.String()}),
}
resource.Test(t, resource.TestCase{
diff --git a/internal/provider/license_resource.go b/internal/provider/license_resource.go
index 2dec235..5cb4778 100644
--- a/internal/provider/license_resource.go
+++ b/internal/provider/license_resource.go
@@ -39,9 +39,12 @@ func (r *LicenseResource) Metadata(ctx context.Context, req resource.MetadataReq
func (r *LicenseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
- MarkdownDescription: "A license for a Coder deployment.\n\nIt's recommended to create multiple instances of this " +
- "resource when updating a license. Modifying an existing license will cause the resource to be replaced, " +
- "which may result in a brief unlicensed period.\n\n" +
+ MarkdownDescription: "A license for a Coder deployment.\n\nIt's recommended to set " +
+ "[`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy) " +
+ "on license resources. Without setting this, Terraform will remove the old " +
+ "license before adding the updated license. This will result in a temporary " +
+ "disruption to your users; during which they may be unable to use features " +
+ "that require a license.\n\n" +
"Terraform does not guarantee this resource " +
"will be created before other resources or attributes that require a licensed deployment. " +
"The `depends_on` meta-argument is instead recommended.",
diff --git a/internal/provider/license_resource_test.go b/internal/provider/license_resource_test.go
index e2d13d6..d9c8ffb 100644
--- a/internal/provider/license_resource_test.go
+++ b/internal/provider/license_resource_test.go
@@ -1,7 +1,6 @@
package provider
import (
- "context"
"os"
"strings"
"testing"
@@ -13,10 +12,11 @@ import (
)
func TestAccLicenseResource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "license_acc", false)
license := os.Getenv("CODER_ENTERPRISE_LICENSE")
@@ -24,7 +24,7 @@ func TestAccLicenseResource(t *testing.T) {
t.Skip("No license found for license resource tests, skipping")
}
- cfg1 := testAccLicenseResourceconfig{
+ cfg1 := testAccLicenseResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
License: license,
@@ -42,13 +42,13 @@ func TestAccLicenseResource(t *testing.T) {
})
}
-type testAccLicenseResourceconfig struct {
+type testAccLicenseResourceConfig struct {
URL string
Token string
License string
}
-func (c testAccLicenseResourceconfig) String(t *testing.T) string {
+func (c testAccLicenseResourceConfig) String(t *testing.T) string {
t.Helper()
tpl := `
provider coderd {
diff --git a/internal/provider/organization_data_source_test.go b/internal/provider/organization_data_source_test.go
index 7f9bef3..1bedc4f 100644
--- a/internal/provider/organization_data_source_test.go
+++ b/internal/provider/organization_data_source_test.go
@@ -1,13 +1,13 @@
package provider
import (
- "context"
"os"
"regexp"
"strings"
"testing"
"text/template"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/terraform-provider-coderd/integration"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -15,10 +15,11 @@ import (
)
func TestAccOrganizationDataSource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "org_data_acc", false)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -37,7 +38,7 @@ func TestAccOrganizationDataSource(t *testing.T) {
cfg := testAccOrganizationDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- ID: PtrTo(firstUser.OrganizationIDs[0].String()),
+ ID: ptr.Ref(firstUser.OrganizationIDs[0].String()),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -55,7 +56,7 @@ func TestAccOrganizationDataSource(t *testing.T) {
cfg := testAccOrganizationDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("coder"),
+ Name: ptr.Ref("coder"),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -73,7 +74,7 @@ func TestAccOrganizationDataSource(t *testing.T) {
cfg := testAccOrganizationDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- IsDefault: PtrTo(true),
+ IsDefault: ptr.Ref(true),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -91,8 +92,8 @@ func TestAccOrganizationDataSource(t *testing.T) {
cfg := testAccOrganizationDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- IsDefault: PtrTo(true),
- Name: PtrTo("coder"),
+ IsDefault: ptr.Ref(true),
+ Name: ptr.Ref("coder"),
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
diff --git a/internal/provider/organization_resource.go b/internal/provider/organization_resource.go
new file mode 100644
index 0000000..8fbaba5
--- /dev/null
+++ b/internal/provider/organization_resource.go
@@ -0,0 +1,652 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ "github.com/coder/coder/v2/coderd/util/slice"
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ resource.Resource = &OrganizationResource{}
+var _ resource.ResourceWithImportState = &OrganizationResource{}
+
+type OrganizationResource struct {
+ *CoderdProviderData
+}
+
+// OrganizationResourceModel describes the resource data model.
+type OrganizationResourceModel struct {
+ ID UUID `tfsdk:"id"`
+
+ Name types.String `tfsdk:"name"`
+ DisplayName types.String `tfsdk:"display_name"`
+ Description types.String `tfsdk:"description"`
+ Icon types.String `tfsdk:"icon"`
+
+ OrgSyncIdpGroups types.Set `tfsdk:"org_sync_idp_groups"`
+ GroupSync types.Object `tfsdk:"group_sync"`
+ RoleSync types.Object `tfsdk:"role_sync"`
+}
+
+type GroupSyncModel struct {
+ Field types.String `tfsdk:"field"`
+ RegexFilter types.String `tfsdk:"regex_filter"`
+ AutoCreateMissing types.Bool `tfsdk:"auto_create_missing"`
+ Mapping types.Map `tfsdk:"mapping"`
+}
+
+var groupSyncAttrTypes = map[string]attr.Type{
+ "field": types.StringType,
+ "regex_filter": types.StringType,
+ "auto_create_missing": types.BoolType,
+ "mapping": types.MapType{ElemType: types.ListType{ElemType: UUIDType}},
+}
+
+func (m GroupSyncModel) ValueObject() types.Object {
+ return types.ObjectValueMust(groupSyncAttrTypes, map[string]attr.Value{
+ "field": m.Field,
+ "regex_filter": m.RegexFilter,
+ "auto_create_missing": m.AutoCreateMissing,
+ "mapping": m.Mapping,
+ })
+}
+
+type RoleSyncModel struct {
+ Field types.String `tfsdk:"field"`
+ Mapping types.Map `tfsdk:"mapping"`
+}
+
+var roleSyncAttrTypes = map[string]attr.Type{
+ "field": types.StringType,
+ "mapping": types.MapType{ElemType: types.ListType{ElemType: types.StringType}},
+}
+
+func (m RoleSyncModel) ValueObject() types.Object {
+ return types.ObjectValueMust(roleSyncAttrTypes, map[string]attr.Value{
+ "field": m.Field,
+ "mapping": m.Mapping,
+ })
+}
+
+func NewOrganizationResource() resource.Resource {
+ return &OrganizationResource{}
+}
+
+func (r *OrganizationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_organization"
+}
+
+func (r *OrganizationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: `An organization on the Coder deployment.
+
+~> **Warning**
+This resource is only compatible with Coder version [2.16.0](https://github.com/coder/coder/releases/tag/v2.16.0) and later.
+`,
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ CustomType: UUIDType,
+ Computed: true,
+ MarkdownDescription: "Organization ID",
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Name of the organization.",
+ Required: true,
+ Validators: []validator.String{
+ codersdkvalidator.Name(),
+ },
+ },
+ "display_name": schema.StringAttribute{
+ MarkdownDescription: "Display name of the organization. Defaults to name.",
+ Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ codersdkvalidator.DisplayName(),
+ },
+ },
+ "description": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ Default: stringdefault.StaticString(""),
+ },
+ "icon": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ Default: stringdefault.StaticString(""),
+ },
+
+ "org_sync_idp_groups": schema.SetAttribute{
+ ElementType: types.StringType,
+ Optional: true,
+ MarkdownDescription: "Claims from the IdP provider that will give users access to this organization.",
+ },
+ },
+
+ Blocks: map[string]schema.Block{
+ "group_sync": schema.SingleNestedBlock{
+ MarkdownDescription: `Group sync settings to sync groups from an IdP.`,
+ Attributes: map[string]schema.Attribute{
+ "field": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The claim field that specifies what groups " +
+ "a user should be in.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "regex_filter": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "A regular expression that will be used to " +
+ "filter the groups returned by the OIDC provider. Any group " +
+ "not matched will be ignored.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ codersdkvalidator.Regexp(),
+ },
+ },
+ "auto_create_missing": schema.BoolAttribute{
+ Optional: true,
+ MarkdownDescription: "Controls whether groups will be created if " +
+ "they are missing.",
+ },
+ "mapping": schema.MapAttribute{
+ ElementType: types.ListType{ElemType: UUIDType},
+ Optional: true,
+ MarkdownDescription: "A map from OIDC group name to Coder group ID.",
+ },
+ },
+ },
+ "role_sync": schema.SingleNestedBlock{
+ MarkdownDescription: `Role sync settings to sync organization roles from an IdP.`,
+ Attributes: map[string]schema.Attribute{
+ "field": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The claim field that specifies what " +
+ "organization roles a user should be given.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "mapping": schema.MapAttribute{
+ ElementType: types.ListType{ElemType: types.StringType},
+ Optional: true,
+ MarkdownDescription: "A map from OIDC group name to Coder " +
+ "organization role.",
+ },
+ },
+ },
+ },
+ }
+}
+
+func (r *OrganizationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ data, ok := req.ProviderData.(*CoderdProviderData)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unable to configure provider data",
+ fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.CoderdProviderData = data
+}
+
+func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ // Read Terraform prior state data into the model
+ var data OrganizationResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var org codersdk.Organization
+ var err error
+ if data.ID.IsNull() {
+ orgName := data.Name.ValueString()
+ org, err = r.Client.OrganizationByName(ctx, orgName)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by name, got error: %s", err))
+ return
+ }
+ data.ID = UUIDValue(org.ID)
+ } else {
+ orgID := data.ID.ValueUUID()
+ org, err = r.Client.Organization(ctx, orgID)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by ID, got error: %s", err))
+ return
+ }
+ }
+
+ if !data.GroupSync.IsNull() {
+ groupSync, err := r.Client.GroupIDPSyncSettings(ctx, data.ID.ValueUUID().String())
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to get organization group sync settings, got error: %s", err))
+ return
+ }
+
+ // Read values from Terraform
+ var groupSyncData GroupSyncModel
+ resp.Diagnostics.Append(data.GroupSync.As(ctx, &groupSyncData, basetypes.ObjectAsOptions{})...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if !groupSyncData.Field.IsNull() {
+ groupSyncData.Field = types.StringValue(groupSync.Field)
+ }
+ if !groupSyncData.RegexFilter.IsNull() {
+ groupSyncData.RegexFilter = types.StringValue(groupSync.RegexFilter.String())
+ }
+ if !groupSyncData.AutoCreateMissing.IsNull() {
+ groupSyncData.AutoCreateMissing = types.BoolValue(groupSync.AutoCreateMissing)
+ }
+ if !groupSyncData.Mapping.IsNull() {
+ elements := make(map[string][]string)
+ for key, ids := range groupSync.Mapping {
+ for _, id := range ids {
+ elements[key] = append(elements[key], id.String())
+ }
+ }
+
+ mapping, diags := types.MapValueFrom(ctx, types.ListType{ElemType: UUIDType}, elements)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ groupSyncData.Mapping = mapping
+ }
+
+ data.GroupSync = groupSyncData.ValueObject()
+ }
+
+ if !data.RoleSync.IsNull() {
+ roleSync, err := r.Client.RoleIDPSyncSettings(ctx, data.ID.ValueUUID().String())
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to get organization role sync settings, got error: %s", err))
+ return
+ }
+
+ // Read values from Terraform
+ var roleSyncData RoleSyncModel
+ resp.Diagnostics.Append(data.RoleSync.As(ctx, &roleSyncData, basetypes.ObjectAsOptions{})...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if !roleSyncData.Field.IsNull() {
+ roleSyncData.Field = types.StringValue(roleSync.Field)
+ }
+ if !roleSyncData.Mapping.IsNull() {
+ mapping, diags := types.MapValueFrom(ctx, types.ListType{ElemType: types.StringType}, roleSync.Mapping)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ roleSyncData.Mapping = mapping
+ }
+
+ data.RoleSync = roleSyncData.ValueObject()
+ }
+
+ // We've fetched the organization ID from state, and the latest values for
+ // everything else from the backend. Ensure that any mutable data is synced
+ // with the backend.
+ data.Name = types.StringValue(org.Name)
+ data.DisplayName = types.StringValue(org.DisplayName)
+ data.Description = types.StringValue(org.Description)
+ data.Icon = types.StringValue(org.Icon)
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ // Read Terraform plan data into the model
+ var data OrganizationResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Trace(ctx, "creating organization", map[string]any{
+ "id": data.ID.ValueUUID(),
+ "name": data.Name.ValueString(),
+ "display_name": data.DisplayName.ValueString(),
+ "description": data.Description.ValueString(),
+ "icon": data.Icon.ValueString(),
+ })
+ org, err := r.Client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
+ Name: data.Name.ValueString(),
+ DisplayName: data.DisplayName.ValueString(),
+ Description: data.Description.ValueString(),
+ Icon: data.Icon.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to create organization", err.Error())
+ return
+ }
+ tflog.Trace(ctx, "successfully created organization", map[string]any{
+ "id": org.ID,
+ "name": org.Name,
+ "display_name": org.DisplayName,
+ "description": org.Description,
+ "icon": org.Icon,
+ })
+ // Fill in `ID` since it must be "computed".
+ data.ID = UUIDValue(org.ID)
+ // We also fill in `DisplayName`, since it's optional but the backend will
+ // default it.
+ data.DisplayName = types.StringValue(org.DisplayName)
+
+ orgID := data.ID.ValueUUID()
+
+ // Apply org sync patches, if specified
+ if !data.OrgSyncIdpGroups.IsNull() {
+ tflog.Trace(ctx, "updating org sync", map[string]any{
+ "orgID": orgID,
+ })
+
+ var claims []string
+ resp.Diagnostics.Append(data.OrgSyncIdpGroups.ElementsAs(ctx, &claims, false)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(r.patchOrgSyncMapping(ctx, orgID, []string{}, claims)...)
+ }
+
+ // Apply group and role sync settings, if specified
+ if !data.GroupSync.IsNull() {
+ tflog.Trace(ctx, "updating group sync", map[string]any{
+ "orgID": orgID,
+ })
+
+ resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+ if !data.RoleSync.IsNull() {
+ tflog.Trace(ctx, "updating role sync", map[string]any{
+ "orgID": orgID,
+ })
+ resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ // Read Terraform plan data into the model
+ var data OrganizationResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ orgID := data.ID.ValueUUID()
+
+ // Update the organization metadata
+ tflog.Trace(ctx, "updating organization", map[string]any{
+ "id": orgID,
+ "new_name": data.Name.ValueString(),
+ "new_display_name": data.DisplayName.ValueString(),
+ "new_description": data.Description.ValueString(),
+ "new_icon": data.Icon.ValueString(),
+ })
+ org, err := r.Client.UpdateOrganization(ctx, orgID.String(), codersdk.UpdateOrganizationRequest{
+ Name: data.Name.ValueString(),
+ DisplayName: data.DisplayName.ValueString(),
+ Description: data.Description.ValueStringPointer(),
+ Icon: data.Icon.ValueStringPointer(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update organization %s, got error: %s", orgID, err))
+ return
+ }
+
+ tflog.Trace(ctx, "successfully updated organization", map[string]any{
+ "id": orgID,
+ "name": org.Name,
+ "display_name": org.DisplayName,
+ "description": org.Description,
+ "icon": org.Icon,
+ })
+
+ // Apply org sync patches, if specified
+ if !data.OrgSyncIdpGroups.IsNull() {
+ tflog.Trace(ctx, "updating org sync mappings", map[string]any{
+ "orgID": orgID,
+ })
+
+ var state OrganizationResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ var currentClaims []string
+ resp.Diagnostics.Append(state.OrgSyncIdpGroups.ElementsAs(ctx, ¤tClaims, false)...)
+
+ var plannedClaims []string
+ resp.Diagnostics.Append(data.OrgSyncIdpGroups.ElementsAs(ctx, &plannedClaims, false)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(r.patchOrgSyncMapping(ctx, orgID, currentClaims, plannedClaims)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
+ if !data.GroupSync.IsNull() {
+ tflog.Trace(ctx, "updating group sync", map[string]any{
+ "orgID": orgID,
+ })
+ resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+ if !data.RoleSync.IsNull() {
+ tflog.Trace(ctx, "updating role sync", map[string]any{
+ "orgID": orgID,
+ })
+ resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ // Read Terraform prior state data into the model
+ var data OrganizationResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ orgID := data.ID.ValueUUID()
+
+ // Remove org sync mappings, if we were managing them
+ if !data.OrgSyncIdpGroups.IsNull() {
+ tflog.Trace(ctx, "deleting org sync mappings", map[string]any{
+ "orgID": orgID,
+ })
+
+ var claims []string
+ resp.Diagnostics.Append(data.OrgSyncIdpGroups.ElementsAs(ctx, &claims, false)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(r.patchOrgSyncMapping(ctx, orgID, claims, []string{})...)
+ }
+
+ tflog.Trace(ctx, "deleting organization", map[string]any{
+ "id": orgID,
+ "name": data.Name.ValueString(),
+ })
+ err := r.Client.DeleteOrganization(ctx, orgID.String())
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete organization %s, got error: %s", orgID, err))
+ return
+ }
+ tflog.Trace(ctx, "successfully deleted organization", map[string]any{
+ "id": orgID,
+ "name": data.Name.ValueString(),
+ })
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+}
+
+func (r *OrganizationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ // Terraform will eventually `Read` in the rest of the fields after we have
+ // set the `name` attribute.
+ resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
+}
+
+func (r *OrganizationResource) patchGroupSync(
+ ctx context.Context,
+ orgID uuid.UUID,
+ groupSyncObject types.Object,
+) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ // Read values from Terraform
+ var groupSyncData GroupSyncModel
+ diags.Append(groupSyncObject.As(ctx, &groupSyncData, basetypes.ObjectAsOptions{})...)
+ if diags.HasError() {
+ return diags
+ }
+
+ // Convert that into the type used to send the PATCH to the backend
+ var groupSync codersdk.GroupSyncSettings
+ groupSync.Field = groupSyncData.Field.ValueString()
+ groupSync.RegexFilter = regexp.MustCompile(groupSyncData.RegexFilter.ValueString())
+ groupSync.AutoCreateMissing = groupSyncData.AutoCreateMissing.ValueBool()
+ groupSync.Mapping = make(map[string][]uuid.UUID)
+ // Terraform doesn't know how to turn one our `UUID` Terraform values into a
+ // `uuid.UUID`, so we have to do the unwrapping manually here.
+ var mapping map[string][]UUID
+ diags.Append(groupSyncData.Mapping.ElementsAs(ctx, &mapping, false)...)
+ if diags.HasError() {
+ return diags
+ }
+ for key, ids := range mapping {
+ for _, id := range ids {
+ groupSync.Mapping[key] = append(groupSync.Mapping[key], id.ValueUUID())
+ }
+ }
+
+ // Perform the PATCH
+ _, err := r.Client.PatchGroupIDPSyncSettings(ctx, orgID.String(), groupSync)
+ if err != nil {
+ diags.AddError("Group Sync Update error", err.Error())
+ return diags
+ }
+
+ return diags
+}
+
+func (r *OrganizationResource) patchRoleSync(
+ ctx context.Context,
+ orgID uuid.UUID,
+ roleSyncObject types.Object,
+) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ // Read values from Terraform
+ var roleSyncData RoleSyncModel
+ diags.Append(roleSyncObject.As(ctx, &roleSyncData, basetypes.ObjectAsOptions{})...)
+ if diags.HasError() {
+ return diags
+ }
+
+ // Convert that into the type used to send the PATCH to the backend
+ var roleSync codersdk.RoleSyncSettings
+ roleSync.Field = roleSyncData.Field.ValueString()
+ diags.Append(roleSyncData.Mapping.ElementsAs(ctx, &roleSync.Mapping, false)...)
+ if diags.HasError() {
+ return diags
+ }
+
+ // Perform the PATCH
+ _, err := r.Client.PatchRoleIDPSyncSettings(ctx, orgID.String(), roleSync)
+ if err != nil {
+ diags.AddError("Role Sync Update error", err.Error())
+ return diags
+ }
+
+ return diags
+}
+
+func (r *OrganizationResource) patchOrgSyncMapping(
+ ctx context.Context,
+ orgID uuid.UUID,
+ currentClaims, plannedClaims []string,
+) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ add, remove := slice.SymmetricDifference(currentClaims, plannedClaims)
+ var addMappings []codersdk.IDPSyncMapping[uuid.UUID]
+ for _, claim := range add {
+ addMappings = append(addMappings, codersdk.IDPSyncMapping[uuid.UUID]{
+ Given: claim,
+ Gets: orgID,
+ })
+ }
+ var removeMappings []codersdk.IDPSyncMapping[uuid.UUID]
+ for _, claim := range remove {
+ removeMappings = append(removeMappings, codersdk.IDPSyncMapping[uuid.UUID]{
+ Given: claim,
+ Gets: orgID,
+ })
+ }
+
+ _, err := r.Client.PatchOrganizationIDPSyncMapping(ctx, codersdk.PatchOrganizationIDPSyncMappingRequest{
+ Add: addMappings,
+ Remove: removeMappings,
+ })
+ if err != nil {
+ diags.AddError("Org Sync Update error", err.Error())
+ }
+
+ return diags
+}
diff --git a/internal/provider/organization_resource_test.go b/internal/provider/organization_resource_test.go
new file mode 100644
index 0000000..239f29d
--- /dev/null
+++ b/internal/provider/organization_resource_test.go
@@ -0,0 +1,219 @@
+package provider
+
+import (
+ "os"
+ "strings"
+ "testing"
+ "text/template"
+
+ "github.com/coder/coder/v2/coderd/util/ptr"
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/coder/terraform-provider-coderd/integration"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+ "github.com/stretchr/testify/require"
+)
+
+func TestAccOrganizationResource(t *testing.T) {
+ t.Parallel()
+ if os.Getenv("TF_ACC") == "" {
+ t.Skip("Acceptance tests are disabled.")
+ }
+
+ ctx := t.Context()
+ client := integration.StartCoder(ctx, t, "organization_acc", true)
+ _, err := client.User(ctx, codersdk.Me)
+ require.NoError(t, err)
+
+ cfg1 := testAccOrganizationResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-org"),
+ DisplayName: ptr.Ref("Example Organization"),
+ Description: ptr.Ref("This is an example organization"),
+ Icon: ptr.Ref("/icon/coder.svg"),
+ }
+
+ cfg2 := cfg1
+ cfg2.Name = ptr.Ref("example-org-new")
+ cfg2.DisplayName = ptr.Ref("Example Organization New")
+
+ cfg3 := cfg2
+ cfg3.OrgSyncIdpGroups = []string{"wibble", "wobble"}
+
+ cfg4 := cfg3
+ cfg4.OrgSyncIdpGroups = []string{"wibbley", "wobbley"}
+
+ cfg5 := cfg4
+ cfg5.GroupSync = ptr.Ref(codersdk.GroupSyncSettings{
+ Field: "wibble",
+ Mapping: map[string][]uuid.UUID{
+ "wibble": {uuid.MustParse("6e57187f-6543-46ab-a62c-a10065dd4314")},
+ },
+ })
+ cfg5.RoleSync = ptr.Ref(codersdk.RoleSyncSettings{
+ Field: "wobble",
+ Mapping: map[string][]string{
+ "wobble": {"wobbly"},
+ },
+ })
+
+ t.Run("CreateImportUpdateReadOk", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read
+ {
+ Config: cfg1.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("name"), knownvalue.StringExact("example-org")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("display_name"), knownvalue.StringExact("Example Organization")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("icon"), knownvalue.StringExact("/icon/coder.svg")),
+ },
+ },
+ // Import
+ {
+ Config: cfg1.String(t),
+ ResourceName: "coderd_organization.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateId: *cfg1.Name,
+ },
+ // Update and Read
+ {
+ Config: cfg2.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("name"), knownvalue.StringExact("example-org-new")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("display_name"), knownvalue.StringExact("Example Organization New")),
+ },
+ },
+ // Add org sync
+ {
+ Config: cfg3.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(0), knownvalue.StringExact("wibble")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(1), knownvalue.StringExact("wobble")),
+ },
+ },
+ // Patch org sync
+ {
+ Config: cfg4.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(0), knownvalue.StringExact("wibbley")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(1), knownvalue.StringExact("wobbley")),
+ },
+ },
+ // Add group and role sync
+ {
+ Config: cfg5.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("group_sync").AtMapKey("field"), knownvalue.StringExact("wibble")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("group_sync").AtMapKey("mapping").AtMapKey("wibble").AtSliceIndex(0), knownvalue.StringExact("6e57187f-6543-46ab-a62c-a10065dd4314")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("role_sync").AtMapKey("field"), knownvalue.StringExact("wobble")),
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("role_sync").AtMapKey("mapping").AtMapKey("wobble").AtSliceIndex(0), knownvalue.StringExact("wobbly")),
+ },
+ },
+ },
+ })
+ })
+
+ t.Run("DefaultDisplayName", func(t *testing.T) {
+ cfg1 := testAccOrganizationResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-org"),
+ Description: ptr.Ref("This is an example organization"),
+ Icon: ptr.Ref("/icon/coder.svg"),
+ }
+ resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg1.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("display_name"), knownvalue.StringExact("example-org")),
+ },
+ },
+ },
+ })
+ })
+}
+
+type testAccOrganizationResourceConfig struct {
+ URL string
+ Token string
+
+ Name *string
+ DisplayName *string
+ Description *string
+ Icon *string
+
+ OrgSyncIdpGroups []string
+ GroupSync *codersdk.GroupSyncSettings
+ RoleSync *codersdk.RoleSyncSettings
+}
+
+func (c testAccOrganizationResourceConfig) String(t *testing.T) string {
+ t.Helper()
+ tpl := `
+provider coderd {
+ url = "{{.URL}}"
+ token = "{{.Token}}"
+}
+
+resource "coderd_organization" "test" {
+ name = {{orNull .Name}}
+ display_name = {{orNull .DisplayName}}
+ description = {{orNull .Description}}
+ icon = {{orNull .Icon}}
+
+ {{- if .OrgSyncIdpGroups}}
+ org_sync_idp_groups = [
+ {{- range $name := .OrgSyncIdpGroups }}
+ "{{$name}}",
+ {{- end}}
+ ]
+ {{- end}}
+
+ {{- if .GroupSync}}
+ group_sync {
+ field = "{{.GroupSync.Field}}"
+ mapping = {
+ {{- range $key, $value := .GroupSync.Mapping}}
+ {{$key}} = {{printf "%q" $value}}
+ {{- end}}
+ }
+ }
+ {{- end}}
+
+ {{- if .RoleSync}}
+ role_sync {
+ field = "{{.RoleSync.Field}}"
+ mapping = {
+ {{- range $key, $value := .RoleSync.Mapping}}
+ {{$key}} = {{printf "%q" $value}}
+ {{- end}}
+ }
+ }
+ {{- end}}
+}
+`
+ funcMap := template.FuncMap{
+ "orNull": PrintOrNull,
+ }
+
+ buf := strings.Builder{}
+ tmpl, err := template.New("organizationResource").Funcs(funcMap).Parse(tpl)
+ require.NoError(t, err)
+
+ err = tmpl.Execute(&buf, c)
+ require.NoError(t, err)
+ return buf.String()
+}
diff --git a/internal/provider/organization_sync_settings_resource.go b/internal/provider/organization_sync_settings_resource.go
new file mode 100644
index 0000000..d492d3d
--- /dev/null
+++ b/internal/provider/organization_sync_settings_resource.go
@@ -0,0 +1,258 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ resource.Resource = &OrganizationSyncSettingsResource{}
+
+type OrganizationSyncSettingsResource struct {
+ *CoderdProviderData
+}
+
+// OrganizationSyncSettingsResourceModel describes the resource data model.
+type OrganizationSyncSettingsResourceModel struct {
+ Field types.String `tfsdk:"field"`
+ AssignDefault types.Bool `tfsdk:"assign_default"`
+ Mapping types.Map `tfsdk:"mapping"`
+}
+
+func NewOrganizationSyncSettingsResource() resource.Resource {
+ return &OrganizationSyncSettingsResource{}
+}
+
+func (r *OrganizationSyncSettingsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_organization_sync_settings"
+}
+
+func (r *OrganizationSyncSettingsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: `IdP sync settings for organizations.
+
+This resource can only be created once. Attempts to create multiple will fail.
+
+~> **Warning**
+This resource is only compatible with Coder version [2.19.0](https://github.com/coder/coder/releases/tag/v2.19.0) and later.
+`,
+ Attributes: map[string]schema.Attribute{
+ "field": schema.StringAttribute{
+ Required: true,
+ MarkdownDescription: "The claim field that specifies what organizations " +
+ "a user should be in.",
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "assign_default": schema.BoolAttribute{
+ Required: true,
+ MarkdownDescription: "When true, every user will be added to the default " +
+ "organization, regardless of claims.",
+ },
+ "mapping": schema.MapAttribute{
+ ElementType: types.ListType{ElemType: UUIDType},
+ Optional: true,
+ MarkdownDescription: "A map from OIDC group name to Coder organization ID.",
+ },
+ },
+ }
+}
+
+func (r *OrganizationSyncSettingsResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ data, ok := req.ProviderData.(*CoderdProviderData)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unable to configure provider data",
+ fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.CoderdProviderData = data
+}
+
+func (r *OrganizationSyncSettingsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ // Read Terraform prior state data into the model
+ var data OrganizationSyncSettingsResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ settings, err := r.Client.OrganizationIDPSyncSettings(ctx)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to get organization sync settings, got error: %s", err))
+ return
+ }
+
+ // Store the latest values that we just fetched.
+ data.Field = types.StringValue(settings.Field)
+ data.AssignDefault = types.BoolValue(settings.AssignDefault)
+
+ if !data.Mapping.IsNull() {
+ // Convert IDs to strings
+ elements := make(map[string][]string)
+ for key, ids := range settings.Mapping {
+ for _, id := range ids {
+ elements[key] = append(elements[key], id.String())
+ }
+ }
+
+ mapping, diags := types.MapValueFrom(ctx, types.ListType{ElemType: UUIDType}, elements)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ data.Mapping = mapping
+ }
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *OrganizationSyncSettingsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ // Read Terraform plan data into the model
+ var data OrganizationSyncSettingsResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Trace(ctx, "creating organization sync", map[string]any{
+ "field": data.Field.ValueString(),
+ "assign_default": data.AssignDefault.ValueBool(),
+ })
+
+ // Create and Update use a shared implementation
+ resp.Diagnostics.Append(r.patch(ctx, data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Trace(ctx, "successfully created organization sync", map[string]any{
+ "field": data.Field.ValueString(),
+ "assign_default": data.AssignDefault.ValueBool(),
+ })
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *OrganizationSyncSettingsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ // Read Terraform plan data into the model
+ var data OrganizationSyncSettingsResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Update the organization metadata
+ tflog.Trace(ctx, "updating organization", map[string]any{
+ "field": data.Field.ValueString(),
+ "assign_default": data.AssignDefault.ValueBool(),
+ })
+
+ // Create and Update use a shared implementation
+ resp.Diagnostics.Append(r.patch(ctx, data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Trace(ctx, "successfully updated organization", map[string]any{
+ "field": data.Field.ValueString(),
+ "assign_default": data.AssignDefault.ValueBool(),
+ })
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *OrganizationSyncSettingsResource) patch(
+ ctx context.Context,
+ data OrganizationSyncSettingsResourceModel,
+) diag.Diagnostics {
+ var diags diag.Diagnostics
+ field := data.Field.ValueString()
+ assignDefault := data.AssignDefault.ValueBool()
+
+ if data.Mapping.IsNull() {
+ _, err := r.Client.PatchOrganizationIDPSyncConfig(ctx, codersdk.PatchOrganizationIDPSyncConfigRequest{
+ Field: field,
+ AssignDefault: assignDefault,
+ })
+
+ if err != nil {
+ diags.AddError("failed to create organization sync", err.Error())
+ return diags
+ }
+ } else {
+ settings := codersdk.OrganizationSyncSettings{
+ Field: field,
+ AssignDefault: assignDefault,
+ Mapping: map[string][]uuid.UUID{},
+ }
+
+ // Terraform doesn't know how to turn one our `UUID` Terraform values into a
+ // `uuid.UUID`, so we have to do the unwrapping manually here.
+ var mapping map[string][]UUID
+ diags.Append(data.Mapping.ElementsAs(ctx, &mapping, false)...)
+ if diags.HasError() {
+ return diags
+ }
+ for key, ids := range mapping {
+ for _, id := range ids {
+ settings.Mapping[key] = append(settings.Mapping[key], id.ValueUUID())
+ }
+ }
+
+ _, err := r.Client.PatchOrganizationIDPSyncSettings(ctx, settings)
+ if err != nil {
+ diags.AddError("failed to create organization sync", err.Error())
+ return diags
+ }
+ }
+
+ return diags
+}
+
+func (r *OrganizationSyncSettingsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ // Read Terraform prior state data into the model
+ var data OrganizationSyncSettingsResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Trace(ctx, "deleting organization sync", map[string]any{})
+ _, err := r.Client.PatchOrganizationIDPSyncConfig(ctx, codersdk.PatchOrganizationIDPSyncConfigRequest{
+ // This disables organization sync without causing state conflicts for
+ // organization resources that might still specify `org_sync_idp_groups`.
+ Field: "",
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to delete organization sync, got error: %s", err))
+ return
+ }
+ tflog.Trace(ctx, "successfully deleted organization sync", map[string]any{})
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+}
diff --git a/internal/provider/organization_sync_settings_resource_test.go b/internal/provider/organization_sync_settings_resource_test.go
new file mode 100644
index 0000000..ac7bf2a
--- /dev/null
+++ b/internal/provider/organization_sync_settings_resource_test.go
@@ -0,0 +1,125 @@
+package provider
+
+import (
+ "os"
+ "strings"
+ "testing"
+ "text/template"
+
+ "github.com/coder/coder/v2/codersdk"
+ "github.com/coder/terraform-provider-coderd/integration"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+ "github.com/stretchr/testify/require"
+)
+
+func TestAccOrganizationSyncSettingsResource(t *testing.T) {
+ t.Parallel()
+ if os.Getenv("TF_ACC") == "" {
+ t.Skip("Acceptance tests are disabled.")
+ }
+
+ ctx := t.Context()
+ client := integration.StartCoder(ctx, t, "organization_sync_settings_acc", true)
+ _, err := client.User(ctx, codersdk.Me)
+ require.NoError(t, err)
+
+ cfg1 := testAccOrganizationSyncSettingsResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+
+ Field: "wibble",
+ AssignDefault: true,
+ }
+
+ cfg2 := cfg1
+ cfg2.Field = "wobble"
+ cfg2.AssignDefault = false
+
+ cfg3 := cfg2
+ cfg3.Mapping = map[string][]uuid.UUID{
+ "wibble": {uuid.MustParse("151b5a4e-391a-464d-a88c-ac50f1458d6f")},
+ }
+
+ t.Run("CreateUpdateReadOk", func(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read
+ {
+ Config: cfg1.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization_sync_settings.test", tfjsonpath.New("field"), knownvalue.StringExact("wibble")),
+ statecheck.ExpectKnownValue("coderd_organization_sync_settings.test", tfjsonpath.New("assign_default"), knownvalue.Bool(true)),
+ },
+ },
+ // Update and Read
+ {
+ Config: cfg2.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization_sync_settings.test", tfjsonpath.New("field"), knownvalue.StringExact("wobble")),
+ statecheck.ExpectKnownValue("coderd_organization_sync_settings.test", tfjsonpath.New("assign_default"), knownvalue.Bool(false)),
+ },
+ },
+ // Add mapping
+ {
+ Config: cfg3.String(t),
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_organization_sync_settings.test", tfjsonpath.New("field"), knownvalue.StringExact("wobble")),
+ statecheck.ExpectKnownValue("coderd_organization_sync_settings.test", tfjsonpath.New("mapping").AtMapKey("wibble").AtSliceIndex(0), knownvalue.StringExact("151b5a4e-391a-464d-a88c-ac50f1458d6f")),
+ },
+ },
+ },
+ })
+ })
+}
+
+type testAccOrganizationSyncSettingsResourceConfig struct {
+ URL string
+ Token string
+
+ Field string
+ AssignDefault bool
+ Mapping map[string][]uuid.UUID
+}
+
+func (c testAccOrganizationSyncSettingsResourceConfig) String(t *testing.T) string {
+ t.Helper()
+ tpl := `
+provider coderd {
+ url = "{{.URL}}"
+ token = "{{.Token}}"
+}
+
+resource "coderd_organization_sync_settings" "test" {
+ field = "{{.Field}}"
+ assign_default = {{.AssignDefault}}
+
+ {{- if .Mapping}}
+ mapping = {
+ {{- range $key, $value := .Mapping}}
+ {{$key}} = [
+ {{- range $id := $value}}
+ "{{$id}}",
+ {{- end}}
+ ]
+ {{- end}}
+ }
+ {{- end}}
+}
+`
+ funcMap := template.FuncMap{}
+
+ buf := strings.Builder{}
+ tmpl, err := template.New("organizationSyncSettingsResource").Funcs(funcMap).Parse(tpl)
+ require.NoError(t, err)
+
+ err = tmpl.Execute(&buf, c)
+ require.NoError(t, err)
+ return buf.String()
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index bfeea5e..8c65385 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -2,6 +2,7 @@ package provider
import (
"context"
+ "fmt"
"net/url"
"os"
"strings"
@@ -78,7 +79,6 @@ This provider is only compatible with Coder version [2.10.1](https://github.com/
func (p *CoderdProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
var data CoderdProviderModel
-
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
@@ -102,7 +102,16 @@ func (p *CoderdProvider) Configure(ctx context.Context, req provider.ConfigureRe
data.Token = types.StringValue(tokenEnv)
}
- url, err := url.Parse(data.URL.ValueString())
+ rawURL := data.URL.ValueString()
+ if !strings.HasPrefix(rawURL, "http://") && !strings.HasPrefix(rawURL, "https://") {
+ scheme := "https"
+ if strings.HasPrefix(rawURL, "localhost") {
+ scheme = "http"
+ }
+ rawURL = fmt.Sprintf("%s://%s", scheme, rawURL)
+ }
+
+ url, err := url.Parse(rawURL)
if err != nil {
resp.Diagnostics.AddError("url", "url is not a valid URL: "+err.Error())
return
@@ -139,6 +148,9 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour
NewTemplateResource,
NewWorkspaceProxyResource,
NewLicenseResource,
+ NewOrganizationResource,
+ NewProvisionerKeyResource,
+ NewOrganizationSyncSettingsResource,
}
}
diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go
index 2f8b921..7dab7ff 100644
--- a/internal/provider/provider_test.go
+++ b/internal/provider/provider_test.go
@@ -16,6 +16,7 @@ var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServe
}
func testAccPreCheck(t *testing.T) {
+ t.Helper()
// You can add code here to run prior to any test case execution, for example assertions
// about the appropriate environment variables being set are common to see in a pre-check
// function.
diff --git a/internal/provider/provisioner_key_resource.go b/internal/provider/provisioner_key_resource.go
new file mode 100644
index 0000000..5904df0
--- /dev/null
+++ b/internal/provider/provisioner_key_resource.go
@@ -0,0 +1,154 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+
+ "github.com/coder/coder/v2/codersdk"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ resource.Resource = &ProvisionerKeyResource{}
+
+func NewProvisionerKeyResource() resource.Resource {
+ return &ProvisionerKeyResource{}
+}
+
+// ProvisionerKeyResource defines the resource implementation.
+type ProvisionerKeyResource struct {
+ *CoderdProviderData
+}
+
+// ProvisionerKeyResourceModel describes the resource data model.
+type ProvisionerKeyResourceModel struct {
+ OrganizationID UUID `tfsdk:"organization_id"`
+ Name types.String `tfsdk:"name"`
+ Tags types.Map `tfsdk:"tags"`
+ Key types.String `tfsdk:"key"`
+}
+
+func (r *ProvisionerKeyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_provisioner_key"
+}
+
+func (r *ProvisionerKeyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "A provisioner key for a Coder deployment.",
+
+ Attributes: map[string]schema.Attribute{
+ "organization_id": schema.StringAttribute{
+ CustomType: UUIDType,
+ MarkdownDescription: "The organization that provisioners connected with this key will be connected to.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "The name of the key.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "tags": schema.MapAttribute{
+ MarkdownDescription: "The tags that provisioners connected with this key will accept jobs for.",
+ ElementType: types.StringType,
+ Optional: true,
+ PlanModifiers: []planmodifier.Map{
+ mapplanmodifier.RequiresReplace(),
+ },
+ },
+ "key": schema.StringAttribute{
+ MarkdownDescription: "The acquired provisioner key",
+ Computed: true,
+ Sensitive: true,
+ },
+ },
+ }
+}
+
+func (r *ProvisionerKeyResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ data, ok := req.ProviderData.(*CoderdProviderData)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.CoderdProviderData = data
+}
+
+func (r *ProvisionerKeyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ // Read Terraform plan data into the model
+ var data ProvisionerKeyResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var tags map[string]string
+ resp.Diagnostics.Append(data.Tags.ElementsAs(ctx, &tags, false)...)
+ createKeyResult, err := r.Client.CreateProvisionerKey(ctx, data.OrganizationID.ValueUUID(), codersdk.CreateProvisionerKeyRequest{
+ Name: data.Name.ValueString(),
+ Tags: tags,
+ })
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create provisioner_key, got error: %s", err))
+ return
+ }
+
+ data.Key = types.StringValue(createKeyResult.Key)
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *ProvisionerKeyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ // Read Terraform prior state data into the model
+ var data ProvisionerKeyResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Provisioner keys are immutable, no reading necessary.
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *ProvisionerKeyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ // Provisioner keys are immutable, updating is always invalid.
+ resp.Diagnostics.AddError("Invalid Update", "Terraform is attempting to update a resource which must be replaced")
+}
+
+func (r *ProvisionerKeyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ // Read Terraform prior state data into the model
+ var data ProvisionerKeyResourceModel
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err := r.Client.DeleteProvisionerKey(ctx, data.OrganizationID.ValueUUID(), data.Name.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete provisionerkey, got error: %s", err))
+ return
+ }
+}
diff --git a/internal/provider/provisioner_key_resource_test.go b/internal/provider/provisioner_key_resource_test.go
new file mode 100644
index 0000000..3ca4757
--- /dev/null
+++ b/internal/provider/provisioner_key_resource_test.go
@@ -0,0 +1,114 @@
+package provider
+
+import (
+ "os"
+ "strings"
+ "testing"
+ "text/template"
+
+ "github.com/coder/terraform-provider-coderd/integration"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/plancheck"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+ "github.com/stretchr/testify/require"
+)
+
+func TestAccProvisionerKeyResource(t *testing.T) {
+ t.Parallel()
+ if os.Getenv("TF_ACC") == "" {
+ t.Skip("Acceptance tests are disabled.")
+ }
+ ctx := t.Context()
+ client := integration.StartCoder(ctx, t, "provisioner_key_acc", true)
+ orgs, err := client.Organizations(ctx)
+ require.NoError(t, err)
+ firstOrg := orgs[0].ID
+
+ cfg1 := testAccProvisionerKeyResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+
+ OrganizationID: firstOrg,
+ Name: "example-provisioner-key",
+ }
+
+ cfg2 := cfg1
+ cfg2.Tags = map[string]string{
+ "wibble": "wobble",
+ }
+
+ cfg3 := cfg2
+ cfg3.Name = "different-provisioner-key"
+
+ resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg1.String(t),
+ },
+ {
+ Config: cfg2.String(t),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("coderd_provisioner_key.test", plancheck.ResourceActionReplace),
+ },
+ },
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("coderd_provisioner_key.test", tfjsonpath.New("tags").AtMapKey("wibble"), knownvalue.StringExact("wobble")),
+ },
+ },
+ {
+ Config: cfg3.String(t),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("coderd_provisioner_key.test", plancheck.ResourceActionReplace),
+ },
+ },
+ },
+ },
+ })
+}
+
+type testAccProvisionerKeyResourceConfig struct {
+ URL string
+ Token string
+
+ OrganizationID uuid.UUID
+ Name string
+ Tags map[string]string
+}
+
+func (c testAccProvisionerKeyResourceConfig) String(t *testing.T) string {
+ t.Helper()
+
+ tpl := `
+provider coderd {
+ url = "{{.URL}}"
+ token = "{{.Token}}"
+}
+
+resource "coderd_provisioner_key" "test" {
+ organization_id = "{{.OrganizationID}}"
+ name = "{{.Name}}"
+
+ tags = {
+ {{- range $key, $value := .Tags}}
+ {{$key}} = "{{$value}}"
+ {{- end}}
+ }
+}
+`
+
+ buf := strings.Builder{}
+ tmpl, err := template.New("provisionerKeyResource").Parse(tpl)
+ require.NoError(t, err)
+
+ err = tmpl.Execute(&buf, c)
+ require.NoError(t, err)
+ return buf.String()
+}
diff --git a/internal/provider/template_data_source_test.go b/internal/provider/template_data_source_test.go
index cebdba7..91f9368 100644
--- a/internal/provider/template_data_source_test.go
+++ b/internal/provider/template_data_source_test.go
@@ -1,7 +1,6 @@
package provider
import (
- "context"
"os"
"regexp"
"strconv"
@@ -14,15 +13,17 @@ import (
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/stretchr/testify/require"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/terraform-provider-coderd/integration"
)
func TestAccTemplateDataSource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "template_data_acc", true)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -49,8 +50,8 @@ func TestAccTemplateDataSource(t *testing.T) {
Description: "An example template",
Icon: "/path/to/icon.png",
VersionID: version.ID,
- DefaultTTLMillis: PtrTo((10 * time.Hour).Milliseconds()),
- ActivityBumpMillis: PtrTo((4 * time.Hour).Milliseconds()),
+ DefaultTTLMillis: ptr.Ref((10 * time.Hour).Milliseconds()),
+ ActivityBumpMillis: ptr.Ref((4 * time.Hour).Milliseconds()),
AutostopRequirement: &codersdk.TemplateAutostopRequirement{
DaysOfWeek: []string{"sunday"},
Weeks: 1,
@@ -58,12 +59,12 @@ func TestAccTemplateDataSource(t *testing.T) {
AutostartRequirement: &codersdk.TemplateAutostartRequirement{
DaysOfWeek: []string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"},
},
- AllowUserCancelWorkspaceJobs: PtrTo(true),
- AllowUserAutostart: PtrTo(true),
- AllowUserAutostop: PtrTo(true),
- FailureTTLMillis: PtrTo((1 * time.Hour).Milliseconds()),
- TimeTilDormantMillis: PtrTo((7 * 24 * time.Hour).Milliseconds()),
- TimeTilDormantAutoDeleteMillis: PtrTo((30 * 24 * time.Hour).Milliseconds()),
+ AllowUserCancelWorkspaceJobs: ptr.Ref(true),
+ AllowUserAutostart: ptr.Ref(true),
+ AllowUserAutostop: ptr.Ref(true),
+ FailureTTLMillis: ptr.Ref((1 * time.Hour).Milliseconds()),
+ TimeTilDormantMillis: ptr.Ref((7 * 24 * time.Hour).Milliseconds()),
+ TimeTilDormantAutoDeleteMillis: ptr.Ref((30 * 24 * time.Hour).Milliseconds()),
DisableEveryoneGroupAccess: true,
RequireActiveVersion: true,
})
@@ -93,9 +94,9 @@ func TestAccTemplateDataSource(t *testing.T) {
UpdateWorkspaceLastUsedAt: false,
UpdateWorkspaceDormantAt: false,
RequireActiveVersion: tpl.RequireActiveVersion,
- DeprecationMessage: PtrTo("This template is deprecated"),
+ DeprecationMessage: ptr.Ref("This template is deprecated"),
DisableEveryoneGroupAccess: true,
- MaxPortShareLevel: PtrTo(codersdk.WorkspaceAgentPortShareLevelOwner),
+ MaxPortShareLevel: ptr.Ref(codersdk.WorkspaceAgentPortShareLevelOwner),
})
require.NoError(t, err)
@@ -153,8 +154,8 @@ func TestAccTemplateDataSource(t *testing.T) {
cfg := testAccTemplateDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- OrganizationID: PtrTo(orgID.String()),
- Name: PtrTo(tpl.Name),
+ OrganizationID: ptr.Ref(orgID.String()),
+ Name: ptr.Ref(tpl.Name),
}
resource.Test(t, resource.TestCase{
IsUnitTest: true,
@@ -173,7 +174,7 @@ func TestAccTemplateDataSource(t *testing.T) {
cfg := testAccTemplateDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- ID: PtrTo(tpl.ID.String()),
+ ID: ptr.Ref(tpl.ID.String()),
}
resource.Test(t, resource.TestCase{
IsUnitTest: true,
@@ -210,7 +211,7 @@ func TestAccTemplateDataSource(t *testing.T) {
cfg := testAccTemplateDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo(tpl.Name),
+ Name: ptr.Ref(tpl.Name),
}
resource.Test(t, resource.TestCase{
IsUnitTest: true,
diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go
index f1d7adc..b95d036 100644
--- a/internal/provider/template_resource.go
+++ b/internal/provider/template_resource.go
@@ -10,8 +10,10 @@ import (
"strings"
"cdr.dev/slog"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/provisionersdk"
+ "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
@@ -257,8 +259,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
MarkdownDescription: "The name of the template.",
Required: true,
Validators: []validator.String{
- stringvalidator.LengthBetween(1, 32),
- stringvalidator.RegexMatches(nameValidRegex, "Template names must be alphanumeric with hyphens."),
+ codersdkvalidator.Name(),
},
},
"display_name": schema.StringAttribute{
@@ -266,8 +267,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
Optional: true,
Computed: true,
Validators: []validator.String{
- stringvalidator.LengthBetween(1, 64),
- stringvalidator.RegexMatches(displayNameRegex, "Template display names must be alphanumeric with spaces."),
+ codersdkvalidator.DisplayName(),
},
},
"description": schema.StringAttribute{
@@ -286,7 +286,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
},
},
"icon": schema.StringAttribute{
- MarkdownDescription: "Relative path or external URL that specifes an icon to be displayed in the dashboard.",
+ MarkdownDescription: "Relative path or external URL that specifies an icon to be displayed in the dashboard.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
@@ -404,7 +404,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
Required: true,
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
- NewActiveVersionValidator(),
+ NewVersionsValidator(),
},
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
@@ -417,8 +417,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques
Optional: true,
Computed: true,
Validators: []validator.String{
- stringvalidator.LengthBetween(1, 64),
- stringvalidator.RegexMatches(templateVersionNameRegex, "Template version names must be alphanumeric with underscores and dots."),
+ codersdkvalidator.TemplateVersionName(),
},
},
"message": schema.StringAttribute{
@@ -574,6 +573,8 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques
// deployment running `v2.15.0` or later.
if data.MaxPortShareLevel.IsUnknown() {
data.MaxPortShareLevel = types.StringValue(string(templateResp.MaxPortShareLevel))
+ } else if data.MaxPortShareLevel.ValueString() == string(templateResp.MaxPortShareLevel) {
+ tflog.Info(ctx, "max port share level set to default, not updating")
} else {
mpslReq := data.toUpdateRequest(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
@@ -868,24 +869,24 @@ func (r *TemplateResource) ConfigValidators(context.Context) []resource.ConfigVa
return []resource.ConfigValidator{}
}
-type activeVersionValidator struct{}
+type versionsValidator struct{}
-func NewActiveVersionValidator() validator.List {
- return &activeVersionValidator{}
+func NewVersionsValidator() validator.List {
+ return &versionsValidator{}
}
// Description implements validator.List.
-func (a *activeVersionValidator) Description(ctx context.Context) string {
+func (a *versionsValidator) Description(ctx context.Context) string {
return a.MarkdownDescription(ctx)
}
// MarkdownDescription implements validator.List.
-func (a *activeVersionValidator) MarkdownDescription(context.Context) string {
- return "Validate that exactly one template version has active set to true."
+func (a *versionsValidator) MarkdownDescription(context.Context) string {
+ return "Validate that template version names are unique and that at most one version is active."
}
// ValidateList implements validator.List.
-func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
+func (a *versionsValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
@@ -909,13 +910,13 @@ func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator
uniqueNames[version.Name.ValueString()] = struct{}{}
}
- // Check if only one item in Version has active set to true
+ // Ensure at most one version is active
active := false
for _, version := range data {
- // `active` is required, so if it's null or unknown, this is Terraform
+ // `active` defaults to false, so if it's null or unknown, this is Terraform
// requesting an early validation.
if version.Active.IsNull() || version.Active.IsUnknown() {
- return
+ continue
}
if version.Active.ValueBool() {
if active {
@@ -925,12 +926,9 @@ func (a *activeVersionValidator) ValidateList(ctx context.Context, req validator
active = true
}
}
- if !active {
- resp.Diagnostics.AddError("Client Error", "At least one template version must be active.")
- }
}
-var _ validator.List = &activeVersionValidator{}
+var _ validator.List = &versionsValidator{}
type versionsPlanModifier struct{}
@@ -957,6 +955,12 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif
return
}
+ hasActiveVersion, diag := hasOneActiveVersion(configVersions)
+ if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+
for i := range planVersions {
hash, err := computeDirectoryHash(planVersions[i].Directory.ValueString())
if err != nil {
@@ -975,6 +979,13 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif
// If this is the first read, init the private state value
if lvBytes == nil {
lv = make(LastVersionsByHash)
+ // If there's no prior private state, this might be resource creation,
+ // in which case one version must be active.
+ if !hasActiveVersion {
+ resp.Diagnostics.AddError("Client Error", "At least one template version must be active when creating a"+
+ " `coderd_template` resource.\n(Subsequent resource updates can be made without an active template in the list).")
+ return
+ }
} else {
err := json.Unmarshal(lvBytes, &lv)
if err != nil {
@@ -983,9 +994,34 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif
}
}
- planVersions.reconcileVersionIDs(lv, configVersions)
+ diag = planVersions.reconcileVersionIDs(lv, configVersions, hasActiveVersion)
+ if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
- resp.PlanValue, resp.Diagnostics = types.ListValueFrom(ctx, req.PlanValue.ElementType(ctx), planVersions)
+ resp.PlanValue, diag = types.ListValueFrom(ctx, req.PlanValue.ElementType(ctx), planVersions)
+ if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ }
+}
+
+func hasOneActiveVersion(data Versions) (hasActiveVersion bool, diags diag.Diagnostics) {
+ active := false
+ for _, version := range data {
+ if version.Active.IsNull() || version.Active.IsUnknown() {
+ // If null or unknown, the value will be defaulted to false
+ continue
+ }
+ if version.Active.ValueBool() {
+ if active {
+ diags.AddError("Client Error", "Only one template version can be active at a time.")
+ return
+ }
+ active = true
+ }
+ }
+ return active, diags
}
func NewVersionsPlanModifier() planmodifier.List {
@@ -1253,7 +1289,7 @@ func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, diag *diag.
TimeTilDormantAutoDeleteMillis: r.TimeTilDormantAutoDeleteMillis.ValueInt64(),
RequireActiveVersion: r.RequireActiveVersion.ValueBool(),
DeprecationMessage: r.DeprecationMessage.ValueStringPointer(),
- MaxPortShareLevel: PtrTo(codersdk.WorkspaceAgentPortShareLevel(r.MaxPortShareLevel.ValueString())),
+ MaxPortShareLevel: ptr.Ref(codersdk.WorkspaceAgentPortShareLevel(r.MaxPortShareLevel.ValueString())),
// If we're managing ACL, we want to delete the everyone group
DisableEveryoneGroupAccess: !r.ACL.IsNull(),
}
@@ -1310,6 +1346,7 @@ type PreviousTemplateVersion struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
TFVars map[string]string `json:"tf_vars"`
+ Active bool `json:"active"`
}
type privateState interface {
@@ -1332,6 +1369,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d
ID: version.ID.ValueUUID(),
Name: version.Name.ValueString(),
TFVars: tfVars,
+ Active: version.Active.ValueBool(),
})
} else {
lv[version.DirectoryHash.ValueString()] = []PreviousTemplateVersion{
@@ -1339,6 +1377,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d
ID: version.ID.ValueUUID(),
Name: version.Name.ValueString(),
TFVars: tfVars,
+ Active: version.Active.ValueBool(),
},
}
}
@@ -1351,7 +1390,7 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d
return ps.SetKey(ctx, LastVersionsKey, lvBytes)
}
-func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVersions Versions) {
+func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVersions Versions, hasOneActiveVersion bool) (diag diag.Diagnostics) {
// We remove versions that we've matched from `lv`, so make a copy for
// resolving tfvar changes at the end.
fullLv := make(LastVersionsByHash)
@@ -1421,6 +1460,39 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe
}
}
}
+
+ // If a version was deactivated, and no active version was set, we need to
+ // return an error to avoid a post-apply plan being non-empty.
+ if !hasOneActiveVersion {
+ for i := range planVersions {
+ if !planVersions[i].ID.IsUnknown() {
+ prevs, ok := fullLv[planVersions[i].DirectoryHash.ValueString()]
+ if !ok {
+ continue
+ }
+ if versionDeactivated(prevs, &planVersions[i]) {
+ diag.AddError("Client Error", "Plan could not determine which version should be active.\n"+
+ "Either specify an active version or modify the contents of the previously active version before marking it as inactive.")
+ return diag
+ }
+ }
+ }
+ }
+ return diag
+}
+
+func versionDeactivated(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool {
+ for _, prev := range prevs {
+ if prev.ID == planned.ID.ValueUUID() {
+ if prev.Active &&
+ !planned.Active.IsNull() &&
+ !planned.Active.IsUnknown() &&
+ !planned.Active.ValueBool() {
+ return true
+ }
+ }
+ }
+ return false
}
func tfVariablesChanged(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool {
diff --git a/internal/provider/template_resource_test.go b/internal/provider/template_resource_test.go
index acb07f2..f56ff3b 100644
--- a/internal/provider/template_resource_test.go
+++ b/internal/provider/template_resource_test.go
@@ -17,15 +17,17 @@ import (
cp "github.com/otiai10/copy"
"github.com/stretchr/testify/require"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/terraform-provider-coderd/integration"
)
func TestAccTemplateResource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "template_acc", false)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -42,12 +44,12 @@ func TestAccTemplateResource(t *testing.T) {
cfg1 := testAccTemplateResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example-template"),
+ Name: ptr.Ref("example-template"),
Versions: []testAccTemplateVersionConfig{
{
// Auto-generated version name
Directory: &exTemplateOne,
- Active: PtrTo(true),
+ Active: ptr.Ref(true),
},
},
ACL: testAccTemplateACLConfig{
@@ -57,28 +59,28 @@ func TestAccTemplateResource(t *testing.T) {
cfg2 := cfg1
cfg2.Versions = slices.Clone(cfg2.Versions)
- cfg2.Name = PtrTo("example-template-new")
+ cfg2.Name = ptr.Ref("example-template-new")
cfg2.Versions[0].Directory = &exTemplateTwo
- cfg2.Versions[0].Name = PtrTo("new")
+ cfg2.Versions[0].Name = ptr.Ref("new")
cfg3 := cfg2
cfg3.Versions = slices.Clone(cfg3.Versions)
cfg3.Versions = append(cfg3.Versions, testAccTemplateVersionConfig{
- Name: PtrTo("legacy-template"),
+ Name: ptr.Ref("legacy-template"),
Directory: &exTemplateOne,
- Active: PtrTo(false),
+ Active: ptr.Ref(false),
TerraformVariables: []testAccTemplateKeyValueConfig{
{
- Key: PtrTo("name"),
- Value: PtrTo("world"),
+ Key: ptr.Ref("name"),
+ Value: ptr.Ref("world"),
},
},
})
cfg4 := cfg3
cfg4.Versions = slices.Clone(cfg4.Versions)
- cfg4.Versions[0].Active = PtrTo(false)
- cfg4.Versions[1].Active = PtrTo(true)
+ cfg4.Versions[0].Active = ptr.Ref(false)
+ cfg4.Versions[1].Active = ptr.Ref(true)
cfg5 := cfg4
cfg5.Versions = slices.Clone(cfg5.Versions)
@@ -240,28 +242,29 @@ func TestAccTemplateResource(t *testing.T) {
cfg1 := testAccTemplateResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example-template2"),
+ Name: ptr.Ref("example-template2"),
Versions: []testAccTemplateVersionConfig{
{
// Auto-generated version name
- Directory: PtrTo("../../integration/template-test/example-template-2/"),
+ Directory: &exTemplateTwo,
TerraformVariables: []testAccTemplateKeyValueConfig{
{
- Key: PtrTo("name"),
- Value: PtrTo("world"),
+ Key: ptr.Ref("name"),
+ Value: ptr.Ref("world"),
},
},
- Active: PtrTo(true),
+ Active: ptr.Ref(true),
},
{
// Auto-generated version name
- Directory: PtrTo("../../integration/template-test/example-template-2/"),
+ Directory: &exTemplateTwo,
TerraformVariables: []testAccTemplateKeyValueConfig{
{
- Key: PtrTo("name"),
- Value: PtrTo("world"),
+ Key: ptr.Ref("name"),
+ Value: ptr.Ref("world"),
},
},
+ Active: ptr.Ref(false),
},
},
ACL: testAccTemplateACLConfig{
@@ -271,28 +274,28 @@ func TestAccTemplateResource(t *testing.T) {
cfg2 := cfg1
cfg2.Versions = slices.Clone(cfg2.Versions)
- cfg2.Versions[1].Name = PtrTo("new-name")
+ cfg2.Versions[1].Name = ptr.Ref("new-name")
cfg3 := cfg2
cfg3.Versions = slices.Clone(cfg3.Versions)
- cfg3.Versions[0].Name = PtrTo("new-name-one")
- cfg3.Versions[1].Name = PtrTo("new-name-two")
+ cfg3.Versions[0].Name = ptr.Ref("new-name-one")
+ cfg3.Versions[1].Name = ptr.Ref("new-name-two")
cfg3.Versions[0], cfg3.Versions[1] = cfg3.Versions[1], cfg3.Versions[0]
cfg4 := cfg1
cfg4.Versions = slices.Clone(cfg4.Versions)
- cfg4.Versions[0].Directory = PtrTo("../../integration/template-test/example-template/")
+ cfg4.Versions[0].Directory = &exTemplateOne
cfg5 := cfg4
cfg5.Versions = slices.Clone(cfg5.Versions)
- cfg5.Versions[1].Directory = PtrTo("../../integration/template-test/example-template/")
+ cfg5.Versions[1].Directory = &exTemplateOne
cfg6 := cfg5
cfg6.Versions = slices.Clone(cfg6.Versions)
cfg6.Versions[0].TerraformVariables = []testAccTemplateKeyValueConfig{
{
- Key: PtrTo("name"),
- Value: PtrTo("world2"),
+ Key: ptr.Ref("name"),
+ Value: ptr.Ref("world2"),
},
}
@@ -368,18 +371,18 @@ func TestAccTemplateResource(t *testing.T) {
cfg1 := testAccTemplateResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example-template3"),
+ Name: ptr.Ref("example-template3"),
Versions: []testAccTemplateVersionConfig{
{
// Auto-generated version name
- Directory: PtrTo("../../integration/template-test/example-template-2/"),
+ Directory: &exTemplateTwo,
TerraformVariables: []testAccTemplateKeyValueConfig{
{
- Key: PtrTo("name"),
- Value: PtrTo("world"),
+ Key: ptr.Ref("name"),
+ Value: ptr.Ref("world"),
},
},
- Active: PtrTo(true),
+ Active: ptr.Ref(true),
},
},
ACL: testAccTemplateACLConfig{
@@ -391,8 +394,8 @@ func TestAccTemplateResource(t *testing.T) {
cfg2.Versions = slices.Clone(cfg2.Versions)
cfg2.Versions[0].TerraformVariables = []testAccTemplateKeyValueConfig{
{
- Key: PtrTo("name"),
- Value: PtrTo("world2"),
+ Key: ptr.Ref("name"),
+ Value: ptr.Ref("world2"),
},
}
@@ -416,14 +419,231 @@ func TestAccTemplateResource(t *testing.T) {
},
})
})
+
+ t.Run("CreateWithNoActiveVersionErrors", func(t *testing.T) {
+ cfg1 := testAccTemplateResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-template"),
+ Versions: []testAccTemplateVersionConfig{
+ {
+ // Auto-generated version name
+ Directory: &exTemplateOne,
+ Active: ptr.Ref(false),
+ },
+ },
+ ACL: testAccTemplateACLConfig{
+ null: true,
+ },
+ }
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ IsUnitTest: true,
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg1.String(t),
+ ExpectError: regexp.MustCompile("At least one template version must be active when creating"),
+ },
+ },
+ })
+ })
+
+ t.Run("AmbiguousActiveVersionResolvedByModifying", func(t *testing.T) {
+ cfg1 := testAccTemplateResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-template"),
+ Versions: []testAccTemplateVersionConfig{
+ {
+ // Auto-generated version name
+ Directory: &exTemplateOne,
+ Active: ptr.Ref(true),
+ },
+ },
+ ACL: testAccTemplateACLConfig{
+ null: true,
+ },
+ }
+
+ cfg2 := cfg1
+ cfg2.Versions = slices.Clone(cfg2.Versions)
+ cfg2.Versions[0].Active = ptr.Ref(false)
+
+ cfg3 := cfg2
+ cfg3.Versions = slices.Clone(cfg3.Versions)
+ cfg3.Versions[0].Directory = &exTemplateTwo
+
+ cfg2b := cfg1
+ cfg2b.Versions = slices.Clone(cfg2b.Versions)
+ cfg2b.Versions = append(cfg2b.Versions, testAccTemplateVersionConfig{
+ Directory: &exTemplateTwo,
+ Active: ptr.Ref(false),
+ })
+
+ cfg3b := cfg2b
+ cfg3b.Versions = slices.Clone(cfg3b.Versions)
+ cfg3b.Versions[1].Active = ptr.Ref(true)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ IsUnitTest: true,
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg1.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccCheckNumTemplateVersions(ctx, client, 1),
+ ),
+ },
+ // With an unmodified version deactivated, it's not clear what
+ // the active version should be.
+ {
+ Config: cfg2.String(t),
+ ExpectError: regexp.MustCompile("Plan could not determine which version should be active."),
+ },
+ // If we modify the version, a new version will be created on `coderd`,
+ // and the old version can remain active.
+ {
+ Config: cfg3.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccCheckNumTemplateVersions(ctx, client, 2),
+ resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{
+ "active": regexp.MustCompile("false"),
+ }),
+ ),
+ },
+ },
+ })
+ })
+
+ t.Run("AmbiguousActiveVersionResolvedByCreatingNewVersion", func(t *testing.T) {
+ cfg1 := testAccTemplateResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-template"),
+ Versions: []testAccTemplateVersionConfig{
+ {
+ // Auto-generated version name
+ Directory: &exTemplateOne,
+ Active: ptr.Ref(true),
+ },
+ },
+ ACL: testAccTemplateACLConfig{
+ null: true,
+ },
+ }
+
+ cfg2 := cfg1
+ cfg2.Versions = slices.Clone(cfg2.Versions)
+ cfg2.Versions[0].Active = ptr.Ref(false)
+ cfg2.Versions = append(cfg2.Versions, testAccTemplateVersionConfig{
+ Directory: &exTemplateTwo,
+ Active: ptr.Ref(false),
+ })
+
+ cfg3 := cfg2
+ cfg3.Versions = slices.Clone(cfg3.Versions)
+ cfg3.Versions[1].Active = ptr.Ref(true)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ IsUnitTest: true,
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg1.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccCheckNumTemplateVersions(ctx, client, 1),
+ ),
+ },
+ // Adding a new version that's not active doesn't help
+ {
+ Config: cfg2.String(t),
+ ExpectError: regexp.MustCompile("Plan could not determine which version should be active."),
+ },
+ // Making that new version active will fix the issue
+ {
+ Config: cfg3.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccCheckNumTemplateVersions(ctx, client, 2),
+ ),
+ },
+ },
+ })
+ })
+
+ t.Run("PushNewInactiveVersion", func(t *testing.T) {
+ cfg1 := testAccTemplateResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-template"),
+ Versions: []testAccTemplateVersionConfig{
+ {
+ // Auto-generated version name
+ Directory: &exTemplateOne,
+ Active: ptr.Ref(true),
+ },
+ },
+ ACL: testAccTemplateACLConfig{
+ null: true,
+ },
+ }
+
+ cfg2 := cfg1
+ cfg2.Versions = slices.Clone(cfg2.Versions)
+ cfg2.Versions[0].Active = ptr.Ref(false)
+ cfg2.Versions[0].Directory = &exTemplateTwo
+
+ cfg3 := cfg2
+ cfg3.Versions = slices.Clone(cfg3.Versions)
+ cfg3.Versions[0].Active = ptr.Ref(true)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ IsUnitTest: true,
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create one active version
+ {
+ Config: cfg1.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccCheckNumTemplateVersions(ctx, client, 1),
+ ),
+ },
+ // Modify an existing version, make it inactive
+ {
+ Config: cfg2.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccCheckNumTemplateVersions(ctx, client, 2),
+ resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{
+ "active": regexp.MustCompile("false"),
+ }),
+ ),
+ },
+ // Make that modification active
+ {
+ Config: cfg3.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccCheckNumTemplateVersions(ctx, client, 2),
+ resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{
+ "active": regexp.MustCompile("true"),
+ }),
+ ),
+ },
+ },
+ })
+ })
}
func TestAccTemplateResourceEnterprise(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
- client := integration.StartCoder(ctx, t, "template_acc", true)
+ ctx := t.Context()
+ client := integration.StartCoder(ctx, t, "template_resource_acc", true)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -433,185 +653,225 @@ func TestAccTemplateResourceEnterprise(t *testing.T) {
})
require.NoError(t, err)
- cfg1 := testAccTemplateResourceConfig{
- URL: client.URL.String(),
- Token: client.SessionToken(),
- Name: PtrTo("example-template"),
- Versions: []testAccTemplateVersionConfig{
- {
- // Auto-generated version name
- Directory: PtrTo("../../integration/template-test/example-template"),
- Active: PtrTo(true),
- },
- },
- ACL: testAccTemplateACLConfig{
- GroupACL: []testAccTemplateKeyValueConfig{
- {
- Key: PtrTo(firstUser.OrganizationIDs[0].String()),
- Value: PtrTo("use"),
- },
+ exTemplateOne := t.TempDir()
+ err = cp.Copy("../../integration/template-test/example-template", exTemplateOne)
+ require.NoError(t, err)
+
+ t.Run("BasicUsage", func(t *testing.T) {
+ cfg1 := testAccTemplateResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-template"),
+ Versions: []testAccTemplateVersionConfig{
{
- Key: PtrTo(group.ID.String()),
- Value: PtrTo("admin"),
+ // Auto-generated version name
+ Directory: &exTemplateOne,
+ Active: ptr.Ref(true),
},
},
- UserACL: []testAccTemplateKeyValueConfig{
- {
- Key: PtrTo(firstUser.ID.String()),
- Value: PtrTo("admin"),
+ ACL: testAccTemplateACLConfig{
+ GroupACL: []testAccTemplateKeyValueConfig{
+ {
+ Key: ptr.Ref(firstUser.OrganizationIDs[0].String()),
+ Value: ptr.Ref("use"),
+ },
+ {
+ Key: ptr.Ref(group.ID.String()),
+ Value: ptr.Ref("admin"),
+ },
+ },
+ UserACL: []testAccTemplateKeyValueConfig{
+ {
+ Key: ptr.Ref(firstUser.ID.String()),
+ Value: ptr.Ref("admin"),
+ },
},
},
- },
- }
+ }
- cfg2 := cfg1
- cfg2.ACL.GroupACL = slices.Clone(cfg2.ACL.GroupACL[1:])
- cfg2.MaxPortShareLevel = PtrTo("owner")
+ cfg2 := cfg1
+ cfg2.ACL.GroupACL = slices.Clone(cfg2.ACL.GroupACL[1:])
+ cfg2.MaxPortShareLevel = ptr.Ref("owner")
- cfg3 := cfg2
- cfg3.ACL.null = true
- cfg3.MaxPortShareLevel = PtrTo("public")
+ cfg3 := cfg2
+ cfg3.ACL.null = true
+ cfg3.MaxPortShareLevel = ptr.Ref("public")
- cfg4 := cfg3
- cfg4.AllowUserAutostart = PtrTo(false)
- cfg4.AutostopRequirement = testAccAutostopRequirementConfig{
- DaysOfWeek: PtrTo([]string{"monday", "tuesday"}),
- Weeks: PtrTo(int64(2)),
- }
+ cfg4 := cfg3
+ cfg4.AllowUserAutostart = ptr.Ref(false)
+ cfg4.AutostopRequirement = testAccAutostopRequirementConfig{
+ DaysOfWeek: ptr.Ref([]string{"monday", "tuesday"}),
+ Weeks: ptr.Ref(int64(2)),
+ }
- resource.Test(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- IsUnitTest: true,
- ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
- Steps: []resource.TestStep{
- {
- Config: cfg1.String(t),
- Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "owner"),
- resource.TestCheckResourceAttr("coderd_template.test", "acl.groups.#", "2"),
- resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.groups.*", map[string]*regexp.Regexp{
- "id": regexp.MustCompile(firstUser.OrganizationIDs[0].String()),
- "role": regexp.MustCompile("^use$"),
- }),
- resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.groups.*", map[string]*regexp.Regexp{
- "id": regexp.MustCompile(group.ID.String()),
- "role": regexp.MustCompile("^admin$"),
- }),
- resource.TestCheckResourceAttr("coderd_template.test", "acl.users.#", "1"),
- resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.users.*", map[string]*regexp.Regexp{
- "id": regexp.MustCompile(firstUser.ID.String()),
- "role": regexp.MustCompile("^admin$"),
- }),
- ),
- },
- {
- Config: cfg2.String(t),
- Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "owner"),
- resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.users.*", map[string]*regexp.Regexp{
- "id": regexp.MustCompile(firstUser.ID.String()),
- "role": regexp.MustCompile("^admin$"),
- }),
- ),
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ IsUnitTest: true,
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg1.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "owner"),
+ resource.TestCheckResourceAttr("coderd_template.test", "acl.groups.#", "2"),
+ resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.groups.*", map[string]*regexp.Regexp{
+ "id": regexp.MustCompile(firstUser.OrganizationIDs[0].String()),
+ "role": regexp.MustCompile("^use$"),
+ }),
+ resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.groups.*", map[string]*regexp.Regexp{
+ "id": regexp.MustCompile(group.ID.String()),
+ "role": regexp.MustCompile("^admin$"),
+ }),
+ resource.TestCheckResourceAttr("coderd_template.test", "acl.users.#", "1"),
+ resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.users.*", map[string]*regexp.Regexp{
+ "id": regexp.MustCompile(firstUser.ID.String()),
+ "role": regexp.MustCompile("^admin$"),
+ }),
+ ),
+ },
+ {
+ Config: cfg2.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "owner"),
+ resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "acl.users.*", map[string]*regexp.Regexp{
+ "id": regexp.MustCompile(firstUser.ID.String()),
+ "role": regexp.MustCompile("^admin$"),
+ }),
+ ),
+ },
+ {
+ Config: cfg3.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "public"),
+ resource.TestCheckNoResourceAttr("coderd_template.test", "acl"),
+ func(s *terraform.State) error {
+ templates, err := client.Templates(ctx, codersdk.TemplateFilter{})
+ if err != nil {
+ return err
+ }
+ if len(templates) != 1 {
+ return fmt.Errorf("expected 1 template, got %d", len(templates))
+ }
+ acl, err := client.TemplateACL(ctx, templates[0].ID)
+ if err != nil {
+ return err
+ }
+ if len(acl.Groups) != 1 {
+ return fmt.Errorf("expected 1 group ACL, got %d", len(acl.Groups))
+ }
+ if acl.Groups[0].Role != "admin" && acl.Groups[0].ID != group.ID {
+ return fmt.Errorf("expected group ACL to be 'use' for %s, got %s", firstUser.OrganizationIDs[0].String(), acl.Groups[0].Role)
+ }
+ if len(acl.Users) != 1 {
+ return fmt.Errorf("expected 1 user ACL, got %d", len(acl.Users))
+ }
+ if acl.Users[0].Role != "admin" && acl.Users[0].ID != firstUser.ID {
+ return fmt.Errorf("expected user ACL to be 'admin' for %s, got %s", firstUser.ID.String(), acl.Users[0].Role)
+ }
+ return nil
+ },
+ ),
+ },
+ {
+ Config: cfg4.String(t),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("coderd_template.test", "allow_user_auto_start", "false"),
+ resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.days_of_week.#", "2"),
+ resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.weeks", "2"),
+ ),
+ },
},
- {
- Config: cfg3.String(t),
- Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "public"),
- resource.TestCheckNoResourceAttr("coderd_template.test", "acl"),
- func(s *terraform.State) error {
- templates, err := client.Templates(ctx, codersdk.TemplateFilter{})
- if err != nil {
- return err
- }
- if len(templates) != 1 {
- return fmt.Errorf("expected 1 template, got %d", len(templates))
- }
- acl, err := client.TemplateACL(ctx, templates[0].ID)
- if err != nil {
- return err
- }
- if len(acl.Groups) != 1 {
- return fmt.Errorf("expected 1 group ACL, got %d", len(acl.Groups))
- }
- if acl.Groups[0].Role != "admin" && acl.Groups[0].ID != group.ID {
- return fmt.Errorf("expected group ACL to be 'use' for %s, got %s", firstUser.OrganizationIDs[0].String(), acl.Groups[0].Role)
- }
- if len(acl.Users) != 1 {
- return fmt.Errorf("expected 1 user ACL, got %d", len(acl.Users))
- }
- if acl.Users[0].Role != "admin" && acl.Users[0].ID != firstUser.ID {
- return fmt.Errorf("expected user ACL to be 'admin' for %s, got %s", firstUser.ID.String(), acl.Users[0].Role)
- }
- return nil
- },
- ),
+ })
+ })
+
+ // Verifies that when `max_port_share_level` is set to to the default value,
+ // an update request that would return HTTP Not Modified is not sent.
+ t.Run("DefaultMaxPortShareLevel", func(t *testing.T) {
+ cfg1 := testAccTemplateResourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ Name: ptr.Ref("example-template"),
+ Versions: []testAccTemplateVersionConfig{
+ {
+ Directory: &exTemplateOne,
+ Active: ptr.Ref(true),
+ },
},
- {
- Config: cfg4.String(t),
- Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestCheckResourceAttr("coderd_template.test", "allow_user_auto_start", "false"),
- resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.days_of_week.#", "2"),
- resource.TestCheckResourceAttr("coderd_template.test", "auto_stop_requirement.weeks", "2"),
- ),
+ MaxPortShareLevel: ptr.Ref("owner"),
+ }
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ IsUnitTest: true,
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg1.String(t),
+ Check: resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "owner"),
+ },
},
- },
+ })
})
}
func TestAccTemplateResourceAGPL(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
- client := integration.StartCoder(ctx, t, "template_acc", false)
+ ctx := t.Context()
+ client := integration.StartCoder(ctx, t, "template_resource_agpl_acc", false)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
+ exTemplateOne := t.TempDir()
+ err = cp.Copy("../../integration/template-test/example-template", exTemplateOne)
+ require.NoError(t, err)
+
cfg1 := testAccTemplateResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example-template"),
+ Name: ptr.Ref("example-template"),
Versions: []testAccTemplateVersionConfig{
{
// Auto-generated version name
- Directory: PtrTo("../../integration/template-test/example-template/"),
- Active: PtrTo(true),
+ Directory: &exTemplateOne,
+ Active: ptr.Ref(true),
},
},
- AllowUserAutostart: PtrTo(false),
+ AllowUserAutostart: ptr.Ref(false),
}
cfg2 := cfg1
cfg2.AllowUserAutostart = nil
- cfg2.AutostopRequirement.DaysOfWeek = PtrTo([]string{"monday", "tuesday"})
+ cfg2.AutostopRequirement.DaysOfWeek = ptr.Ref([]string{"monday", "tuesday"})
cfg3 := cfg2
cfg3.AutostopRequirement.null = true
- cfg3.AutostartRequirement = PtrTo([]string{})
+ cfg3.AutostartRequirement = ptr.Ref([]string{})
cfg4 := cfg3
- cfg4.FailureTTL = PtrTo(int64(1))
+ cfg4.FailureTTL = ptr.Ref(int64(1))
cfg5 := cfg4
cfg5.FailureTTL = nil
cfg5.AutostartRequirement = nil
- cfg5.RequireActiveVersion = PtrTo(true)
+ cfg5.RequireActiveVersion = ptr.Ref(true)
cfg6 := cfg5
cfg6.RequireActiveVersion = nil
cfg6.ACL = testAccTemplateACLConfig{
GroupACL: []testAccTemplateKeyValueConfig{
{
- Key: PtrTo(firstUser.OrganizationIDs[0].String()),
- Value: PtrTo("use"),
+ Key: ptr.Ref(firstUser.OrganizationIDs[0].String()),
+ Value: ptr.Ref("use"),
},
},
}
cfg7 := cfg6
cfg7.ACL.null = true
- cfg7.MaxPortShareLevel = PtrTo("owner")
+ cfg7.MaxPortShareLevel = ptr.Ref("owner")
for _, cfg := range []testAccTemplateResourceConfig{cfg1, cfg2, cfg3, cfg4} {
resource.Test(t, resource.TestCase{
@@ -649,10 +909,11 @@ func TestAccTemplateResourceAGPL(t *testing.T) {
}
func TestAccTemplateResourceVariables(t *testing.T) {
+ t.Parallel()
cfg := `
provider coderd {
- url = "%s"
- token = "%s"
+ url = %q
+ token = %q
}
data "coderd_organization" "default" {
@@ -676,21 +937,25 @@ resource "coderd_template" "sample" {
versions = [
{
name = "${var.PRIOR_GIT_COMMIT_SHA}"
- directory = "../../integration/template-test/example-template"
+ directory = %q
active = var.ACTIVE
},
{
name = "${var.CURRENT_GIT_COMMIT_SHA}"
- directory = "../../integration/template-test/example-template"
+ directory = %q
active = false
}
]
}`
- ctx := context.Background()
- client := integration.StartCoder(ctx, t, "template_acc", false)
+ ctx := t.Context()
+ client := integration.StartCoder(ctx, t, "template_resource_variables_acc", false)
+
+ exTemplateOne := t.TempDir()
+ err := cp.Copy("../../integration/template-test/example-template", exTemplateOne)
+ require.NoError(t, err)
- cfg = fmt.Sprintf(cfg, client.URL.String(), client.SessionToken())
+ cfg = fmt.Sprintf(cfg, client.URL.String(), client.SessionToken(), exTemplateOne, exTemplateOne)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@@ -907,14 +1172,17 @@ func testAccCheckNumTemplateVersions(ctx context.Context, client *codersdk.Clien
}
func TestReconcileVersionIDs(t *testing.T) {
+ t.Parallel()
aUUID := uuid.New()
bUUID := uuid.New()
cases := []struct {
- Name string
- planVersions Versions
- configVersions Versions
- inputState LastVersionsByHash
- expectedVersions Versions
+ Name string
+ planVersions Versions
+ configVersions Versions
+ inputState LastVersionsByHash
+ expectedVersions Versions
+ cfgHasActiveVersion bool
+ expectError bool
}{
{
Name: "IdenticalDontRename",
@@ -1241,13 +1509,48 @@ func TestReconcileVersionIDs(t *testing.T) {
},
},
},
+ {
+ Name: "NoPossibleActiveVersion",
+ planVersions: []TemplateVersion{
+ {
+ Name: types.StringValue("foo"),
+ DirectoryHash: types.StringValue("aaa"),
+ ID: NewUUIDUnknown(),
+ TerraformVariables: []Variable{},
+ Active: types.BoolValue(false),
+ },
+ },
+ configVersions: []TemplateVersion{
+ {
+ Name: types.StringValue("foo"),
+ },
+ },
+ inputState: map[string][]PreviousTemplateVersion{
+ "aaa": {
+ {
+ ID: aUUID,
+ Name: "foo",
+ TFVars: map[string]string{},
+ Active: true,
+ },
+ },
+ },
+ cfgHasActiveVersion: false,
+ expectError: true,
+ },
}
for _, c := range cases {
c := c
t.Run(c.Name, func(t *testing.T) {
- c.planVersions.reconcileVersionIDs(c.inputState, c.configVersions)
- require.Equal(t, c.expectedVersions, c.planVersions)
+ t.Parallel()
+
+ diag := c.planVersions.reconcileVersionIDs(c.inputState, c.configVersions, c.cfgHasActiveVersion)
+ if c.expectError {
+ require.True(t, diag.HasError())
+ } else {
+ require.Equal(t, c.expectedVersions, c.planVersions)
+ }
})
}
diff --git a/internal/provider/user_data_source.go b/internal/provider/user_data_source.go
index be367ea..d221a1e 100644
--- a/internal/provider/user_data_source.go
+++ b/internal/provider/user_data_source.go
@@ -41,7 +41,6 @@ type UserDataSourceModel struct {
OrganizationIDs types.Set `tfsdk:"organization_ids"`
CreatedAt types.Int64 `tfsdk:"created_at"` // Unix timestamp
LastSeenAt types.Int64 `tfsdk:"last_seen_at"`
- ThemePreference types.String `tfsdk:"theme_preference"`
}
func (d *UserDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
@@ -101,10 +100,6 @@ func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaReques
MarkdownDescription: "Unix timestamp of when the user was last seen.",
Computed: true,
},
- "theme_preference": schema.StringAttribute{
- MarkdownDescription: "The user's preferred theme.",
- Computed: true,
- },
},
}
}
@@ -188,7 +183,6 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
data.OrganizationIDs = types.SetValueMust(UUIDType, orgIDs)
data.CreatedAt = types.Int64Value(user.CreatedAt.Unix())
data.LastSeenAt = types.Int64Value(user.LastSeenAt.Unix())
- data.ThemePreference = types.StringValue(user.ThemePreference)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
diff --git a/internal/provider/user_data_source_test.go b/internal/provider/user_data_source_test.go
index 2d69d13..2890381 100644
--- a/internal/provider/user_data_source_test.go
+++ b/internal/provider/user_data_source_test.go
@@ -1,13 +1,13 @@
package provider
import (
- "context"
"os"
"regexp"
"strings"
"testing"
"text/template"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/terraform-provider-coderd/integration"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -15,10 +15,11 @@ import (
)
func TestAccUserDataSource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "user_data_acc", false)
firstUser, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
@@ -53,9 +54,10 @@ func TestAccUserDataSource(t *testing.T) {
cfg := testAccUserDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Username: PtrTo(user.Username),
+ Username: ptr.Ref(user.Username),
}
resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
@@ -71,9 +73,10 @@ func TestAccUserDataSource(t *testing.T) {
cfg := testAccUserDataSourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- ID: PtrTo(user.ID.String()),
+ ID: ptr.Ref(user.ID.String()),
}
resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
// User by ID
@@ -91,6 +94,7 @@ func TestAccUserDataSource(t *testing.T) {
Token: client.SessionToken(),
}
resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
// Neither ID nor Username
@@ -103,6 +107,24 @@ func TestAccUserDataSource(t *testing.T) {
})
})
+ t.Run("InvalidUUIDError", func(t *testing.T) {
+ cfg := testAccUserDataSourceConfig{
+ URL: client.URL.String(),
+ Token: client.SessionToken(),
+ ID: ptr.Ref("invalid-uuid"),
+ }
+ resource.Test(t, resource.TestCase{
+ IsUnitTest: true,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: cfg.String(t),
+ ExpectError: regexp.MustCompile(`The provided value cannot be parsed as a UUID`),
+ },
+ },
+ })
+ })
}
type testAccUserDataSourceConfig struct {
diff --git a/internal/provider/user_resource.go b/internal/provider/user_resource.go
index a560df5..79d248d 100644
--- a/internal/provider/user_resource.go
+++ b/internal/provider/user_resource.go
@@ -22,6 +22,7 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/coder/coder/v2/codersdk"
+ "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator"
)
// Ensure provider defined types fully satisfy framework interfaces.
@@ -71,8 +72,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
MarkdownDescription: "Username of the user.",
Required: true,
Validators: []validator.String{
- stringvalidator.LengthBetween(1, 32),
- stringvalidator.RegexMatches(nameValidRegex, "Username must be alphanumeric with hyphens."),
+ codersdkvalidator.Name(),
},
},
"name": schema.StringAttribute{
@@ -80,7 +80,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
Computed: true,
Optional: true,
Validators: []validator.String{
- stringvalidator.LengthBetween(1, 128),
+ codersdkvalidator.UserRealName(),
},
},
"email": schema.StringAttribute{
@@ -248,14 +248,15 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp
client := r.data.Client
+ // Lookup by ID to handle imports
user, err := client.User(ctx, data.ID.ValueString())
if err != nil {
if isNotFound(err) {
- resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("User with ID %q not found. Marking as deleted.", data.ID.ValueString()))
+ resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("User with ID %q not found. Marking resource as deleted.", data.ID.ValueString()))
resp.State.RemoveResource(ctx)
return
}
- resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get current user, got error: %s", err))
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get current user by ID, got error: %s", err))
return
}
if len(user.OrganizationIDs) < 1 {
@@ -274,6 +275,30 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp
data.LoginType = types.StringValue(string(user.LoginType))
data.Suspended = types.BoolValue(user.Status == codersdk.UserStatusSuspended)
+ // The user-by-ID API returns deleted users if the authorized user has
+ // permission. It does not indicate whether the user is deleted or not.
+ // The user-by-username API will never return deleted users.
+ // So, we do another lookup by username.
+ userByName, err := client.User(ctx, data.Username.ValueString())
+ if err != nil {
+ if isNotFound(err) {
+ resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf(
+ "User with username %q not found. Marking resource as deleted.",
+ data.Username.ValueString()))
+ resp.State.RemoveResource(ctx)
+ return
+ }
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get current user by username, got error: %s", err))
+ return
+ }
+ if userByName.ID != data.ID.ValueUUID() {
+ resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf(
+ "The username %q has been reassigned to a new user not managed by this Terraform resource. Marking resource as deleted.",
+ user.Username))
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
diff --git a/internal/provider/user_resource_test.go b/internal/provider/user_resource_test.go
index a7bb470..8c78acc 100644
--- a/internal/provider/user_resource_test.go
+++ b/internal/provider/user_resource_test.go
@@ -1,43 +1,45 @@
package provider
import (
- "context"
"os"
"strings"
"testing"
"text/template"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/terraform-provider-coderd/integration"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stretchr/testify/require"
)
func TestAccUserResource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "user_acc", false)
cfg1 := testAccUserResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Username: PtrTo("example"),
- Name: PtrTo("Example User"),
- Email: PtrTo("example@coder.com"),
- Roles: PtrTo([]string{"owner", "auditor"}),
- LoginType: PtrTo("password"),
- Password: PtrTo("SomeSecurePassword!"),
+ Username: ptr.Ref("example"),
+ Name: ptr.Ref("Example User"),
+ Email: ptr.Ref("example@coder.com"),
+ Roles: ptr.Ref([]string{"owner", "auditor"}),
+ LoginType: ptr.Ref("password"),
+ Password: ptr.Ref("SomeSecurePassword!"),
}
cfg2 := cfg1
- cfg2.Username = PtrTo("exampleNew")
+ cfg2.Username = ptr.Ref("exampleNew")
cfg3 := cfg2
- cfg3.Name = PtrTo("Example New")
+ cfg3.Name = ptr.Ref("Example New")
cfg4 := cfg3
- cfg4.LoginType = PtrTo("github")
+ cfg4.LoginType = ptr.Ref("github")
cfg4.Password = nil
resource.Test(t, resource.TestCase{
@@ -99,6 +101,19 @@ func TestAccUserResource(t *testing.T) {
resource.TestCheckResourceAttr("coderd_user.test", "login_type", "github"),
),
},
+ // Verify config drift via deletion is handled
+ {
+ Config: cfg4.String(t),
+ Check: func(*terraform.State) error {
+ user, err := client.User(ctx, "exampleNew")
+ if err != nil {
+ return err
+ }
+ return client.DeleteUser(ctx, user.ID)
+ },
+ // The Plan should be to create the entire resource
+ ExpectNonEmptyPlan: true,
+ },
},
})
}
diff --git a/internal/provider/util.go b/internal/provider/util.go
index 12be3f3..3f35a25 100644
--- a/internal/provider/util.go
+++ b/internal/provider/util.go
@@ -8,22 +8,12 @@ import (
"net/http"
"os"
"path/filepath"
- "regexp"
+ "strings"
"github.com/coder/coder/v2/codersdk"
"github.com/google/uuid"
)
-var (
- nameValidRegex = regexp.MustCompile("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$")
- templateVersionNameRegex = regexp.MustCompile(`^[a-zA-Z0-9]+(?:[_.-]{1}[a-zA-Z0-9]+)*$`)
- displayNameRegex = regexp.MustCompile(`^[^\s](.*[^\s])?$`)
-)
-
-func PtrTo[T any](v T) *T {
- return &v
-}
-
func PrintOrNull(v any) string {
if v == nil {
return "null"
@@ -94,13 +84,15 @@ func computeDirectoryHash(directory string) (string, error) {
return hex.EncodeToString(hash.Sum(nil)), nil
}
-// memberDiff returns the members to add and remove from the group, given the current members and the planned members.
-// plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set.
-func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) {
- curSet := make(map[uuid.UUID]struct{}, len(curMembers))
+// memberDiff returns the members to add and remove from the group, given the
+// current members and the planned members. plannedMembers is deliberately our
+// custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a
+// set.
+func memberDiff(currentMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) {
+ curSet := make(map[uuid.UUID]struct{}, len(currentMembers))
planSet := make(map[uuid.UUID]struct{}, len(plannedMembers))
- for _, userID := range curMembers {
+ for _, userID := range currentMembers {
curSet[userID] = struct{}{}
}
for _, plannedUserID := range plannedMembers {
@@ -109,7 +101,7 @@ func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []st
add = append(add, plannedUserID.ValueString())
}
}
- for _, curUserID := range curMembers {
+ for _, curUserID := range currentMembers {
if _, exists := planSet[curUserID]; !exists {
remove = append(remove, curUserID.String())
}
@@ -119,5 +111,15 @@ func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []st
func isNotFound(err error) bool {
var sdkErr *codersdk.Error
- return errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound
+ if !errors.As(err, &sdkErr) {
+ return false
+ }
+ if sdkErr.StatusCode() == http.StatusNotFound {
+ return true
+ }
+ // `httpmw/ExtractUserContext` returns a 400 w/ this message if the user is not found
+ if sdkErr.StatusCode() == http.StatusBadRequest && strings.Contains(sdkErr.Message, "must be an existing uuid or username") {
+ return true
+ }
+ return false
}
diff --git a/internal/provider/uuid.go b/internal/provider/uuid.go
index 8cd8912..ac37b04 100644
--- a/internal/provider/uuid.go
+++ b/internal/provider/uuid.go
@@ -48,16 +48,16 @@ func (t uuidType) ValueFromString(ctx context.Context, in basetypes.StringValue)
return NewUUIDUnknown(), diags
}
- value, err := uuid.Parse(in.ValueString())
- if err != nil {
- // The framework doesn't want us to return validation errors here
- // for some reason. They get caught by `ValidateAttribute` instead,
- // and this function isn't called directly by our provider - UUIDValue
- // takes a valid UUID instead of a string.
- return NewUUIDUnknown(), diags
- }
-
- return UUIDValue(value), diags
+ // This function deliberately does not handle invalid UUIDs.
+ // Instead, `ValidateAttribute` will be called
+ // on the stored string during `validate` `plan` and `apply`,
+ // which will also create an error diagnostic.
+ // For that reason, storing the zero UUID is fine.
+ v, _ := uuid.Parse(in.ValueString())
+ return UUID{
+ StringValue: in,
+ value: v,
+ }, diags
}
// ValueFromTerraform implements basetypes.StringTypable.
diff --git a/internal/provider/uuid_internal_test.go b/internal/provider/uuid_internal_test.go
index 6283bb9..4b2fe05 100644
--- a/internal/provider/uuid_internal_test.go
+++ b/internal/provider/uuid_internal_test.go
@@ -1,12 +1,12 @@
package provider
import (
- "context"
"testing"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/stretchr/testify/require"
)
@@ -37,9 +37,12 @@ func TestUUIDTypeValueFromTerraform(t *testing.T) {
expected: UUIDValue(ValidUUID),
},
{
- name: "invalid UUID",
- input: tftypes.NewValue(tftypes.String, "invalid"),
- expected: NewUUIDUnknown(),
+ name: "invalid UUID",
+ input: tftypes.NewValue(tftypes.String, "invalid"),
+ expected: UUID{
+ StringValue: basetypes.NewStringValue("invalid"),
+ value: uuid.Nil,
+ },
},
}
@@ -47,11 +50,10 @@ func TestUUIDTypeValueFromTerraform(t *testing.T) {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
- ctx := context.Background()
+ ctx := t.Context()
actual, err := uuidType.ValueFromTerraform(UUIDType, ctx, test.input)
require.NoError(t, err)
-
require.Equal(t, test.expected, actual)
})
}
@@ -82,10 +84,9 @@ func TestUUIDToStringValue(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()
- ctx := context.Background()
+ ctx := t.Context()
s, _ := test.uuid.ToStringValue(ctx)
-
require.Equal(t, test.expected, s)
})
}
diff --git a/internal/provider/workspace_proxy_resource_test.go b/internal/provider/workspace_proxy_resource_test.go
index a2447ea..cecad70 100644
--- a/internal/provider/workspace_proxy_resource_test.go
+++ b/internal/provider/workspace_proxy_resource_test.go
@@ -1,36 +1,37 @@
package provider
import (
- "context"
"os"
"regexp"
"strings"
"testing"
"text/template"
+ "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/terraform-provider-coderd/integration"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/stretchr/testify/require"
)
func TestAccWorkspaceProxyResource(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
+ ctx := t.Context()
client := integration.StartCoder(ctx, t, "ws_proxy_acc", true)
cfg1 := testAccWorkspaceProxyResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example"),
- DisplayName: PtrTo("Example WS Proxy"),
- Icon: PtrTo("/emojis/1f407.png"),
+ Name: ptr.Ref("example"),
+ DisplayName: ptr.Ref("Example WS Proxy"),
+ Icon: ptr.Ref("/emojis/1f407.png"),
}
cfg2 := cfg1
- cfg2.Name = PtrTo("example-new")
- cfg2.DisplayName = PtrTo("Example WS Proxy New")
+ cfg2.Name = ptr.Ref("example-new")
+ cfg2.DisplayName = ptr.Ref("Example WS Proxy New")
resource.Test(t, resource.TestCase{
IsUnitTest: true,
@@ -55,18 +56,19 @@ func TestAccWorkspaceProxyResource(t *testing.T) {
}
func TestAccWorkspaceProxyResourceAGPL(t *testing.T) {
+ t.Parallel()
if os.Getenv("TF_ACC") == "" {
t.Skip("Acceptance tests are disabled.")
}
- ctx := context.Background()
- client := integration.StartCoder(ctx, t, "ws_proxy_acc", false)
+ ctx := t.Context()
+ client := integration.StartCoder(ctx, t, "ws_proxy_acc_agpl", false)
cfg1 := testAccWorkspaceProxyResourceConfig{
URL: client.URL.String(),
Token: client.SessionToken(),
- Name: PtrTo("example"),
- DisplayName: PtrTo("Example WS Proxy"),
- Icon: PtrTo("/emojis/1f407.png"),
+ Name: ptr.Ref("example"),
+ DisplayName: ptr.Ref("Example WS Proxy"),
+ Icon: ptr.Ref("/emojis/1f407.png"),
}
resource.Test(t, resource.TestCase{