diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml deleted file mode 100644 index 342c983..0000000 --- a/.github/workflows/cla.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# uncomment when configured correctly -# name: "CLA Assistant" -# on: -# issue_comment: -# types: [created] -# pull_request_target: -# types: [opened,closed,synchronize] - -# jobs: -# CLAssistant: -# runs-on: ubuntu-latest -# steps: -# - name: "CLA Assistant" -# if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' -# uses: contributor-assistant/github-action@v2.4.0 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# # the below token should have repo scope and must be manually added by you in the repository's secret -# PERSONAL_ACCESS_TOKEN : ${{ secrets.CDRCOMMUNITY_GITHUB_TOKEN }} -# with: -# remote-organization-name: 'coder' -# remote-repository-name: 'cla' -# path-to-signatures: 'v2022-09-04/signatures.json' -# path-to-document: 'https://github.com/coder/cla/blob/main/README.md' -# # branch should not be protected -# branch: 'main' -# allowlist: dependabot* diff --git a/docs/data-sources/group.md b/docs/data-sources/group.md index 8a50e9e..90dabe5 100644 --- a/docs/data-sources/group.md +++ b/docs/data-sources/group.md @@ -58,7 +58,7 @@ resource "coderd_template" "example" { - `display_name` (String) - `members` (Attributes Set) Members of the group. (see [below for nested schema](#nestedatt--members)) - `quota_allowance` (Number) The number of quota credits to allocate to each user in the group. -- `source` (String) The source of the group. Either 'oidc' or 'user'. +- `source` (String) The source of the group. Either `oidc` or `user`. ### Nested Schema for `members` @@ -69,7 +69,7 @@ Read-Only: - `email` (String) - `id` (String) - `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'. +- `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 366c16a..969a26a 100644 --- a/docs/data-sources/user.md +++ b/docs/data-sources/user.md @@ -48,9 +48,9 @@ resource "coderd_group" "bosses" { - `created_at` (Number) Unix timestamp of when the user was created. - `email` (String) Email of the user. - `last_seen_at` (Number) Unix timestamp of when the user was last seen. -- `login_type` (String) Type of login for the user. Valid types are 'none', 'password', 'github', and 'oidc'. +- `login_type` (String) Type of login for the user. Valid types are `none`, `password', `github`, and `oidc`. - `name` (String) Display name of the user. - `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'. +- `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/resources/group.md b/docs/resources/group.md index 2d265d9..1fca072 100644 --- a/docs/resources/group.md +++ b/docs/resources/group.md @@ -55,7 +55,7 @@ resource "coderd_group" "group1" { - `avatar_url` (String) The URL of the group's avatar. - `display_name` (String) The display name of the group. Defaults to the group name. -- `members` (Set of String) Members of the group, by ID. If null, members will not be added or removed by Terraform. To have a group resource with unmanaged members, but be able to read the members in Terraform, use `data.coderd_group` +- `members` (Set of String) Members of the group, by ID. If `null`, members will not be added or removed by Terraform. To have a group resource with unmanaged members, but be able to read the members in Terraform, use `data.coderd_group` - `organization_id` (String) The organization ID that the group belongs to. Defaults to the provider default organization ID. - `quota_allowance` (Number) The number of quota credits to allocate to each user in the group. diff --git a/docs/resources/license.md b/docs/resources/license.md new file mode 100644 index 0000000..58c773c --- /dev/null +++ b/docs/resources/license.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +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. + 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. +--- + +# coderd_license (Resource) + +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. + +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. + + + + +## Schema + +### Required + +- `license` (String, Sensitive) A license key for Coder. + +### Read-Only + +- `expires_at` (Number) Unix timestamp of when the license expires. +- `id` (Number) Integer ID of the license. diff --git a/docs/resources/template.md b/docs/resources/template.md index 92d9fc7..cacd1b7 100644 --- a/docs/resources/template.md +++ b/docs/resources/template.md @@ -4,7 +4,7 @@ page_title: "coderd_template Resource - terraform-provider-coderd" subcategory: "" description: |- A Coder template. - Logs from building template versions are streamed from the provisioner when the TF_LOG environment variable is INFO or higher. + Logs from building template versions can be optionally streamed from the provisioner by setting the TF_LOG environment variable to INFO or higher. When importing, the ID supplied can be either a template UUID retrieved via the API or /. --- @@ -12,7 +12,7 @@ description: |- A Coder template. -Logs from building template versions are streamed from the provisioner when the `TF_LOG` environment variable is `INFO` or higher. +Logs from building template versions can be optionally streamed from the provisioner by setting the `TF_LOG` environment variable to `INFO` or higher. When importing, the ID supplied can be either a template UUID retrieved via the API or `/`. @@ -71,7 +71,7 @@ resource "coderd_template" "ubuntu-main" { - `acl` (Attributes) (Enterprise) Access control list for the template. If null, ACL policies will not be added, removed, or read by Terraform. (see [below for nested schema](#nestedatt--acl)) - `activity_bump_ms` (Number) The activity bump duration for all workspaces created from this template, in milliseconds. Defaults to one hour. - `allow_user_auto_start` (Boolean) (Enterprise) Whether users can auto-start workspaces created from this template. Defaults to true. -- `allow_user_auto_stop` (Boolean) (Enterprise) Whether users can auto-start workspaces created from this template. Defaults to true. +- `allow_user_auto_stop` (Boolean) (Enterprise) Whether users can auto-stop workspaces created from this template. Defaults to true. - `allow_user_cancel_workspace_jobs` (Boolean) Whether users can cancel in-progress workspace jobs using this template. Defaults to true. - `auto_start_permitted_days_of_week` (Set of String) (Enterprise) List of days of the week in which autostart is allowed to happen, for all workspaces created from this template. Defaults to all days. If no days are specified, autostart is not allowed. - `auto_stop_requirement` (Attributes) (Enterprise) The auto-stop requirement for all workspaces created from this template. (see [below for nested schema](#nestedatt--auto_stop_requirement)) @@ -81,6 +81,7 @@ resource "coderd_template" "ubuntu-main" { - `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. +- `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. - `time_til_dormant_autodelete_ms` (Number) (Enterprise) The max lifetime before Coder permanently deletes dormant workspaces created from this template. @@ -101,7 +102,7 @@ Optional: - `active` (Boolean) Whether this version is the active version of the template. Only one version can be active at a time. - `message` (String) A message describing the changes in this version of the template. Messages longer than 72 characters will be truncated. -- `name` (String) The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents are updated. +- `name` (String) The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents, or the `tf_vars` attribute are updated. - `provisioner_tags` (Attributes Set) Provisioner tags for the template version. (see [below for nested schema](#nestedatt--versions--provisioner_tags)) - `tf_vars` (Attributes Set) Terraform variables for the template version. (see [below for nested schema](#nestedatt--versions--tf_vars)) diff --git a/docs/resources/user.md b/docs/resources/user.md index 1671fa6..5fcb7eb 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -56,10 +56,10 @@ resource "coderd_user" "admin" { ### Optional -- `login_type` (String) Type of login for the user. Valid types are 'none', 'password', 'github', and 'oidc'. +- `login_type` (String) Type of login for the user. Valid types are `none`, `password`, `github`, and `oidc`. - `name` (String) Display name of the user. Defaults to username. -- `password` (String, Sensitive) Password for the user. Required when login_type is 'password'. Passwords are saved into the state as plain text and should only be used for testing purposes. -- `roles` (Set of String) Roles assigned to the user. Valid roles are 'owner', 'template-admin', 'user-admin', and 'auditor'. +- `password` (String, Sensitive) Password for the user. Required when `login_type` is `password`. Passwords are saved into the state as plain text and should only be used for testing purposes. +- `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. ### Read-Only diff --git a/docs/resources/workspace_proxy.md b/docs/resources/workspace_proxy.md index ad77a82..72262ff 100644 --- a/docs/resources/workspace_proxy.md +++ b/docs/resources/workspace_proxy.md @@ -48,7 +48,7 @@ resource "kubernetes_deployment" "syd_wsproxy" { ### Required -- `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. - `name` (String) Name of the workspace proxy. ### Optional diff --git a/go.mod b/go.mod index eebc6ee..be07ad3 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,19 @@ module github.com/coder/terraform-provider-coderd -go 1.22.5 +go 1.22.6 + +toolchain go1.22.8 require ( cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 - github.com/coder/coder/v2 v2.14.2 - github.com/docker/docker v27.1.2+incompatible - github.com/docker/go-connections v0.4.0 + github.com/coder/coder/v2 v2.16.0 + github.com/docker/docker v27.2.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.11.0 + github.com/hashicorp/terraform-plugin-framework v1.12.0 github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 - github.com/hashicorp/terraform-plugin-go v0.23.0 + github.com/hashicorp/terraform-plugin-go v0.24.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 @@ -20,14 +22,14 @@ require ( require ( github.com/BurntSushi/toml v1.2.1 // indirect - github.com/DataDog/appsec-internal-go v1.5.0 // indirect + github.com/DataDog/appsec-internal-go v1.7.0 // indirect github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect github.com/DataDog/datadog-go/v5 v5.3.0 // indirect - github.com/DataDog/go-libddwaf/v2 v2.4.2 // indirect + github.com/DataDog/go-libddwaf/v3 v3.3.0 // indirect github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect github.com/DataDog/gostackparse v0.7.0 // indirect - github.com/DataDog/sketches-go v1.4.2 // 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 @@ -45,9 +47,8 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 // indirect - github.com/coder/serpent v0.7.0 // indirect - github.com/coder/terraform-provider-coder v0.23.0 // indirect - github.com/containerd/log v0.1.0 // 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect @@ -72,12 +73,12 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // 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.0 // indirect + github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // 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.8.0 // indirect - github.com/hashicorp/hcl/v2 v2.21.0 // indirect + github.com/hashicorp/hc-install v0.9.0 // indirect + github.com/hashicorp/hcl/v2 v2.22.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 @@ -91,37 +92,38 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect 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/morikuni/aec v1.0.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect - github.com/oklog/run v1.0.0 // indirect + github.com/moby/moby v27.3.1+incompatible // indirect + github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // 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/outcaste-io/ristretto v0.2.3 // indirect github.com/philhofer/fwd v1.1.2 // indirect - github.com/pion/transport/v2 v2.0.0 // indirect + github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/udp v0.1.4 // indirect 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.19.1 // indirect + github.com/prometheus/client_golang v1.20.2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/prometheus/common v0.59.1 // 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/secure-systems-lab/go-securesystemslib v0.7.0 // 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.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tinylib/msgp v1.1.8 // indirect github.com/valyala/fasthttp v1.55.0 // indirect @@ -133,38 +135,36 @@ require ( github.com/zclconf/go-cty v1.15.0 // indirect github.com/zeebo/errs v1.3.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect - go.nhat.io/otelsql v0.13.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.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/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace 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.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/crypto v0.27.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.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.6.0 // indirect + golang.org/x/tools v0.25.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-20240725223205-93522f1f2a9f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect - google.golang.org/grpc v1.65.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/DataDog/dd-trace-go.v1 v1.64.0 // indirect + gopkg.in/DataDog/dd-trace-go.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.5.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 b7bc394..eb5ba20 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJ cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= 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.5.11 h1:Havn1kGjz3whCfoD8dxMLP73Ph5w+ODyZB9RUsDxtGk= -cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4= +cloud.google.com/go/longrunning v0.6.0 h1:mM1ZmaNsQsnb+5n1DNPeL0KwQd9jQRqSqSDEkBZr+aI= +cloud.google.com/go/longrunning v0.6.0/go.mod h1:uHzSZqW89h7/pasCWNYdUpwGz3PcVWhrWupreVPYLts= 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,22 +16,22 @@ 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.5.0 h1:8kS5zSx5T49uZ8dZTdT19QVAvC/B8ByyZdhQKYQWHno= -github.com/DataDog/appsec-internal-go v1.5.0/go.mod h1:pEp8gjfNLtEOmz+iZqC8bXhu0h4k7NUsW/qiQb34k1U= +github.com/DataDog/appsec-internal-go v1.7.0 h1:iKRNLih83dJeVya3IoUfK+6HLD/hQsIbyBlfvLmAeb0= +github.com/DataDog/appsec-internal-go v1.7.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.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c= github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= 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/v2 v2.4.2 h1:ilquGKUmN9/Ty0sIxiEyznVRxP3hKfmH15Y1SMq5gjA= -github.com/DataDog/go-libddwaf/v2 v2.4.2/go.mod h1:gsCdoijYQfj8ce/T2bEDNPZFIYnmHluAgVDpuQOWMZE= +github.com/DataDog/go-libddwaf/v3 v3.3.0 h1:jS72fuQpFgJZEdEJDmHJCPAgNTEMZoz1EUvimPUOiJ4= +github.com/DataDog/go-libddwaf/v3 v3.3.0/go.mod h1:Bz/0JkpGf689mzbUjKJeheJINqsyyhM8p9PDuHdK2Ec= github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= github.com/DataDog/go-tuf v1.0.2-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/sketches-go v1.4.2 h1:gppNudE9d19cQ98RYABOetxIhpTCl4m7CnbRZjvVA/o= -github.com/DataDog/sketches-go v1.4.2/go.mod h1:xJIXldczJyyjnbDop7ZZcLxJdV3+7Kra7H1KMgpgkLk= +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= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -73,22 +73,24 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY 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.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= -github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= +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/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.14.2 h1:RNNDDwjNK5D1XMQlK7LWrS4niVclkl1FXoaOaW7N5rs= -github.com/coder/coder/v2 v2.14.2/go.mod h1:dO79BI5XlP8rrtne1JpRcVehe27bNMXdZKyn1NsWbjA= +github.com/coder/coder/v2 v2.16.0 h1:+IzbcLU7YFUp6knJJhS4xw8yphuqrIUKt7mIk7LwUQA= +github.com/coder/coder/v2 v2.16.0/go.mod h1:/kiN4IfNwd5T7xGEyVbp7jiNOVaHtdiIDyLqN9OqboE= 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.7.0 h1:zGpD2GlF3lKIVkMjNGKbkip88qzd5r/TRcc30X/SrT0= -github.com/coder/serpent v0.7.0/go.mod h1:REkJ5ZFHQUWFTPLExhXYZ1CaHFjxvGNRlLXLdsI08YA= -github.com/coder/terraform-provider-coder v0.23.0 h1:DuNLWxhnGlXyG0g+OCAZRI6xd8+bJjIEnE4F3hYgA4E= -github.com/coder/terraform-provider-coder v0.23.0/go.mod h1:wMun9UZ9HT2CzF6qPPBup1odzBpVUc0/xSFoXgdI3tk= +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/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= @@ -103,15 +105,18 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/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.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= -github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +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/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= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -130,6 +135,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm 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/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= 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= @@ -166,8 +173,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm 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.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= -github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -218,19 +225,25 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= -github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-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.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI= -github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= -github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= -github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +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/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= @@ -239,12 +252,12 @@ github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7 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.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE= -github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= +github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= +github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E= github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo= -github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= -github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= +github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= +github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= 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= @@ -303,11 +316,13 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k 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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -319,8 +334,8 @@ 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.1.1+incompatible h1:WdCIKJ4WIxhrKti5c+Z7sj2SLADbsuB/reEBpQ4rtOQ= -github.com/moby/moby v27.1.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +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/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= @@ -331,12 +346,14 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G 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/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/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= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -349,13 +366,16 @@ 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/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.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc= +github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= +github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8= github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -367,23 +387,27 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= +github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 h1:Qp27Idfgi6ACvFQat5+VJvlYToylpM/hcyLBI3WaKPA= -github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+uGV8BrTv8W/SIwk= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +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= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +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/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= 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= @@ -401,8 +425,8 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2 github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +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/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= @@ -414,12 +438,12 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 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/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= @@ -444,6 +468,7 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/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/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= @@ -468,30 +493,30 @@ 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.13.0 h1:L6obwZRxgFQqeSvo7jCemP659fu7pqsDHQNuZ3Ev1yI= -go.nhat.io/otelsql v0.13.0/go.mod h1:HyYpqd7G9BK+9cPLydV+2JN/4J5D3wlX6+jDLTk52GE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +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.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.24.0 h1:JYE2HM7pZbOt5Jhk8ndWZTUWYOVift2cHjXVMkPdmdc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.24.0/go.mod h1:yMb/8c6hVsnma0RpsBMNo0fEiQKeclawtgaIaOp2MLY= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= -go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +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.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -506,17 +531,19 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U 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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +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= 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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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/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= @@ -527,10 +554,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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= @@ -561,16 +591,22 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc 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.3.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.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +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.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 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= @@ -578,11 +614,14 @@ 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= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= @@ -590,33 +629,33 @@ 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.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 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-20240730163845-b1a4ccb954bf h1:OqdXDEakZCVtDiZTjcxfwbHPCT11ycCEsTKesBVKvyY= -google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M= -google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk= -google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/DataDog/dd-trace-go.v1 v1.64.0 h1:zXQo6iv+dKRrDBxMXjRXLSKN2lY9uM34XFI4nPyp0eA= -gopkg.in/DataDog/dd-trace-go.v1 v1.64.0/go.mod h1:qzwVu8Qr8CqzQNw2oKEXRdD+fMnjYatjYMGE0tdCVG4= +gopkg.in/DataDog/dd-trace-go.v1 v1.67.0 h1:3Cb46zyKIlEWac21tvDF2O4KyMlOHQxrQkyiaUpdwM0= +gopkg.in/DataDog/dd-trace-go.v1 v1.67.0/go.mod h1:6DdiJPKOeJfZyd/IUGCAd5elY8qPGkztK6wbYYsMjag= 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= @@ -631,11 +670,18 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ= -honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= 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= diff --git a/integration/integration.go b/integration/integration.go index 0b6f98e..f2ed0dd 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -53,7 +53,6 @@ func StartCoder(ctx context.Context, t *testing.T, name string, useLicense bool) 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_IN_MEMORY=true", // We don't necessarily care about real persistence here. "CODER_TELEMETRY_ENABLE=false", // Avoid creating noise. }, Labels: map[string]string{}, @@ -96,7 +95,7 @@ func StartCoder(ctx context.Context, t *testing.T, name string, useLicense bool) t.Logf("not ready yet: %s", err.Error()) } return err == nil - }, 10*time.Second, time.Second, "coder failed to become ready in time") + }, 15*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 7f289c4..3bfdae4 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -97,7 +97,7 @@ func TestIntegration(t *testing.T) { assert.Equal(t, "dean", user.Username) // Check group - defaultOrg, err := c.OrganizationByName(ctx, "first-organization") + defaultOrg, err := c.OrganizationByName(ctx, "coder") assert.NoError(t, err) group, err := c.GroupByOrgAndName(ctx, defaultOrg.ID, "employees") assert.NoError(t, err) @@ -109,7 +109,7 @@ func TestIntegration(t *testing.T) { name: "template-test", preF: func(t testing.TB, c *codersdk.Client) {}, assertF: func(t testing.TB, c *codersdk.Client) { - defaultOrg, err := c.OrganizationByName(ctx, "first-organization") + defaultOrg, err := c.OrganizationByName(ctx, "coder") assert.NoError(t, err) user, err := c.User(ctx, "ethan") require.NoError(t, err) diff --git a/internal/provider/group_data_source.go b/internal/provider/group_data_source.go index 95c8e6e..007abaa 100644 --- a/internal/provider/group_data_source.go +++ b/internal/provider/group_data_source.go @@ -93,7 +93,7 @@ func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaReque Computed: true, }, "source": schema.StringAttribute{ - MarkdownDescription: "The source of the group. Either 'oidc' or 'user'.", + MarkdownDescription: "The source of the group. Either `oidc` or `user`.", Computed: true, }, "members": schema.SetNestedAttribute{ @@ -120,11 +120,11 @@ func (d *GroupDataSource) Schema(ctx context.Context, req datasource.SchemaReque Computed: true, }, "status": schema.StringAttribute{ - MarkdownDescription: "The status of the member. Can be 'active', 'dormant' or 'suspended'.", + MarkdownDescription: "The status of the member. Can be `active`, `dormant` or `suspended`.", Computed: true, }, "login_type": schema.StringAttribute{ - MarkdownDescription: "The login type of the member. Can be 'oidc', 'token', 'password', 'github' or 'none'.", + MarkdownDescription: "The login type of the member. Can be `oidc`, `token`, `password`, `github` or `none`.", Computed: true, }, "theme_preference": schema.StringAttribute{ @@ -187,6 +187,11 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, groupID := data.ID.ValueUUID() group, err = client.Group(ctx, groupID) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Group with ID %s not found. Marking as deleted.", groupID.String())) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group by ID, got error: %s", err)) return } @@ -195,6 +200,11 @@ func (d *GroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, } else { group, err = client.GroupByOrgAndName(ctx, data.OrganizationID.ValueUUID(), data.Name.ValueString()) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Group with name %s not found in organization with ID %s. Marking as deleted.", data.Name.ValueString(), data.OrganizationID.ValueString())) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Failed to get group by name and org ID", err.Error()) return } diff --git a/internal/provider/group_resource.go b/internal/provider/group_resource.go index c7f11bd..5bd5a58 100644 --- a/internal/provider/group_resource.go +++ b/internal/provider/group_resource.go @@ -77,13 +77,18 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest, "name": schema.StringAttribute{ 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."), + }, }, "display_name": schema.StringAttribute{ MarkdownDescription: "The display name of the group. Defaults to the group name.", Computed: true, Optional: true, Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), + stringvalidator.LengthBetween(1, 64), + stringvalidator.RegexMatches(displayNameRegex, "Group display names must be alphanumeric with spaces"), }, Default: stringdefault.StaticString(""), }, @@ -110,7 +115,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest, }, }, "members": schema.SetAttribute{ - MarkdownDescription: "Members of the group, by ID. If null, members will not be added or removed by Terraform. To have a group resource with unmanaged members, but be able to read the members in Terraform, use `data.coderd_group`", + MarkdownDescription: "Members of the group, by ID. If `null`, members will not be added or removed by Terraform. To have a group resource with unmanaged members, but be able to read the members in Terraform, use `data.coderd_group`", ElementType: UUIDType, Optional: true, }, @@ -215,6 +220,11 @@ func (r *GroupResource) Read(ctx context.Context, req resource.ReadRequest, resp group, err := client.Group(ctx, groupID) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Group with ID %s not found. Marking as deleted.", groupID.String())) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group, got error: %s", err)) return } diff --git a/internal/provider/license_resource.go b/internal/provider/license_resource.go new file mode 100644 index 0000000..2dec235 --- /dev/null +++ b/internal/provider/license_resource.go @@ -0,0 +1,195 @@ +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/int32planmodifier" + "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 = &LicenseResource{} + +func NewLicenseResource() resource.Resource { + return &LicenseResource{} +} + +// LicenseResource defines the resource implementation. +type LicenseResource struct { + data *CoderdProviderData +} + +// LicenseResourceModel describes the resource data model. +type LicenseResourceModel struct { + ID types.Int32 `tfsdk:"id"` + ExpiresAt types.Int64 `tfsdk:"expires_at"` + License types.String `tfsdk:"license"` +} + +func (r *LicenseResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_license" +} + +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" + + "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.", + + Attributes: map[string]schema.Attribute{ + "id": schema.Int32Attribute{ + MarkdownDescription: "Integer ID of the license.", + Computed: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.UseStateForUnknown(), + }, + }, + "expires_at": schema.Int64Attribute{ + MarkdownDescription: "Unix timestamp of when the license expires.", + Computed: true, + }, + "license": schema.StringAttribute{ + MarkdownDescription: "A license key for Coder.", + Required: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *LicenseResource) 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.data = data +} + +func (r *LicenseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data LicenseResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + client := r.data.Client + + license, err := client.AddLicense(ctx, codersdk.AddLicenseRequest{ + License: data.License.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to add license, got error: %s", err)) + return + } + data.ID = types.Int32Value(license.ID) + expiresAt, err := license.ExpiresAt() + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse license expiration, got error: %s", err)) + return + } + data.ExpiresAt = types.Int64Value(expiresAt.Unix()) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *LicenseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data LicenseResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + licenses, err := r.data.Client.Licenses(ctx) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to list licenses, got error: %s", err)) + return + } + + found := false + for _, license := range licenses { + if license.ID == data.ID.ValueInt32() { + found = true + expiresAt, err := license.ExpiresAt() + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to parse license expiration, got error: %s", err)) + return + } + data.ExpiresAt = types.Int64Value(expiresAt.Unix()) + } + } + if !found { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("License with ID %d not found. Marking as deleted.", data.ID.ValueInt32())) + resp.State.RemoveResource(ctx) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *LicenseResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data LicenseResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update is handled by replacement + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *LicenseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data LicenseResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + client := r.data.Client + + err := client.DeleteLicense(ctx, data.ID.ValueInt32()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete license, got error: %s", err)) + return + } +} diff --git a/internal/provider/license_resource_test.go b/internal/provider/license_resource_test.go new file mode 100644 index 0000000..e2d13d6 --- /dev/null +++ b/internal/provider/license_resource_test.go @@ -0,0 +1,74 @@ +package provider + +import ( + "context" + "os" + "strings" + "testing" + "text/template" + + "github.com/coder/terraform-provider-coderd/integration" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/require" +) + +func TestAccLicenseResource(t *testing.T) { + if os.Getenv("TF_ACC") == "" { + t.Skip("Acceptance tests are disabled.") + } + ctx := context.Background() + client := integration.StartCoder(ctx, t, "license_acc", false) + + license := os.Getenv("CODER_ENTERPRISE_LICENSE") + if license == "" { + t.Skip("No license found for license resource tests, skipping") + } + + cfg1 := testAccLicenseResourceconfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + License: license, + } + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: cfg1.String(t), + }, + }, + }) +} + +type testAccLicenseResourceconfig struct { + URL string + Token string + License string +} + +func (c testAccLicenseResourceconfig) String(t *testing.T) string { + t.Helper() + tpl := ` +provider coderd { + url = "{{.URL}}" + token = "{{.Token}}" +} + +resource "coderd_license" "test" { + license = "{{.License}}" +} +` + funcMap := template.FuncMap{ + "orNull": PrintOrNull, + } + + buf := strings.Builder{} + tmpl, err := template.New("licenseResource").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_data_source.go b/internal/provider/organization_data_source.go index 59b598c..835a2ed 100644 --- a/internal/provider/organization_data_source.go +++ b/internal/provider/organization_data_source.go @@ -127,6 +127,11 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe orgID := data.ID.ValueUUID() org, err = client.Organization(ctx, orgID) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Organization with ID %s not found. Marking as deleted.", data.ID.ValueString())) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by ID, got error: %s", err)) return } @@ -137,6 +142,11 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe } else if data.IsDefault.ValueBool() { // Get Default org, err = client.OrganizationByName(ctx, "default") if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", "Default organization not found. Marking as deleted.") + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get default organization, got error: %s", err)) return } @@ -147,6 +157,11 @@ func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRe } else { // By Name org, err = client.OrganizationByName(ctx, data.Name.ValueString()) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Organization with name %s not found. Marking as deleted.", data.Name)) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by name, got error: %s", err)) return } diff --git a/internal/provider/organization_data_source_test.go b/internal/provider/organization_data_source_test.go index fe744db..7f9bef3 100644 --- a/internal/provider/organization_data_source_test.go +++ b/internal/provider/organization_data_source_test.go @@ -26,7 +26,7 @@ func TestAccOrganizationDataSource(t *testing.T) { defaultCheckFn := resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("data.coderd_organization.test", "id", firstUser.OrganizationIDs[0].String()), resource.TestCheckResourceAttr("data.coderd_organization.test", "is_default", "true"), - resource.TestCheckResourceAttr("data.coderd_organization.test", "name", "first-organization"), + resource.TestCheckResourceAttr("data.coderd_organization.test", "name", "coder"), resource.TestCheckResourceAttr("data.coderd_organization.test", "members.#", "1"), resource.TestCheckTypeSetElemAttr("data.coderd_organization.test", "members.*", firstUser.ID.String()), resource.TestCheckResourceAttrSet("data.coderd_organization.test", "created_at"), @@ -55,7 +55,7 @@ func TestAccOrganizationDataSource(t *testing.T) { cfg := testAccOrganizationDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("first-organization"), + Name: PtrTo("coder"), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -92,7 +92,7 @@ func TestAccOrganizationDataSource(t *testing.T) { URL: client.URL.String(), Token: client.SessionToken(), IsDefault: PtrTo(true), - Name: PtrTo("first-organization"), + Name: PtrTo("coder"), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 651dcb4..bfeea5e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -138,6 +138,7 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour NewGroupResource, NewTemplateResource, NewWorkspaceProxyResource, + NewLicenseResource, } } diff --git a/internal/provider/template_data_source.go b/internal/provider/template_data_source.go index 406ef33..3a5f59a 100644 --- a/internal/provider/template_data_source.go +++ b/internal/provider/template_data_source.go @@ -262,6 +262,11 @@ func (d *TemplateDataSource) Read(ctx context.Context, req datasource.ReadReques template, err = client.TemplateByName(ctx, data.OrganizationID.ValueUUID(), data.Name.ValueString()) } if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", "Template not found. Marking as deleted.") + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get template, got error: %s", err)) return } diff --git a/internal/provider/template_data_source_test.go b/internal/provider/template_data_source_test.go index 79c1f80..cebdba7 100644 --- a/internal/provider/template_data_source_test.go +++ b/internal/provider/template_data_source_test.go @@ -28,7 +28,7 @@ func TestAccTemplateDataSource(t *testing.T) { require.NoError(t, err) orgID := firstUser.OrganizationIDs[0] - version, err := newVersion(ctx, client, newVersionRequest{ + version, err, _ := newVersion(ctx, client, newVersionRequest{ OrganizationID: orgID, Version: &TemplateVersion{ Name: types.StringValue("main"), diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go index 9255f2f..a23f408 100644 --- a/internal/provider/template_resource.go +++ b/internal/provider/template_resource.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "slices" "strings" "cdr.dev/slog" @@ -68,6 +69,7 @@ type TemplateResourceModel struct { TimeTilDormantAutoDeleteMillis types.Int64 `tfsdk:"time_til_dormant_autodelete_ms"` RequireActiveVersion types.Bool `tfsdk:"require_active_version"` DeprecationMessage types.String `tfsdk:"deprecation_message"` + MaxPortShareLevel types.String `tfsdk:"max_port_share_level"` // If null, we are not managing ACL via Terraform (such as for AGPL). ACL types.Object `tfsdk:"acl"` @@ -91,7 +93,9 @@ func (m *TemplateResourceModel) EqualTemplateMetadata(other *TemplateResourceMod m.FailureTTLMillis.Equal(other.FailureTTLMillis) && m.TimeTilDormantMillis.Equal(other.TimeTilDormantMillis) && m.TimeTilDormantAutoDeleteMillis.Equal(other.TimeTilDormantAutoDeleteMillis) && - m.RequireActiveVersion.Equal(other.RequireActiveVersion) + m.RequireActiveVersion.Equal(other.RequireActiveVersion) && + m.DeprecationMessage.Equal(other.DeprecationMessage) && + m.MaxPortShareLevel.Equal(other.MaxPortShareLevel) } func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features map[codersdk.FeatureName]codersdk.Feature) (diags diag.Diagnostics) { @@ -109,7 +113,8 @@ func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features len(m.AutostartPermittedDaysOfWeek.Elements()) != 7 requiresActiveVersion := m.RequireActiveVersion.ValueBool() requiresACL := !m.ACL.IsNull() - if requiresScheduling || requiresActiveVersion || requiresACL { + requiresSharedPortsControl := m.MaxPortShareLevel.ValueString() != "" && m.MaxPortShareLevel.ValueString() != string(codersdk.WorkspaceAgentPortShareLevelPublic) + if requiresScheduling || requiresActiveVersion || requiresACL || requiresSharedPortsControl { if requiresScheduling && !features[codersdk.FeatureAdvancedTemplateScheduling].Enabled { diags.AddError( "Feature not enabled", @@ -131,6 +136,13 @@ func (m *TemplateResourceModel) CheckEntitlements(ctx context.Context, features ) return } + if requiresSharedPortsControl && !features[codersdk.FeatureControlSharedPorts].Enabled { + diags.AddError( + "Feature not enabled", + "Your license is not entitled to use port sharing control, so you cannot set max_port_share_level.", + ) + return + } } return } @@ -230,8 +242,8 @@ func (r *TemplateResource) Metadata(ctx context.Context, req resource.MetadataRe func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "A Coder template.\n\nLogs from building template versions are streamed from the provisioner " + - "when the `TF_LOG` environment variable is `INFO` or higher.\n\n" + + MarkdownDescription: "A Coder template.\n\nLogs from building template versions can be optionally streamed from the provisioner " + + "by setting the `TF_LOG` environment variable to `INFO` or higher.\n\n" + "When importing, the ID supplied can be either a template UUID retrieved via the API or `/`.", Attributes: map[string]schema.Attribute{ @@ -248,12 +260,17 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Required: true, Validators: []validator.String{ stringvalidator.LengthBetween(1, 32), + stringvalidator.RegexMatches(nameValidRegex, "Template names must be alphanumeric with hyphens."), }, }, "display_name": schema.StringAttribute{ MarkdownDescription: "The display name of the template. Defaults to the template name.", Optional: true, Computed: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 64), + stringvalidator.RegexMatches(displayNameRegex, "Template display names must be alphanumeric with spaces."), + }, }, "description": schema.StringAttribute{ MarkdownDescription: "A description of the template.", @@ -334,7 +351,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Default: booldefault.StaticBool(true), }, "allow_user_auto_stop": schema.BoolAttribute{ - MarkdownDescription: "(Enterprise) Whether users can auto-start workspaces created from this template. Defaults to true.", + MarkdownDescription: "(Enterprise) Whether users can auto-stop workspaces created from this template. Defaults to true.", Optional: true, Computed: true, Default: booldefault.StaticBool(true), @@ -363,6 +380,14 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Computed: true, Default: booldefault.StaticBool(false), }, + "max_port_share_level": schema.StringAttribute{ + MarkdownDescription: "(Enterprise) The maximum port share level for workspaces created from this template. Defaults to `owner` on an Enterprise deployment, or `public` otherwise.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(string(codersdk.WorkspaceAgentPortShareLevelAuthenticated), string(codersdk.WorkspaceAgentPortShareLevelOwner), string(codersdk.WorkspaceAgentPortShareLevelPublic)), + }, + }, "deprecation_message": schema.StringAttribute{ MarkdownDescription: "If set, the template will be marked as deprecated with the provided message and users will be blocked from creating new workspaces from it. Does nothing if set when the resource is created.", Optional: true, @@ -390,11 +415,12 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Computed: true, }, "name": schema.StringAttribute{ - MarkdownDescription: "The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents are updated.", + MarkdownDescription: "The name of the template version. Automatically generated if not provided. If provided, the name *must* change each time the directory contents, or the `tf_vars` attribute are updated.", Optional: true, Computed: true, Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), + stringvalidator.LengthBetween(1, 64), + stringvalidator.RegexMatches(templateVersionNameRegex, "Template version names must be alphanumeric with underscores and dots."), }, }, "message": schema.StringAttribute{ @@ -489,9 +515,9 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques if idx > 0 { newVersionRequest.TemplateID = &templateResp.ID } - versionResp, err := newVersion(ctx, client, newVersionRequest) + versionResp, err, logs := newVersion(ctx, client, newVersionRequest) if err != nil { - resp.Diagnostics.AddError("Client Error", err.Error()) + resp.Diagnostics.AddError("Provisioner Error", formatLogs(err, logs)) return } if idx == 0 { @@ -525,7 +551,7 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques if resp.Diagnostics.HasError() { return } - err = client.UpdateTemplateACL(ctx, templateResp.ID, convertACLToRequest(acl)) + err = client.UpdateTemplateACL(ctx, templateResp.ID, convertACLToRequest(codersdk.TemplateACL{}, acl)) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to create template ACL: %s", err)) return @@ -546,6 +572,23 @@ func (r *TemplateResource) Create(ctx context.Context, req resource.CreateReques data.ID = UUIDValue(templateResp.ID) data.DisplayName = types.StringValue(templateResp.DisplayName) + // TODO: Remove this update call once this provider requires a Coder + // deployment running `v2.15.0` or later. + if data.MaxPortShareLevel.IsUnknown() { + data.MaxPortShareLevel = types.StringValue(string(templateResp.MaxPortShareLevel)) + } else { + mpslReq := data.toUpdateRequest(ctx, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + mpslResp, err := client.UpdateTemplateMeta(ctx, data.ID.ValueUUID(), *mpslReq) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to set max port share level via update: %s", err)) + return + } + data.MaxPortShareLevel = types.StringValue(string(mpslResp.MaxPortShareLevel)) + } + resp.Diagnostics.Append(data.Versions.setPrivateState(ctx, resp.Private)...) if resp.Diagnostics.HasError() { return @@ -570,6 +613,11 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r template, err := client.Template(ctx, templateID) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Template with ID %s not found. Marking as deleted.", templateID.String())) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template: %s", err)) return } @@ -579,6 +627,7 @@ func (r *TemplateResource) Read(ctx context.Context, req resource.ReadRequest, r resp.Diagnostics.Append(diag...) return } + data.MaxPortShareLevel = types.StringValue(string(template.MaxPortShareLevel)) if !data.ACL.IsNull() { tflog.Info(ctx, "reading template ACL") @@ -653,11 +702,16 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques client := r.data.Client + // TODO(ethanndickson): Remove this once the provider requires a Coder + // deployment running `v2.15.0` or later. + if newState.MaxPortShareLevel.IsUnknown() { + newState.MaxPortShareLevel = curState.MaxPortShareLevel + } templateMetadataChanged := !newState.EqualTemplateMetadata(&curState) // This is required, as the API will reject no-diff updates. if templateMetadataChanged { tflog.Info(ctx, "change in template metadata detected, updating.") - updateReq := newState.toUpdateRequest(ctx, resp) + updateReq := newState.toUpdateRequest(ctx, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -678,7 +732,13 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques if resp.Diagnostics.HasError() { return } - err := client.UpdateTemplateACL(ctx, templateID, convertACLToRequest(acl)) + curACL, err := client.TemplateACL(ctx, templateID) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template ACL: %s", err)) + return + } + + err = client.UpdateTemplateACL(ctx, templateID, convertACLToRequest(curACL, acl)) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update template ACL: %s", err)) return @@ -689,13 +749,13 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques for idx := range newState.Versions { if newState.Versions[idx].ID.IsUnknown() { tflog.Info(ctx, "discovered a new or modified template version") - uploadResp, err := newVersion(ctx, client, newVersionRequest{ + uploadResp, err, logs := newVersion(ctx, client, newVersionRequest{ Version: &newState.Versions[idx], OrganizationID: orgID, TemplateID: &templateID, }) if err != nil { - resp.Diagnostics.AddError("Client Error", err.Error()) + resp.Diagnostics.AddError("Provisioner Error", formatLogs(err, logs)) return } versionResp, err := client.TemplateVersion(ctx, uploadResp.ID) @@ -740,6 +800,14 @@ func (r *TemplateResource) Update(ctx context.Context, req resource.UpdateReques } } } + // TODO(ethanndickson): Remove this once the provider requires a Coder + // deployment running `v2.15.0` or later. + templateResp, err := client.Template(ctx, templateID) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get template: %s", err)) + return + } + newState.MaxPortShareLevel = types.StringValue(string(templateResp.MaxPortShareLevel)) resp.Diagnostics.Append(newState.Versions.setPrivateState(ctx, resp.Private)...) if resp.Diagnostics.HasError() { @@ -938,13 +1006,14 @@ func uploadDirectory(ctx context.Context, client *codersdk.Client, logger slog.L return &resp, nil } -func waitForJob(ctx context.Context, client *codersdk.Client, version *codersdk.TemplateVersion) error { +func waitForJob(ctx context.Context, client *codersdk.Client, version *codersdk.TemplateVersion) ([]codersdk.ProvisionerJobLog, error) { const maxRetries = 3 + var jobLogs []codersdk.ProvisionerJobLog for retries := 0; retries < maxRetries; retries++ { logs, closer, err := client.TemplateVersionLogsAfter(ctx, version.ID, 0) defer closer.Close() if err != nil { - return fmt.Errorf("begin streaming logs: %w", err) + return jobLogs, fmt.Errorf("begin streaming logs: %w", err) } for { logs, ok := <-logs @@ -958,21 +1027,24 @@ func waitForJob(ctx context.Context, client *codersdk.Client, version *codersdk. "level": logs.Level, "created_at": logs.CreatedAt, }) + if logs.Output != "" { + jobLogs = append(jobLogs, logs) + } } latestResp, err := client.TemplateVersion(ctx, version.ID) if err != nil { - return err + return jobLogs, err } if latestResp.Job.Status.Active() { tflog.Warn(ctx, fmt.Sprintf("provisioner job still active, continuing to wait...: %s", latestResp.Job.Status)) continue } if latestResp.Job.Status != codersdk.ProvisionerJobSucceeded { - return fmt.Errorf("provisioner job did not succeed: %s (%s)", latestResp.Job.Status, latestResp.Job.Error) + return jobLogs, fmt.Errorf("provisioner job did not succeed: %s (%s)", latestResp.Job.Status, latestResp.Job.Error) } - return nil + return jobLogs, nil } - return fmt.Errorf("provisioner job did not complete after %d retries", maxRetries) + return jobLogs, fmt.Errorf("provisioner job did not complete after %d retries", maxRetries) } type newVersionRequest struct { @@ -981,22 +1053,23 @@ type newVersionRequest struct { TemplateID *uuid.UUID } -func newVersion(ctx context.Context, client *codersdk.Client, req newVersionRequest) (*codersdk.TemplateVersion, error) { +func newVersion(ctx context.Context, client *codersdk.Client, req newVersionRequest) (*codersdk.TemplateVersion, error, []codersdk.ProvisionerJobLog) { + var logs []codersdk.ProvisionerJobLog directory := req.Version.Directory.ValueString() tflog.Info(ctx, "uploading directory") uploadResp, err := uploadDirectory(ctx, client, slog.Make(newTFLogSink(ctx)), directory) if err != nil { - return nil, fmt.Errorf("failed to upload directory: %s", err) + return nil, fmt.Errorf("failed to upload directory: %s", err), logs } tflog.Info(ctx, "successfully uploaded directory") tflog.Info(ctx, "discovering and parsing vars files") varFiles, err := codersdk.DiscoverVarsFiles(directory) if err != nil { - return nil, fmt.Errorf("failed to discover vars files: %s", err) + return nil, fmt.Errorf("failed to discover vars files: %s", err), logs } vars, err := codersdk.ParseUserVariableValues(varFiles, "", []string{}) if err != nil { - return nil, fmt.Errorf("failed to parse user variable values: %s", err) + return nil, fmt.Errorf("failed to parse user variable values: %s", err), logs } tflog.Info(ctx, "discovered and parsed vars files", map[string]any{ "vars": vars, @@ -1021,15 +1094,15 @@ func newVersion(ctx context.Context, client *codersdk.Client, req newVersionRequ tflog.Info(ctx, "creating template version") versionResp, err := client.CreateTemplateVersion(ctx, req.OrganizationID, tmplVerReq) if err != nil { - return nil, fmt.Errorf("failed to create template version: %s", err) + return nil, fmt.Errorf("failed to create template version: %s", err), logs } tflog.Info(ctx, "waiting for template version import job.") - err = waitForJob(ctx, client, &versionResp) + logs, err = waitForJob(ctx, client, &versionResp) if err != nil { - return nil, fmt.Errorf("failed to wait for job: %s", err) + return nil, fmt.Errorf("failed to wait for job: %s", err), logs } tflog.Info(ctx, "successfully created template version") - return &versionResp, nil + return &versionResp, nil, logs } func markActive(ctx context.Context, client *codersdk.Client, templateID uuid.UUID, versionID uuid.UUID) error { @@ -1041,21 +1114,33 @@ func markActive(ctx context.Context, client *codersdk.Client, templateID uuid.UU ID: versionID, }) if err != nil { - return fmt.Errorf("Failed to update active template version: %s", err) + return fmt.Errorf("failed to update active template version: %s", err) } tflog.Info(ctx, "marked template version as active") return nil } -func convertACLToRequest(permissions ACL) codersdk.UpdateTemplateACL { +func convertACLToRequest(curACL codersdk.TemplateACL, newACL ACL) codersdk.UpdateTemplateACL { var userPerms = make(map[string]codersdk.TemplateRole) - for _, perm := range permissions.UserPermissions { + for _, perm := range newACL.UserPermissions { userPerms[perm.ID.ValueString()] = codersdk.TemplateRole(perm.Role.ValueString()) } var groupPerms = make(map[string]codersdk.TemplateRole) - for _, perm := range permissions.GroupPermissions { + for _, perm := range newACL.GroupPermissions { groupPerms[perm.ID.ValueString()] = codersdk.TemplateRole(perm.Role.ValueString()) } + // For each user or group to remove, we need to set their role to empty + // string. + for _, perm := range curACL.Users { + if _, ok := userPerms[perm.ID.String()]; !ok { + userPerms[perm.ID.String()] = "" + } + } + for _, perm := range curACL.Groups { + if _, ok := groupPerms[perm.ID.String()]; !ok { + groupPerms[perm.ID.String()] = "" + } + } return codersdk.UpdateTemplateACL{ UserPerms: userPerms, GroupPerms: groupPerms, @@ -1112,25 +1197,27 @@ func (r *TemplateResourceModel) readResponse(ctx context.Context, template *code r.TimeTilDormantAutoDeleteMillis = types.Int64Value(template.TimeTilDormantAutoDeleteMillis) r.RequireActiveVersion = types.BoolValue(template.RequireActiveVersion) r.DeprecationMessage = types.StringValue(template.DeprecationMessage) + // TODO(ethanndickson): MaxPortShareLevel deliberately omitted, as it can't + // be set during a create request, and we call this during `Create`. return nil } -func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, resp *resource.UpdateResponse) *codersdk.UpdateTemplateMeta { +func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, diag *diag.Diagnostics) *codersdk.UpdateTemplateMeta { var days []string - resp.Diagnostics.Append( + diag.Append( r.AutostartPermittedDaysOfWeek.ElementsAs(ctx, &days, false)..., ) - if resp.Diagnostics.HasError() { + if diag.HasError() { return nil } autoStart := &codersdk.TemplateAutostartRequirement{ DaysOfWeek: days, } var reqs AutostopRequirement - resp.Diagnostics.Append( + diag.Append( r.AutostopRequirement.As(ctx, &reqs, basetypes.ObjectAsOptions{})..., ) - if resp.Diagnostics.HasError() { + if diag.HasError() { return nil } autoStop := &codersdk.TemplateAutostopRequirement{ @@ -1154,6 +1241,7 @@ func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, resp *resou TimeTilDormantAutoDeleteMillis: r.TimeTilDormantAutoDeleteMillis.ValueInt64(), RequireActiveVersion: r.RequireActiveVersion.ValueBool(), DeprecationMessage: r.DeprecationMessage.ValueStringPointer(), + MaxPortShareLevel: PtrTo(codersdk.WorkspaceAgentPortShareLevel(r.MaxPortShareLevel.ValueString())), // If we're managing ACL, we want to delete the everyone group DisableEveryoneGroupAccess: !r.ACL.IsNull(), } @@ -1207,8 +1295,9 @@ type LastVersionsByHash = map[string][]PreviousTemplateVersion var LastVersionsKey = "last_versions" type PreviousTemplateVersion struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + TFVars map[string]string `json:"tf_vars"` } type privateState interface { @@ -1220,18 +1309,24 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d lv := make(LastVersionsByHash) for _, version := range v { vbh, ok := lv[version.DirectoryHash.ValueString()] + tfVars := make(map[string]string, len(version.TerraformVariables)) + for _, tfVar := range version.TerraformVariables { + tfVars[tfVar.Name.ValueString()] = tfVar.Value.ValueString() + } // Store the IDs and names of all versions with the same directory hash, // in the order they appear if ok { lv[version.DirectoryHash.ValueString()] = append(vbh, PreviousTemplateVersion{ - ID: version.ID.ValueUUID(), - Name: version.Name.ValueString(), + ID: version.ID.ValueUUID(), + Name: version.Name.ValueString(), + TFVars: tfVars, }) } else { lv[version.DirectoryHash.ValueString()] = []PreviousTemplateVersion{ { - ID: version.ID.ValueUUID(), - Name: version.Name.ValueString(), + ID: version.ID.ValueUUID(), + Name: version.Name.ValueString(), + TFVars: tfVars, }, } } @@ -1245,6 +1340,13 @@ func (v Versions) setPrivateState(ctx context.Context, ps privateState) (diags d } func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVersions Versions) { + // We remove versions that we've matched from `lv`, so make a copy for + // resolving tfvar changes at the end. + fullLv := make(LastVersionsByHash) + for k, v := range lv { + fullLv[k] = slices.Clone(v) + } + for i := range planVersions { prevList, ok := lv[planVersions[i].DirectoryHash.ValueString()] // If not in state, mark as known after apply since we'll create a new version. @@ -1284,4 +1386,59 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe lv[planVersions[i].DirectoryHash.ValueString()] = prevList[1:] } } + + // If only the Terraform variables have changed, + // we need to create a new version with the new variables. + for i := range planVersions { + if !planVersions[i].ID.IsUnknown() { + prevs, ok := fullLv[planVersions[i].DirectoryHash.ValueString()] + if !ok { + continue + } + if tfVariablesChanged(prevs, &planVersions[i]) { + planVersions[i].ID = NewUUIDUnknown() + // We could always set the name to unknown here, to generate a + // random one (this is what the Web UI currently does when + // only updating tfvars). + // However, I think it'd be weird if the provider just started + // ignoring the name you set in the config, we'll instead + // require that users update the name if they update the tfvars. + if configVersions[i].Name.IsNull() { + planVersions[i].Name = types.StringUnknown() + } + } + } + } +} + +func tfVariablesChanged(prevs []PreviousTemplateVersion, planned *TemplateVersion) bool { + for _, prev := range prevs { + if prev.ID == planned.ID.ValueUUID() { + // If the previous version has no TFVars, then it was created using + // an older provider version. + if prev.TFVars == nil { + return true + } + for _, tfVar := range planned.TerraformVariables { + if prev.TFVars[tfVar.Name.ValueString()] != tfVar.Value.ValueString() { + return true + } + } + return false + } + } + return true + +} + +func formatLogs(err error, logs []codersdk.ProvisionerJobLog) string { + var b strings.Builder + b.WriteString(err.Error() + "\n") + for _, log := range logs { + if !log.CreatedAt.IsZero() { + b.WriteString(log.CreatedAt.Local().Format("2006-01-02 15:04:05.000Z07:00") + " ") + } + b.WriteString(log.Output + "\n") + } + return b.String() } diff --git a/internal/provider/template_resource_test.go b/internal/provider/template_resource_test.go index b8b7f19..82f19ac 100644 --- a/internal/provider/template_resource_test.go +++ b/internal/provider/template_resource_test.go @@ -113,6 +113,7 @@ func TestAccTemplateResource(t *testing.T) { resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant_ms", "0"), resource.TestCheckResourceAttr("coderd_template.test", "time_til_dormant_autodelete_ms", "0"), resource.TestCheckResourceAttr("coderd_template.test", "require_active_version", "false"), + resource.TestCheckResourceAttr("coderd_template.test", "max_port_share_level", "public"), resource.TestMatchTypeSetElemNestedAttrs("coderd_template.test", "versions.*", map[string]*regexp.Regexp{ "name": regexp.MustCompile(".+"), "id": regexp.MustCompile(".+"), @@ -286,6 +287,15 @@ func TestAccTemplateResource(t *testing.T) { cfg5.Versions = slices.Clone(cfg5.Versions) cfg5.Versions[1].Directory = PtrTo("../../integration/template-test/example-template/") + cfg6 := cfg5 + cfg6.Versions = slices.Clone(cfg6.Versions) + cfg6.Versions[0].TerraformVariables = []testAccTemplateKeyValueConfig{ + { + Key: PtrTo("name"), + Value: PtrTo("world2"), + }, + } + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, IsUnitTest: true, @@ -343,6 +353,66 @@ func TestAccTemplateResource(t *testing.T) { testAccCheckNumTemplateVersions(ctx, client, 4), ), }, + // Update the Terraform variables of the first version + { + Config: cfg6.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 5), + ), + }, + }, + }) + }) + + t.Run("AutoGenNameUpdateTFVars", func(t *testing.T) { + cfg1 := testAccTemplateResourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + Name: PtrTo("example-template3"), + Versions: []testAccTemplateVersionConfig{ + { + // Auto-generated version name + Directory: PtrTo("../../integration/template-test/example-template-2/"), + TerraformVariables: []testAccTemplateKeyValueConfig{ + { + Key: PtrTo("name"), + Value: PtrTo("world"), + }, + }, + Active: PtrTo(true), + }, + }, + ACL: testAccTemplateACLConfig{ + null: true, + }, + } + + cfg2 := cfg1 + cfg2.Versions = slices.Clone(cfg2.Versions) + cfg2.Versions[0].TerraformVariables = []testAccTemplateKeyValueConfig{ + { + Key: PtrTo("name"), + Value: PtrTo("world2"), + }, + } + + 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), + ), + }, + { + Config: cfg2.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckNumTemplateVersions(ctx, client, 2), + ), + }, }, }) }) @@ -357,6 +427,12 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { firstUser, err := client.User(ctx, codersdk.Me) require.NoError(t, err) + group, err := client.CreateGroup(ctx, firstUser.OrganizationIDs[0], codersdk.CreateGroupRequest{ + Name: "bosses", + QuotaAllowance: 200, + }) + require.NoError(t, err) + cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), @@ -366,13 +442,6 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { // Auto-generated version name Directory: PtrTo("../../integration/template-test/example-template"), Active: PtrTo(true), - // TODO(ethanndickson): Remove this when we add in `*.tfvars` parsing - TerraformVariables: []testAccTemplateKeyValueConfig{ - { - Key: PtrTo("name"), - Value: PtrTo("world"), - }, - }, }, }, ACL: testAccTemplateACLConfig{ @@ -381,6 +450,10 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { Key: PtrTo(firstUser.OrganizationIDs[0].String()), Value: PtrTo("use"), }, + { + Key: PtrTo(group.ID.String()), + Value: PtrTo("admin"), + }, }, UserACL: []testAccTemplateKeyValueConfig{ { @@ -392,11 +465,16 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { } cfg2 := cfg1 - cfg2.ACL.null = true + cfg2.ACL.GroupACL = slices.Clone(cfg2.ACL.GroupACL[1:]) + cfg2.MaxPortShareLevel = PtrTo("owner") cfg3 := cfg2 - cfg3.AllowUserAutostart = PtrTo(false) - cfg3.AutostopRequirement = testAccAutostopRequirementConfig{ + cfg3.ACL.null = true + cfg3.MaxPortShareLevel = PtrTo("public") + + cfg4 := cfg3 + cfg4.AllowUserAutostart = PtrTo(false) + cfg4.AutostopRequirement = testAccAutostopRequirementConfig{ DaysOfWeek: PtrTo([]string{"monday", "tuesday"}), Weeks: PtrTo(int64(2)), } @@ -409,13 +487,19 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { { Config: cfg1.String(t), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("coderd_template.test", "acl.groups.#", "1"), + 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(".+"), + "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(".+"), + "id": regexp.MustCompile(firstUser.ID.String()), "role": regexp.MustCompile("^admin$"), }), ), @@ -423,6 +507,17 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { { 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{}) @@ -439,7 +534,7 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { if len(acl.Groups) != 1 { return fmt.Errorf("expected 1 group ACL, got %d", len(acl.Groups)) } - if acl.Groups[0].Role != "use" && acl.Groups[0].ID != firstUser.OrganizationIDs[0] { + 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 { @@ -453,7 +548,7 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { ), }, { - Config: cfg3.String(t), + 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"), @@ -514,6 +609,10 @@ func TestAccTemplateResourceAGPL(t *testing.T) { }, } + cfg7 := cfg6 + cfg7.ACL.null = true + cfg7.MaxPortShareLevel = PtrTo("owner") + for _, cfg := range []testAccTemplateResourceConfig{cfg1, cfg2, cfg3, cfg4} { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -541,6 +640,10 @@ func TestAccTemplateResourceAGPL(t *testing.T) { Config: cfg6.String(t), ExpectError: regexp.MustCompile("Your license is not entitled to use template access control"), }, + { + Config: cfg7.String(t), + ExpectError: regexp.MustCompile("Your license is not entitled to use port sharing control"), + }, }, }) } @@ -566,6 +669,7 @@ type testAccTemplateResourceConfig struct { TimeTilDormantAutodelete *int64 RequireActiveVersion *bool DeprecationMessage *string + MaxPortShareLevel *string Versions []testAccTemplateVersionConfig ACL testAccTemplateACLConfig @@ -672,6 +776,7 @@ resource "coderd_template" "test" { time_til_dormant_autodelete_ms = {{orNull .TimeTilDormantAutodelete}} require_active_version = {{orNull .RequireActiveVersion}} deprecation_message = {{orNull .DeprecationMessage}} + max_port_share_level = {{orNull .MaxPortShareLevel}} acl = ` + c.ACL.String(t) + ` @@ -759,14 +864,16 @@ func TestReconcileVersionIDs(t *testing.T) { Name: "IdenticalDontRename", planVersions: []TemplateVersion{ { - Name: types.StringValue("foo"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, { - Name: types.StringValue("bar"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("bar"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, }, configVersions: []TemplateVersion{ @@ -780,21 +887,24 @@ func TestReconcileVersionIDs(t *testing.T) { inputState: map[string][]PreviousTemplateVersion{ "aaa": { { - ID: aUUID, - Name: "bar", + ID: aUUID, + Name: "bar", + TFVars: map[string]string{}, }, }, }, expectedVersions: []TemplateVersion{ { - Name: types.StringValue("foo"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, { - Name: types.StringValue("bar"), - DirectoryHash: types.StringValue("aaa"), - ID: UUIDValue(aUUID), + Name: types.StringValue("bar"), + DirectoryHash: types.StringValue("aaa"), + ID: UUIDValue(aUUID), + TerraformVariables: []Variable{}, }, }, }, @@ -802,14 +912,16 @@ func TestReconcileVersionIDs(t *testing.T) { Name: "IdenticalRenameFirst", planVersions: []TemplateVersion{ { - Name: types.StringValue("foo"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, { - Name: types.StringValue("bar"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("bar"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, }, configVersions: []TemplateVersion{ @@ -823,21 +935,24 @@ func TestReconcileVersionIDs(t *testing.T) { inputState: map[string][]PreviousTemplateVersion{ "aaa": { { - ID: aUUID, - Name: "baz", + ID: aUUID, + Name: "baz", + TFVars: map[string]string{}, }, }, }, expectedVersions: []TemplateVersion{ { - Name: types.StringValue("foo"), - DirectoryHash: types.StringValue("aaa"), - ID: UUIDValue(aUUID), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: UUIDValue(aUUID), + TerraformVariables: []Variable{}, }, { - Name: types.StringValue("bar"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("bar"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, }, }, @@ -845,14 +960,16 @@ func TestReconcileVersionIDs(t *testing.T) { Name: "IdenticalHashesInState", planVersions: []TemplateVersion{ { - Name: types.StringValue("foo"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, { - Name: types.StringValue("bar"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("bar"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, }, configVersions: []TemplateVersion{ @@ -866,25 +983,29 @@ func TestReconcileVersionIDs(t *testing.T) { inputState: map[string][]PreviousTemplateVersion{ "aaa": { { - ID: aUUID, - Name: "qux", + ID: aUUID, + Name: "qux", + TFVars: map[string]string{}, }, { - ID: bUUID, - Name: "baz", + ID: bUUID, + Name: "baz", + TFVars: map[string]string{}, }, }, }, expectedVersions: []TemplateVersion{ { - Name: types.StringValue("foo"), - DirectoryHash: types.StringValue("aaa"), - ID: UUIDValue(aUUID), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: UUIDValue(aUUID), + TerraformVariables: []Variable{}, }, { - Name: types.StringValue("bar"), - DirectoryHash: types.StringValue("aaa"), - ID: UUIDValue(bUUID), + Name: types.StringValue("bar"), + DirectoryHash: types.StringValue("aaa"), + ID: UUIDValue(bUUID), + TerraformVariables: []Variable{}, }, }, }, @@ -892,14 +1013,16 @@ func TestReconcileVersionIDs(t *testing.T) { Name: "UnknownUsesStateInOrder", planVersions: []TemplateVersion{ { - Name: types.StringValue("foo"), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, { - Name: types.StringUnknown(), - DirectoryHash: types.StringValue("aaa"), - ID: NewUUIDUnknown(), + Name: types.StringUnknown(), + DirectoryHash: types.StringValue("aaa"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, }, }, configVersions: []TemplateVersion{ @@ -913,55 +1036,152 @@ func TestReconcileVersionIDs(t *testing.T) { inputState: map[string][]PreviousTemplateVersion{ "aaa": { { - ID: aUUID, - Name: "qux", + ID: aUUID, + Name: "qux", + TFVars: map[string]string{}, }, { - ID: bUUID, - Name: "baz", + ID: bUUID, + Name: "baz", + TFVars: map[string]string{}, }, }, }, expectedVersions: []TemplateVersion{ + { + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: UUIDValue(aUUID), + TerraformVariables: []Variable{}, + }, + { + Name: types.StringValue("baz"), + DirectoryHash: types.StringValue("aaa"), + ID: UUIDValue(bUUID), + TerraformVariables: []Variable{}, + }, + }, + }, + { + Name: "NewVersionNewRandomName", + planVersions: []TemplateVersion{ + { + Name: types.StringValue("weird_draught12"), + DirectoryHash: types.StringValue("bbb"), + ID: UUIDValue(aUUID), + TerraformVariables: []Variable{}, + }, + }, + configVersions: []TemplateVersion{ + { + Name: types.StringNull(), + }, + }, + inputState: map[string][]PreviousTemplateVersion{ + "aaa": { + { + ID: aUUID, + Name: "weird_draught12", + TFVars: map[string]string{}, + }, + }, + }, + expectedVersions: []TemplateVersion{ + { + Name: types.StringUnknown(), + DirectoryHash: types.StringValue("bbb"), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{}, + }, + }, + }, + { + Name: "IdenticalNewVars", + planVersions: []TemplateVersion{ { Name: types.StringValue("foo"), DirectoryHash: types.StringValue("aaa"), ID: UUIDValue(aUUID), + TerraformVariables: []Variable{ + { + Name: types.StringValue("foo"), + Value: types.StringValue("bar"), + }, + }, }, + }, + configVersions: []TemplateVersion{ { - Name: types.StringValue("baz"), + Name: types.StringValue("foo"), + }, + }, + inputState: map[string][]PreviousTemplateVersion{ + "aaa": { + { + ID: aUUID, + Name: "foo", + TFVars: map[string]string{ + "foo": "foo", + }, + }, + }, + }, + expectedVersions: []TemplateVersion{ + { + Name: types.StringValue("foo"), DirectoryHash: types.StringValue("aaa"), - ID: UUIDValue(bUUID), + ID: NewUUIDUnknown(), + TerraformVariables: []Variable{ + { + Name: types.StringValue("foo"), + Value: types.StringValue("bar"), + }, + }, }, }, }, { - Name: "NewVersionNewRandomName", + Name: "IdenticalSameVars", planVersions: []TemplateVersion{ { - Name: types.StringValue("weird_draught12"), - DirectoryHash: types.StringValue("bbb"), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), ID: UUIDValue(aUUID), + TerraformVariables: []Variable{ + { + Name: types.StringValue("foo"), + Value: types.StringValue("bar"), + }, + }, }, }, configVersions: []TemplateVersion{ { - Name: types.StringNull(), + Name: types.StringValue("foo"), }, }, inputState: map[string][]PreviousTemplateVersion{ "aaa": { { ID: aUUID, - Name: "weird_draught12", + Name: "foo", + TFVars: map[string]string{ + "foo": "bar", + }, }, }, }, expectedVersions: []TemplateVersion{ { - Name: types.StringUnknown(), - DirectoryHash: types.StringValue("bbb"), - ID: NewUUIDUnknown(), + Name: types.StringValue("foo"), + DirectoryHash: types.StringValue("aaa"), + ID: UUIDValue(aUUID), + TerraformVariables: []Variable{ + { + Name: types.StringValue("foo"), + Value: types.StringValue("bar"), + }, + }, }, }, }, diff --git a/internal/provider/user_data_source.go b/internal/provider/user_data_source.go index 7c5846f..be367ea 100644 --- a/internal/provider/user_data_source.go +++ b/internal/provider/user_data_source.go @@ -72,12 +72,12 @@ func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaReques Computed: true, }, "roles": schema.SetAttribute{ - MarkdownDescription: "Roles assigned to the user. Valid roles are 'owner', 'template-admin', 'user-admin', and 'auditor'.", + MarkdownDescription: "Roles assigned to the user. Valid roles are `owner`, `template-admin`, `user-admin`, and `auditor`.", Computed: true, ElementType: types.StringType, }, "login_type": schema.StringAttribute{ - MarkdownDescription: "Type of login for the user. Valid types are 'none', 'password', 'github', and 'oidc'.", + MarkdownDescription: "Type of login for the user. Valid types are `none`, `password', `github`, and `oidc`.", Computed: true, }, "suspended": schema.BoolAttribute{ @@ -149,6 +149,11 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } user, err := client.User(ctx, ident) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("User with identifier %q not found. Marking as deleted.", ident)) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get current user, got error: %s", err)) return } diff --git a/internal/provider/user_resource.go b/internal/provider/user_resource.go index 4e8de49..5dc4203 100644 --- a/internal/provider/user_resource.go +++ b/internal/provider/user_resource.go @@ -71,18 +71,25 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r "username": schema.StringAttribute{ MarkdownDescription: "Username of the user.", Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 32), + stringvalidator.RegexMatches(nameValidRegex, "Username must be alphanumeric with hyphens."), + }, }, "name": schema.StringAttribute{ MarkdownDescription: "Display name of the user. Defaults to username.", Computed: true, Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 128), + }, }, "email": schema.StringAttribute{ MarkdownDescription: "Email address of the user.", Required: true, }, "roles": schema.SetAttribute{ - MarkdownDescription: "Roles assigned to the user. Valid roles are 'owner', 'template-admin', 'user-admin', and 'auditor'.", + MarkdownDescription: "Roles assigned to the user. Valid roles are `owner`, `template-admin`, `user-admin`, and `auditor`.", Computed: true, Optional: true, ElementType: types.StringType, @@ -94,7 +101,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), }, "login_type": schema.StringAttribute{ - MarkdownDescription: "Type of login for the user. Valid types are 'none', 'password', 'github', and 'oidc'.", + MarkdownDescription: "Type of login for the user. Valid types are `none`, `password`, `github`, and `oidc`.", Computed: true, Optional: true, Validators: []validator.String{ @@ -106,7 +113,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r }, }, "password": schema.StringAttribute{ - MarkdownDescription: "Password for the user. Required when login_type is 'password'. Passwords are saved into the state as plain text and should only be used for testing purposes.", + MarkdownDescription: "Password for the user. Required when `login_type` is `password`. Passwords are saved into the state as plain text and should only be used for testing purposes.", Optional: true, Sensitive: true, }, @@ -244,6 +251,11 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp 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.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get current user, got error: %s", err)) return } diff --git a/internal/provider/util.go b/internal/provider/util.go index 03d899f..12be3f3 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -3,13 +3,23 @@ package provider import ( "crypto/sha256" "encoding/hex" + "errors" "fmt" + "net/http" "os" "path/filepath" + "regexp" + "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 } @@ -106,3 +116,8 @@ func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []st } return add, remove } + +func isNotFound(err error) bool { + var sdkErr *codersdk.Error + return errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound +} diff --git a/internal/provider/workspace_proxy_resource.go b/internal/provider/workspace_proxy_resource.go index a95dc68..211c778 100644 --- a/internal/provider/workspace_proxy_resource.go +++ b/internal/provider/workspace_proxy_resource.go @@ -60,7 +60,7 @@ func (r *WorkspaceProxyResource) Schema(ctx context.Context, req resource.Schema Computed: true, }, "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.", Required: true, }, "session_token": schema.StringAttribute{ @@ -142,6 +142,11 @@ func (r *WorkspaceProxyResource) Read(ctx context.Context, req resource.ReadRequ client := r.data.Client wsp, err := client.WorkspaceProxyByID(ctx, data.ID.ValueUUID()) if err != nil { + if isNotFound(err) { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Workspace proxy with ID %s not found. Marking as deleted.", data.ID.ValueString())) + resp.State.RemoveResource(ctx) + return + } resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to read workspace proxy: %v", err)) return }