From 176fb6a9f4329c1618b7424fdad7fa9792f1dab8 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Tue, 2 Apr 2024 19:01:40 -0400 Subject: [PATCH 001/114] feat: add owner group to workspace data (#204) --- docs/data-sources/workspace.md | 1 + provider/workspace.go | 17 +++++++++++++++++ provider/workspace_test.go | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index f5b434a9..2ed7c63d 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -32,6 +32,7 @@ resource "kubernetes_pod" "dev" { - `name` (String) Name of the workspace. - `owner` (String) Username of the workspace owner. - `owner_email` (String) Email address of the workspace owner. +- `owner_groups` (List of String) List of groups the workspace owner belongs to. - `owner_id` (String) UUID of the workspace owner. - `owner_name` (String) Name of the workspace owner. - `owner_oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. diff --git a/provider/workspace.go b/provider/workspace.go index be7bf03c..1511d2b5 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -2,6 +2,7 @@ package provider import ( "context" + "encoding/json" "os" "reflect" "strconv" @@ -36,6 +37,14 @@ func workspaceDataSource() *schema.Resource { ownerEmail := os.Getenv("CODER_WORKSPACE_OWNER_EMAIL") _ = rd.Set("owner_email", ownerEmail) + ownerGroupsText := os.Getenv("CODER_WORKSPACE_OWNER_GROUPS") + var ownerGroups []string + err := json.Unmarshal([]byte(ownerGroupsText), &ownerGroups) + if err != nil { + return diag.Errorf("couldn't parse owner groups %q", ownerGroupsText) + } + _ = rd.Set("owner_groups", ownerGroups) + ownerName := os.Getenv("CODER_WORKSPACE_OWNER_NAME") _ = rd.Set("owner_name", ownerName) @@ -141,6 +150,14 @@ func workspaceDataSource() *schema.Resource { "This is only available if the workspace owner authenticated with OpenID Connect. " + "If a valid token cannot be obtained, this value will be an empty string.", }, + "owner_groups": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "List of groups the workspace owner belongs to.", + }, "id": { Type: schema.TypeString, Computed: true, diff --git a/provider/workspace_test.go b/provider/workspace_test.go index 38f9c743..78aeac4b 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -16,6 +16,7 @@ func TestWorkspace(t *testing.T) { t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner") t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com") t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123") + t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID") t.Setenv("CODER_WORKSPACE_TEMPLATE_NAME", "template123") t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") @@ -47,6 +48,8 @@ func TestWorkspace(t *testing.T) { require.Equal(t, "Mr Owner", attribs["owner_name"]) require.Equal(t, "owner123@example.com", attribs["owner_email"]) require.Equal(t, "abc123", attribs["owner_session_token"]) + require.Equal(t, "group1", attribs["owner_groups.0"]) + require.Equal(t, "group2", attribs["owner_groups.1"]) require.Equal(t, "templateID", attribs["template_id"]) require.Equal(t, "template123", attribs["template_name"]) require.Equal(t, "v1.2.3", attribs["template_version"]) @@ -80,6 +83,8 @@ func TestWorkspace(t *testing.T) { require.Equal(t, "owner123", attribs["owner"]) require.Equal(t, "Mr Owner", attribs["owner_name"]) require.Equal(t, "owner123@example.com", attribs["owner_email"]) + require.Equal(t, "group1", attribs["owner_groups.0"]) + require.Equal(t, "group2", attribs["owner_groups.1"]) require.Equal(t, "templateID", attribs["template_id"]) require.Equal(t, "template123", attribs["template_name"]) require.Equal(t, "v1.2.3", attribs["template_version"]) From ebce4cef4ec4324c021c096162249ad0397e47ab Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 3 Apr 2024 10:14:51 -0500 Subject: [PATCH 002/114] fix: check if owner groups exist before marshaling (#205) --- provider/workspace.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/provider/workspace.go b/provider/workspace.go index 1511d2b5..0e1abf97 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -39,9 +39,11 @@ func workspaceDataSource() *schema.Resource { ownerGroupsText := os.Getenv("CODER_WORKSPACE_OWNER_GROUPS") var ownerGroups []string - err := json.Unmarshal([]byte(ownerGroupsText), &ownerGroups) - if err != nil { - return diag.Errorf("couldn't parse owner groups %q", ownerGroupsText) + if ownerGroupsText != "" { + err := json.Unmarshal([]byte(ownerGroupsText), &ownerGroups) + if err != nil { + return diag.Errorf("couldn't parse owner groups %q", ownerGroupsText) + } } _ = rd.Set("owner_groups", ownerGroups) From d27a3b6f77c014706add893e53f91caced1dc334 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:50:46 -0500 Subject: [PATCH 003/114] build(deps): Bump contributor-assistant/github-action from 2.3.1 to 2.3.2 (#201) Bumps [contributor-assistant/github-action](https://github.com/contributor-assistant/github-action) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/contributor-assistant/github-action/releases) - [Commits](https://github.com/contributor-assistant/github-action/compare/v2.3.1...v2.3.2) --- updated-dependencies: - dependency-name: contributor-assistant/github-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cla.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 28b5e829..db910ed8 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -11,7 +11,7 @@ jobs: 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.3.1 + uses: contributor-assistant/github-action@v2.3.2 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 From 9ae64fda47c11d10449bfdb126f55b92715cb8ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:51:06 -0500 Subject: [PATCH 004/114] build(deps): Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#200) Bumps google.golang.org/protobuf from 1.30.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2a99deb9..ceee673d 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,6 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 59028a90..ecd9bb7e 100644 --- a/go.sum +++ b/go.sum @@ -239,8 +239,8 @@ google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From f3a205c94add5c861cb9ca3e4626560e786e38d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:51:25 -0500 Subject: [PATCH 005/114] build(deps): Bump github.com/stretchr/testify from 1.8.4 to 1.9.0 (#199) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ceee673d..85e6c0ff 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) diff --git a/go.sum b/go.sum index ecd9bb7e..fcd3ced0 100644 --- a/go.sum +++ b/go.sum @@ -160,8 +160,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -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/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= From b94b7ea775e25b570ce2ee37682f17115a9e3ffb Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 18 Apr 2024 11:28:14 +0100 Subject: [PATCH 006/114] fix(provider): coalesce arch to armv7 if on 32-bit arm (#210) This PR modifies the `coder_provisioner` datasource to return `arch` as `armv7` if `GOARCH=arm`. This fixes an issue where users on 32-bit arm platforms would be unable to create a template with ``` resource "coder_agent" "main" { arch = data.coder_provisioner.me.arch ... } ``` and would instead need to manually specify ``` resource "coder_agent" "main" { arch = "armv7" ... } ``` --- docs/resources/agent_instance.md | 2 +- provider/provisioner.go | 4 ++++ provider/provisioner_test.go | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/resources/agent_instance.md b/docs/resources/agent_instance.md index fa8574fa..6af2bb46 100644 --- a/docs/resources/agent_instance.md +++ b/docs/resources/agent_instance.md @@ -3,7 +3,7 @@ page_title: "coder_agent_instance Resource - terraform-provider-coder" subcategory: "" description: |- - Use this resource to associate an instance ID with an agent for zero-trust authentication. This association is done automatically for "googlecomputeinstance", "awsinstance", "azurermlinuxvirtualmachine", and "azurermwindowsvirtual_machine" resources. + Use this resource to associate an instance ID with an agent for zero-trust authentication. This association is done automatically for "google_compute_instance", "aws_instance", "azurerm_linux_virtual_machine", and "azurerm_windows_virtual_machine" resources. --- # coder_agent_instance (Resource) diff --git a/provider/provisioner.go b/provider/provisioner.go index 49d8f401..314e89cd 100644 --- a/provider/provisioner.go +++ b/provider/provisioner.go @@ -16,6 +16,10 @@ func provisionerDataSource() *schema.Resource { rd.SetId(uuid.NewString()) rd.Set("os", runtime.GOOS) rd.Set("arch", runtime.GOARCH) + // Fix for #11782: if we're on 32-bit ARM, set arch to armv7. + if runtime.GOARCH == "arm" { + rd.Set("arch", "armv7") + } return nil }, diff --git a/provider/provisioner_test.go b/provider/provisioner_test.go index 777006f7..e9f83e43 100644 --- a/provider/provisioner_test.go +++ b/provider/provisioner_test.go @@ -31,9 +31,14 @@ func TestProvisioner(t *testing.T) { attribs := resource.Primary.Attributes require.Equal(t, runtime.GOOS, attribs["os"]) - require.Equal(t, runtime.GOARCH, attribs["arch"]) + if runtime.GOARCH == "arm" { + require.Equal(t, "armv7", attribs["arch"]) + } else { + require.Equal(t, runtime.GOARCH, attribs["arch"]) + } return nil }, }}, }) } + From 8c5e8ff9579d1d766d5317422bf3c1988b710434 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 23 Apr 2024 17:40:03 +0300 Subject: [PATCH 007/114] fix: set owner name and email to "default" (#209) --- provider/workspace.go | 6 ++++++ provider/workspace_test.go | 43 +++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/provider/workspace.go b/provider/workspace.go index 0e1abf97..b8ff1684 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -35,6 +35,9 @@ func workspaceDataSource() *schema.Resource { _ = rd.Set("owner", owner) ownerEmail := os.Getenv("CODER_WORKSPACE_OWNER_EMAIL") + if ownerEmail == "" { + ownerEmail = "default@example.com" + } _ = rd.Set("owner_email", ownerEmail) ownerGroupsText := os.Getenv("CODER_WORKSPACE_OWNER_GROUPS") @@ -48,6 +51,9 @@ func workspaceDataSource() *schema.Resource { _ = rd.Set("owner_groups", ownerGroups) ownerName := os.Getenv("CODER_WORKSPACE_OWNER_NAME") + if ownerName == "" { + ownerName = "default" + } _ = rd.Set("owner_name", ownerName) ownerID := os.Getenv("CODER_WORKSPACE_OWNER_ID") diff --git a/provider/workspace_test.go b/provider/workspace_test.go index 78aeac4b..d5866af5 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/coder/terraform-provider-coder/provider" @@ -43,20 +44,30 @@ func TestWorkspace(t *testing.T) { value := attribs["transition"] require.NotNil(t, value) t.Log(value) - require.Equal(t, "8080", attribs["access_port"]) - require.Equal(t, "owner123", attribs["owner"]) - require.Equal(t, "Mr Owner", attribs["owner_name"]) - require.Equal(t, "owner123@example.com", attribs["owner_email"]) - require.Equal(t, "abc123", attribs["owner_session_token"]) - require.Equal(t, "group1", attribs["owner_groups.0"]) - require.Equal(t, "group2", attribs["owner_groups.1"]) - require.Equal(t, "templateID", attribs["template_id"]) - require.Equal(t, "template123", attribs["template_name"]) - require.Equal(t, "v1.2.3", attribs["template_version"]) + assert.Equal(t, "https://example.com:8080", attribs["access_url"]) + assert.Equal(t, "8080", attribs["access_port"]) + assert.Equal(t, "owner123", attribs["owner"]) + assert.Equal(t, "Mr Owner", attribs["owner_name"]) + assert.Equal(t, "owner123@example.com", attribs["owner_email"]) + assert.Equal(t, "group1", attribs["owner_groups.0"]) + assert.Equal(t, "group2", attribs["owner_groups.1"]) + assert.Equal(t, "templateID", attribs["template_id"]) + assert.Equal(t, "template123", attribs["template_name"]) + assert.Equal(t, "v1.2.3", attribs["template_version"]) return nil }, }}, }) +} + +func TestWorkspace_UndefinedOwner(t *testing.T) { + t.Setenv("CODER_WORKSPACE_OWNER", "owner123") + t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123") + t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) + t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID") + t.Setenv("CODER_WORKSPACE_TEMPLATE_NAME", "template123") + t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") + resource.Test(t, resource.TestCase{ Providers: map[string]*schema.Provider{ "coder": provider.New(), @@ -79,15 +90,9 @@ func TestWorkspace(t *testing.T) { value := attribs["transition"] require.NotNil(t, value) t.Log(value) - require.Equal(t, "https://example.com:8080", attribs["access_url"]) - require.Equal(t, "owner123", attribs["owner"]) - require.Equal(t, "Mr Owner", attribs["owner_name"]) - require.Equal(t, "owner123@example.com", attribs["owner_email"]) - require.Equal(t, "group1", attribs["owner_groups.0"]) - require.Equal(t, "group2", attribs["owner_groups.1"]) - require.Equal(t, "templateID", attribs["template_id"]) - require.Equal(t, "template123", attribs["template_name"]) - require.Equal(t, "v1.2.3", attribs["template_version"]) + assert.Equal(t, "owner123", attribs["owner"]) + assert.Equal(t, "default@example.com", attribs["owner_email"]) + // Skip other asserts return nil }, }}, From 204ea02a4356de982fa67186a2625500fe3bd55f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:19:13 +0100 Subject: [PATCH 008/114] build(deps): Bump golang.org/x/net from 0.17.0 to 0.23.0 (#211) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 85e6c0ff..7af7f224 100644 --- a/go.mod +++ b/go.mod @@ -52,9 +52,9 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/zclconf/go-cty v1.10.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect diff --git a/go.sum b/go.sum index fcd3ced0..93ed263d 100644 --- a/go.sum +++ b/go.sum @@ -181,8 +181,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -193,8 +193,8 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -215,8 +215,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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= From 6d1b7363af798f3108e051aa5bc67f89aeef2094 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Mon, 29 Apr 2024 11:25:09 +0300 Subject: [PATCH 009/114] docs: remove jetbrains projector example from coder_app (#215) --- docs/resources/app.md | 8 -------- examples/resources/coder_app/resource.tf | 8 -------- 2 files changed, 16 deletions(-) diff --git a/docs/resources/app.md b/docs/resources/app.md index 56800b76..aec85255 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -47,14 +47,6 @@ resource "coder_app" "vim" { icon = "${data.coder_workspace.me.access_url}/icon/vim.svg" command = "vim" } - -resource "coder_app" "intellij" { - agent_id = coder_agent.dev.id - icon = "${data.coder_workspace.me.access_url}/icon/intellij.svg" - slug = "intellij" - display_name = "JetBrains IntelliJ" - command = "projector run" -} ``` diff --git a/examples/resources/coder_app/resource.tf b/examples/resources/coder_app/resource.tf index 20fd8b41..9345dfc5 100644 --- a/examples/resources/coder_app/resource.tf +++ b/examples/resources/coder_app/resource.tf @@ -32,11 +32,3 @@ resource "coder_app" "vim" { icon = "${data.coder_workspace.me.access_url}/icon/vim.svg" command = "vim" } - -resource "coder_app" "intellij" { - agent_id = coder_agent.dev.id - icon = "${data.coder_workspace.me.access_url}/icon/intellij.svg" - slug = "intellij" - display_name = "JetBrains IntelliJ" - command = "projector run" -} From 3cc9802d8c2ac8a6254b1c7242fbc9e19756bc46 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 30 Apr 2024 05:58:43 +0300 Subject: [PATCH 010/114] chore: add a minimal nix flake (#216) --- README.md | 11 ++++++++++ flake.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 29 ++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/README.md b/README.md index 151a7299..d4b48fac 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,17 @@ Terraform provider for [Coder](https://github.com/coder/coder). ### Developing +#### Prerequisites + +- [Go](https://golang.org/doc/install) +- [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) + +We recommend using [`nix`](https://nixos.org/download.html) to manage your development environment. If you have `nix` installed, you can run `nix develop` to enter a shell with all the necessary dependencies. + +Alternatively, you can install the dependencies manually. + +#### Building + Follow the instructions outlined in the [Terraform documentation](https://developer.hashicorp.com/terraform/cli/config/config-file#development-overrides-for-provider-developers) to setup your local Terraform to use your local version rather than the registry version. diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..d8033e14 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1714272655, + "narHash": "sha256-3/ghIWCve93ngkx5eNPdHIKJP/pMzSr5Wc4rNKE1wOc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "12430e43bd9b81a6b4e79e64f87c624ade701eaf", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..87719bf4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,29 @@ +{ + description = "Terraform provider for Coder"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + }; + }; + in + { + devShell = pkgs.mkShell { + name = "devShell"; + buildInputs = with pkgs; [ + terraform + go_1_20 + ]; + }; + } + ); +} From 8fa0fbf8655a58e973a93bbe2e69bb86bca3bb55 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 13 May 2024 11:06:48 -0400 Subject: [PATCH 011/114] fix: add test case to validate empty provider (#206) --- provider/provider_test.go | 34 ++++++++++++++++++++++++++++++++++ provider/provisioner_test.go | 1 + 2 files changed, 35 insertions(+) diff --git a/provider/provider_test.go b/provider/provider_test.go index cd00f9a4..c1e3c686 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -3,6 +3,9 @@ package provider_test import ( "testing" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" "github.com/coder/terraform-provider-coder/provider" @@ -14,3 +17,34 @@ func TestProvider(t *testing.T) { err := tfProvider.InternalValidate() require.NoError(t, err) } + +// TestProviderEmpty ensures that the provider can be configured without +// any actual input data. This is important for adding new fields +// with backwards compatibility guarantees. +func TestProviderEmpty(t *testing.T) { + t.Parallel() + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_provisioner" "me" {} + data "coder_workspace" "me" {} + data "coder_external_auth" "git" { + id = "git" + } + data "coder_git_auth" "git" { + id = "git" + } + data "coder_parameter" "param" { + name = "hey" + }`, + Check: func(state *terraform.State) error { + return nil + }, + }}, + }) +} diff --git a/provider/provisioner_test.go b/provider/provisioner_test.go index e9f83e43..f1521ef9 100644 --- a/provider/provisioner_test.go +++ b/provider/provisioner_test.go @@ -12,6 +12,7 @@ import ( ) func TestProvisioner(t *testing.T) { + t.Parallel() resource.Test(t, resource.TestCase{ Providers: map[string]*schema.Provider{ "coder": provider.New(), From 041e000b50a7b697db9d58d9cfc08c6ef5f3b9af Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 16 May 2024 17:29:09 +0200 Subject: [PATCH 012/114] feat: support workspace tags (#223) --- .../coder_workspace_tags/resource.tf | 50 +++++++++++++++++++ provider/examples_test.go | 28 +++++++---- provider/provider.go | 11 ++-- provider/workspace_tags.go | 32 ++++++++++++ provider/workspace_tags_test.go | 48 ++++++++++++++++++ 5 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 examples/resources/coder_workspace_tags/resource.tf create mode 100644 provider/workspace_tags.go create mode 100644 provider/workspace_tags_test.go diff --git a/examples/resources/coder_workspace_tags/resource.tf b/examples/resources/coder_workspace_tags/resource.tf new file mode 100644 index 00000000..6c26b061 --- /dev/null +++ b/examples/resources/coder_workspace_tags/resource.tf @@ -0,0 +1,50 @@ +provider "coder" {} + +data "coder_parameter" "os_selector" { + name = "os_selector" + display_name = "Operating System" + mutable = false + + default = "osx" + + option { + icon = "/icons/linux.png" + name = "Linux" + value = "linux" + } + option { + icon = "/icons/osx.png" + name = "OSX" + value = "osx" + } + option { + icon = "/icons/windows.png" + name = "Windows" + value = "windows" + } +} + +data "coder_parameter" "feature_cache_enabled" { + name = "feature_cache_enabled" + display_name = "Enable cache?" + type = "bool" + + default = false +} + +data "coder_parameter" "feature_debug_enabled" { + name = "feature_debug_enabled" + display_name = "Enable debug?" + type = "bool" + + default = true +} + +data "coder_workspace_tags" "custom_workspace_tags" { + tags = { + "cluster" = "developers" + "os" = data.coder_parameter.os_selector.value + "debug" = "${data.coder_parameter.feature_debug_enabled.value}+12345" + "cache" = data.coder_parameter.feature_cache_enabled.value == "true" ? "nix-with-cache" : "no-cache" + } +} \ No newline at end of file diff --git a/provider/examples_test.go b/provider/examples_test.go index 9be7ce02..6fa73d21 100644 --- a/provider/examples_test.go +++ b/provider/examples_test.go @@ -14,19 +14,25 @@ import ( func TestExamples(t *testing.T) { t.Parallel() - t.Run("coder_parameter", func(t *testing.T) { - t.Parallel() + for _, testDir := range []string{ + "coder_parameter", + "coder_workspace_tags", + } { + t.Run(testDir, func(t *testing.T) { + testDir := testDir + t.Parallel() - resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, - Steps: []resource.TestStep{{ - Config: mustReadFile(t, "../examples/resources/coder_parameter/resource.tf"), - }}, + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: mustReadFile(t, "../examples/resources/"+testDir+"/resource.tf"), + }}, + }) }) - }) + } } func mustReadFile(t *testing.T, path string) string { diff --git a/provider/provider.go b/provider/provider.go index fb262c8c..4ea75ba8 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -68,11 +68,12 @@ func New() *schema.Provider { }, nil }, DataSourcesMap: map[string]*schema.Resource{ - "coder_workspace": workspaceDataSource(), - "coder_provisioner": provisionerDataSource(), - "coder_parameter": parameterDataSource(), - "coder_git_auth": gitAuthDataSource(), - "coder_external_auth": externalAuthDataSource(), + "coder_workspace": workspaceDataSource(), + "coder_workspace_tags": workspaceTagDataSource(), + "coder_provisioner": provisionerDataSource(), + "coder_parameter": parameterDataSource(), + "coder_git_auth": gitAuthDataSource(), + "coder_external_auth": externalAuthDataSource(), }, ResourcesMap: map[string]*schema.Resource{ "coder_agent": agentResource(), diff --git a/provider/workspace_tags.go b/provider/workspace_tags.go new file mode 100644 index 00000000..088ff546 --- /dev/null +++ b/provider/workspace_tags.go @@ -0,0 +1,32 @@ +package provider + +import ( + "context" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type WorkspaceTags struct { + Tags map[string]string +} + +func workspaceTagDataSource() *schema.Resource { + return &schema.Resource{ + Description: "Use this data source to configure workspace tags to select provisioners.", + ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { + rd.SetId(uuid.NewString()) + return nil + }, + Schema: map[string]*schema.Schema{ + "tags": { + Type: schema.TypeMap, + Description: `Key-value map with workspace tags`, + ForceNew: true, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} diff --git a/provider/workspace_tags_test.go b/provider/workspace_tags_test.go new file mode 100644 index 00000000..2d0f1c49 --- /dev/null +++ b/provider/workspace_tags_test.go @@ -0,0 +1,48 @@ +package provider_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/require" + + "github.com/coder/terraform-provider-coder/provider" +) + +func TestWorkspaceTags(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + data "coder_parameter" "animal" { + name = "animal" + type = "string" + default = "chris" + } + data "coder_workspace_tags" "wt" { + tags = { + "cat" = "james" + "dog" = data.coder_parameter.animal.value + } + }`, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 2) + resource := state.Modules[0].Resources["data.coder_workspace_tags.wt"] + require.NotNil(t, resource) + + attribs := resource.Primary.Attributes + require.Equal(t, "james", attribs["tags.cat"]) + require.Equal(t, "chris", attribs["tags.dog"]) + return nil + }, + }}, + }) +} From 4f9201481404d78754711bc5fb1982d56a869e4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 16:05:51 +0300 Subject: [PATCH 013/114] build(deps): Bump contributor-assistant/github-action from 2.3.2 to 2.4.0 (#221) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cla.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index db910ed8..412cac6b 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -11,7 +11,7 @@ jobs: 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.3.2 + 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 From d921c8319d4d9108489bde8703d3fc5425923052 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 12:20:00 +0300 Subject: [PATCH 014/114] build(deps): Bump goreleaser/goreleaser-action from 5.0.0 to 5.1.0 (#225) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c21c4ac..00fd83ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5.0.0 + uses: goreleaser/goreleaser-action@v5.1.0 with: version: latest args: release --rm-dist From 2642fa5e14d1a0bf87b9ce4abc4a4db5f209d307 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 23 May 2024 12:45:44 +0100 Subject: [PATCH 015/114] chore: add devcontainer config (#228) --- .devcontainer/devcontainer.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..fa152f4d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "terraform-provider-coder", + "image": "mcr.microsoft.com/devcontainers/go:1.20", + "features": { + "ghcr.io/devcontainers/features/terraform:1": { + "installTerraformDocs": true + } + } +} From c683ad5b3888469cc26d625b6c890f70a022a024 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 23 May 2024 12:45:53 +0100 Subject: [PATCH 016/114] chore: add terraform 1.8.x to test matrix (#229) --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5830a500..fefaf2f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,6 +54,7 @@ jobs: - "1.5.*" - "1.6.*" - "1.7.*" + - "1.8.*" steps: - name: Set up Go uses: actions/setup-go@v5 From 7e5a28b99dfea0f40526b0bedd61aac39c1b3d9e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 24 May 2024 14:45:08 +0100 Subject: [PATCH 017/114] chore: make gen to add missing docs (#231) --- docs/data-sources/workspace_tags.md | 24 +++++++++++++++++++ docs/resources/metadata.md | 7 ++++++ examples/resources/coder_metadata/resource.tf | 7 ++++++ 3 files changed, 38 insertions(+) create mode 100644 docs/data-sources/workspace_tags.md diff --git a/docs/data-sources/workspace_tags.md b/docs/data-sources/workspace_tags.md new file mode 100644 index 00000000..62f72b0f --- /dev/null +++ b/docs/data-sources/workspace_tags.md @@ -0,0 +1,24 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coder_workspace_tags Data Source - terraform-provider-coder" +subcategory: "" +description: |- + Use this data source to configure workspace tags to select provisioners. +--- + +# coder_workspace_tags (Data Source) + +Use this data source to configure workspace tags to select provisioners. + + + + +## Schema + +### Optional + +- `tags` (Map of String) Key-value map with workspace tags + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/resources/metadata.md b/docs/resources/metadata.md index 2d67e526..9d6ff92f 100644 --- a/docs/resources/metadata.md +++ b/docs/resources/metadata.md @@ -18,6 +18,13 @@ data "coder_workspace" "me" { resource "kubernetes_pod" "dev" { count = data.coder_workspace.me.start_count + metadata { + name = "k8s_example" + namespace = "example" + } + spec { + # Draw the rest of the pod! + } } resource "tls_private_key" "example_key_pair" { diff --git a/examples/resources/coder_metadata/resource.tf b/examples/resources/coder_metadata/resource.tf index c4facfbb..0491ce57 100644 --- a/examples/resources/coder_metadata/resource.tf +++ b/examples/resources/coder_metadata/resource.tf @@ -3,6 +3,13 @@ data "coder_workspace" "me" { resource "kubernetes_pod" "dev" { count = data.coder_workspace.me.start_count + metadata { + name = "k8s_example" + namespace = "example" + } + spec { + # Draw the rest of the pod! + } } resource "tls_private_key" "example_key_pair" { From eb43b9f95bbbb54af72496b3a72f05c6dfffacbf Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 24 May 2024 14:48:20 +0100 Subject: [PATCH 018/114] feat: add coder_workspace_owner datasource (#230) - Adds a coder_workspace_owner data source populated from fields of coder_workspace prefix with `owner_`. - Adds `coder_workspace_owner.ssh_{public,private}_key`. - Deprecates same fields of coder_workspace. --- docs/data-sources/workspace.md | 14 +-- docs/data-sources/workspace_owner.md | 28 ++++++ provider/provider.go | 13 +-- provider/workspace.go | 7 ++ provider/workspace_owner.go | 127 +++++++++++++++++++++++++++ provider/workspace_owner_test.go | 119 +++++++++++++++++++++++++ provider/workspace_test.go | 4 + 7 files changed, 299 insertions(+), 13 deletions(-) create mode 100644 docs/data-sources/workspace_owner.md create mode 100644 provider/workspace_owner.go create mode 100644 provider/workspace_owner_test.go diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index 2ed7c63d..2a813722 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -30,13 +30,13 @@ resource "kubernetes_pod" "dev" { - `access_url` (String) The access URL of the Coder deployment provisioning this workspace. - `id` (String) UUID of the workspace. - `name` (String) Name of the workspace. -- `owner` (String) Username of the workspace owner. -- `owner_email` (String) Email address of the workspace owner. -- `owner_groups` (List of String) List of groups the workspace owner belongs to. -- `owner_id` (String) UUID of the workspace owner. -- `owner_name` (String) Name of the workspace owner. -- `owner_oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. -- `owner_session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. +- `owner` (String, Deprecated) Username of the workspace owner. +- `owner_email` (String, Deprecated) Email address of the workspace owner. +- `owner_groups` (List of String, Deprecated) List of groups the workspace owner belongs to. +- `owner_id` (String, Deprecated) UUID of the workspace owner. +- `owner_name` (String, Deprecated) Name of the workspace owner. +- `owner_oidc_access_token` (String, Deprecated) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. +- `owner_session_token` (String, Deprecated) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. - `start_count` (Number) A computed count based on "transition" state. If "start", count will equal 1. - `template_id` (String) ID of the workspace's template. - `template_name` (String) Name of the workspace's template. diff --git a/docs/data-sources/workspace_owner.md b/docs/data-sources/workspace_owner.md new file mode 100644 index 00000000..646b1340 --- /dev/null +++ b/docs/data-sources/workspace_owner.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coder_workspace_owner Data Source - terraform-provider-coder" +subcategory: "" +description: |- + Use this data source to fetch information about the workspace owner. +--- + +# coder_workspace_owner (Data Source) + +Use this data source to fetch information about the workspace owner. + + + + +## Schema + +### Read-Only + +- `email` (String) The email address of the user. +- `full_name` (String) The full name of the user. +- `groups` (List of String) The groups of which the user is a member. +- `id` (String) The UUID of the workspace owner. +- `name` (String) The username of the user. +- `oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. +- `session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started. +- `ssh_private_key` (String, Sensitive) The user's generated SSH private key. +- `ssh_public_key` (String) The user's generated SSH public key. diff --git a/provider/provider.go b/provider/provider.go index 4ea75ba8..c1991a26 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -68,12 +68,13 @@ func New() *schema.Provider { }, nil }, DataSourcesMap: map[string]*schema.Resource{ - "coder_workspace": workspaceDataSource(), - "coder_workspace_tags": workspaceTagDataSource(), - "coder_provisioner": provisionerDataSource(), - "coder_parameter": parameterDataSource(), - "coder_git_auth": gitAuthDataSource(), - "coder_external_auth": externalAuthDataSource(), + "coder_workspace": workspaceDataSource(), + "coder_workspace_tags": workspaceTagDataSource(), + "coder_provisioner": provisionerDataSource(), + "coder_parameter": parameterDataSource(), + "coder_git_auth": gitAuthDataSource(), + "coder_external_auth": externalAuthDataSource(), + "coder_workspace_owner": workspaceOwnerDataSource(), }, ResourcesMap: map[string]*schema.Resource{ "coder_agent": agentResource(), diff --git a/provider/workspace.go b/provider/workspace.go index b8ff1684..098d64cc 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -135,21 +135,25 @@ func workspaceDataSource() *schema.Resource { Type: schema.TypeString, Computed: true, Description: "Username of the workspace owner.", + Deprecated: "Use `coder_workspace_owner.name` instead.", }, "owner_email": { Type: schema.TypeString, Computed: true, Description: "Email address of the workspace owner.", + Deprecated: "Use `coder_workspace_owner.email` instead.", }, "owner_id": { Type: schema.TypeString, Computed: true, Description: "UUID of the workspace owner.", + Deprecated: "Use `coder_workspace_owner.id` instead.", }, "owner_name": { Type: schema.TypeString, Computed: true, Description: "Name of the workspace owner.", + Deprecated: "Use `coder_workspace_owner.full_name` instead.", }, "owner_oidc_access_token": { Type: schema.TypeString, @@ -157,6 +161,7 @@ func workspaceDataSource() *schema.Resource { Description: "A valid OpenID Connect access token of the workspace owner. " + "This is only available if the workspace owner authenticated with OpenID Connect. " + "If a valid token cannot be obtained, this value will be an empty string.", + Deprecated: "Use `coder_workspace_owner.oidc_access_token` instead.", }, "owner_groups": { Type: schema.TypeList, @@ -165,6 +170,7 @@ func workspaceDataSource() *schema.Resource { }, Computed: true, Description: "List of groups the workspace owner belongs to.", + Deprecated: "Use `coder_workspace_owner.groups` instead.", }, "id": { Type: schema.TypeString, @@ -180,6 +186,7 @@ func workspaceDataSource() *schema.Resource { Type: schema.TypeString, Computed: true, Description: "Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.", + Deprecated: "Use `coder_workspace_owner.session_token` instead.", }, "template_id": { Type: schema.TypeString, diff --git a/provider/workspace_owner.go b/provider/workspace_owner.go new file mode 100644 index 00000000..5721b5c5 --- /dev/null +++ b/provider/workspace_owner.go @@ -0,0 +1,127 @@ +package provider + +import ( + "context" + "encoding/json" + "os" + "strings" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type Role struct { + Name string `json:"name"` + DisplayName string `json:"display-name"` +} + +func workspaceOwnerDataSource() *schema.Resource { + return &schema.Resource{ + Description: "Use this data source to fetch information about the workspace owner.", + ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { + if idStr, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_ID"); ok { + rd.SetId(idStr) + } else { + rd.SetId(uuid.NewString()) + } + + if username, ok := os.LookupEnv("CODER_WORKSPACE_OWNER"); ok { + _ = rd.Set("name", username) + } else { + _ = rd.Set("name", "default") + } + + if fullname, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_NAME"); ok { + _ = rd.Set("full_name", fullname) + } else { // compat: field can be blank, fill in default + _ = rd.Set("full_name", "default") + } + + if email, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_EMAIL"); ok { + _ = rd.Set("email", email) + } else { + _ = rd.Set("email", "default@example.com") + } + + if sshPubKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY"); ok { + _ = rd.Set("ssh_public_key", sshPubKey) + } + + if sshPrivKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY"); ok { + _ = rd.Set("ssh_private_key", sshPrivKey) + } + + var groups []string + if groupsRaw, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_GROUPS"); ok { + if err := json.NewDecoder(strings.NewReader(groupsRaw)).Decode(&groups); err != nil { + return diag.Errorf("invalid user groups: %s", err.Error()) + } + _ = rd.Set("groups", groups) + } + + if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN"); ok { + _ = rd.Set("session_token", tok) + } + + if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN"); ok { + _ = rd.Set("oidc_access_token", tok) + } + + return nil + }, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the workspace owner.", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The username of the user.", + }, + "full_name": { + Type: schema.TypeString, + Computed: true, + Description: "The full name of the user.", + }, + "email": { + Type: schema.TypeString, + Computed: true, + Description: "The email address of the user.", + }, + "ssh_public_key": { + Type: schema.TypeString, + Computed: true, + Description: "The user's generated SSH public key.", + }, + "ssh_private_key": { + Type: schema.TypeString, + Computed: true, + Description: "The user's generated SSH private key.", + Sensitive: true, + }, + "groups": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + Description: "The groups of which the user is a member.", + }, + "session_token": { + Type: schema.TypeString, + Computed: true, + Description: "Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started.", + }, + "oidc_access_token": { + Type: schema.TypeString, + Computed: true, + Description: "A valid OpenID Connect access token of the workspace owner. " + + "This is only available if the workspace owner authenticated with OpenID Connect. " + + "If a valid token cannot be obtained, this value will be an empty string.", + }, + }, + } +} diff --git a/provider/workspace_owner_test.go b/provider/workspace_owner_test.go new file mode 100644 index 00000000..90839cfc --- /dev/null +++ b/provider/workspace_owner_test.go @@ -0,0 +1,119 @@ +package provider_test + +import ( + "os" + "testing" + + "github.com/coder/terraform-provider-coder/provider" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testSSHEd25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJeNcdBMtd4Jo9f2W8RZef0ld7Ypye5zTQEf0vUXa/Eq owner123@host456` + // nolint:gosec // This key was generated specifically for this purpose. + testSSHEd25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY----- + b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW + QyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKgAAAJgp3mfQKd5n + 0AAAAAtzc2gtZWQyNTUxOQAAACCXjXHQTLXeCaPX9lvEWXn9JXe2Kcnuc00BH9L1F2vxKg + AAAEBia7mAQFoLBILlvTJroTkOUomzfcPY9ckpViQOjYFkAZeNcdBMtd4Jo9f2W8RZef0l + d7Ypye5zTQEf0vUXa/EqAAAAE3ZzY29kZUAzY2Y4MWY5YmM3MmQBAg== + -----END OPENSSH PRIVATE KEY-----` +) + +func TestWorkspaceOwnerDatasource(t *testing.T) { + t.Run("OK", func(t *testing.T) { + t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111") + t.Setenv("CODER_WORKSPACE_OWNER", "owner123") + t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner") + t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com") + t.Setenv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY", testSSHEd25519PublicKey) + t.Setenv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY", testSSHEd25519PrivateKey) + t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) + t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", `supersecret`) + t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", `alsosupersecret`) + + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_workspace_owner" "me" {} + `, + Check: func(s *terraform.State) error { + require.Len(t, s.Modules, 1) + require.Len(t, s.Modules[0].Resources, 1) + resource := s.Modules[0].Resources["data.coder_workspace_owner.me"] + require.NotNil(t, resource) + + attrs := resource.Primary.Attributes + assert.Equal(t, "11111111-1111-1111-1111-111111111111", attrs["id"]) + assert.Equal(t, "owner123", attrs["name"]) + assert.Equal(t, "Mr Owner", attrs["full_name"]) + assert.Equal(t, "owner123@example.com", attrs["email"]) + assert.Equal(t, testSSHEd25519PublicKey, attrs["ssh_public_key"]) + assert.Equal(t, testSSHEd25519PrivateKey, attrs["ssh_private_key"]) + assert.Equal(t, `group1`, attrs["groups.0"]) + assert.Equal(t, `group2`, attrs["groups.1"]) + assert.Equal(t, `supersecret`, attrs["session_token"]) + assert.Equal(t, `alsosupersecret`, attrs["oidc_access_token"]) + return nil + }, + }}, + }) + }) + + t.Run("Defaults", func(t *testing.T) { + for _, v := range []string{ + "CODER_WORKSPACE_OWNER", + "CODER_WORKSPACE_OWNER_ID", + "CODER_WORKSPACE_OWNER_EMAIL", + "CODER_WORKSPACE_OWNER_NAME", + "CODER_WORKSPACE_OWNER_SESSION_TOKEN", + "CODER_WORKSPACE_OWNER_GROUPS", + "CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", + "CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY", + "CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY", + } { // https://github.com/golang/go/issues/52817 + t.Setenv(v, "") + os.Unsetenv(v) + } + + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_workspace_owner" "me" {} + `, + Check: func(s *terraform.State) error { + require.Len(t, s.Modules, 1) + require.Len(t, s.Modules[0].Resources, 1) + resource := s.Modules[0].Resources["data.coder_workspace_owner.me"] + require.NotNil(t, resource) + + attrs := resource.Primary.Attributes + assert.NotEmpty(t, attrs["id"]) + assert.Equal(t, "default", attrs["name"]) + assert.Equal(t, "default", attrs["full_name"]) + assert.Equal(t, "default@example.com", attrs["email"]) + assert.Empty(t, attrs["ssh_public_key"]) + assert.Empty(t, attrs["ssh_private_key"]) + assert.Empty(t, attrs["groups.0"]) + assert.Empty(t, attrs["session_token"]) + assert.Empty(t, attrs["oidc_access_token"]) + return nil + }, + }}, + }) + }) +} diff --git a/provider/workspace_test.go b/provider/workspace_test.go index d5866af5..d285b30c 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -14,10 +14,12 @@ import ( func TestWorkspace(t *testing.T) { t.Setenv("CODER_WORKSPACE_OWNER", "owner123") + t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111") t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner") t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com") t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123") t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) + t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", "supersecret") t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID") t.Setenv("CODER_WORKSPACE_TEMPLATE_NAME", "template123") t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") @@ -47,6 +49,7 @@ func TestWorkspace(t *testing.T) { assert.Equal(t, "https://example.com:8080", attribs["access_url"]) assert.Equal(t, "8080", attribs["access_port"]) assert.Equal(t, "owner123", attribs["owner"]) + assert.Equal(t, "11111111-1111-1111-1111-111111111111", attribs["owner_id"]) assert.Equal(t, "Mr Owner", attribs["owner_name"]) assert.Equal(t, "owner123@example.com", attribs["owner_email"]) assert.Equal(t, "group1", attribs["owner_groups.0"]) @@ -54,6 +57,7 @@ func TestWorkspace(t *testing.T) { assert.Equal(t, "templateID", attribs["template_id"]) assert.Equal(t, "template123", attribs["template_name"]) assert.Equal(t, "v1.2.3", attribs["template_version"]) + assert.Equal(t, "supersecret", attribs["owner_oidc_access_token"]) return nil }, }}, From 96993eb480472411a8bfb198893d4a8752a38a94 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 27 May 2024 09:30:48 +0100 Subject: [PATCH 019/114] fix: provider: coder_workspace_owner: avoid setting null values (#232) * chore: add coder_workspace_owner to TestProviderEmpty, rm unused struct * fix: do not set null values --- provider/provider_test.go | 1 + provider/workspace_owner.go | 33 +++++++++------------------------ 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/provider/provider_test.go b/provider/provider_test.go index c1e3c686..7069db09 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -33,6 +33,7 @@ func TestProviderEmpty(t *testing.T) { provider "coder" {} data "coder_provisioner" "me" {} data "coder_workspace" "me" {} + data "coder_workspace_owner" "me" {} data "coder_external_auth" "git" { id = "git" } diff --git a/provider/workspace_owner.go b/provider/workspace_owner.go index 5721b5c5..13e36187 100644 --- a/provider/workspace_owner.go +++ b/provider/workspace_owner.go @@ -11,62 +11,47 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -type Role struct { - Name string `json:"name"` - DisplayName string `json:"display-name"` -} - func workspaceOwnerDataSource() *schema.Resource { return &schema.Resource{ Description: "Use this data source to fetch information about the workspace owner.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { - if idStr, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_ID"); ok { + if idStr := os.Getenv("CODER_WORKSPACE_OWNER_ID"); idStr != "" { rd.SetId(idStr) } else { rd.SetId(uuid.NewString()) } - if username, ok := os.LookupEnv("CODER_WORKSPACE_OWNER"); ok { + if username := os.Getenv("CODER_WORKSPACE_OWNER"); username != "" { _ = rd.Set("name", username) } else { _ = rd.Set("name", "default") } - if fullname, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_NAME"); ok { + if fullname := os.Getenv("CODER_WORKSPACE_OWNER_NAME"); fullname != "" { _ = rd.Set("full_name", fullname) } else { // compat: field can be blank, fill in default _ = rd.Set("full_name", "default") } - if email, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_EMAIL"); ok { + if email := os.Getenv("CODER_WORKSPACE_OWNER_EMAIL"); email != "" { _ = rd.Set("email", email) } else { _ = rd.Set("email", "default@example.com") } - if sshPubKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY"); ok { - _ = rd.Set("ssh_public_key", sshPubKey) - } - - if sshPrivKey, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY"); ok { - _ = rd.Set("ssh_private_key", sshPrivKey) - } + _ = rd.Set("ssh_public_key", os.Getenv("CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY")) + _ = rd.Set("ssh_private_key", os.Getenv("CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY")) var groups []string if groupsRaw, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_GROUPS"); ok { if err := json.NewDecoder(strings.NewReader(groupsRaw)).Decode(&groups); err != nil { return diag.Errorf("invalid user groups: %s", err.Error()) } - _ = rd.Set("groups", groups) - } - - if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN"); ok { - _ = rd.Set("session_token", tok) } + _ = rd.Set("groups", groups) - if tok, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN"); ok { - _ = rd.Set("oidc_access_token", tok) - } + _ = rd.Set("session_token", os.Getenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN")) + _ = rd.Set("oidc_access_token", os.Getenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN")) return nil }, From 4f417ad002cd7620d5f830dcbd5831865cb51946 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 28 May 2024 15:34:21 +0100 Subject: [PATCH 020/114] chore: update to go1.22 (#234) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 6 +++--- go.mod | 4 +++- go.sum | 6 ++++++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fa152f4d..1a56c6f8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "terraform-provider-coder", - "image": "mcr.microsoft.com/devcontainers/go:1.20", + "image": "mcr.microsoft.com/devcontainers/go:1.22", "features": { "ghcr.io/devcontainers/features/terraform:1": { "installTerraformDocs": true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00fd83ec..ad8d96c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.20.6 + go-version: 1.22.3 - name: Import GPG key id: import_gpg diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fefaf2f5..2a4a6bce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.20" + go-version: "1.22" id: go - name: Check out code into the Go module directory @@ -59,7 +59,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.20" + go-version: "1.22" id: go - uses: hashicorp/setup-terraform@v3 @@ -89,7 +89,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.20" + go-version: "1.22" id: go - uses: hashicorp/setup-terraform@v3 diff --git a/go.mod b/go.mod index 7af7f224..b7c3b071 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/coder/terraform-provider-coder -go 1.20 +go 1.22 + +toolchain go1.22.3 require ( github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 93ed263d..01bac480 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= @@ -35,6 +36,7 @@ github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6 github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -102,6 +104,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -115,6 +118,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -136,6 +140,7 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= +github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -151,6 +156,7 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From df757123402eb41ab4e26ca0a58123ab87d679bb Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 4 Jun 2024 10:44:00 +0100 Subject: [PATCH 021/114] chore: add integration test for provider (#233) This PR adds integration tests for the provider against an arbitrary version of Coder. Example usage: ``` CODER_VERSION="v2.10.0-devel-3a9a7d199-amd64" go test -v ./integration Testing methodology: ``` - Create an ephemeral Coder instance running in a container - Perform initial setup - Import a predefined template that creates a single file - Create a workspace that populates the file with the actual output - Compare the actual versus expected output --- Makefile | 14 +- README.md | 51 ++++-- go.mod | 41 ++++- go.sum | 127 ++++++++++++-- integration/integration_test.go | 247 +++++++++++++++++++++++++++ integration/test-data-source/main.tf | 65 +++++++ 6 files changed, 506 insertions(+), 39 deletions(-) create mode 100644 integration/integration_test.go create mode 100644 integration/test-data-source/main.tf diff --git a/Makefile b/Makefile index d694a040..43c93a83 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,19 @@ fmt: gen: go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest +build: terraform-provider-coder + +# Builds the provider. Note that as coder/coder is based on +# alpine, we need to disable cgo. +terraform-provider-coder: provider/*.go main.go + CGO_ENABLED=0 go build . + +# Run integration tests +.PHONY: test-integration +test-integration: terraform-provider-coder + go test -v ./integration + # Run acceptance tests .PHONY: testacc testacc: - TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m \ No newline at end of file + TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m diff --git a/README.md b/README.md index d4b48fac..2bceb73a 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ to setup your local Terraform to use your local version rather than the registry 1. Create a file named `.terraformrc` in your `$HOME` directory 2. Add the following content: + ```hcl provider_installation { # Override the coder/coder provider to use your local version @@ -33,18 +34,46 @@ to setup your local Terraform to use your local version rather than the registry direct {} } ``` + 3. (optional, but recommended) Validate your configuration: - 1. Create a new `main.tf` file and include: - ```hcl - terraform { - required_providers { - coder = { - source = "coder/coder" - } - } - } - ``` + 1. Create a new `main.tf` file and include: + ```hcl + terraform { + required_providers { + coder = { + source = "coder/coder" + } + } + } + ``` 2. Run `terraform init` and observe a warning like `Warning: Provider development overrides are in effect` 4. Run `go build -o terraform-provider-coder` to build the provider binary, which Terraform will try locate and execute 5. All local Terraform runs will now use your local provider! -6. _**NOTE**: we vendor in this provider into `github.com/coder/coder`, so if you're testing with a local clone then you should also run `go mod edit -replace github.com/coder/terraform-provider-coder=/path/to/terraform-provider-coder` in your clone._ \ No newline at end of file +6. _**NOTE**: we vendor in this provider into `github.com/coder/coder`, so if you're testing with a local clone then you should also run `go mod edit -replace github.com/coder/terraform-provider-coder=/path/to/terraform-provider-coder` in your clone._ + +#### Terraform Acceptance Tests + +To run Terraform acceptance tests, run `make testacc`. This will test the provider against the locally installed version of Terraform. + +> **Note:** our [CI workflow](./github/workflows/test.yml) runs a test matrix against multiple Terraform versions. + +#### Integration Tests + +The tests under the `./integration` directory perform the following steps: + +- Build the local version of the provider, +- Run an in-memory Coder instance with a specified version, +- Validate the behaviour of the local provider against that specific version of Coder. + +To run these integration tests locally: + +1. Pull the version of the Coder image you wish to test: + + ```console + docker pull ghcr.io/coder/coder:main-x.y.z-devel-abcd1234 + ``` + +1. Run `CODER_VERSION=main-x.y.z-devel-abcd1234 make test-integration`. + +> **Note:** you can specify `CODER_IMAGE` if the Coder image you wish to test is hosted somewhere other than `ghcr.io/coder/coder`. +> For example, `CODER_IMAGE=example.com/repo/coder CODER_VERSION=foobar make test-integration`. diff --git a/go.mod b/go.mod index b7c3b071..9eb4c7d3 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22 toolchain go1.22.3 require ( + github.com/docker/docker v26.1.3+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 @@ -18,10 +19,18 @@ require ( github.com/Microsoft/go-winio v0.5.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -47,20 +56,34 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/oklog/run v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/zclconf/go-cty v1.10.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/appengine v1.6.7 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 01bac480..0919b1c8 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= @@ -17,14 +19,28 @@ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJE github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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 v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo= +github.com/docker/docker v26.1.3+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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -35,23 +51,33 @@ github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -107,6 +133,8 @@ github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZ github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -138,13 +166,24 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 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/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -159,6 +198,8 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -177,41 +218,76 @@ github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37w github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= +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/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -221,32 +297,45 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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/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= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -262,3 +351,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/integration/integration_test.go b/integration/integration_test.go new file mode 100644 index 00000000..f2c55ed8 --- /dev/null +++ b/integration/integration_test.go @@ -0,0 +1,247 @@ +package integration + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestIntegration performs an integration test against an ephemeral Coder deployment. +// For each directory containing a `main.tf` under `/integration`, performs the following: +// - Pushes the template to a temporary Coder instance running in Docker +// - Creates a workspace from the template. Templates here are expected to create a +// local_file resource containing JSON that can be marshalled as a map[string]string +// - Fetches the content of the JSON file created and compares it against the expected output. +// +// NOTE: all interfaces to this Coder deployment are performed without github.com/coder/coder/v2/codersdk +// in order to avoid a circular dependency. +func TestIntegration(t *testing.T) { + if os.Getenv("TF_ACC") == "1" { + t.Skip("Skipping integration tests during tf acceptance tests") + } + + timeoutStr := os.Getenv("TIMEOUT_MINS") + if timeoutStr == "" { + timeoutStr = "10" + } + timeoutMins, err := strconv.Atoi(timeoutStr) + require.NoError(t, err, "invalid value specified for timeout") + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMins)*time.Minute) + t.Cleanup(cancel) + + // Given: we have an existing Coder deployment running locally + ctrID := setup(ctx, t) + + for _, tt := range []struct { + // Name of the folder under `integration/` containing a test template + templateName string + // map of string to regex to be passed to assertOutput() + expectedOutput map[string]string + }{ + { + templateName: "test-data-source", + expectedOutput: map[string]string{ + "provisioner.arch": runtime.GOARCH, + "provisioner.id": `[a-zA-Z0-9-]+`, + "provisioner.os": runtime.GOOS, + "workspace.access_port": `\d+`, + "workspace.access_url": `https?://\D+:\d+`, + "workspace.id": `[a-zA-z0-9-]+`, + "workspace.name": `test-data-source`, + "workspace.owner": `testing`, + "workspace.owner_email": `testing@coder\.com`, + "workspace.owner_groups": `\[\]`, + "workspace.owner_id": `[a-zA-Z0-9]+`, + "workspace.owner_name": `default`, + "workspace.owner_oidc_access_token": `^$`, // TODO: need a test OIDC integration + "workspace.owner_session_token": `[a-zA-Z0-9-]+`, + "workspace.start_count": `1`, + "workspace.template_id": `[a-zA-Z0-9-]+`, + "workspace.template_name": `test-data-source`, + "workspace.template_version": `.+`, + "workspace.transition": `start`, + "workspace_owner.email": `testing@coder\.com`, + "workspace_owner.full_name": `default`, + "workspace_owner.groups": `\[\]`, + "workspace_owner.id": `[a-zA-Z0-9-]+`, + "workspace_owner.name": `testing`, + "workspace_owner.oidc_access_token": `^$`, // TODO: test OIDC integration + "workspace_owner.session_token": `.+`, + "workspace_owner.ssh_private_key": `^$`, // Depends on coder/coder#13366 + "workspace_owner.ssh_public_key": `^$`, // Depends on coder/coder#13366 + }, + }, + } { + t.Run(tt.templateName, func(t *testing.T) { + // Import named template + _, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates push %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, tt.templateName, tt.templateName, tt.templateName)) + require.Equal(t, 0, rc) + // Create a workspace + _, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`coder create %s -t %s --yes`, tt.templateName, tt.templateName)) + require.Equal(t, 0, rc) + // Fetch the output created by the template + out, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`cat /tmp/%s.json`, tt.templateName)) + require.Equal(t, 0, rc) + actual := make(map[string]string) + require.NoError(t, json.NewDecoder(strings.NewReader(out)).Decode(&actual)) + assertOutput(t, tt.expectedOutput, actual) + }) + } +} + +func setup(ctx context.Context, t *testing.T) string { + var ( + // For this test to work, we pass in a custom terraformrc to use + // the locally built version of the provider. + testTerraformrc = `provider_installation { + dev_overrides { + "coder/coder" = "/src" + } + direct{} + }` + localURL = "http://localhost:3000" + ) + + coderImg := os.Getenv("CODER_IMAGE") + if coderImg == "" { + coderImg = "ghcr.io/coder/coder" + } + + coderVersion := os.Getenv("CODER_VERSION") + if coderVersion == "" { + coderVersion = "latest" + } + + t.Logf("using coder image %s:%s", coderImg, coderVersion) + + // Ensure the binary is built + binPath, err := filepath.Abs("../terraform-provider-coder") + require.NoError(t, err) + if _, err := os.Stat(binPath); os.IsNotExist(err) { + t.Fatalf("not found: %q - please build the provider first", binPath) + } + tmpDir := t.TempDir() + // Create a terraformrc to point to our freshly built provider! + tfrcPath := filepath.Join(tmpDir, "integration.tfrc") + err = os.WriteFile(tfrcPath, []byte(testTerraformrc), 0o644) + require.NoError(t, err, "write terraformrc to tempdir") + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err, "init docker client") + + srcPath, err := filepath.Abs("..") + require.NoError(t, err, "get abs path of parent") + t.Logf("src path is %s\n", srcPath) + + // Stand up a temporary Coder instance + ctr, err := cli.ContainerCreate(ctx, &container.Config{ + Image: coderImg + ":" + coderVersion, + Env: []string{ + "CODER_ACCESS_URL=" + localURL, // 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. + "TF_CLI_CONFIG_FILE=/tmp/integration.tfrc", // Our custom tfrc from above. + }, + Labels: map[string]string{}, + }, &container.HostConfig{ + Binds: []string{ + tfrcPath + ":/tmp/integration.tfrc", // Custom tfrc from above. + srcPath + ":/src", // Bind-mount in the repo with the built binary and templates. + }, + }, nil, nil, "") + require.NoError(t, err, "create test deployment") + + t.Logf("created container %s\n", ctr.ID) + t.Cleanup(func() { // Make sure we clean up after ourselves. + // TODO: also have this execute if you Ctrl+C! + t.Logf("stopping container %s\n", ctr.ID) + _ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{ + Force: true, + }) + }) + + err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{}) + require.NoError(t, err, "start container") + t.Logf("started container %s\n", ctr.ID) + + // nolint:gosec // For testing only. + var ( + testEmail = "testing@coder.com" + testPassword = "InsecurePassw0rd!" + testUsername = "testing" + ) + + // Wait for container to come up + require.Eventually(t, func() bool { + _, rc := execContainer(ctx, t, ctr.ID, fmt.Sprintf(`curl -s --fail %s/api/v2/buildinfo`, localURL)) + if rc == 0 { + return true + } + t.Logf("not ready yet...") + return false + }, 10*time.Second, time.Second, "coder failed to become ready in time") + + // Perform first time setup + _, rc := execContainer(ctx, t, ctr.ID, fmt.Sprintf(`coder login %s --first-user-email=%q --first-user-password=%q --first-user-trial=false --first-user-username=%q`, localURL, testEmail, testPassword, testUsername)) + require.Equal(t, 0, rc, "failed to perform first-time setup") + return ctr.ID +} + +// execContainer executes the given command in the given container and returns +// the output and the exit code of the command. +func execContainer(ctx context.Context, t *testing.T, containerID, command string) (string, int) { + t.Helper() + t.Logf("exec container cmd: %q", command) + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + require.NoError(t, err, "connect to docker") + defer cli.Close() + execConfig := types.ExecConfig{ + AttachStdout: true, + AttachStderr: true, + Cmd: []string{"/bin/sh", "-c", command}, + } + ex, err := cli.ContainerExecCreate(ctx, containerID, execConfig) + require.NoError(t, err, "create container exec") + resp, err := cli.ContainerExecAttach(ctx, ex.ID, types.ExecStartCheck{}) + require.NoError(t, err, "attach to container exec") + defer resp.Close() + var buf bytes.Buffer + _, err = stdcopy.StdCopy(&buf, &buf, resp.Reader) + require.NoError(t, err, "read stdout") + out := buf.String() + t.Log("exec container output:\n" + out) + execResp, err := cli.ContainerExecInspect(ctx, ex.ID) + require.NoError(t, err, "get exec exit code") + return out, execResp.ExitCode +} + +// assertOutput asserts that, for each key-value pair in expected: +// 1. actual[k] as a regex matches expected[k], and +// 2. the set of keys of expected are not a subset of actual. +func assertOutput(t *testing.T, expected, actual map[string]string) { + t.Helper() + + for expectedKey, expectedValExpr := range expected { + actualVal := actual[expectedKey] + assert.Regexp(t, expectedValExpr, actualVal) + } + for actualKey := range actual { + _, ok := expected[actualKey] + assert.True(t, ok, "unexpected field in actual %q=%q", actualKey, actual[actualKey]) + } +} diff --git a/integration/test-data-source/main.tf b/integration/test-data-source/main.tf new file mode 100644 index 00000000..580592cb --- /dev/null +++ b/integration/test-data-source/main.tf @@ -0,0 +1,65 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + local = { + source = "hashicorp/local" + } + } +} + +// TODO: test coder_external_auth and coder_git_auth +// data coder_external_auth "me" {} +// data coder_git_auth "me" {} +data "coder_provisioner" "me" {} +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +locals { + # NOTE: these must all be strings in the output + output = { + "provisioner.arch" : data.coder_provisioner.me.arch, + "provisioner.id" : data.coder_provisioner.me.id, + "provisioner.os" : data.coder_provisioner.me.os, + "workspace.access_port" : tostring(data.coder_workspace.me.access_port), + "workspace.access_url" : data.coder_workspace.me.access_url, + "workspace.id" : data.coder_workspace.me.id, + "workspace.name" : data.coder_workspace.me.name, + "workspace.owner" : data.coder_workspace.me.owner, + "workspace.owner_email" : data.coder_workspace.me.owner_email, + "workspace.owner_groups" : jsonencode(data.coder_workspace.me.owner_groups), + "workspace.owner_id" : data.coder_workspace.me.owner_id, + "workspace.owner_name" : data.coder_workspace.me.owner_name, + "workspace.owner_oidc_access_token" : data.coder_workspace.me.owner_oidc_access_token, + "workspace.owner_session_token" : data.coder_workspace.me.owner_session_token, + "workspace.start_count" : tostring(data.coder_workspace.me.start_count), + "workspace.template_id" : data.coder_workspace.me.template_id, + "workspace.template_name" : data.coder_workspace.me.template_name, + "workspace.template_version" : data.coder_workspace.me.template_version, + "workspace.transition" : data.coder_workspace.me.transition, + "workspace_owner.email" : data.coder_workspace_owner.me.email, + "workspace_owner.full_name" : data.coder_workspace_owner.me.full_name, + "workspace_owner.groups" : jsonencode(data.coder_workspace_owner.me.groups), + "workspace_owner.id" : data.coder_workspace_owner.me.id, + "workspace_owner.name" : data.coder_workspace_owner.me.name, + "workspace_owner.oidc_access_token" : data.coder_workspace_owner.me.oidc_access_token, + "workspace_owner.session_token" : data.coder_workspace_owner.me.session_token, + "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, + "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, + } +} + +variable "output_path" { + type = string +} + +resource "local_file" "output" { + filename = var.output_path + content = jsonencode(local.output) +} + +output "output" { + value = local.output + sensitive = true +} From ecdd884abc896f50dd82a7898830c440952b443e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:34:36 +0100 Subject: [PATCH 022/114] build(deps): Bump github.com/docker/docker (#239) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.1.3+incompatible to 26.1.4+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v26.1.3...v26.1.4) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9eb4c7d3..625aae19 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 toolchain go1.22.3 require ( - github.com/docker/docker v26.1.3+incompatible + github.com/docker/docker v26.1.4+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 diff --git a/go.sum b/go.sum index 0919b1c8..d07d5ff3 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/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 v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo= -github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= +github.com/docker/docker v26.1.4+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= From 62fa89d7ed61b00c175f3dc17d70b3fcd3fd0e40 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 10 Jun 2024 09:38:41 +0100 Subject: [PATCH 023/114] ci: run integration test (#240) --- .github/workflows/test.yml | 10 +++++++++ integration/integration_test.go | 37 ++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a4a6bce..217c065f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,9 +33,19 @@ jobs: go mod download - name: Build + env: + CGO_ENABLED: "0" run: | go build -v . + - name: Run integration test + timeout-minutes: 10 + env: + CODER_IMAGE: "ghcr.io/coder/coder" + CODER_VERSION: "latest" + run: | + go test -v ./integration + # run acceptance tests in a matrix with Terraform core versions test: name: Matrix Test diff --git a/integration/integration_test.go b/integration/integration_test.go index f2c55ed8..1c2046be 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "io" "os" "path/filepath" "runtime" @@ -15,6 +16,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/docker/docker/pkg/stdcopy" "github.com/stretchr/testify/assert" @@ -44,17 +46,14 @@ func TestIntegration(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMins)*time.Minute) t.Cleanup(cancel) - // Given: we have an existing Coder deployment running locally - ctrID := setup(ctx, t) - for _, tt := range []struct { // Name of the folder under `integration/` containing a test template - templateName string + name string // map of string to regex to be passed to assertOutput() expectedOutput map[string]string }{ { - templateName: "test-data-source", + name: "test-data-source", expectedOutput: map[string]string{ "provisioner.arch": runtime.GOARCH, "provisioner.id": `[a-zA-Z0-9-]+`, @@ -82,20 +81,22 @@ func TestIntegration(t *testing.T) { "workspace_owner.name": `testing`, "workspace_owner.oidc_access_token": `^$`, // TODO: test OIDC integration "workspace_owner.session_token": `.+`, - "workspace_owner.ssh_private_key": `^$`, // Depends on coder/coder#13366 - "workspace_owner.ssh_public_key": `^$`, // Depends on coder/coder#13366 + "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, + "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, }, }, } { - t.Run(tt.templateName, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { + // Given: we have an existing Coder deployment running locally + ctrID := setup(ctx, t, tt.name) // Import named template - _, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates push %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, tt.templateName, tt.templateName, tt.templateName)) + _, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates push %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, tt.name, tt.name, tt.name)) require.Equal(t, 0, rc) // Create a workspace - _, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`coder create %s -t %s --yes`, tt.templateName, tt.templateName)) + _, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`coder create %s -t %s --yes`, tt.name, tt.name)) require.Equal(t, 0, rc) // Fetch the output created by the template - out, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`cat /tmp/%s.json`, tt.templateName)) + out, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`cat /tmp/%s.json`, tt.name)) require.Equal(t, 0, rc) actual := make(map[string]string) require.NoError(t, json.NewDecoder(strings.NewReader(out)).Decode(&actual)) @@ -104,7 +105,7 @@ func TestIntegration(t *testing.T) { } } -func setup(ctx context.Context, t *testing.T) string { +func setup(ctx context.Context, t *testing.T, name string) string { var ( // For this test to work, we pass in a custom terraformrc to use // the locally built version of the provider. @@ -148,9 +149,17 @@ func setup(ctx context.Context, t *testing.T) string { require.NoError(t, err, "get abs path of parent") t.Logf("src path is %s\n", srcPath) + // Ensure the image is available locally. + refStr := coderImg + ":" + coderVersion + t.Logf("ensuring image %q", refStr) + resp, err := cli.ImagePull(ctx, refStr, image.PullOptions{}) + require.NoError(t, err) + _, err = io.ReadAll(resp) + require.NoError(t, err) + // Stand up a temporary Coder instance ctr, err := cli.ContainerCreate(ctx, &container.Config{ - Image: coderImg + ":" + coderVersion, + Image: refStr, Env: []string{ "CODER_ACCESS_URL=" + localURL, // Set explicitly to avoid creating try.coder.app URLs. "CODER_IN_MEMORY=true", // We don't necessarily care about real persistence here. @@ -163,7 +172,7 @@ func setup(ctx context.Context, t *testing.T) string { tfrcPath + ":/tmp/integration.tfrc", // Custom tfrc from above. srcPath + ":/src", // Bind-mount in the repo with the built binary and templates. }, - }, nil, nil, "") + }, nil, nil, "terraform-provider-coder-integration-"+name) require.NoError(t, err, "create test deployment") t.Logf("created container %s\n", ctr.ID) From d51a4a7c793d326ae80cb40ad3b18919fcf2ab02 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 25 Jun 2024 14:52:58 +0200 Subject: [PATCH 024/114] feat: annotate resources with`SchemaVersion` (#244) --- .gitignore | 5 ++- integration/integration_test.go | 2 ++ provider/agent.go | 7 +++-- provider/app.go | 2 ++ provider/env.go | 2 ++ provider/externalauth.go | 7 +++-- provider/gitauth.go | 7 +++-- provider/helpers/env.go | 31 +++++++++++++++++++ provider/metadata.go | 2 ++ provider/parameter.go | 2 ++ provider/provisioner.go | 2 ++ provider/script.go | 2 ++ provider/workspace.go | 54 +++++++++++---------------------- provider/workspace_tags.go | 2 ++ 14 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 provider/helpers/env.go diff --git a/.gitignore b/.gitignore index e5a80c26..4d5d5ad6 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ website/vendor !command/test-fixtures/**/.terraform/ # Keep windows files with windows line endings -*.winfile eol=crlf \ No newline at end of file +*.winfile eol=crlf + +# Binary +terraform-provider-coder diff --git a/integration/integration_test.go b/integration/integration_test.go index 1c2046be..cf58b99e 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -164,7 +164,9 @@ func setup(ctx context.Context, t *testing.T, name string) string { "CODER_ACCESS_URL=" + localURL, // 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. + "CODER_VERBOSE=TRUE", // Debug logging. "TF_CLI_CONFIG_FILE=/tmp/integration.tfrc", // Our custom tfrc from above. + "TF_LOG=DEBUG", // Debug logging in Terraform provider }, Labels: map[string]string{}, }, &container.HostConfig{ diff --git a/provider/agent.go b/provider/agent.go index 7b197870..0ff5ca21 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -3,7 +3,6 @@ package provider import ( "context" "fmt" - "os" "reflect" "strings" @@ -12,10 +11,14 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "golang.org/x/xerrors" + + "github.com/coder/terraform-provider-coder/provider/helpers" ) func agentResource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this resource to associate an agent.", CreateContext: func(_ context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { // This should be a real authentication token! @@ -363,7 +366,7 @@ func updateInitScript(resourceData *schema.ResourceData, i interface{}) diag.Dia if err != nil { return diag.Errorf("parse access url: %s", err) } - script := os.Getenv(fmt.Sprintf("CODER_AGENT_SCRIPT_%s_%s", operatingSystem, arch)) + script := helpers.OptionalEnv(fmt.Sprintf("CODER_AGENT_SCRIPT_%s_%s", operatingSystem, arch)) if script != "" { script = strings.ReplaceAll(script, "${ACCESS_URL}", accessURL.String()) script = strings.ReplaceAll(script, "${AUTH_TYPE}", auth) diff --git a/provider/app.go b/provider/app.go index ed0ac53e..c2690311 100644 --- a/provider/app.go +++ b/provider/app.go @@ -25,6 +25,8 @@ var ( func appResource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this resource to define shortcuts to access applications in a workspace.", CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { resourceData.SetId(uuid.NewString()) diff --git a/provider/env.go b/provider/env.go index 66d03a22..8f55ff8c 100644 --- a/provider/env.go +++ b/provider/env.go @@ -12,6 +12,8 @@ import ( func envResource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: `Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the "coder_agent" resource.`, CreateContext: func(_ context.Context, rd *schema.ResourceData, _ interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) diff --git a/provider/externalauth.go b/provider/externalauth.go index 31dadd66..13c85fab 100644 --- a/provider/externalauth.go +++ b/provider/externalauth.go @@ -3,15 +3,18 @@ package provider import ( "context" "fmt" - "os" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/coder/terraform-provider-coder/provider/helpers" ) // externalAuthDataSource returns a schema for an external authentication data source. func externalAuthDataSource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this data source to require users to authenticate with an external service prior to workspace creation. This can be used to pre-authenticate external services in a workspace. (e.g. gcloud, gh, docker, etc)", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { id, ok := rd.Get("id").(string) @@ -20,7 +23,7 @@ func externalAuthDataSource() *schema.Resource { } rd.SetId(id) - accessToken := os.Getenv(ExternalAuthAccessTokenEnvironmentVariable(id)) + accessToken := helpers.OptionalEnv(ExternalAuthAccessTokenEnvironmentVariable(id)) rd.Set("access_token", accessToken) return nil }, diff --git a/provider/gitauth.go b/provider/gitauth.go index aa36d493..72c05bcd 100644 --- a/provider/gitauth.go +++ b/provider/gitauth.go @@ -3,15 +3,18 @@ package provider import ( "context" "fmt" - "os" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/coder/terraform-provider-coder/provider/helpers" ) // gitAuthDataSource returns a schema for a Git authentication data source. func gitAuthDataSource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + DeprecationMessage: "Use the `coder_external_auth` data source instead.", Description: "Use this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated `git clone` in startup scripts.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { @@ -25,7 +28,7 @@ func gitAuthDataSource() *schema.Resource { } rd.SetId(id) - accessToken := os.Getenv(GitAuthAccessTokenEnvironmentVariable(id)) + accessToken := helpers.OptionalEnv(GitAuthAccessTokenEnvironmentVariable(id)) rd.Set("access_token", accessToken) return nil diff --git a/provider/helpers/env.go b/provider/helpers/env.go new file mode 100644 index 00000000..b1007820 --- /dev/null +++ b/provider/helpers/env.go @@ -0,0 +1,31 @@ +package helpers + +import ( + "fmt" + "os" +) + +// RequireEnv requires environment variable to be present. +func RequireEnv(name string) (string, error) { + val := os.Getenv(name) + if val == "" { + return "", fmt.Errorf("%s is required", name) + } + return val, nil +} + +// OptionalEnv returns the value for environment variable if it exists, +// otherwise returns an empty string. +func OptionalEnv(name string) string { + return OptionalEnvOrDefault(name, "") +} + +// OptionalEnvOrDefault returns the value for environment variable if it exists, +// otherwise returns the default value. +func OptionalEnvOrDefault(name, defaultValue string) string { + val := os.Getenv(name) + if val == "" { + return defaultValue + } + return val +} diff --git a/provider/metadata.go b/provider/metadata.go index 3bd5e6f5..00b488e4 100644 --- a/provider/metadata.go +++ b/provider/metadata.go @@ -11,6 +11,8 @@ import ( func metadataResource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this resource to attach metadata to a resource. They will be " + "displayed in the Coder dashboard.", CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { diff --git a/provider/parameter.go b/provider/parameter.go index 12dbc019..d0f71dab 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -63,6 +63,8 @@ type Parameter struct { func parameterDataSource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this data source to configure editable options for workspaces.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) diff --git a/provider/provisioner.go b/provider/provisioner.go index 314e89cd..9d356798 100644 --- a/provider/provisioner.go +++ b/provider/provisioner.go @@ -11,6 +11,8 @@ import ( func provisionerDataSource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this data source to get information about the Coder provisioner.", ReadContext: func(c context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) diff --git a/provider/script.go b/provider/script.go index 4a05440e..1474dbd2 100644 --- a/provider/script.go +++ b/provider/script.go @@ -15,6 +15,8 @@ var ScriptCRONParser = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cr func scriptResource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this resource to run a script from an agent.", CreateContext: func(_ context.Context, rd *schema.ResourceData, _ interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) diff --git a/provider/workspace.go b/provider/workspace.go index 098d64cc..f06e9e1e 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -3,44 +3,38 @@ package provider import ( "context" "encoding/json" - "os" "reflect" "strconv" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/coder/terraform-provider-coder/provider/helpers" ) func workspaceDataSource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this data source to get information for the active workspace build.", ReadContext: func(c context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { - transition := os.Getenv("CODER_WORKSPACE_TRANSITION") - if transition == "" { - // Default to start! - transition = "start" - } + transition := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_TRANSITION", "start") // Default to start! _ = rd.Set("transition", transition) + count := 0 if transition == "start" { count = 1 } _ = rd.Set("start_count", count) - owner := os.Getenv("CODER_WORKSPACE_OWNER") - if owner == "" { - owner = "default" - } + owner := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER", "default") _ = rd.Set("owner", owner) - ownerEmail := os.Getenv("CODER_WORKSPACE_OWNER_EMAIL") - if ownerEmail == "" { - ownerEmail = "default@example.com" - } + ownerEmail := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER_EMAIL", "default@example.com") _ = rd.Set("owner_email", ownerEmail) - ownerGroupsText := os.Getenv("CODER_WORKSPACE_OWNER_GROUPS") + ownerGroupsText := helpers.OptionalEnv("CODER_WORKSPACE_OWNER_GROUPS") var ownerGroups []string if ownerGroupsText != "" { err := json.Unmarshal([]byte(ownerGroupsText), &ownerGroups) @@ -50,43 +44,31 @@ func workspaceDataSource() *schema.Resource { } _ = rd.Set("owner_groups", ownerGroups) - ownerName := os.Getenv("CODER_WORKSPACE_OWNER_NAME") - if ownerName == "" { - ownerName = "default" - } + ownerName := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER_NAME", "default") _ = rd.Set("owner_name", ownerName) - ownerID := os.Getenv("CODER_WORKSPACE_OWNER_ID") - if ownerID == "" { - ownerID = uuid.Nil.String() - } + ownerID := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER_ID", uuid.Nil.String()) _ = rd.Set("owner_id", ownerID) - ownerOIDCAccessToken := os.Getenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN") + ownerOIDCAccessToken := helpers.OptionalEnv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN") _ = rd.Set("owner_oidc_access_token", ownerOIDCAccessToken) - name := os.Getenv("CODER_WORKSPACE_NAME") - if name == "" { - name = "default" - } + name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default") rd.Set("name", name) - sessionToken := os.Getenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN") + sessionToken := helpers.OptionalEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN") _ = rd.Set("owner_session_token", sessionToken) - id := os.Getenv("CODER_WORKSPACE_ID") - if id == "" { - id = uuid.NewString() - } + id := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_ID", uuid.NewString()) rd.SetId(id) - templateID := os.Getenv("CODER_WORKSPACE_TEMPLATE_ID") + templateID := helpers.OptionalEnv("CODER_WORKSPACE_TEMPLATE_ID") // FIXME switch to `helpers.RequireEnv(...)` _ = rd.Set("template_id", templateID) - templateName := os.Getenv("CODER_WORKSPACE_TEMPLATE_NAME") + templateName := helpers.OptionalEnv("CODER_WORKSPACE_TEMPLATE_NAME") // FIXME switch to `helpers.RequireEnv(...)` _ = rd.Set("template_name", templateName) - templateVersion := os.Getenv("CODER_WORKSPACE_TEMPLATE_VERSION") + templateVersion := helpers.OptionalEnv("CODER_WORKSPACE_TEMPLATE_VERSION") // FIXME switch to `helpers.RequireEnv(...)` _ = rd.Set("template_version", templateVersion) config, valid := i.(config) diff --git a/provider/workspace_tags.go b/provider/workspace_tags.go index 088ff546..09736d02 100644 --- a/provider/workspace_tags.go +++ b/provider/workspace_tags.go @@ -14,6 +14,8 @@ type WorkspaceTags struct { func workspaceTagDataSource() *schema.Resource { return &schema.Resource{ + SchemaVersion: 1, + Description: "Use this data source to configure workspace tags to select provisioners.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) From b73066b8638522272627b780277512014a842992 Mon Sep 17 00:00:00 2001 From: Stephen Kirby <58410745+stirby@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:48:36 -0500 Subject: [PATCH 025/114] chore(docs): move coder_parameter example (#243) * moved coder_parameter * swapped test target, renamed parameter example file * make gen * removed coder agent examples from test * attempting data source testing * added git auth to test * reverted git auth test * Refactor examples_test.go * took a stab at expanding example test * added provider to coder_ap * manually reverted test changes --------- Co-authored-by: Marcin Tojek --- docs/data-sources/git_auth.md | 3 +- docs/data-sources/parameter.md | 122 +++++++++++++++++- docs/data-sources/workspace_tags.md | 53 ++++++++ .../coder_git_auth/data-source.tf | 3 +- .../coder_parameter/data-source.tf} | 0 .../coder_workspace_tags/data-source.tf} | 0 provider/examples_test.go | 24 ++-- 7 files changed, 190 insertions(+), 15 deletions(-) rename examples/{resources/coder_parameter/resource.tf => data-sources/coder_parameter/data-source.tf} (100%) rename examples/{resources/coder_workspace_tags/resource.tf => data-sources/coder_workspace_tags/data-source.tf} (100%) diff --git a/docs/data-sources/git_auth.md b/docs/data-sources/git_auth.md index 53e01981..fe64805e 100644 --- a/docs/data-sources/git_auth.md +++ b/docs/data-sources/git_auth.md @@ -13,8 +13,7 @@ Use this data source to require users to authenticate with a Git provider prior ## Example Usage ```terraform -provider "coder" { -} +provider "coder" {} data "coder_git_auth" "github" { # Matches the ID of the git auth provider in Coder. diff --git a/docs/data-sources/parameter.md b/docs/data-sources/parameter.md index 4bded2d3..9c6a11f5 100644 --- a/docs/data-sources/parameter.md +++ b/docs/data-sources/parameter.md @@ -10,7 +10,127 @@ description: |- Use this data source to configure editable options for workspaces. - +## Example Usage + +```terraform +provider "coder" {} + +data "coder_parameter" "example" { + name = "Region" + description = "Specify a region to place your workspace." + mutable = false + type = "string" + default = "asia-central1-a" + option { + value = "us-central1-a" + name = "US Central" + icon = "/icon/usa.svg" + } + option { + value = "asia-central1-a" + name = "Asia" + icon = "/icon/asia.svg" + } +} + +data "coder_parameter" "ami" { + name = "Machine Image" + description = <<-EOT + # Provide the machine image + See the [registry](https://container.registry.blah/namespace) for options. + EOT + option { + value = "ami-xxxxxxxx" + name = "Ubuntu" + icon = "/icon/ubuntu.svg" + } +} + +data "coder_parameter" "is_public_instance" { + name = "Is public instance?" + type = "bool" + icon = "/icon/docker.svg" + default = false +} + +data "coder_parameter" "cores" { + name = "CPU Cores" + type = "number" + icon = "/icon/cpu.svg" + default = 3 + order = 10 +} + +data "coder_parameter" "disk_size" { + name = "Disk Size" + type = "number" + default = "5" + order = 8 + validation { + # This can apply to number. + min = 0 + max = 10 + monotonic = "increasing" + } +} + +data "coder_parameter" "cat_lives" { + name = "Cat Lives" + type = "number" + default = "9" + validation { + # This can apply to number. + min = 0 + max = 10 + monotonic = "decreasing" + } +} + +data "coder_parameter" "fairy_tale" { + name = "Fairy Tale" + type = "string" + mutable = true + default = "Hansel and Gretel" + ephemeral = true +} + +data "coder_parameter" "users" { + name = "system_users" + display_name = "System users" + type = "list(string)" + default = jsonencode(["root", "user1", "user2"]) +} + +data "coder_parameter" "home_volume_size" { + name = "Home Volume Size" + description = <<-EOF + How large should your home volume be? + EOF + type = "number" + default = 30 + mutable = true + order = 3 + + option { + name = "30GB" + value = 30 + } + + option { + name = "60GB" + value = 60 + } + + option { + name = "100GB" + value = 100 + } + + validation { + monotonic = "increasing" + } +} +``` ## Schema diff --git a/docs/data-sources/workspace_tags.md b/docs/data-sources/workspace_tags.md index 62f72b0f..010adfe3 100644 --- a/docs/data-sources/workspace_tags.md +++ b/docs/data-sources/workspace_tags.md @@ -10,7 +10,60 @@ description: |- Use this data source to configure workspace tags to select provisioners. +## Example Usage +```terraform +provider "coder" {} + +data "coder_parameter" "os_selector" { + name = "os_selector" + display_name = "Operating System" + mutable = false + + default = "osx" + + option { + icon = "/icons/linux.png" + name = "Linux" + value = "linux" + } + option { + icon = "/icons/osx.png" + name = "OSX" + value = "osx" + } + option { + icon = "/icons/windows.png" + name = "Windows" + value = "windows" + } +} + +data "coder_parameter" "feature_cache_enabled" { + name = "feature_cache_enabled" + display_name = "Enable cache?" + type = "bool" + + default = false +} + +data "coder_parameter" "feature_debug_enabled" { + name = "feature_debug_enabled" + display_name = "Enable debug?" + type = "bool" + + default = true +} + +data "coder_workspace_tags" "custom_workspace_tags" { + tags = { + "cluster" = "developers" + "os" = data.coder_parameter.os_selector.value + "debug" = "${data.coder_parameter.feature_debug_enabled.value}+12345" + "cache" = data.coder_parameter.feature_cache_enabled.value == "true" ? "nix-with-cache" : "no-cache" + } +} +``` ## Schema diff --git a/examples/data-sources/coder_git_auth/data-source.tf b/examples/data-sources/coder_git_auth/data-source.tf index eeed89aa..488554f2 100644 --- a/examples/data-sources/coder_git_auth/data-source.tf +++ b/examples/data-sources/coder_git_auth/data-source.tf @@ -1,5 +1,4 @@ -provider "coder" { -} +provider "coder" {} data "coder_git_auth" "github" { # Matches the ID of the git auth provider in Coder. diff --git a/examples/resources/coder_parameter/resource.tf b/examples/data-sources/coder_parameter/data-source.tf similarity index 100% rename from examples/resources/coder_parameter/resource.tf rename to examples/data-sources/coder_parameter/data-source.tf diff --git a/examples/resources/coder_workspace_tags/resource.tf b/examples/data-sources/coder_workspace_tags/data-source.tf similarity index 100% rename from examples/resources/coder_workspace_tags/resource.tf rename to examples/data-sources/coder_workspace_tags/data-source.tf diff --git a/provider/examples_test.go b/provider/examples_test.go index 6fa73d21..ab68954b 100644 --- a/provider/examples_test.go +++ b/provider/examples_test.go @@ -1,13 +1,13 @@ package provider_test import ( + "fmt" "os" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/coder/terraform-provider-coder/provider" ) @@ -22,19 +22,23 @@ func TestExamples(t *testing.T) { testDir := testDir t.Parallel() - resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, - Steps: []resource.TestStep{{ - Config: mustReadFile(t, "../examples/resources/"+testDir+"/resource.tf"), - }}, - }) + resourceTest(t, testDir) }) } } +func resourceTest(t *testing.T, testDir string) { + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: mustReadFile(t, fmt.Sprintf("../examples/data-sources/%s/data-source.tf", testDir)), + }}, + }) +} + func mustReadFile(t *testing.T, path string) string { content, err := os.ReadFile(path) require.NoError(t, err) From dd5f55c82a7dca0179eb45b1e38aea7c81948c88 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 26 Jun 2024 11:09:31 +0200 Subject: [PATCH 026/114] feat: include deprecation message in docs (#245) --- Makefile | 1 + docs/data-sources/workspace.md | 14 +++--- docs/index.md | 2 +- docs/resources/agent.md | 6 +-- docs/resources/app.md | 4 +- scripts/docsgen/main.go | 89 ++++++++++++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 scripts/docsgen/main.go diff --git a/Makefile b/Makefile index 43c93a83..38a9a69d 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ fmt: gen: go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest + go run ./scripts/docsgen/... build: terraform-provider-coder diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index 2a813722..e9de5889 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -30,13 +30,13 @@ resource "kubernetes_pod" "dev" { - `access_url` (String) The access URL of the Coder deployment provisioning this workspace. - `id` (String) UUID of the workspace. - `name` (String) Name of the workspace. -- `owner` (String, Deprecated) Username of the workspace owner. -- `owner_email` (String, Deprecated) Email address of the workspace owner. -- `owner_groups` (List of String, Deprecated) List of groups the workspace owner belongs to. -- `owner_id` (String, Deprecated) UUID of the workspace owner. -- `owner_name` (String, Deprecated) Name of the workspace owner. -- `owner_oidc_access_token` (String, Deprecated) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. -- `owner_session_token` (String, Deprecated) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. +- `owner` (String, Deprecated: Use `coder_workspace_owner.name` instead.) Username of the workspace owner. +- `owner_email` (String, Deprecated: Use `coder_workspace_owner.email` instead.) Email address of the workspace owner. +- `owner_groups` (List of String, Deprecated: Use `coder_workspace_owner.groups` instead.) List of groups the workspace owner belongs to. +- `owner_id` (String, Deprecated: Use `coder_workspace_owner.id` instead.) UUID of the workspace owner. +- `owner_name` (String, Deprecated: Use `coder_workspace_owner.full_name` instead.) Name of the workspace owner. +- `owner_oidc_access_token` (String, Deprecated: Use `coder_workspace_owner.oidc_access_token` instead.) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. +- `owner_session_token` (String, Deprecated: Use `coder_workspace_owner.session_token` instead.) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. - `start_count` (Number) A computed count based on "transition" state. If "start", count will equal 1. - `template_id` (String) ID of the workspace's template. - `template_name` (String) Name of the workspace's template. diff --git a/docs/index.md b/docs/index.md index 13bf6b44..84843dec 100644 --- a/docs/index.md +++ b/docs/index.md @@ -62,5 +62,5 @@ resource "google_compute_instance" "dev" { ### Optional -- `feature_use_managed_variables` (Boolean, Deprecated) Feature: use managed Terraform variables. The feature flag is not used anymore as Terraform variables are now exclusively utilized for template-wide variables. +- `feature_use_managed_variables` (Boolean, Deprecated: Terraform variables are now exclusively utilized for template-wide variables after the removal of support for legacy parameters.) Feature: use managed Terraform variables. The feature flag is not used anymore as Terraform variables are now exclusively utilized for template-wide variables. - `url` (String) The URL to access Coder. \ No newline at end of file diff --git a/docs/resources/agent.md b/docs/resources/agent.md index 68963947..d20374d0 100644 --- a/docs/resources/agent.md +++ b/docs/resources/agent.md @@ -76,15 +76,15 @@ resource "kubernetes_pod" "dev" { - `dir` (String) The starting directory when a user creates a shell session. Defaults to $HOME. - `display_apps` (Block Set, Max: 1) The list of built-in apps to display in the agent bar. (see [below for nested schema](#nestedblock--display_apps)) - `env` (Map of String) A mapping of environment variables to set inside the workspace. -- `login_before_ready` (Boolean, Deprecated) This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the startup_script is done and has exited. When enabled, users may see an incomplete workspace when logging in. +- `login_before_ready` (Boolean, Deprecated: Configure startup_script_behavior instead. This attribute will be removed in a future version of the provider.) This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the startup_script is done and has exited. When enabled, users may see an incomplete workspace when logging in. - `metadata` (Block List) Each "metadata" block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases. (see [below for nested schema](#nestedblock--metadata)) - `motd_file` (String) The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be /etc/motd. - `order` (Number) The order determines the position of agents in the UI presentation. The lowest order is shown first and agents with equal order are sorted by name (ascending order). - `shutdown_script` (String) A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a "coder_script" resource with "run_on_stop" set to true. -- `shutdown_script_timeout` (Number, Deprecated) Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time. +- `shutdown_script_timeout` (Number, Deprecated: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time. - `startup_script` (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a "coder_script" resource with "run_on_start" set to true. - `startup_script_behavior` (String) This option sets the behavior of the "startup_script". When set to "blocking", the startup_script must exit before the workspace is ready. When set to "non-blocking", the startup_script may run in the background and the workspace will be ready immediately. Default is "non-blocking", although "blocking" is recommended. This option is an alias for defining a "coder_script" resource with "start_blocks_login" set to true (blocking). -- `startup_script_timeout` (Number, Deprecated) Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time. +- `startup_script_timeout` (Number, Deprecated: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time. - `troubleshooting_url` (String) A URL to a document with instructions for troubleshooting problems with the agent. ### Read-Only diff --git a/docs/resources/app.md b/docs/resources/app.md index aec85255..e9ca7b2f 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -64,9 +64,9 @@ resource "coder_app" "vim" { - `external` (Boolean) Specifies whether "url" is opened on the client machine instead of proxied through the workspace. - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. -- `name` (String, Deprecated) A display name to identify the app. +- `name` (String, Deprecated: `name` on apps is deprecated, use `display_name` instead) A display name to identify the app. - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). -- `relative_path` (Boolean, Deprecated) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to true. +- `relative_path` (Boolean, Deprecated: `relative_path` on apps is deprecated, use `subdomain` instead.) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to true. - `share` (String) Determines the "level" which the application is shared at. Valid levels are "owner" (default), "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. Defaults to false. - `url` (String) An external url if "external=true" or a URL to be proxied to from inside the workspace. This should be of the form "http://localhost:PORT[/SUBPATH]". Either "command" or "url" may be specified, but not both. diff --git a/scripts/docsgen/main.go b/scripts/docsgen/main.go new file mode 100644 index 00000000..90244ccd --- /dev/null +++ b/scripts/docsgen/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/coder/terraform-provider-coder/provider" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "golang.org/x/xerrors" +) + +// This script patches Markdown docs generated by `terraform-plugin-docs` to expose the original deprecation message. + +const docsDir = "docs" // FIXME expose as flag? + +var reDeprecatedProperty = regexp.MustCompile("`([^`]+)` \\(([^,\\)]+), Deprecated\\) ([^\n]+)") + +func main() { + p := provider.New() + err := exposeDeprecationMessage(p) + if err != nil { + log.Fatal(err) + } +} + +func exposeDeprecationMessage(p *schema.Provider) error { + // Patch data-sources + for dataSourceName, dataSource := range p.DataSourcesMap { + docFile := filepath.Join(docsDir, "data-sources", strings.Replace(dataSourceName, "coder_", "", 1)+".md") + + err := adjustDocFile(docFile, dataSource.Schema) + if err != nil { + return xerrors.Errorf("unable to adjust data-source doc file (data-source: %s): %w", dataSourceName, err) + } + } + + // Patch resources + for resourceName, resource := range p.ResourcesMap { + docFile := filepath.Join(docsDir, "resources", strings.Replace(resourceName, "coder_", "", 1)+".md") + + err := adjustDocFile(docFile, resource.Schema) + if err != nil { + return xerrors.Errorf("unable to adjust resource doc file (resource: %s): %w", resourceName, err) + } + } + + // Patch index + docFile := filepath.Join(docsDir, "index.md") + err := adjustDocFile(docFile, p.Schema) + if err != nil { + return xerrors.Errorf("unable to adjust index doc file: %w", err) + } + return nil +} + +func adjustDocFile(docPath string, schemas map[string]*schema.Schema) error { + doc, err := os.ReadFile(docPath) + if err != nil { + return xerrors.Errorf("can't read the source doc file: %w", err) + } + + result := writeDeprecationMessage(doc, schemas) + + err = os.WriteFile(docPath, result, 0644) + if err != nil { + return xerrors.Errorf("can't write modified doc file: %w", err) + } + return nil +} + +func writeDeprecationMessage(doc []byte, schemas map[string]*schema.Schema) []byte { + return reDeprecatedProperty.ReplaceAllFunc(doc, func(m []byte) []byte { + matches := reDeprecatedProperty.FindSubmatch(m) + propertyName := matches[1] + description := matches[3] + + sch := schemas[string(propertyName)] + if string(description) != sch.Description { + log.Printf("warn: same property name `%s` but description does not match, most likely a different property", propertyName) + return m + } + return bytes.Replace(m, []byte("Deprecated"), []byte(fmt.Sprintf("Deprecated: %s", sch.Deprecated)), 1) + }) +} From 9f66e78cd771cfff45413378367dda45d98ea2fe Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 27 Jun 2024 11:06:29 +0200 Subject: [PATCH 027/114] feat: require environment variables (#246) --- provider/helpers/env.go | 6 ++++++ provider/workspace.go | 15 ++++++++++++--- provider/workspace_test.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/provider/helpers/env.go b/provider/helpers/env.go index b1007820..6ffab3f6 100644 --- a/provider/helpers/env.go +++ b/provider/helpers/env.go @@ -6,7 +6,13 @@ import ( ) // RequireEnv requires environment variable to be present. +// The constraint can be verified only during execution of the workspace build +// (determined with env `CODER_WORKSPACE_BUILD_ID`). func RequireEnv(name string) (string, error) { + if os.Getenv("CODER_WORKSPACE_BUILD_ID") == "" { + return os.Getenv(name), nil + } + val := os.Getenv(name) if val == "" { return "", fmt.Errorf("%s is required", name) diff --git a/provider/workspace.go b/provider/workspace.go index f06e9e1e..d8bfd3a5 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -62,13 +62,22 @@ func workspaceDataSource() *schema.Resource { id := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_ID", uuid.NewString()) rd.SetId(id) - templateID := helpers.OptionalEnv("CODER_WORKSPACE_TEMPLATE_ID") // FIXME switch to `helpers.RequireEnv(...)` + templateID, err := helpers.RequireEnv("CODER_WORKSPACE_TEMPLATE_ID") + if err != nil { + return diag.Errorf("template ID is missing: %s", err.Error()) + } _ = rd.Set("template_id", templateID) - templateName := helpers.OptionalEnv("CODER_WORKSPACE_TEMPLATE_NAME") // FIXME switch to `helpers.RequireEnv(...)` + templateName, err := helpers.RequireEnv("CODER_WORKSPACE_TEMPLATE_NAME") + if err != nil { + return diag.Errorf("template name is missing: %s", err.Error()) + } _ = rd.Set("template_name", templateName) - templateVersion := helpers.OptionalEnv("CODER_WORKSPACE_TEMPLATE_VERSION") // FIXME switch to `helpers.RequireEnv(...)` + templateVersion, err := helpers.RequireEnv("CODER_WORKSPACE_TEMPLATE_VERSION") + if err != nil { + return diag.Errorf("template version is missing: %s", err.Error()) + } _ = rd.Set("template_version", templateVersion) config, valid := i.(config) diff --git a/provider/workspace_test.go b/provider/workspace_test.go index d285b30c..e53f30d4 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -1,6 +1,7 @@ package provider_test import ( + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -102,3 +103,34 @@ func TestWorkspace_UndefinedOwner(t *testing.T) { }}, }) } + +func TestWorkspace_MissingTemplateName(t *testing.T) { + t.Setenv("CODER_WORKSPACE_BUILD_ID", "1") // Let's pretend this is a workspace build + + t.Setenv("CODER_WORKSPACE_OWNER", "owner123") + t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111") + t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner") + t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com") + t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123") + t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) + t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", "supersecret") + t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID") + // CODER_WORKSPACE_TEMPLATE_NAME is missing + t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") + + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com:8080" + } + data "coder_workspace" "me" { + }`, + ExpectError: regexp.MustCompile("CODER_WORKSPACE_TEMPLATE_NAME is required"), + }}, + }) +} From e6ba29a45b948c117462d2509f1c680b22af294e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 27 Jun 2024 10:42:36 +0100 Subject: [PATCH 028/114] chore: improve integration test compatibility with older Coder versions (#247) Previously, testing with coder versions older than 2.7 would fail due to the template push command not creating the template automatically. Also, improves logic around pulling the image if it does not exist locally. --- go.mod | 2 ++ go.sum | 4 +++ integration/integration_test.go | 63 +++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 625aae19..b5e26e56 100644 --- a/go.mod +++ b/go.mod @@ -76,6 +76,8 @@ require ( go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index d07d5ff3..047612ff 100644 --- a/go.sum +++ b/go.sum @@ -252,9 +252,13 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/integration/integration_test.go b/integration/integration_test.go index cf58b99e..49b36456 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -21,6 +21,8 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" + "golang.org/x/mod/semver" ) // TestIntegration performs an integration test against an ephemeral Coder deployment. @@ -37,6 +39,16 @@ func TestIntegration(t *testing.T) { t.Skip("Skipping integration tests during tf acceptance tests") } + coderImg := os.Getenv("CODER_IMAGE") + if coderImg == "" { + coderImg = "ghcr.io/coder/coder" + } + + coderVersion := os.Getenv("CODER_VERSION") + if coderVersion == "" { + coderVersion = "latest" + } + timeoutStr := os.Getenv("TIMEOUT_MINS") if timeoutStr == "" { timeoutStr = "10" @@ -88,9 +100,17 @@ func TestIntegration(t *testing.T) { } { t.Run(tt.name, func(t *testing.T) { // Given: we have an existing Coder deployment running locally - ctrID := setup(ctx, t, tt.name) + ctrID := setup(ctx, t, tt.name, coderImg, coderVersion) // Import named template - _, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates push %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, tt.name, tt.name, tt.name)) + + // NOTE: Template create command was deprecated after this version + // ref: https://github.com/coder/coder/pull/11390 + templateCreateCmd := "push" + if semver.Compare(coderVersion, "v2.7.0") < 1 { + t.Logf("using now-deprecated templates create command for older coder version") + templateCreateCmd = "create" + } + _, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates %s %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, templateCreateCmd, tt.name, tt.name, tt.name)) require.Equal(t, 0, rc) // Create a workspace _, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`coder create %s -t %s --yes`, tt.name, tt.name)) @@ -105,7 +125,7 @@ func TestIntegration(t *testing.T) { } } -func setup(ctx context.Context, t *testing.T, name string) string { +func setup(ctx context.Context, t *testing.T, name, coderImg, coderVersion string) string { var ( // For this test to work, we pass in a custom terraformrc to use // the locally built version of the provider. @@ -118,16 +138,6 @@ func setup(ctx context.Context, t *testing.T, name string) string { localURL = "http://localhost:3000" ) - coderImg := os.Getenv("CODER_IMAGE") - if coderImg == "" { - coderImg = "ghcr.io/coder/coder" - } - - coderVersion := os.Getenv("CODER_VERSION") - if coderVersion == "" { - coderVersion = "latest" - } - t.Logf("using coder image %s:%s", coderImg, coderVersion) // Ensure the binary is built @@ -151,11 +161,7 @@ func setup(ctx context.Context, t *testing.T, name string) string { // Ensure the image is available locally. refStr := coderImg + ":" + coderVersion - t.Logf("ensuring image %q", refStr) - resp, err := cli.ImagePull(ctx, refStr, image.PullOptions{}) - require.NoError(t, err) - _, err = io.ReadAll(resp) - require.NoError(t, err) + ensureImage(ctx, t, cli, refStr) // Stand up a temporary Coder instance ctr, err := cli.ContainerCreate(ctx, &container.Config{ @@ -213,6 +219,25 @@ func setup(ctx context.Context, t *testing.T, name string) string { return ctr.ID } +func ensureImage(ctx context.Context, t *testing.T, cli *client.Client, ref string) { + t.Helper() + + t.Logf("ensuring image %q", ref) + images, err := cli.ImageList(ctx, image.ListOptions{}) + require.NoError(t, err, "list images") + for _, img := range images { + if slices.Contains(img.RepoTags, ref) { + t.Logf("image %q found locally, not pulling", ref) + return + } + } + t.Logf("image %s not found locally, attempting to pull", ref) + resp, err := cli.ImagePull(ctx, ref, image.PullOptions{}) + require.NoError(t, err) + _, err = io.ReadAll(resp) + require.NoError(t, err) +} + // execContainer executes the given command in the given container and returns // the output and the exit code of the command. func execContainer(ctx context.Context, t *testing.T, containerID, command string) (string, int) { @@ -249,7 +274,7 @@ func assertOutput(t *testing.T, expected, actual map[string]string) { for expectedKey, expectedValExpr := range expected { actualVal := actual[expectedKey] - assert.Regexp(t, expectedValExpr, actualVal) + assert.Regexp(t, expectedValExpr, actualVal, "output key %q does not have expected value", expectedKey) } for actualKey := range actual { _, ok := expected[actualKey] From 60401e6865b69ee49374c9800b561e58dd2dadd7 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 27 Jun 2024 12:15:14 +0100 Subject: [PATCH 029/114] ci: run integration test against mainline and stable versions (#248) - Adds `scripts/coderversion` to fetch stable and mainline versions - Adds `minVersion` constraint to integration tests - Runs integration tests against both stable and mainline versions - Adds separate integration test for fields introduced in v2.12.0 --- .github/workflows/test.yml | 14 +++- go.mod | 6 +- go.sum | 4 + integration/integration_test.go | 37 ++++++++- integration/test-data-source/main.tf | 9 --- integration/workspace-owner/main.tf | 65 ++++++++++++++++ scripts/coderversion/main.go | 109 +++++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 16 deletions(-) create mode 100644 integration/workspace-owner/main.tf create mode 100644 scripts/coderversion/main.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 217c065f..f17d1c10 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,13 +38,21 @@ jobs: run: | go build -v . - - name: Run integration test + - name: Run integration test (mainline) timeout-minutes: 10 env: CODER_IMAGE: "ghcr.io/coder/coder" - CODER_VERSION: "latest" run: | - go test -v ./integration + source <(go run ./scripts/coderversion) + CODER_VERSION="${CODER_MAINLINE_VERSION}" go test -v ./integration + + - name: Run integration test (stable) + timeout-minutes: 10 + env: + CODER_IMAGE: "ghcr.io/coder/coder" + run: | + source <(go run ./scripts/coderversion) + CODER_VERSION="${CODER_STABLE_VERSION}" go test -v ./integration # run acceptance tests in a matrix with Terraform core versions test: diff --git a/go.mod b/go.mod index b5e26e56..7e8aebe9 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,17 @@ require ( github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 + github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.9.0 + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 + golang.org/x/mod v0.18.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) require ( + github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect @@ -76,8 +80,6 @@ require ( go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index 047612ff..79ed9f85 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= @@ -147,6 +149,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U= +github.com/masterminds/semver v1.5.0/go.mod h1:s7KNT9fnd7edGzwwP7RBX4H0v/CYd5qdOLfkL1V75yg= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= diff --git a/integration/integration_test.go b/integration/integration_test.go index 49b36456..75b35d20 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -57,15 +57,19 @@ func TestIntegration(t *testing.T) { require.NoError(t, err, "invalid value specified for timeout") ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMins)*time.Minute) t.Cleanup(cancel) + ctrID := setup(ctx, t, t.Name(), coderImg, coderVersion) for _, tt := range []struct { // Name of the folder under `integration/` containing a test template name string + // Minimum coder version for which to run this test + minVersion string // map of string to regex to be passed to assertOutput() expectedOutput map[string]string }{ { - name: "test-data-source", + name: "test-data-source", + minVersion: "v0.0.0", expectedOutput: map[string]string{ "provisioner.arch": runtime.GOARCH, "provisioner.id": `[a-zA-Z0-9-]+`, @@ -86,6 +90,31 @@ func TestIntegration(t *testing.T) { "workspace.template_name": `test-data-source`, "workspace.template_version": `.+`, "workspace.transition": `start`, + }, + }, + { + name: "workspace-owner", + minVersion: "v2.12.0", + expectedOutput: map[string]string{ + "provisioner.arch": runtime.GOARCH, + "provisioner.id": `[a-zA-Z0-9-]+`, + "provisioner.os": runtime.GOOS, + "workspace.access_port": `\d+`, + "workspace.access_url": `https?://\D+:\d+`, + "workspace.id": `[a-zA-z0-9-]+`, + "workspace.name": ``, + "workspace.owner": `testing`, + "workspace.owner_email": `testing@coder\.com`, + "workspace.owner_groups": `\[\]`, + "workspace.owner_id": `[a-zA-Z0-9]+`, + "workspace.owner_name": `default`, + "workspace.owner_oidc_access_token": `^$`, // TODO: need a test OIDC integration + "workspace.owner_session_token": `[a-zA-Z0-9-]+`, + "workspace.start_count": `1`, + "workspace.template_id": `[a-zA-Z0-9-]+`, + "workspace.template_name": `workspace-owner`, + "workspace.template_version": `.+`, + "workspace.transition": `start`, "workspace_owner.email": `testing@coder\.com`, "workspace_owner.full_name": `default`, "workspace_owner.groups": `\[\]`, @@ -98,9 +127,13 @@ func TestIntegration(t *testing.T) { }, }, } { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if coderVersion != "latest" && semver.Compare(coderVersion, tt.minVersion) < 0 { + t.Skipf("skipping due to CODER_VERSION %q < minVersion %q", coderVersion, tt.minVersion) + } // Given: we have an existing Coder deployment running locally - ctrID := setup(ctx, t, tt.name, coderImg, coderVersion) // Import named template // NOTE: Template create command was deprecated after this version diff --git a/integration/test-data-source/main.tf b/integration/test-data-source/main.tf index 580592cb..838125a0 100644 --- a/integration/test-data-source/main.tf +++ b/integration/test-data-source/main.tf @@ -38,15 +38,6 @@ locals { "workspace.template_name" : data.coder_workspace.me.template_name, "workspace.template_version" : data.coder_workspace.me.template_version, "workspace.transition" : data.coder_workspace.me.transition, - "workspace_owner.email" : data.coder_workspace_owner.me.email, - "workspace_owner.full_name" : data.coder_workspace_owner.me.full_name, - "workspace_owner.groups" : jsonencode(data.coder_workspace_owner.me.groups), - "workspace_owner.id" : data.coder_workspace_owner.me.id, - "workspace_owner.name" : data.coder_workspace_owner.me.name, - "workspace_owner.oidc_access_token" : data.coder_workspace_owner.me.oidc_access_token, - "workspace_owner.session_token" : data.coder_workspace_owner.me.session_token, - "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, - "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, } } diff --git a/integration/workspace-owner/main.tf b/integration/workspace-owner/main.tf new file mode 100644 index 00000000..580592cb --- /dev/null +++ b/integration/workspace-owner/main.tf @@ -0,0 +1,65 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + local = { + source = "hashicorp/local" + } + } +} + +// TODO: test coder_external_auth and coder_git_auth +// data coder_external_auth "me" {} +// data coder_git_auth "me" {} +data "coder_provisioner" "me" {} +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +locals { + # NOTE: these must all be strings in the output + output = { + "provisioner.arch" : data.coder_provisioner.me.arch, + "provisioner.id" : data.coder_provisioner.me.id, + "provisioner.os" : data.coder_provisioner.me.os, + "workspace.access_port" : tostring(data.coder_workspace.me.access_port), + "workspace.access_url" : data.coder_workspace.me.access_url, + "workspace.id" : data.coder_workspace.me.id, + "workspace.name" : data.coder_workspace.me.name, + "workspace.owner" : data.coder_workspace.me.owner, + "workspace.owner_email" : data.coder_workspace.me.owner_email, + "workspace.owner_groups" : jsonencode(data.coder_workspace.me.owner_groups), + "workspace.owner_id" : data.coder_workspace.me.owner_id, + "workspace.owner_name" : data.coder_workspace.me.owner_name, + "workspace.owner_oidc_access_token" : data.coder_workspace.me.owner_oidc_access_token, + "workspace.owner_session_token" : data.coder_workspace.me.owner_session_token, + "workspace.start_count" : tostring(data.coder_workspace.me.start_count), + "workspace.template_id" : data.coder_workspace.me.template_id, + "workspace.template_name" : data.coder_workspace.me.template_name, + "workspace.template_version" : data.coder_workspace.me.template_version, + "workspace.transition" : data.coder_workspace.me.transition, + "workspace_owner.email" : data.coder_workspace_owner.me.email, + "workspace_owner.full_name" : data.coder_workspace_owner.me.full_name, + "workspace_owner.groups" : jsonencode(data.coder_workspace_owner.me.groups), + "workspace_owner.id" : data.coder_workspace_owner.me.id, + "workspace_owner.name" : data.coder_workspace_owner.me.name, + "workspace_owner.oidc_access_token" : data.coder_workspace_owner.me.oidc_access_token, + "workspace_owner.session_token" : data.coder_workspace_owner.me.session_token, + "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, + "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, + } +} + +variable "output_path" { + type = string +} + +resource "local_file" "output" { + filename = var.output_path + content = jsonencode(local.output) +} + +output "output" { + value = local.output + sensitive = true +} diff --git a/scripts/coderversion/main.go b/scripts/coderversion/main.go new file mode 100644 index 00000000..fa2c1705 --- /dev/null +++ b/scripts/coderversion/main.go @@ -0,0 +1,109 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + "github.com/masterminds/semver" +) + +func main() { + releases := fetchReleases() + + mainlineVer := semver.MustParse("v0.0.0") + for _, rel := range releases { + if rel == "" { + debug("ignoring untagged version %s\n", rel) + continue + } + + ver, err := semver.NewVersion(rel) + if err != nil { + debug("skipping invalid version %s\n", rel) + } + + if ver.Compare(mainlineVer) > 0 { + mainlineVer = ver + continue + } + } + + mainline := fmt.Sprintf("v%d.%d.%d", mainlineVer.Major(), mainlineVer.Minor(), mainlineVer.Patch()) + _, _ = fmt.Fprintf(os.Stdout, "CODER_MAINLINE_VERSION=%q\n", mainline) + + expectedStableMinor := mainlineVer.Minor() - 1 + if expectedStableMinor < 0 { + expectedStableMinor = 0 + } + debug("expected stable minor: %d\n", expectedStableMinor) + stableVer := semver.MustParse("v0.0.0") + for _, rel := range releases { + debug("check version %s\n", rel) + if rel == "" { + debug("ignoring untagged version %s\n", rel) + continue + } + + ver, err := semver.NewVersion(rel) + if err != nil { + debug("skipping invalid version %s\n", rel) + } + + if ver.Minor() != expectedStableMinor { + debug("skipping version %s\n", rel) + continue + } + + if ver.Compare(stableVer) > 0 { + stableVer = ver + continue + } + } + + stable := fmt.Sprintf("v%d.%d.%d", stableVer.Major(), stableVer.Minor(), stableVer.Patch()) + _, _ = fmt.Fprintf(os.Stdout, "CODER_STABLE_VERSION=%q\n", stable) +} + +type release struct { + TagName string `json:"tag_name"` +} + +const releasesURL = "https://api.github.com/repos/coder/coder/releases" + +// fetchReleases fetches the releases of coder/coder +// this is done directly via JSON API to avoid pulling in the entire +// github client +func fetchReleases() []string { + resp, err := http.Get(releasesURL) + if err != nil { + fatal("get releases: %w", err) + } + defer resp.Body.Close() + + var releases []release + if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { + fatal("parse releases: %w", err) + } + + var ss []string + for _, rel := range releases { + if rel.TagName != "" { + ss = append(ss, rel.TagName) + + } + } + return ss +} + +func debug(format string, args ...any) { + if _, ok := os.LookupEnv("VERBOSE"); ok { + _, _ = fmt.Fprintf(os.Stderr, format, args...) + } +} + +func fatal(format string, args ...any) { + _, _ = fmt.Fprintf(os.Stderr, format, args...) + os.Exit(1) +} From 35e6ac88f9b86dc8e141a67f3e68add3ca6bad14 Mon Sep 17 00:00:00 2001 From: Stephen Kirby <58410745+stirby@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:16:51 -0500 Subject: [PATCH 030/114] docs: add missing code examples (#249) * coder_env code example * added coder_script example * updated coder_script description to note parallelism * short coder_external_auth example * added external auth link, make gen * fixed icon link for coder_parameter * coder_provisioner example * fixed inline links for provisioner * added coder_workspace_owner examples --- docs/data-sources/external_auth.md | 18 +++++- docs/data-sources/parameter.md | 2 +- docs/data-sources/provisioner.md | 24 +++++++- docs/data-sources/workspace_owner.md | 31 ++++++++++ docs/resources/env.md | 24 +++++++- docs/resources/script.md | 59 ++++++++++++++++++- .../coder_external_auth/data-source.tf | 11 ++++ .../coder_provisioner/data-source.tf | 17 ++++++ .../coder_workspace_owner/data-source.tf | 28 +++++++++ examples/resources/coder_env/resource.tf | 19 ++++++ examples/resources/coder_script/resource.tf | 52 ++++++++++++++++ provider/externalauth.go | 2 +- provider/parameter.go | 2 +- provider/provisioner.go | 4 +- provider/script.go | 2 +- 15 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 examples/data-sources/coder_external_auth/data-source.tf create mode 100644 examples/data-sources/coder_provisioner/data-source.tf create mode 100644 examples/data-sources/coder_workspace_owner/data-source.tf create mode 100644 examples/resources/coder_env/resource.tf create mode 100644 examples/resources/coder_script/resource.tf diff --git a/docs/data-sources/external_auth.md b/docs/data-sources/external_auth.md index af4df43b..e4089f24 100644 --- a/docs/data-sources/external_auth.md +++ b/docs/data-sources/external_auth.md @@ -3,14 +3,28 @@ page_title: "coder_external_auth Data Source - terraform-provider-coder" subcategory: "" description: |- - Use this data source to require users to authenticate with an external service prior to workspace creation. This can be used to pre-authenticate external services in a workspace. (e.g. gcloud, gh, docker, etc) + Use this data source to require users to authenticate with an external service prior to workspace creation. This can be used to pre-authenticate external services https://coder.com/docs/admin/external-auth in a workspace. (e.g. Google Cloud, Github, Docker, etc.) --- # coder_external_auth (Data Source) -Use this data source to require users to authenticate with an external service prior to workspace creation. This can be used to pre-authenticate external services in a workspace. (e.g. gcloud, gh, docker, etc) +Use this data source to require users to authenticate with an external service prior to workspace creation. This can be used to [pre-authenticate external services](https://coder.com/docs/admin/external-auth) in a workspace. (e.g. Google Cloud, Github, Docker, etc.) +## Example Usage +```terraform +provider "coder" {} + + +data "coder_external_auth" "github" { + id = "github" +} + +data "coder_external_auth" "azure-identity" { + id = "azure-identiy" + optional = true +} +``` ## Schema diff --git a/docs/data-sources/parameter.md b/docs/data-sources/parameter.md index 9c6a11f5..178c6d9d 100644 --- a/docs/data-sources/parameter.md +++ b/docs/data-sources/parameter.md @@ -145,7 +145,7 @@ data "coder_parameter" "home_volume_size" { - `description` (String) Describe what this parameter does. - `display_name` (String) The displayed name of the parameter as it will appear in the interface. - `ephemeral` (Boolean) The value of an ephemeral parameter will not be preserved between consecutive workspace builds. -- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. +- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. - `mutable` (Boolean) Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution! - `option` (Block List, Max: 64) Each "option" block defines a value for a user to select from. (see [below for nested schema](#nestedblock--option)) - `order` (Number) The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order). diff --git a/docs/data-sources/provisioner.md b/docs/data-sources/provisioner.md index 4316aeea..ba930a2a 100644 --- a/docs/data-sources/provisioner.md +++ b/docs/data-sources/provisioner.md @@ -10,13 +10,33 @@ description: |- Use this data source to get information about the Coder provisioner. +## Example Usage +```terraform +provider "coder" {} + +data "coder_provisioner" "dev" {} + +data "coder_workspace" "dev" {} + +resource "coder_agent" "main" { + arch = data.coder_provisioner.dev.arch + os = data.coder_provisioner.dev.os + dir = "/workspace" + display_apps { + vscode = true + vscode_insiders = false + web_terminal = true + ssh_helper = false + } +} +``` ## Schema ### Read-Only -- `arch` (String) The architecture of the host. This exposes `runtime.GOARCH` (see https://pkg.go.dev/runtime#pkg-constants). +- `arch` (String) The architecture of the host. This exposes `runtime.GOARCH` (see [Go constants](https://pkg.go.dev/runtime#pkg-constants)). - `id` (String) The ID of this resource. -- `os` (String) The operating system of the host. This exposes `runtime.GOOS` (see https://pkg.go.dev/runtime#pkg-constants). +- `os` (String) The operating system of the host. This exposes `runtime.GOOS` (see [Go constants](https://pkg.go.dev/runtime#pkg-constants)). diff --git a/docs/data-sources/workspace_owner.md b/docs/data-sources/workspace_owner.md index 646b1340..0deff622 100644 --- a/docs/data-sources/workspace_owner.md +++ b/docs/data-sources/workspace_owner.md @@ -10,7 +10,38 @@ description: |- Use this data source to fetch information about the workspace owner. +## Example Usage +```terraform +provider "coder" {} + +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + dir = local.repo_dir + env = { + OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token, + } +} + +# Add git credentials from coder_workspace_owner +resource "coder_env" "git_author_name" { + agent_id = coder_agent.agent_id + name = "GIT_AUTHOR_NAME" + value = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) +} + +resource "coder_env" "git_author_email" { + agent_id = var.agent_id + name = "GIT_AUTHOR_EMAIL" + value = data.coder_workspace_owner.me.email + count = data.coder_workspace_owner.me.email != "" ? 1 : 0 +} +``` ## Schema diff --git a/docs/resources/env.md b/docs/resources/env.md index b948bad9..3531335c 100644 --- a/docs/resources/env.md +++ b/docs/resources/env.md @@ -10,7 +10,29 @@ description: |- Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the "coder_agent" resource. - +## Example Usage + +```terraform +data "coder_workspace" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" +} + +resource "coder_env" "welcome_message" { + agent_id = coder_agent.dev.id + name = "WELCOME_MESSAGE" + value = "Welcome to your Coder workspace!" +} + +resource "coder_env" "internal_api_url" { + agent_id = coder_agent.dev.id + name = "INTERNAL_API_URL" + value = "https://api.internal.company.com/v1" +} +``` ## Schema diff --git a/docs/resources/script.md b/docs/resources/script.md index a16b39f5..d4d5dd9f 100644 --- a/docs/resources/script.md +++ b/docs/resources/script.md @@ -3,14 +3,69 @@ page_title: "coder_script Resource - terraform-provider-coder" subcategory: "" description: |- - Use this resource to run a script from an agent. + Use this resource to run a script from an agent. When multiple scripts are assigned to the same agent, they are executed in parallel. --- # coder_script (Resource) -Use this resource to run a script from an agent. +Use this resource to run a script from an agent. When multiple scripts are assigned to the same agent, they are executed in parallel. +## Example Usage +```terraform +data "coder_workspace" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" +} + +resource "coder_script" "dotfiles" { + agent_id = coder_agent.dev.agent_id + display_name = "Dotfiles" + icon = "/icon/dotfiles.svg" + run_on_start = true + script = templatefile("~/get_dotfiles.sh", { + DOTFILES_URI : var.dotfiles_uri, + DOTFILES_USER : var.dotfiles_user + }) +} + +resource "coder_script" "code-server" { + agent_id = coder_agent.dev.agent_id + display_name = "code-server" + icon = "/icon/code.svg" + run_on_start = true + start_blocks_login = true + script = templatefile("./install-code-server.sh", { + LOG_PATH : "/tmp/code-server.log" + }) +} + +resource "coder_script" "nightly_sleep_reminder" { + agent_id = coder_agent.dev.agent_id + display_name = "Nightly update" + icon = "/icon/database.svg" + cron = "0 22 * * *" + script = </tmp/pid.log 2>&1 & + EOF +} +``` ## Schema diff --git a/examples/data-sources/coder_external_auth/data-source.tf b/examples/data-sources/coder_external_auth/data-source.tf new file mode 100644 index 00000000..330ff216 --- /dev/null +++ b/examples/data-sources/coder_external_auth/data-source.tf @@ -0,0 +1,11 @@ +provider "coder" {} + + +data "coder_external_auth" "github" { + id = "github" +} + +data "coder_external_auth" "azure-identity" { + id = "azure-identiy" + optional = true +} diff --git a/examples/data-sources/coder_provisioner/data-source.tf b/examples/data-sources/coder_provisioner/data-source.tf new file mode 100644 index 00000000..a94823ed --- /dev/null +++ b/examples/data-sources/coder_provisioner/data-source.tf @@ -0,0 +1,17 @@ +provider "coder" {} + +data "coder_provisioner" "dev" {} + +data "coder_workspace" "dev" {} + +resource "coder_agent" "main" { + arch = data.coder_provisioner.dev.arch + os = data.coder_provisioner.dev.os + dir = "/workspace" + display_apps { + vscode = true + vscode_insiders = false + web_terminal = true + ssh_helper = false + } +} \ No newline at end of file diff --git a/examples/data-sources/coder_workspace_owner/data-source.tf b/examples/data-sources/coder_workspace_owner/data-source.tf new file mode 100644 index 00000000..fc27db6c --- /dev/null +++ b/examples/data-sources/coder_workspace_owner/data-source.tf @@ -0,0 +1,28 @@ +provider "coder" {} + +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + dir = local.repo_dir + env = { + OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token, + } +} + +# Add git credentials from coder_workspace_owner +resource "coder_env" "git_author_name" { + agent_id = coder_agent.agent_id + name = "GIT_AUTHOR_NAME" + value = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) +} + +resource "coder_env" "git_author_email" { + agent_id = var.agent_id + name = "GIT_AUTHOR_EMAIL" + value = data.coder_workspace_owner.me.email + count = data.coder_workspace_owner.me.email != "" ? 1 : 0 +} \ No newline at end of file diff --git a/examples/resources/coder_env/resource.tf b/examples/resources/coder_env/resource.tf new file mode 100644 index 00000000..9f8e28f2 --- /dev/null +++ b/examples/resources/coder_env/resource.tf @@ -0,0 +1,19 @@ +data "coder_workspace" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" +} + +resource "coder_env" "welcome_message" { + agent_id = coder_agent.dev.id + name = "WELCOME_MESSAGE" + value = "Welcome to your Coder workspace!" +} + +resource "coder_env" "internal_api_url" { + agent_id = coder_agent.dev.id + name = "INTERNAL_API_URL" + value = "https://api.internal.company.com/v1" +} \ No newline at end of file diff --git a/examples/resources/coder_script/resource.tf b/examples/resources/coder_script/resource.tf new file mode 100644 index 00000000..b7fced38 --- /dev/null +++ b/examples/resources/coder_script/resource.tf @@ -0,0 +1,52 @@ +data "coder_workspace" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" +} + +resource "coder_script" "dotfiles" { + agent_id = coder_agent.dev.agent_id + display_name = "Dotfiles" + icon = "/icon/dotfiles.svg" + run_on_start = true + script = templatefile("~/get_dotfiles.sh", { + DOTFILES_URI : var.dotfiles_uri, + DOTFILES_USER : var.dotfiles_user + }) +} + +resource "coder_script" "code-server" { + agent_id = coder_agent.dev.agent_id + display_name = "code-server" + icon = "/icon/code.svg" + run_on_start = true + start_blocks_login = true + script = templatefile("./install-code-server.sh", { + LOG_PATH : "/tmp/code-server.log" + }) +} + +resource "coder_script" "nightly_sleep_reminder" { + agent_id = coder_agent.dev.agent_id + display_name = "Nightly update" + icon = "/icon/database.svg" + cron = "0 22 * * *" + script = </tmp/pid.log 2>&1 & + EOF +} \ No newline at end of file diff --git a/provider/externalauth.go b/provider/externalauth.go index 13c85fab..a11a67c4 100644 --- a/provider/externalauth.go +++ b/provider/externalauth.go @@ -15,7 +15,7 @@ func externalAuthDataSource() *schema.Resource { return &schema.Resource{ SchemaVersion: 1, - Description: "Use this data source to require users to authenticate with an external service prior to workspace creation. This can be used to pre-authenticate external services in a workspace. (e.g. gcloud, gh, docker, etc)", + Description: "Use this data source to require users to authenticate with an external service prior to workspace creation. This can be used to [pre-authenticate external services](https://coder.com/docs/admin/external-auth) in a workspace. (e.g. Google Cloud, Github, Docker, etc.)", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { id, ok := rd.Get("id").(string) if !ok || id == "" { diff --git a/provider/parameter.go b/provider/parameter.go index d0f71dab..281537f6 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -220,7 +220,7 @@ func parameterDataSource() *schema.Resource { "icon": { Type: schema.TypeString, Description: "A URL to an icon that will display in the dashboard. View built-in " + - "icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " + + "icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " + "built-in icon with `data.coder_workspace.me.access_url + \"/icon/\"`.", ForceNew: true, Optional: true, diff --git a/provider/provisioner.go b/provider/provisioner.go index 9d356798..49a40d21 100644 --- a/provider/provisioner.go +++ b/provider/provisioner.go @@ -29,12 +29,12 @@ func provisionerDataSource() *schema.Resource { "os": { Type: schema.TypeString, Computed: true, - Description: "The operating system of the host. This exposes `runtime.GOOS` (see https://pkg.go.dev/runtime#pkg-constants).", + Description: "The operating system of the host. This exposes `runtime.GOOS` (see [Go constants](https://pkg.go.dev/runtime#pkg-constants)).", }, "arch": { Type: schema.TypeString, Computed: true, - Description: "The architecture of the host. This exposes `runtime.GOARCH` (see https://pkg.go.dev/runtime#pkg-constants).", + Description: "The architecture of the host. This exposes `runtime.GOARCH` (see [Go constants](https://pkg.go.dev/runtime#pkg-constants)).", }, }, } diff --git a/provider/script.go b/provider/script.go index 1474dbd2..536a5732 100644 --- a/provider/script.go +++ b/provider/script.go @@ -17,7 +17,7 @@ func scriptResource() *schema.Resource { return &schema.Resource{ SchemaVersion: 1, - Description: "Use this resource to run a script from an agent.", + Description: "Use this resource to run a script from an agent. When multiple scripts are assigned to the same agent, they are executed in parallel.", CreateContext: func(_ context.Context, rd *schema.ResourceData, _ interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) runOnStart, _ := rd.Get("run_on_start").(bool) From 7c884015ef0088005f3a61e8dd780d6f4a3b7a3b Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 2 Jul 2024 14:34:51 +0100 Subject: [PATCH 031/114] fix(scripts/coderversion): fix error-wrapping directive (#251) --- scripts/coderversion/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/coderversion/main.go b/scripts/coderversion/main.go index fa2c1705..b6f57d4b 100644 --- a/scripts/coderversion/main.go +++ b/scripts/coderversion/main.go @@ -78,13 +78,13 @@ const releasesURL = "https://api.github.com/repos/coder/coder/releases" func fetchReleases() []string { resp, err := http.Get(releasesURL) if err != nil { - fatal("get releases: %w", err) + fatal("get releases: %s", err.Error()) } defer resp.Body.Close() var releases []release if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { - fatal("parse releases: %w", err) + fatal("parse releases: %s", err.Error()) } var ss []string From d2e2d968046dcb68e2e67a07f05a5dd61058cf65 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 3 Jul 2024 16:01:57 +0300 Subject: [PATCH 032/114] docs: improve docs by using code-styled markdown (#253) * docs: improve docs by using code-styled markdown * `make gen` * fixup! * make deprecated bold * fix tests * fix tests * more formatting * fixes * make better * add deprecation warning to `coder_git_auth` --- docs/data-sources/git_auth.md | 5 +++++ docs/data-sources/parameter.md | 8 ++++---- docs/data-sources/workspace.md | 18 ++++++++--------- docs/index.md | 2 +- docs/resources/agent.md | 26 ++++++++++++------------ docs/resources/agent_instance.md | 4 ++-- docs/resources/app.md | 20 +++++++++---------- docs/resources/env.md | 6 +++--- docs/resources/metadata.md | 8 ++++---- docs/resources/script.md | 4 ++-- provider/agent.go | 34 ++++++++++++++++---------------- provider/app.go | 34 ++++++++++++++++---------------- provider/env.go | 4 ++-- provider/gitauth.go | 2 +- provider/metadata.go | 10 +++++----- provider/parameter.go | 10 +++++----- provider/script.go | 10 +++++----- provider/script_test.go | 4 ++-- provider/workspace.go | 4 ++-- scripts/docsgen/main.go | 2 +- 20 files changed, 110 insertions(+), 105 deletions(-) diff --git a/docs/data-sources/git_auth.md b/docs/data-sources/git_auth.md index fe64805e..e665aeb1 100644 --- a/docs/data-sources/git_auth.md +++ b/docs/data-sources/git_auth.md @@ -3,11 +3,16 @@ page_title: "coder_git_auth Data Source - terraform-provider-coder" subcategory: "" description: |- + ~> Deprecated + Use the coder_external_auth data source instead. Use this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated git clone in startup scripts. --- # coder_git_auth (Data Source) +~> **Deprecated** +Use the `coder_external_auth` data source instead. + Use this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated `git clone` in startup scripts. ## Example Usage diff --git a/docs/data-sources/parameter.md b/docs/data-sources/parameter.md index 178c6d9d..f40a9c83 100644 --- a/docs/data-sources/parameter.md +++ b/docs/data-sources/parameter.md @@ -145,11 +145,11 @@ data "coder_parameter" "home_volume_size" { - `description` (String) Describe what this parameter does. - `display_name` (String) The displayed name of the parameter as it will appear in the interface. - `ephemeral` (Boolean) The value of an ephemeral parameter will not be preserved between consecutive workspace builds. -- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. +- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. - `mutable` (Boolean) Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution! -- `option` (Block List, Max: 64) Each "option" block defines a value for a user to select from. (see [below for nested schema](#nestedblock--option)) +- `option` (Block List, Max: 64) Each `option` block defines a value for a user to select from. (see [below for nested schema](#nestedblock--option)) - `order` (Number) The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order). -- `type` (String) The type of this parameter. Must be one of: "number", "string", "bool", or "list(string)". +- `type` (String) The type of this parameter. Must be one of: `"number"`, `"string"`, `"bool"`, or `"list(string)"`. - `validation` (Block List, Max: 1) Validate the input of a parameter. (see [below for nested schema](#nestedblock--validation)) ### Read-Only @@ -169,7 +169,7 @@ Required: Optional: - `description` (String) Describe what selecting this value does. -- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. +- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index e9de5889..8b824c37 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -30,15 +30,15 @@ resource "kubernetes_pod" "dev" { - `access_url` (String) The access URL of the Coder deployment provisioning this workspace. - `id` (String) UUID of the workspace. - `name` (String) Name of the workspace. -- `owner` (String, Deprecated: Use `coder_workspace_owner.name` instead.) Username of the workspace owner. -- `owner_email` (String, Deprecated: Use `coder_workspace_owner.email` instead.) Email address of the workspace owner. -- `owner_groups` (List of String, Deprecated: Use `coder_workspace_owner.groups` instead.) List of groups the workspace owner belongs to. -- `owner_id` (String, Deprecated: Use `coder_workspace_owner.id` instead.) UUID of the workspace owner. -- `owner_name` (String, Deprecated: Use `coder_workspace_owner.full_name` instead.) Name of the workspace owner. -- `owner_oidc_access_token` (String, Deprecated: Use `coder_workspace_owner.oidc_access_token` instead.) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. -- `owner_session_token` (String, Deprecated: Use `coder_workspace_owner.session_token` instead.) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. -- `start_count` (Number) A computed count based on "transition" state. If "start", count will equal 1. +- `owner` (String, **Deprecated**: Use `coder_workspace_owner.name` instead.) Username of the workspace owner. +- `owner_email` (String, **Deprecated**: Use `coder_workspace_owner.email` instead.) Email address of the workspace owner. +- `owner_groups` (List of String, **Deprecated**: Use `coder_workspace_owner.groups` instead.) List of groups the workspace owner belongs to. +- `owner_id` (String, **Deprecated**: Use `coder_workspace_owner.id` instead.) UUID of the workspace owner. +- `owner_name` (String, **Deprecated**: Use `coder_workspace_owner.full_name` instead.) Name of the workspace owner. +- `owner_oidc_access_token` (String, **Deprecated**: Use `coder_workspace_owner.oidc_access_token` instead.) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. +- `owner_session_token` (String, **Deprecated**: Use `coder_workspace_owner.session_token` instead.) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. +- `start_count` (Number) A computed count based on `transition` state. If `start`, count will equal 1. - `template_id` (String) ID of the workspace's template. - `template_name` (String) Name of the workspace's template. - `template_version` (String) Version of the workspace's template. -- `transition` (String) Either "start" or "stop". Use this to start/stop resources with "count". +- `transition` (String) Either `start` or `stop`. Use this to start/stop resources with `count`. diff --git a/docs/index.md b/docs/index.md index 84843dec..a4dfd70e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -62,5 +62,5 @@ resource "google_compute_instance" "dev" { ### Optional -- `feature_use_managed_variables` (Boolean, Deprecated: Terraform variables are now exclusively utilized for template-wide variables after the removal of support for legacy parameters.) Feature: use managed Terraform variables. The feature flag is not used anymore as Terraform variables are now exclusively utilized for template-wide variables. +- `feature_use_managed_variables` (Boolean, **Deprecated**: Terraform variables are now exclusively utilized for template-wide variables after the removal of support for legacy parameters.) Feature: use managed Terraform variables. The feature flag is not used anymore as Terraform variables are now exclusively utilized for template-wide variables. - `url` (String) The URL to access Coder. \ No newline at end of file diff --git a/docs/resources/agent.md b/docs/resources/agent.md index d20374d0..e444d2d7 100644 --- a/docs/resources/agent.md +++ b/docs/resources/agent.md @@ -66,32 +66,32 @@ resource "kubernetes_pod" "dev" { ### Required -- `arch` (String) The architecture the agent will run on. Must be one of: "amd64", "armv7", "arm64". -- `os` (String) The operating system the agent will run on. Must be one of: "linux", "darwin", or "windows". +- `arch` (String) The architecture the agent will run on. Must be one of: `"amd64"`, `"armv7"`, `"arm64"`. +- `os` (String) The operating system the agent will run on. Must be one of: `"linux"`, `"darwin"`, or `"windows"`. ### Optional -- `auth` (String) The authentication type the agent will use. Must be one of: "token", "google-instance-identity", "aws-instance-identity", "azure-instance-identity". +- `auth` (String) The authentication type the agent will use. Must be one of: `"token"`, `"google-instance-identity"`, `"aws-instance-identity"`, `"azure-instance-identity"`. - `connection_timeout` (Number) Time in seconds until the agent is marked as timed out when a connection with the server cannot be established. A value of zero never marks the agent as timed out. -- `dir` (String) The starting directory when a user creates a shell session. Defaults to $HOME. +- `dir` (String) The starting directory when a user creates a shell session. Defaults to `"$HOME"`. - `display_apps` (Block Set, Max: 1) The list of built-in apps to display in the agent bar. (see [below for nested schema](#nestedblock--display_apps)) - `env` (Map of String) A mapping of environment variables to set inside the workspace. -- `login_before_ready` (Boolean, Deprecated: Configure startup_script_behavior instead. This attribute will be removed in a future version of the provider.) This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the startup_script is done and has exited. When enabled, users may see an incomplete workspace when logging in. -- `metadata` (Block List) Each "metadata" block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases. (see [below for nested schema](#nestedblock--metadata)) -- `motd_file` (String) The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be /etc/motd. +- `login_before_ready` (Boolean, **Deprecated**: Configure `startup_script_behavior` instead. This attribute will be removed in a future version of the provider.) This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the `startup_script` is done and has exited. When enabled, users may see an incomplete workspace when logging in. +- `metadata` (Block List) Each `metadata` block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases. (see [below for nested schema](#nestedblock--metadata)) +- `motd_file` (String) The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be `"/etc/motd"`. - `order` (Number) The order determines the position of agents in the UI presentation. The lowest order is shown first and agents with equal order are sorted by name (ascending order). -- `shutdown_script` (String) A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a "coder_script" resource with "run_on_stop" set to true. -- `shutdown_script_timeout` (Number, Deprecated: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time. -- `startup_script` (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a "coder_script" resource with "run_on_start" set to true. -- `startup_script_behavior` (String) This option sets the behavior of the "startup_script". When set to "blocking", the startup_script must exit before the workspace is ready. When set to "non-blocking", the startup_script may run in the background and the workspace will be ready immediately. Default is "non-blocking", although "blocking" is recommended. This option is an alias for defining a "coder_script" resource with "start_blocks_login" set to true (blocking). -- `startup_script_timeout` (Number, Deprecated: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time. +- `shutdown_script` (String) A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a `coder_script` resource with `run_on_stop` set to `true`. +- `shutdown_script_timeout` (Number, **Deprecated**: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time. +- `startup_script` (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a `coder_script` resource with `run_on_start` set to `true`. +- `startup_script_behavior` (String) This option sets the behavior of the `startup_script`. When set to `"blocking"`, the `startup_script` must exit before the workspace is ready. When set to `"non-blocking"`, the `startup_script` may run in the background and the workspace will be ready immediately. Default is `"non-blocking"`, although `"blocking"` is recommended. This option is an alias for defining a `coder_script` resource with `start_blocks_login` set to `true` (blocking). +- `startup_script_timeout` (Number, **Deprecated**: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time. - `troubleshooting_url` (String) A URL to a document with instructions for troubleshooting problems with the agent. ### Read-Only - `id` (String) The ID of this resource. - `init_script` (String) Run this script on startup of an instance to initialize the agent. -- `token` (String, Sensitive) Set the environment variable "CODER_AGENT_TOKEN" with this token to authenticate an agent. +- `token` (String, Sensitive) Set the environment variable `CODER_AGENT_TOKEN` with this token to authenticate an agent. ### Nested Schema for `display_apps` diff --git a/docs/resources/agent_instance.md b/docs/resources/agent_instance.md index 6af2bb46..ec855b12 100644 --- a/docs/resources/agent_instance.md +++ b/docs/resources/agent_instance.md @@ -8,7 +8,7 @@ description: |- # coder_agent_instance (Resource) -Use this resource to associate an instance ID with an agent for zero-trust authentication. This association is done automatically for "google_compute_instance", "aws_instance", "azurerm_linux_virtual_machine", and "azurerm_windows_virtual_machine" resources. +Use this resource to associate an instance ID with an agent for zero-trust authentication. This association is done automatically for `"google_compute_instance"`, `"aws_instance"`, `"azurerm_linux_virtual_machine"`, and `"azurerm_windows_virtual_machine"` resources. ## Example Usage @@ -34,7 +34,7 @@ resource "coder_agent_instance" "dev" { ### Required -- `agent_id` (String) The "id" property of a "coder_agent" resource to associate with. +- `agent_id` (String) The `id` property of a `coder_agent` resource to associate with. - `instance_id` (String) The instance identifier of a provisioned resource. ### Read-Only diff --git a/docs/resources/app.md b/docs/resources/app.md index e9ca7b2f..f61ed799 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -54,22 +54,22 @@ resource "coder_app" "vim" { ### Required -- `agent_id` (String) The "id" property of a "coder_agent" resource to associate with. +- `agent_id` (String) The `id` property of a `coder_agent` resource to associate with. - `slug` (String) A hostname-friendly name for the app. This is used in URLs to access the app. May contain alphanumerics and hyphens. Cannot start/end with a hyphen or contain two consecutive hyphens. ### Optional -- `command` (String) A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either "command" or "url" may be specified, but not both. +- `command` (String) A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either `command` or `url` may be specified, but not both. - `display_name` (String) A display name to identify the app. Defaults to the slug. -- `external` (Boolean) Specifies whether "url" is opened on the client machine instead of proxied through the workspace. +- `external` (Boolean) Specifies whether `url` is opened on the client machine instead of proxied through the workspace. - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) -- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. -- `name` (String, Deprecated: `name` on apps is deprecated, use `display_name` instead) A display name to identify the app. +- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. +- `name` (String, **Deprecated**: `name` on apps is deprecated, use `display_name` instead) A display name to identify the app. - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). -- `relative_path` (Boolean, Deprecated: `relative_path` on apps is deprecated, use `subdomain` instead.) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to true. -- `share` (String) Determines the "level" which the application is shared at. Valid levels are "owner" (default), "authenticated" and "public". Level "owner" disables sharing on the app, so only the workspace owner can access it. Level "authenticated" shares the app with all authenticated users. Level "public" shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). -- `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. Defaults to false. -- `url` (String) An external url if "external=true" or a URL to be proxied to from inside the workspace. This should be of the form "http://localhost:PORT[/SUBPATH]". Either "command" or "url" may be specified, but not both. +- `relative_path` (Boolean, **Deprecated**: `relative_path` on apps is deprecated, use `subdomain` instead.) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to `true`. +- `share` (String) Determines the level which the application is shared at. Valid levels are `"owner"` (default), `"authenticated"` and `"public"`. Level `"owner"` disables sharing on the app, so only the workspace owner can access it. Level `"authenticated"` shares the app with all authenticated users. Level `"public"` shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). +- `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with `subdomain` set to `true` will not be accessible. Defaults to `false`. +- `url` (String) An external url if `external=true` or a URL to be proxied to from inside the workspace. This should be of the form `http://localhost:PORT[/SUBPATH]`. Either `command` or `url` may be specified, but not both. ### Read-Only @@ -82,4 +82,4 @@ Required: - `interval` (Number) Duration in seconds to wait between healthcheck requests. - `threshold` (Number) Number of consecutive heathcheck failures before returning an unhealthy status. -- `url` (String) HTTP address used determine the application readiness. A successful health check is a HTTP response code less than 500 returned before healthcheck.interval seconds. +- `url` (String) HTTP address used determine the application readiness. A successful health check is a HTTP response code less than 500 returned before `healthcheck.interval` seconds. diff --git a/docs/resources/env.md b/docs/resources/env.md index 3531335c..3a5a7f3a 100644 --- a/docs/resources/env.md +++ b/docs/resources/env.md @@ -3,12 +3,12 @@ page_title: "coder_env Resource - terraform-provider-coder" subcategory: "" description: |- - Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the "coder_agent" resource. + Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the coder_agent resource. --- # coder_env (Resource) -Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the "coder_agent" resource. +Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the `coder_agent` resource. ## Example Usage @@ -39,7 +39,7 @@ resource "coder_env" "internal_api_url" { ### Required -- `agent_id` (String) The "id" property of a "coder_agent" resource to associate with. +- `agent_id` (String) The `id` property of a `coder_agent` resource to associate with. - `name` (String) The name of the environment variable. ### Optional diff --git a/docs/resources/metadata.md b/docs/resources/metadata.md index 9d6ff92f..f739db14 100644 --- a/docs/resources/metadata.md +++ b/docs/resources/metadata.md @@ -59,14 +59,14 @@ resource "coder_metadata" "pod_info" { ### Required -- `resource_id` (String) The "id" property of another resource that metadata should be attached to. +- `resource_id` (String) The `id` property of another resource that metadata should be attached to. ### Optional - `daily_cost` (Number) (Enterprise) The cost of this resource every 24 hours. Use the smallest denomination of your preferred currency. For example, if you work in USD, use cents. - `hide` (Boolean) Hide the resource from the UI. -- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. -- `item` (Block List) Each "item" block defines a single metadata item consisting of a key/value pair. (see [below for nested schema](#nestedblock--item)) +- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. +- `item` (Block List) Each `item` block defines a single metadata item consisting of a key/value pair. (see [below for nested schema](#nestedblock--item)) ### Read-Only @@ -81,7 +81,7 @@ Required: Optional: -- `sensitive` (Boolean) Set to "true" to for items such as API keys whose values should be hidden from view by default. Note that this does not prevent metadata from being retrieved using the API, so it is not suitable for secrets that should not be exposed to workspace users. +- `sensitive` (Boolean) Set to `true` to for items such as API keys whose values should be hidden from view by default. Note that this does not prevent metadata from being retrieved using the API, so it is not suitable for secrets that should not be exposed to workspace users. - `value` (String) The value of this metadata item. Read-Only: diff --git a/docs/resources/script.md b/docs/resources/script.md index d4d5dd9f..22ac1b50 100644 --- a/docs/resources/script.md +++ b/docs/resources/script.md @@ -72,14 +72,14 @@ resource "coder_script" "shutdown" { ### Required -- `agent_id` (String) The "id" property of a "coder_agent" resource to associate with. +- `agent_id` (String) The `id` property of a `coder_agent` resource to associate with. - `display_name` (String) The display name of the script to display logs in the dashboard. - `script` (String) The content of the script that will be run. ### Optional - `cron` (String) The cron schedule to run the script on. This is a cron expression. -- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `data.coder_workspace.me.access_url + "/icon/"`. +- `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. - `log_path` (String) The path of a file to write the logs to. If relative, it will be appended to tmp. - `run_on_start` (Boolean) This option defines whether or not the script should run when the agent starts. The script should exit when it is done to signal that the agent is ready. - `run_on_stop` (Boolean) This option defines whether or not the script should run when the agent stops. The script should exit when it is done to signal that the workspace can be stopped. diff --git a/provider/agent.go b/provider/agent.go index 0ff5ca21..352dd5d9 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -92,7 +92,7 @@ func agentResource() *schema.Resource { Type: schema.TypeString, ForceNew: true, Required: true, - Description: `The architecture the agent will run on. Must be one of: "amd64", "armv7", "arm64".`, + Description: "The architecture the agent will run on. Must be one of: `\"amd64\"`, `\"armv7\"`, `\"arm64\"`.", ValidateFunc: validation.StringInSlice([]string{"amd64", "armv7", "arm64"}, false), }, "auth": { @@ -100,14 +100,14 @@ func agentResource() *schema.Resource { Default: "token", ForceNew: true, Optional: true, - Description: `The authentication type the agent will use. Must be one of: "token", "google-instance-identity", "aws-instance-identity", "azure-instance-identity".`, + Description: "The authentication type the agent will use. Must be one of: `\"token\"`, `\"google-instance-identity\"`, `\"aws-instance-identity\"`, `\"azure-instance-identity\"`.", ValidateFunc: validation.StringInSlice([]string{"token", "google-instance-identity", "aws-instance-identity", "azure-instance-identity"}, false), }, "dir": { Type: schema.TypeString, ForceNew: true, Optional: true, - Description: "The starting directory when a user creates a shell session. Defaults to $HOME.", + Description: "The starting directory when a user creates a shell session. Defaults to `\"$HOME\"`.", }, "env": { ForceNew: true, @@ -119,12 +119,12 @@ func agentResource() *schema.Resource { Type: schema.TypeString, ForceNew: true, Required: true, - Description: `The operating system the agent will run on. Must be one of: "linux", "darwin", or "windows".`, + Description: "The operating system the agent will run on. Must be one of: `\"linux\"`, `\"darwin\"`, or `\"windows\"`.", ValidateFunc: validation.StringInSlice([]string{"linux", "darwin", "windows"}, false), }, "startup_script": { ForceNew: true, - Description: `A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a "coder_script" resource with "run_on_start" set to true.`, + Description: "A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a `coder_script` resource with `run_on_start` set to `true`.", Type: schema.TypeString, Optional: true, }, @@ -141,7 +141,7 @@ func agentResource() *schema.Resource { Type: schema.TypeString, ForceNew: true, Optional: true, - Description: `A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a "coder_script" resource with "run_on_stop" set to true.`, + Description: "A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a `coder_script` resource with `run_on_stop` set to `true`.", }, "shutdown_script_timeout": { Type: schema.TypeInt, @@ -155,7 +155,7 @@ func agentResource() *schema.Resource { "token": { ForceNew: true, Sensitive: true, - Description: `Set the environment variable "CODER_AGENT_TOKEN" with this token to authenticate an agent.`, + Description: "Set the environment variable `CODER_AGENT_TOKEN` with this token to authenticate an agent.", Type: schema.TypeString, Computed: true, }, @@ -177,7 +177,7 @@ func agentResource() *schema.Resource { Type: schema.TypeString, ForceNew: true, Optional: true, - Description: "The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be /etc/motd.", + Description: "The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be `\"/etc/motd\"`.", }, "login_before_ready": { // Note: When this is removed, "startup_script_behavior" should @@ -186,8 +186,8 @@ func agentResource() *schema.Resource { Default: true, ForceNew: true, Optional: true, - Description: "This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the startup_script is done and has exited. When enabled, users may see an incomplete workspace when logging in.", - Deprecated: "Configure startup_script_behavior instead. This attribute will be removed in a future version of the provider.", + Description: "This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the `startup_script` is done and has exited. When enabled, users may see an incomplete workspace when logging in.", + Deprecated: "Configure `startup_script_behavior` instead. This attribute will be removed in a future version of the provider.", ConflictsWith: []string{"startup_script_behavior"}, }, "startup_script_behavior": { @@ -200,13 +200,13 @@ func agentResource() *schema.Resource { Type: schema.TypeString, ForceNew: true, Optional: true, - Description: `This option sets the behavior of the "startup_script". When set to "blocking", the startup_script must exit before the workspace is ready. When set to "non-blocking", the startup_script may run in the background and the workspace will be ready immediately. Default is "non-blocking", although "blocking" is recommended. This option is an alias for defining a "coder_script" resource with "start_blocks_login" set to true (blocking).`, + Description: "This option sets the behavior of the `startup_script`. When set to `\"blocking\"`, the `startup_script` must exit before the workspace is ready. When set to `\"non-blocking\"`, the `startup_script` may run in the background and the workspace will be ready immediately. Default is `\"non-blocking\"`, although `\"blocking\"` is recommended. This option is an alias for defining a `coder_script` resource with `start_blocks_login` set to `true` (blocking).", ValidateFunc: validation.StringInSlice([]string{"blocking", "non-blocking"}, false), ConflictsWith: []string{"login_before_ready"}, }, "metadata": { Type: schema.TypeList, - Description: "Each \"metadata\" block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases.", + Description: "Each `metadata` block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases.", ForceNew: true, Optional: true, Elem: &schema.Resource{ @@ -313,9 +313,9 @@ func agentResource() *schema.Resource { func agentInstanceResource() *schema.Resource { return &schema.Resource{ Description: "Use this resource to associate an instance ID with an agent for zero-trust " + - "authentication. This association is done automatically for \"google_compute_instance\", " + - "\"aws_instance\", \"azurerm_linux_virtual_machine\", and " + - "\"azurerm_windows_virtual_machine\" resources.", + "authentication. This association is done automatically for `\"google_compute_instance\"`, " + + "`\"aws_instance\"`, `\"azurerm_linux_virtual_machine\"`, and " + + "`\"azurerm_windows_virtual_machine\"` resources.", CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { resourceData.SetId(uuid.NewString()) return nil @@ -329,14 +329,14 @@ func agentInstanceResource() *schema.Resource { Schema: map[string]*schema.Schema{ "agent_id": { Type: schema.TypeString, - Description: `The "id" property of a "coder_agent" resource to associate with.`, + Description: "The `id` property of a `coder_agent` resource to associate with.", ForceNew: true, Required: true, }, "instance_id": { ForceNew: true, Required: true, - Description: `The instance identifier of a provisioned resource.`, + Description: "The instance identifier of a provisioned resource.", Type: schema.TypeString, }, }, diff --git a/provider/app.go b/provider/app.go index c2690311..91c08f0f 100644 --- a/provider/app.go +++ b/provider/app.go @@ -41,7 +41,7 @@ func appResource() *schema.Resource { Schema: map[string]*schema.Schema{ "agent_id": { Type: schema.TypeString, - Description: `The "id" property of a "coder_agent" resource to associate with.`, + Description: "The `id` property of a `coder_agent` resource to associate with.", ForceNew: true, Required: true, }, @@ -49,7 +49,7 @@ func appResource() *schema.Resource { Type: schema.TypeString, Description: "A command to run in a terminal opening this app. In the web, " + "this will open in a new tab. In the CLI, this will SSH and execute the command. " + - "Either \"command\" or \"url\" may be specified, but not both.", + "Either `command` or `url` may be specified, but not both.", ConflictsWith: []string{"url"}, Optional: true, ForceNew: true, @@ -58,7 +58,7 @@ func appResource() *schema.Resource { Type: schema.TypeString, Description: "A URL to an icon that will display in the dashboard. View built-in " + "icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " + - "built-in icon with `data.coder_workspace.me.access_url + \"/icon/\"`.", + "built-in icon with `\"${data.coder_workspace.me.access_url}/icon/\"`.", ForceNew: true, Optional: true, ValidateFunc: func(i interface{}, s string) ([]string, []error) { @@ -84,7 +84,7 @@ func appResource() *schema.Resource { } if !appSlugRegex.MatchString(valStr) { - return diag.Errorf("invalid coder_app slug, must be a valid hostname (%q, cannot contain two consecutive hyphens or start/end with a hyphen): %q", appSlugRegex.String(), valStr) + return diag.Errorf(`invalid "coder_app" slug, must be a valid hostname (%q, cannot contain two consecutive hyphens or start/end with a hyphen): %q`, appSlugRegex.String(), valStr) } return nil @@ -109,7 +109,7 @@ func appResource() *schema.Resource { Description: "Determines whether the app will be accessed via it's own " + "subdomain or whether it will be accessed via a path on Coder. If " + "wildcards have not been setup by the administrator then apps with " + - "\"subdomain\" set to true will not be accessible. Defaults to false.", + "`subdomain` set to `true` will not be accessible. Defaults to `false`.", ForceNew: true, Optional: true, }, @@ -117,19 +117,19 @@ func appResource() *schema.Resource { Type: schema.TypeBool, Deprecated: "`relative_path` on apps is deprecated, use `subdomain` instead.", Description: "Specifies whether the URL will be accessed via a relative " + - "path or wildcard. Use if wildcard routing is unavailable. Defaults to true.", + "path or wildcard. Use if wildcard routing is unavailable. Defaults to `true`.", ForceNew: true, Optional: true, ConflictsWith: []string{"subdomain"}, }, "share": { Type: schema.TypeString, - Description: `Determines the "level" which the application ` + - `is shared at. Valid levels are "owner" (default), ` + - `"authenticated" and "public". Level "owner" disables ` + + Description: "Determines the level which the application " + + "is shared at. Valid levels are `\"owner\"` (default), " + + "`\"authenticated\"` and `\"public\"`. Level `\"owner\"` disables " + "sharing on the app, so only the workspace owner can " + - `access it. Level "authenticated" shares the app with ` + - `all authenticated users. Level "public" shares it with ` + + "access it. Level `\"authenticated\"` shares the app with " + + "all authenticated users. Level `\"public\"` shares it with " + "any user, including unauthenticated users. Permitted " + "application sharing levels can be configured site-wide " + "via a flag on `coder server` (Enterprise only).", @@ -147,21 +147,21 @@ func appResource() *schema.Resource { return nil } - return diag.Errorf(`invalid app share %q, must be one of "owner", "authenticated", "public"`, valStr) + return diag.Errorf("invalid app share %q, must be one of \"owner\", \"authenticated\", \"public\"", valStr) }, }, "url": { Type: schema.TypeString, - Description: "An external url if \"external=true\" or a URL to be proxied to from inside the workspace. " + - "This should be of the form \"http://localhost:PORT[/SUBPATH]\". " + - "Either \"command\" or \"url\" may be specified, but not both.", + Description: "An external url if `external=true` or a URL to be proxied to from inside the workspace. " + + "This should be of the form `http://localhost:PORT[/SUBPATH]`. " + + "Either `command` or `url` may be specified, but not both.", ForceNew: true, Optional: true, ConflictsWith: []string{"command"}, }, "external": { Type: schema.TypeBool, - Description: "Specifies whether \"url\" is opened on the client machine " + + Description: "Specifies whether `url` is opened on the client machine " + "instead of proxied through the workspace.", Default: false, ForceNew: true, @@ -179,7 +179,7 @@ func appResource() *schema.Resource { Schema: map[string]*schema.Schema{ "url": { Type: schema.TypeString, - Description: "HTTP address used determine the application readiness. A successful health check is a HTTP response code less than 500 returned before healthcheck.interval seconds.", + Description: "HTTP address used determine the application readiness. A successful health check is a HTTP response code less than 500 returned before `healthcheck.interval` seconds.", ForceNew: true, Required: true, }, diff --git a/provider/env.go b/provider/env.go index 8f55ff8c..d45201ee 100644 --- a/provider/env.go +++ b/provider/env.go @@ -14,7 +14,7 @@ func envResource() *schema.Resource { return &schema.Resource{ SchemaVersion: 1, - Description: `Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the "coder_agent" resource.`, + Description: "Use this resource to set an environment variable in a workspace. Note that this resource cannot be used to overwrite existing environment variables set on the `coder_agent` resource.", CreateContext: func(_ context.Context, rd *schema.ResourceData, _ interface{}) diag.Diagnostics { rd.SetId(uuid.NewString()) @@ -25,7 +25,7 @@ func envResource() *schema.Resource { Schema: map[string]*schema.Schema{ "agent_id": { Type: schema.TypeString, - Description: `The "id" property of a "coder_agent" resource to associate with.`, + Description: "The `id` property of a `coder_agent` resource to associate with.", ForceNew: true, Required: true, }, diff --git a/provider/gitauth.go b/provider/gitauth.go index 72c05bcd..e8230ade 100644 --- a/provider/gitauth.go +++ b/provider/gitauth.go @@ -16,7 +16,7 @@ func gitAuthDataSource() *schema.Resource { SchemaVersion: 1, DeprecationMessage: "Use the `coder_external_auth` data source instead.", - Description: "Use this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated `git clone` in startup scripts.", + Description: "~> **Deprecated**\nUse the `coder_external_auth` data source instead.\n\nUse this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated `git clone` in startup scripts.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { rawID, ok := rd.GetOk("id") if !ok { diff --git a/provider/metadata.go b/provider/metadata.go index 00b488e4..abfe0a05 100644 --- a/provider/metadata.go +++ b/provider/metadata.go @@ -38,7 +38,7 @@ func metadataResource() *schema.Resource { Schema: map[string]*schema.Schema{ "resource_id": { Type: schema.TypeString, - Description: "The \"id\" property of another resource that metadata should be attached to.", + Description: "The `id` property of another resource that metadata should be attached to.", ForceNew: true, Required: true, }, @@ -51,8 +51,8 @@ func metadataResource() *schema.Resource { "icon": { Type: schema.TypeString, Description: "A URL to an icon that will display in the dashboard. View built-in " + - "icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " + - "built-in icon with `data.coder_workspace.me.access_url + \"/icon/\"`.", + "icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " + + "built-in icon with `\"${data.coder_workspace.me.access_url}/icon/\"`.", ForceNew: true, Optional: true, ValidateFunc: func(i interface{}, s string) ([]string, []error) { @@ -73,7 +73,7 @@ func metadataResource() *schema.Resource { }, "item": { Type: schema.TypeList, - Description: "Each \"item\" block defines a single metadata item consisting of a key/value pair.", + Description: "Each `item` block defines a single metadata item consisting of a key/value pair.", ForceNew: true, Optional: true, Elem: &schema.Resource{ @@ -92,7 +92,7 @@ func metadataResource() *schema.Resource { }, "sensitive": { Type: schema.TypeBool, - Description: "Set to \"true\" to for items such as API keys whose values should be " + + Description: "Set to `true` to for items such as API keys whose values should be " + "hidden from view by default. Note that this does not prevent metadata from " + "being retrieved using the API, so it is not suitable for secrets that should " + "not be exposed to workspace users.", diff --git a/provider/parameter.go b/provider/parameter.go index 281537f6..00dd5f34 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -204,7 +204,7 @@ func parameterDataSource() *schema.Resource { Default: "string", Optional: true, ValidateFunc: validation.StringInSlice([]string{"number", "string", "bool", "list(string)"}, false), - Description: `The type of this parameter. Must be one of: "number", "string", "bool", or "list(string)".`, + Description: "The type of this parameter. Must be one of: `\"number\"`, `\"string\"`, `\"bool\"`, or `\"list(string)\"`.", }, "mutable": { Type: schema.TypeBool, @@ -221,7 +221,7 @@ func parameterDataSource() *schema.Resource { Type: schema.TypeString, Description: "A URL to an icon that will display in the dashboard. View built-in " + "icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " + - "built-in icon with `data.coder_workspace.me.access_url + \"/icon/\"`.", + "built-in icon with `\"${data.coder_workspace.me.access_url}/icon/\"`.", ForceNew: true, Optional: true, ValidateFunc: func(i interface{}, s string) ([]string, []error) { @@ -234,7 +234,7 @@ func parameterDataSource() *schema.Resource { }, "option": { Type: schema.TypeList, - Description: "Each \"option\" block defines a value for a user to select from.", + Description: "Each `option` block defines a value for a user to select from.", ForceNew: true, Optional: true, MaxItems: 64, @@ -261,8 +261,8 @@ func parameterDataSource() *schema.Resource { "icon": { Type: schema.TypeString, Description: "A URL to an icon that will display in the dashboard. View built-in " + - "icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " + - "built-in icon with `data.coder_workspace.me.access_url + \"/icon/\"`.", + "icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " + + "built-in icon with `\"${data.coder_workspace.me.access_url}/icon/\"`.", ForceNew: true, Optional: true, ValidateFunc: func(i interface{}, s string) ([]string, []error) { diff --git a/provider/script.go b/provider/script.go index 536a5732..df436ead 100644 --- a/provider/script.go +++ b/provider/script.go @@ -26,10 +26,10 @@ func scriptResource() *schema.Resource { cron, _ := rd.Get("cron").(string) if !runOnStart && !runOnStop && cron == "" { - return diag.Errorf("at least one of run_on_start, run_on_stop, or cron must be set") + return diag.Errorf(`at least one of "run_on_start", "run_on_stop", or "cron" must be set`) } if !runOnStart && startBlocksLogin { - return diag.Errorf("start_blocks_login can only be set if run_on_start is true") + return diag.Errorf(`"start_blocks_login" can only be set if "run_on_start" is "true"`) } return nil }, @@ -38,7 +38,7 @@ func scriptResource() *schema.Resource { Schema: map[string]*schema.Schema{ "agent_id": { Type: schema.TypeString, - Description: `The "id" property of a "coder_agent" resource to associate with.`, + Description: "The `id` property of a `coder_agent` resource to associate with.", ForceNew: true, Required: true, }, @@ -59,8 +59,8 @@ func scriptResource() *schema.Resource { ForceNew: true, Optional: true, Description: "A URL to an icon that will display in the dashboard. View built-in " + - "icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a " + - "built-in icon with `data.coder_workspace.me.access_url + \"/icon/\"`.", + "icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " + + "built-in icon with `\"${data.coder_workspace.me.access_url}/icon/\"`.", }, "script": { ForceNew: true, diff --git a/provider/script_test.go b/provider/script_test.go index 937c6008..9b6bd570 100644 --- a/provider/script_test.go +++ b/provider/script_test.go @@ -69,7 +69,7 @@ func TestScriptNeverRuns(t *testing.T) { script = "Wow" } `, - ExpectError: regexp.MustCompile(`at least one of run_on_start, run_on_stop, or cron must be set`), + ExpectError: regexp.MustCompile(`at least one of "run_on_start", "run_on_stop", or "cron" must be set`), }}, }) } @@ -94,7 +94,7 @@ func TestScriptStartBlocksLoginRequiresRunOnStart(t *testing.T) { start_blocks_login = true } `, - ExpectError: regexp.MustCompile(`start_blocks_login can only be set if run_on_start is true`), + ExpectError: regexp.MustCompile(`"start_blocks_login" can only be set if "run_on_start" is "true"`), }}, }) resource.Test(t, resource.TestCase{ diff --git a/provider/workspace.go b/provider/workspace.go index d8bfd3a5..3f667e8f 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -115,12 +115,12 @@ func workspaceDataSource() *schema.Resource { "start_count": { Type: schema.TypeInt, Computed: true, - Description: `A computed count based on "transition" state. If "start", count will equal 1.`, + Description: "A computed count based on `transition` state. If `start`, count will equal 1.", }, "transition": { Type: schema.TypeString, Computed: true, - Description: `Either "start" or "stop". Use this to start/stop resources with "count".`, + Description: "Either `start` or `stop`. Use this to start/stop resources with `count`.", }, "owner": { Type: schema.TypeString, diff --git a/scripts/docsgen/main.go b/scripts/docsgen/main.go index 90244ccd..d83cf123 100644 --- a/scripts/docsgen/main.go +++ b/scripts/docsgen/main.go @@ -84,6 +84,6 @@ func writeDeprecationMessage(doc []byte, schemas map[string]*schema.Schema) []by log.Printf("warn: same property name `%s` but description does not match, most likely a different property", propertyName) return m } - return bytes.Replace(m, []byte("Deprecated"), []byte(fmt.Sprintf("Deprecated: %s", sch.Deprecated)), 1) + return bytes.Replace(m, []byte("Deprecated"), []byte(fmt.Sprintf("**Deprecated**: %s", sch.Deprecated)), 1) }) } From 3022367b60b7d7c535101798ce17270832552fd6 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 5 Jul 2024 11:40:20 +0300 Subject: [PATCH 033/114] ci: drop terraform versions < 1.5.x, add 1.9.x for tests (#258) --- .github/workflows/test.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f17d1c10..57faf626 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,15 +64,11 @@ jobs: fail-fast: false matrix: terraform: - - "1.0.*" - - "1.1.*" - - "1.2.*" - - "1.3.*" - - "1.4.*" - "1.5.*" - "1.6.*" - "1.7.*" - "1.8.*" + - "1.9.*" steps: - name: Set up Go uses: actions/setup-go@v5 @@ -112,7 +108,7 @@ jobs: - uses: hashicorp/setup-terraform@v3 with: - terraform_version: "1.3.*" + terraform_version: "latest" terraform_wrapper: false - name: Check out code into the Go module directory From 7ed088d0942fc4b24cf26c3e031a15618667f3c0 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 5 Jul 2024 11:40:47 +0300 Subject: [PATCH 034/114] chore!: remove deprecated items from `coder_workspace` data source (#255) --- docs/data-sources/workspace.md | 53 ++++++++++--- docs/data-sources/workspace_owner.md | 6 +- .../coder_workspace/data-source.tf | 46 ++++++++++- .../coder_workspace_owner/data-source.tf | 6 +- integration/integration_test.go | 38 +++------- integration/test-data-source/main.tf | 10 +-- integration/workspace-owner/main.tf | 10 +-- provider/workspace.go | 76 ------------------- provider/workspace_test.go | 29 +------ 9 files changed, 107 insertions(+), 167 deletions(-) diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index 8b824c37..26396ba1 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -13,11 +13,51 @@ Use this data source to get information for the active workspace build. ## Example Usage ```terraform -data "coder_workspace" "dev" { +provider "coder" {} + +provider "docker" {} + +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + dir = "/workspace" } -resource "kubernetes_pod" "dev" { - count = data.coder_workspace.dev.transition == "start" ? 1 : 0 +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = docker_image.main.name + # Uses lower() to avoid Docker restriction on container names. + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + # Hostname makes the shell more user friendly: coder@my-workspace:~$ + hostname = data.coder_workspace.me.name + # Use the docker gateway if the access URL is 127.0.0.1 + entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")] + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] + host { + host = "host.docker.internal" + ip = "host-gateway" + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } } ``` @@ -30,13 +70,6 @@ resource "kubernetes_pod" "dev" { - `access_url` (String) The access URL of the Coder deployment provisioning this workspace. - `id` (String) UUID of the workspace. - `name` (String) Name of the workspace. -- `owner` (String, **Deprecated**: Use `coder_workspace_owner.name` instead.) Username of the workspace owner. -- `owner_email` (String, **Deprecated**: Use `coder_workspace_owner.email` instead.) Email address of the workspace owner. -- `owner_groups` (List of String, **Deprecated**: Use `coder_workspace_owner.groups` instead.) List of groups the workspace owner belongs to. -- `owner_id` (String, **Deprecated**: Use `coder_workspace_owner.id` instead.) UUID of the workspace owner. -- `owner_name` (String, **Deprecated**: Use `coder_workspace_owner.full_name` instead.) Name of the workspace owner. -- `owner_oidc_access_token` (String, **Deprecated**: Use `coder_workspace_owner.oidc_access_token` instead.) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. -- `owner_session_token` (String, **Deprecated**: Use `coder_workspace_owner.session_token` instead.) Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started. - `start_count` (Number) A computed count based on `transition` state. If `start`, count will equal 1. - `template_id` (String) ID of the workspace's template. - `template_name` (String) Name of the workspace's template. diff --git a/docs/data-sources/workspace_owner.md b/docs/data-sources/workspace_owner.md index 0deff622..1c64ea50 100644 --- a/docs/data-sources/workspace_owner.md +++ b/docs/data-sources/workspace_owner.md @@ -15,14 +15,12 @@ Use this data source to fetch information about the workspace owner. ```terraform provider "coder" {} -data "coder_workspace" "me" {} - data "coder_workspace_owner" "me" {} resource "coder_agent" "dev" { arch = "amd64" os = "linux" - dir = local.repo_dir + dir = "/workspace" env = { OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token, } @@ -36,7 +34,7 @@ resource "coder_env" "git_author_name" { } resource "coder_env" "git_author_email" { - agent_id = var.agent_id + agent_id = coder_agent.dev.id name = "GIT_AUTHOR_EMAIL" value = data.coder_workspace_owner.me.email count = data.coder_workspace_owner.me.email != "" ? 1 : 0 diff --git a/examples/data-sources/coder_workspace/data-source.tf b/examples/data-sources/coder_workspace/data-source.tf index 4898439b..8eb4a8f8 100644 --- a/examples/data-sources/coder_workspace/data-source.tf +++ b/examples/data-sources/coder_workspace/data-source.tf @@ -1,6 +1,46 @@ -data "coder_workspace" "dev" { +provider "coder" {} + +provider "docker" {} + +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + +resource "coder_agent" "dev" { + arch = "amd64" + os = "linux" + dir = "/workspace" } -resource "kubernetes_pod" "dev" { - count = data.coder_workspace.dev.transition == "start" ? 1 : 0 +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = docker_image.main.name + # Uses lower() to avoid Docker restriction on container names. + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + # Hostname makes the shell more user friendly: coder@my-workspace:~$ + hostname = data.coder_workspace.me.name + # Use the docker gateway if the access URL is 127.0.0.1 + entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")] + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] + host { + host = "host.docker.internal" + ip = "host-gateway" + } + # Add labels in Docker to keep track of orphan resources. + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } } diff --git a/examples/data-sources/coder_workspace_owner/data-source.tf b/examples/data-sources/coder_workspace_owner/data-source.tf index fc27db6c..cad73e1e 100644 --- a/examples/data-sources/coder_workspace_owner/data-source.tf +++ b/examples/data-sources/coder_workspace_owner/data-source.tf @@ -1,13 +1,11 @@ provider "coder" {} -data "coder_workspace" "me" {} - data "coder_workspace_owner" "me" {} resource "coder_agent" "dev" { arch = "amd64" os = "linux" - dir = local.repo_dir + dir = "/workspace" env = { OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token, } @@ -21,7 +19,7 @@ resource "coder_env" "git_author_name" { } resource "coder_env" "git_author_email" { - agent_id = var.agent_id + agent_id = coder_agent.dev.id name = "GIT_AUTHOR_EMAIL" value = data.coder_workspace_owner.me.email count = data.coder_workspace_owner.me.email != "" ? 1 : 0 diff --git a/integration/integration_test.go b/integration/integration_test.go index 75b35d20..8208d20a 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -71,25 +71,18 @@ func TestIntegration(t *testing.T) { name: "test-data-source", minVersion: "v0.0.0", expectedOutput: map[string]string{ - "provisioner.arch": runtime.GOARCH, - "provisioner.id": `[a-zA-Z0-9-]+`, - "provisioner.os": runtime.GOOS, - "workspace.access_port": `\d+`, - "workspace.access_url": `https?://\D+:\d+`, - "workspace.id": `[a-zA-z0-9-]+`, - "workspace.name": `test-data-source`, - "workspace.owner": `testing`, - "workspace.owner_email": `testing@coder\.com`, - "workspace.owner_groups": `\[\]`, - "workspace.owner_id": `[a-zA-Z0-9]+`, - "workspace.owner_name": `default`, - "workspace.owner_oidc_access_token": `^$`, // TODO: need a test OIDC integration - "workspace.owner_session_token": `[a-zA-Z0-9-]+`, - "workspace.start_count": `1`, - "workspace.template_id": `[a-zA-Z0-9-]+`, - "workspace.template_name": `test-data-source`, - "workspace.template_version": `.+`, - "workspace.transition": `start`, + "provisioner.arch": runtime.GOARCH, + "provisioner.id": `[a-zA-Z0-9-]+`, + "provisioner.os": runtime.GOOS, + "workspace.access_port": `\d+`, + "workspace.access_url": `https?://\D+:\d+`, + "workspace.id": `[a-zA-z0-9-]+`, + "workspace.name": `test-data-source`, + "workspace.start_count": `1`, + "workspace.template_id": `[a-zA-Z0-9-]+`, + "workspace.template_name": `test-data-source`, + "workspace.template_version": `.+`, + "workspace.transition": `start`, }, }, { @@ -103,13 +96,6 @@ func TestIntegration(t *testing.T) { "workspace.access_url": `https?://\D+:\d+`, "workspace.id": `[a-zA-z0-9-]+`, "workspace.name": ``, - "workspace.owner": `testing`, - "workspace.owner_email": `testing@coder\.com`, - "workspace.owner_groups": `\[\]`, - "workspace.owner_id": `[a-zA-Z0-9]+`, - "workspace.owner_name": `default`, - "workspace.owner_oidc_access_token": `^$`, // TODO: need a test OIDC integration - "workspace.owner_session_token": `[a-zA-Z0-9-]+`, "workspace.start_count": `1`, "workspace.template_id": `[a-zA-Z0-9-]+`, "workspace.template_name": `workspace-owner`, diff --git a/integration/test-data-source/main.tf b/integration/test-data-source/main.tf index 838125a0..6d4b85cd 100644 --- a/integration/test-data-source/main.tf +++ b/integration/test-data-source/main.tf @@ -9,9 +9,8 @@ terraform { } } -// TODO: test coder_external_auth and coder_git_auth +// TODO: test coder_external_auth // data coder_external_auth "me" {} -// data coder_git_auth "me" {} data "coder_provisioner" "me" {} data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} @@ -26,13 +25,6 @@ locals { "workspace.access_url" : data.coder_workspace.me.access_url, "workspace.id" : data.coder_workspace.me.id, "workspace.name" : data.coder_workspace.me.name, - "workspace.owner" : data.coder_workspace.me.owner, - "workspace.owner_email" : data.coder_workspace.me.owner_email, - "workspace.owner_groups" : jsonencode(data.coder_workspace.me.owner_groups), - "workspace.owner_id" : data.coder_workspace.me.owner_id, - "workspace.owner_name" : data.coder_workspace.me.owner_name, - "workspace.owner_oidc_access_token" : data.coder_workspace.me.owner_oidc_access_token, - "workspace.owner_session_token" : data.coder_workspace.me.owner_session_token, "workspace.start_count" : tostring(data.coder_workspace.me.start_count), "workspace.template_id" : data.coder_workspace.me.template_id, "workspace.template_name" : data.coder_workspace.me.template_name, diff --git a/integration/workspace-owner/main.tf b/integration/workspace-owner/main.tf index 580592cb..2be11d8e 100644 --- a/integration/workspace-owner/main.tf +++ b/integration/workspace-owner/main.tf @@ -9,9 +9,8 @@ terraform { } } -// TODO: test coder_external_auth and coder_git_auth +// TODO: test coder_external_auth // data coder_external_auth "me" {} -// data coder_git_auth "me" {} data "coder_provisioner" "me" {} data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} @@ -26,13 +25,6 @@ locals { "workspace.access_url" : data.coder_workspace.me.access_url, "workspace.id" : data.coder_workspace.me.id, "workspace.name" : data.coder_workspace.me.name, - "workspace.owner" : data.coder_workspace.me.owner, - "workspace.owner_email" : data.coder_workspace.me.owner_email, - "workspace.owner_groups" : jsonencode(data.coder_workspace.me.owner_groups), - "workspace.owner_id" : data.coder_workspace.me.owner_id, - "workspace.owner_name" : data.coder_workspace.me.owner_name, - "workspace.owner_oidc_access_token" : data.coder_workspace.me.owner_oidc_access_token, - "workspace.owner_session_token" : data.coder_workspace.me.owner_session_token, "workspace.start_count" : tostring(data.coder_workspace.me.start_count), "workspace.template_id" : data.coder_workspace.me.template_id, "workspace.template_name" : data.coder_workspace.me.template_name, diff --git a/provider/workspace.go b/provider/workspace.go index 3f667e8f..575fd60f 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -2,7 +2,6 @@ package provider import ( "context" - "encoding/json" "reflect" "strconv" @@ -28,37 +27,9 @@ func workspaceDataSource() *schema.Resource { } _ = rd.Set("start_count", count) - owner := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER", "default") - _ = rd.Set("owner", owner) - - ownerEmail := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER_EMAIL", "default@example.com") - _ = rd.Set("owner_email", ownerEmail) - - ownerGroupsText := helpers.OptionalEnv("CODER_WORKSPACE_OWNER_GROUPS") - var ownerGroups []string - if ownerGroupsText != "" { - err := json.Unmarshal([]byte(ownerGroupsText), &ownerGroups) - if err != nil { - return diag.Errorf("couldn't parse owner groups %q", ownerGroupsText) - } - } - _ = rd.Set("owner_groups", ownerGroups) - - ownerName := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER_NAME", "default") - _ = rd.Set("owner_name", ownerName) - - ownerID := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_OWNER_ID", uuid.Nil.String()) - _ = rd.Set("owner_id", ownerID) - - ownerOIDCAccessToken := helpers.OptionalEnv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN") - _ = rd.Set("owner_oidc_access_token", ownerOIDCAccessToken) - name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default") rd.Set("name", name) - sessionToken := helpers.OptionalEnv("CODER_WORKSPACE_OWNER_SESSION_TOKEN") - _ = rd.Set("owner_session_token", sessionToken) - id := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_ID", uuid.NewString()) rd.SetId(id) @@ -122,47 +93,6 @@ func workspaceDataSource() *schema.Resource { Computed: true, Description: "Either `start` or `stop`. Use this to start/stop resources with `count`.", }, - "owner": { - Type: schema.TypeString, - Computed: true, - Description: "Username of the workspace owner.", - Deprecated: "Use `coder_workspace_owner.name` instead.", - }, - "owner_email": { - Type: schema.TypeString, - Computed: true, - Description: "Email address of the workspace owner.", - Deprecated: "Use `coder_workspace_owner.email` instead.", - }, - "owner_id": { - Type: schema.TypeString, - Computed: true, - Description: "UUID of the workspace owner.", - Deprecated: "Use `coder_workspace_owner.id` instead.", - }, - "owner_name": { - Type: schema.TypeString, - Computed: true, - Description: "Name of the workspace owner.", - Deprecated: "Use `coder_workspace_owner.full_name` instead.", - }, - "owner_oidc_access_token": { - Type: schema.TypeString, - Computed: true, - Description: "A valid OpenID Connect access token of the workspace owner. " + - "This is only available if the workspace owner authenticated with OpenID Connect. " + - "If a valid token cannot be obtained, this value will be an empty string.", - Deprecated: "Use `coder_workspace_owner.oidc_access_token` instead.", - }, - "owner_groups": { - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Computed: true, - Description: "List of groups the workspace owner belongs to.", - Deprecated: "Use `coder_workspace_owner.groups` instead.", - }, "id": { Type: schema.TypeString, Computed: true, @@ -173,12 +103,6 @@ func workspaceDataSource() *schema.Resource { Computed: true, Description: "Name of the workspace.", }, - "owner_session_token": { - Type: schema.TypeString, - Computed: true, - Description: "Session token for authenticating with a Coder deployment. It is regenerated everytime a workspace is started.", - Deprecated: "Use `coder_workspace_owner.session_token` instead.", - }, "template_id": { Type: schema.TypeString, Computed: true, diff --git a/provider/workspace_test.go b/provider/workspace_test.go index e53f30d4..12d5210b 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -14,13 +14,6 @@ import ( ) func TestWorkspace(t *testing.T) { - t.Setenv("CODER_WORKSPACE_OWNER", "owner123") - t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111") - t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner") - t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com") - t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123") - t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) - t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", "supersecret") t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID") t.Setenv("CODER_WORKSPACE_TEMPLATE_NAME", "template123") t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") @@ -49,16 +42,9 @@ func TestWorkspace(t *testing.T) { t.Log(value) assert.Equal(t, "https://example.com:8080", attribs["access_url"]) assert.Equal(t, "8080", attribs["access_port"]) - assert.Equal(t, "owner123", attribs["owner"]) - assert.Equal(t, "11111111-1111-1111-1111-111111111111", attribs["owner_id"]) - assert.Equal(t, "Mr Owner", attribs["owner_name"]) - assert.Equal(t, "owner123@example.com", attribs["owner_email"]) - assert.Equal(t, "group1", attribs["owner_groups.0"]) - assert.Equal(t, "group2", attribs["owner_groups.1"]) assert.Equal(t, "templateID", attribs["template_id"]) assert.Equal(t, "template123", attribs["template_name"]) assert.Equal(t, "v1.2.3", attribs["template_version"]) - assert.Equal(t, "supersecret", attribs["owner_oidc_access_token"]) return nil }, }}, @@ -66,9 +52,6 @@ func TestWorkspace(t *testing.T) { } func TestWorkspace_UndefinedOwner(t *testing.T) { - t.Setenv("CODER_WORKSPACE_OWNER", "owner123") - t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123") - t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID") t.Setenv("CODER_WORKSPACE_TEMPLATE_NAME", "template123") t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") @@ -95,8 +78,9 @@ func TestWorkspace_UndefinedOwner(t *testing.T) { value := attribs["transition"] require.NotNil(t, value) t.Log(value) - assert.Equal(t, "owner123", attribs["owner"]) - assert.Equal(t, "default@example.com", attribs["owner_email"]) + assert.Equal(t, "templateID", attribs["template_id"]) + assert.Equal(t, "template123", attribs["template_name"]) + assert.Equal(t, "v1.2.3", attribs["template_version"]) // Skip other asserts return nil }, @@ -107,13 +91,6 @@ func TestWorkspace_UndefinedOwner(t *testing.T) { func TestWorkspace_MissingTemplateName(t *testing.T) { t.Setenv("CODER_WORKSPACE_BUILD_ID", "1") // Let's pretend this is a workspace build - t.Setenv("CODER_WORKSPACE_OWNER", "owner123") - t.Setenv("CODER_WORKSPACE_OWNER_ID", "11111111-1111-1111-1111-111111111111") - t.Setenv("CODER_WORKSPACE_OWNER_NAME", "Mr Owner") - t.Setenv("CODER_WORKSPACE_OWNER_EMAIL", "owner123@example.com") - t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", "abc123") - t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) - t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", "supersecret") t.Setenv("CODER_WORKSPACE_TEMPLATE_ID", "templateID") // CODER_WORKSPACE_TEMPLATE_NAME is missing t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") From 5ca08cbcbeef38fa215823ab90d4c50eb5159ed7 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 5 Jul 2024 11:41:01 +0300 Subject: [PATCH 035/114] chore!: remove deprecated `coder_git_auth` data source (#254) --- docs/data-sources/git_auth.md | 52 ------------------ .../coder_git_auth/data-source.tf | 20 ------- provider/gitauth.go | 53 ------------------- provider/gitauth_test.go | 44 --------------- provider/provider.go | 1 - provider/provider_test.go | 3 -- 6 files changed, 173 deletions(-) delete mode 100644 docs/data-sources/git_auth.md delete mode 100644 examples/data-sources/coder_git_auth/data-source.tf delete mode 100644 provider/gitauth.go delete mode 100644 provider/gitauth_test.go diff --git a/docs/data-sources/git_auth.md b/docs/data-sources/git_auth.md deleted file mode 100644 index e665aeb1..00000000 --- a/docs/data-sources/git_auth.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "coder_git_auth Data Source - terraform-provider-coder" -subcategory: "" -description: |- - ~> Deprecated - Use the coder_external_auth data source instead. - Use this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated git clone in startup scripts. ---- - -# coder_git_auth (Data Source) - -~> **Deprecated** -Use the `coder_external_auth` data source instead. - -Use this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated `git clone` in startup scripts. - -## Example Usage - -```terraform -provider "coder" {} - -data "coder_git_auth" "github" { - # Matches the ID of the git auth provider in Coder. - id = "github" -} - -resource "coder_agent" "dev" { - os = "linux" - arch = "amd64" - dir = "~/coder" - env = { - GITHUB_TOKEN : data.coder_git_auth.github.access_token - } - startup_script = < -## Schema - -### Required - -- `id` (String) The identifier of a configured git auth provider set up in your Coder deployment. - -### Read-Only - -- `access_token` (String) The access token returned by the git authentication provider. This can be used to pre-authenticate command-line tools. diff --git a/examples/data-sources/coder_git_auth/data-source.tf b/examples/data-sources/coder_git_auth/data-source.tf deleted file mode 100644 index 488554f2..00000000 --- a/examples/data-sources/coder_git_auth/data-source.tf +++ /dev/null @@ -1,20 +0,0 @@ -provider "coder" {} - -data "coder_git_auth" "github" { - # Matches the ID of the git auth provider in Coder. - id = "github" -} - -resource "coder_agent" "dev" { - os = "linux" - arch = "amd64" - dir = "~/coder" - env = { - GITHUB_TOKEN : data.coder_git_auth.github.access_token - } - startup_script = < **Deprecated**\nUse the `coder_external_auth` data source instead.\n\nUse this data source to require users to authenticate with a Git provider prior to workspace creation. This can be used to perform an authenticated `git clone` in startup scripts.", - ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { - rawID, ok := rd.GetOk("id") - if !ok { - return diag.Errorf("id is required") - } - id, ok := rawID.(string) - if !ok { - return diag.Errorf("unexpected type %q for id", rawID) - } - rd.SetId(id) - - accessToken := helpers.OptionalEnv(GitAuthAccessTokenEnvironmentVariable(id)) - rd.Set("access_token", accessToken) - - return nil - }, - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Required: true, - Description: "The identifier of a configured git auth provider set up in your Coder deployment.", - }, - "access_token": { - Type: schema.TypeString, - Computed: true, - Description: "The access token returned by the git authentication provider. This can be used to pre-authenticate command-line tools.", - }, - }, - } -} - -func GitAuthAccessTokenEnvironmentVariable(id string) string { - return fmt.Sprintf("CODER_GIT_AUTH_ACCESS_TOKEN_%s", id) -} diff --git a/provider/gitauth_test.go b/provider/gitauth_test.go deleted file mode 100644 index 481d79f5..00000000 --- a/provider/gitauth_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package provider_test - -import ( - "testing" - - "github.com/coder/terraform-provider-coder/provider" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - - "github.com/stretchr/testify/require" -) - -func TestGitAuth(t *testing.T) { - t.Parallel() - - resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, - Steps: []resource.TestStep{{ - Config: ` - provider "coder" { - } - data "coder_git_auth" "github" { - id = "github" - } - `, - Check: func(state *terraform.State) error { - require.Len(t, state.Modules, 1) - require.Len(t, state.Modules[0].Resources, 1) - resource := state.Modules[0].Resources["data.coder_git_auth.github"] - require.NotNil(t, resource) - - attribs := resource.Primary.Attributes - require.Equal(t, "github", attribs["id"]) - - return nil - }, - }}, - }) -} diff --git a/provider/provider.go b/provider/provider.go index c1991a26..4d513139 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -72,7 +72,6 @@ func New() *schema.Provider { "coder_workspace_tags": workspaceTagDataSource(), "coder_provisioner": provisionerDataSource(), "coder_parameter": parameterDataSource(), - "coder_git_auth": gitAuthDataSource(), "coder_external_auth": externalAuthDataSource(), "coder_workspace_owner": workspaceOwnerDataSource(), }, diff --git a/provider/provider_test.go b/provider/provider_test.go index 7069db09..742eb30b 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -37,9 +37,6 @@ func TestProviderEmpty(t *testing.T) { data "coder_external_auth" "git" { id = "git" } - data "coder_git_auth" "git" { - id = "git" - } data "coder_parameter" "param" { name = "hey" }`, From f46148f247afcb5fa8b1127cf27ebab77d9120f0 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 5 Jul 2024 11:41:12 +0300 Subject: [PATCH 036/114] chore!: remove deprecated feature_use_managed_variables (#256) --- docs/index.md | 1 - provider/provider.go | 7 ------- 2 files changed, 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index a4dfd70e..859964d4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -62,5 +62,4 @@ resource "google_compute_instance" "dev" { ### Optional -- `feature_use_managed_variables` (Boolean, **Deprecated**: Terraform variables are now exclusively utilized for template-wide variables after the removal of support for legacy parameters.) Feature: use managed Terraform variables. The feature flag is not used anymore as Terraform variables are now exclusively utilized for template-wide variables. - `url` (String) The URL to access Coder. \ No newline at end of file diff --git a/provider/provider.go b/provider/provider.go index 4d513139..1a37b706 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -35,13 +35,6 @@ func New() *schema.Provider { return nil, nil }, }, - "feature_use_managed_variables": { - Type: schema.TypeBool, - Description: "Feature: use managed Terraform variables. The feature flag is not used anymore as Terraform variables are now exclusively utilized for template-wide variables.", - Default: true, - Optional: true, - Deprecated: "Terraform variables are now exclusively utilized for template-wide variables after the removal of support for legacy parameters.", - }, }, ConfigureContextFunc: func(c context.Context, resourceData *schema.ResourceData) (interface{}, diag.Diagnostics) { rawURL, ok := resourceData.Get("url").(string) From dea044287505e28e99bac4b232c9a9e07f7eb8e3 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 5 Jul 2024 11:44:06 +0300 Subject: [PATCH 037/114] chore!: remove deprecated fields from `coder_app` and `coder_agent` resources (#257) --- docs/resources/agent.md | 3 --- docs/resources/app.md | 2 -- provider/agent.go | 47 ++++++----------------------------------- provider/agent_test.go | 32 ---------------------------- provider/app.go | 17 --------------- 5 files changed, 6 insertions(+), 95 deletions(-) diff --git a/docs/resources/agent.md b/docs/resources/agent.md index e444d2d7..8c786d6e 100644 --- a/docs/resources/agent.md +++ b/docs/resources/agent.md @@ -76,15 +76,12 @@ resource "kubernetes_pod" "dev" { - `dir` (String) The starting directory when a user creates a shell session. Defaults to `"$HOME"`. - `display_apps` (Block Set, Max: 1) The list of built-in apps to display in the agent bar. (see [below for nested schema](#nestedblock--display_apps)) - `env` (Map of String) A mapping of environment variables to set inside the workspace. -- `login_before_ready` (Boolean, **Deprecated**: Configure `startup_script_behavior` instead. This attribute will be removed in a future version of the provider.) This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the `startup_script` is done and has exited. When enabled, users may see an incomplete workspace when logging in. - `metadata` (Block List) Each `metadata` block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases. (see [below for nested schema](#nestedblock--metadata)) - `motd_file` (String) The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be `"/etc/motd"`. - `order` (Number) The order determines the position of agents in the UI presentation. The lowest order is shown first and agents with equal order are sorted by name (ascending order). - `shutdown_script` (String) A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a `coder_script` resource with `run_on_stop` set to `true`. -- `shutdown_script_timeout` (Number, **Deprecated**: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time. - `startup_script` (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a `coder_script` resource with `run_on_start` set to `true`. - `startup_script_behavior` (String) This option sets the behavior of the `startup_script`. When set to `"blocking"`, the `startup_script` must exit before the workspace is ready. When set to `"non-blocking"`, the `startup_script` may run in the background and the workspace will be ready immediately. Default is `"non-blocking"`, although `"blocking"` is recommended. This option is an alias for defining a `coder_script` resource with `start_blocks_login` set to `true` (blocking). -- `startup_script_timeout` (Number, **Deprecated**: This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.) Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time. - `troubleshooting_url` (String) A URL to a document with instructions for troubleshooting problems with the agent. ### Read-Only diff --git a/docs/resources/app.md b/docs/resources/app.md index f61ed799..1cb5d08b 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -64,9 +64,7 @@ resource "coder_app" "vim" { - `external` (Boolean) Specifies whether `url` is opened on the client machine instead of proxied through the workspace. - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. -- `name` (String, **Deprecated**: `name` on apps is deprecated, use `display_name` instead) A display name to identify the app. - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). -- `relative_path` (Boolean, **Deprecated**: `relative_path` on apps is deprecated, use `subdomain` instead.) Specifies whether the URL will be accessed via a relative path or wildcard. Use if wildcard routing is unavailable. Defaults to `true`. - `share` (String) Determines the level which the application is shared at. Valid levels are `"owner"` (default), `"authenticated"` and `"public"`. Level `"owner"` disables sharing on the app, so only the workspace owner can access it. Level `"authenticated"` shares the app with all authenticated users. Level `"public"` shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with `subdomain` set to `true` will not be accessible. Defaults to `false`. - `url` (String) An external url if `external=true` or a URL to be proxied to from inside the workspace. This should be of the form `http://localhost:PORT[/SUBPATH]`. Either `command` or `url` may be specified, but not both. diff --git a/provider/agent.go b/provider/agent.go index 352dd5d9..d0ce4070 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -128,30 +128,12 @@ func agentResource() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "startup_script_timeout": { - Type: schema.TypeInt, - Default: 300, - ForceNew: true, - Optional: true, - Description: "Time in seconds until the agent lifecycle status is marked as timed out during start, this happens when the startup script has not completed (exited) in the given time.", - Deprecated: "This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.", - ValidateFunc: validation.IntAtLeast(1), - }, "shutdown_script": { Type: schema.TypeString, ForceNew: true, Optional: true, Description: "A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a `coder_script` resource with `run_on_stop` set to `true`.", }, - "shutdown_script_timeout": { - Type: schema.TypeInt, - Default: 300, - ForceNew: true, - Optional: true, - Description: "Time in seconds until the agent lifecycle status is marked as timed out during shutdown, this happens when the shutdown script has not completed (exited) in the given time.", - Deprecated: "This feature is deprecated and has no effect. This attribute will be removed in a future version of the provider.", - ValidateFunc: validation.IntAtLeast(1), - }, "token": { ForceNew: true, Sensitive: true, @@ -179,30 +161,13 @@ func agentResource() *schema.Resource { Optional: true, Description: "The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be `\"/etc/motd\"`.", }, - "login_before_ready": { - // Note: When this is removed, "startup_script_behavior" should - // be set to "non-blocking" by default (instead of empty string). - Type: schema.TypeBool, - Default: true, - ForceNew: true, - Optional: true, - Description: "This option defines whether or not the user can (by default) login to the workspace before it is ready. Ready means that e.g. the `startup_script` is done and has exited. When enabled, users may see an incomplete workspace when logging in.", - Deprecated: "Configure `startup_script_behavior` instead. This attribute will be removed in a future version of the provider.", - ConflictsWith: []string{"startup_script_behavior"}, - }, "startup_script_behavior": { - // Note: Our default value is "non-blocking" but we do not set - // it here because we want to be able to differentiate between - // the user setting this or "login_before_ready". For all - // intents and purposes, until deprecation, setting - // "login_before_ready = false" is equivalent to setting - // "startup_script_behavior = blocking". - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Description: "This option sets the behavior of the `startup_script`. When set to `\"blocking\"`, the `startup_script` must exit before the workspace is ready. When set to `\"non-blocking\"`, the `startup_script` may run in the background and the workspace will be ready immediately. Default is `\"non-blocking\"`, although `\"blocking\"` is recommended. This option is an alias for defining a `coder_script` resource with `start_blocks_login` set to `true` (blocking).", - ValidateFunc: validation.StringInSlice([]string{"blocking", "non-blocking"}, false), - ConflictsWith: []string{"login_before_ready"}, + Type: schema.TypeString, + Default: "non-blocking", + ForceNew: true, + Optional: true, + Description: "This option sets the behavior of the `startup_script`. When set to `\"blocking\"`, the `startup_script` must exit before the workspace is ready. When set to `\"non-blocking\"`, the `startup_script` may run in the background and the workspace will be ready immediately. Default is `\"non-blocking\"`, although `\"blocking\"` is recommended. This option is an alias for defining a `coder_script` resource with `start_blocks_login` set to `true` (blocking).", + ValidateFunc: validation.StringInSlice([]string{"blocking", "non-blocking"}, false), }, "metadata": { Type: schema.TypeList, diff --git a/provider/agent_test.go b/provider/agent_test.go index 491e59f9..0df2cf2d 100644 --- a/provider/agent_test.go +++ b/provider/agent_test.go @@ -34,11 +34,9 @@ func TestAgent(t *testing.T) { hi = "test" } startup_script = "echo test" - startup_script_timeout = 120 troubleshooting_url = "https://example.com/troubleshoot" motd_file = "/etc/motd" shutdown_script = "echo bye bye" - shutdown_script_timeout = 120 order = 4 } `, @@ -55,12 +53,10 @@ func TestAgent(t *testing.T) { "dir", "env.hi", "startup_script", - "startup_script_timeout", "connection_timeout", "troubleshooting_url", "motd_file", "shutdown_script", - "shutdown_script_timeout", "order", } { value := resource.Primary.Attributes[key] @@ -109,34 +105,6 @@ func TestAgent_StartupScriptBehavior(t *testing.T) { require.Equal(t, "non-blocking", state.Primary.Attributes["startup_script_behavior"]) }, }, - { - Name: "login_before_ready (deprecated)", - Config: ` - resource "coder_agent" "new" { - os = "linux" - arch = "amd64" - login_before_ready = false - } - `, - Check: func(state *terraform.ResourceState) { - require.Equal(t, "false", state.Primary.Attributes["login_before_ready"]) - // startup_script_behavior must be empty, this indicates that - // login_before_ready should be used instead. - require.Equal(t, "", state.Primary.Attributes["startup_script_behavior"]) - }, - }, - { - Name: "no login_before_ready with startup_script_behavior", - Config: ` - resource "coder_agent" "new" { - os = "linux" - arch = "amd64" - login_before_ready = false - startup_script_behavior = "blocking" - } - `, - ExpectError: regexp.MustCompile("conflicts with"), - }, } { tc := tc t.Run(tc.Name, func(t *testing.T) { diff --git a/provider/app.go b/provider/app.go index 91c08f0f..754b0e2a 100644 --- a/provider/app.go +++ b/provider/app.go @@ -96,14 +96,6 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, - "name": { - Type: schema.TypeString, - Description: "A display name to identify the app.", - Deprecated: "`name` on apps is deprecated, use `display_name` instead", - ForceNew: true, - Optional: true, - ConflictsWith: []string{"display_name"}, - }, "subdomain": { Type: schema.TypeBool, Description: "Determines whether the app will be accessed via it's own " + @@ -113,15 +105,6 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, - "relative_path": { - Type: schema.TypeBool, - Deprecated: "`relative_path` on apps is deprecated, use `subdomain` instead.", - Description: "Specifies whether the URL will be accessed via a relative " + - "path or wildcard. Use if wildcard routing is unavailable. Defaults to `true`.", - ForceNew: true, - Optional: true, - ConflictsWith: []string{"subdomain"}, - }, "share": { Type: schema.TypeString, Description: "Determines the level which the application " + From f478379740aab9e2b3887cd04cbc740c97e5cfaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:08:06 +0300 Subject: [PATCH 038/114] build(deps): Bump goreleaser/goreleaser-action from 5.1.0 to 6.0.0 (#238) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Muhammad Atif Ali --- .github/workflows/release.yml | 4 ++-- .goreleaser.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad8d96c9..6bf1284c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,10 +38,10 @@ jobs: passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5.1.0 + uses: goreleaser/goreleaser-action@v6.0.0 with: version: latest - args: release --rm-dist + args: release --clean env: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} # GitHub sets this automatically diff --git a/.goreleaser.yml b/.goreleaser.yml index 0e43eeec..3866324a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -57,4 +57,4 @@ release: # If you want to manually examine the release before its live, uncomment this line: # draft: true changelog: - skip: true \ No newline at end of file + disable: true From 1d525fa819e14edf745e6acc5e110a8d14f470e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:34:54 +0100 Subject: [PATCH 039/114] build(deps): Bump github.com/docker/docker (#263) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.1.4+incompatible to 26.1.5+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v26.1.4...v26.1.5) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7e8aebe9..d83e1250 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 toolchain go1.22.3 require ( - github.com/docker/docker v26.1.4+incompatible + github.com/docker/docker v26.1.5+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 diff --git a/go.sum b/go.sum index 79ed9f85..866fe7b5 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/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 v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g= +github.com/docker/docker v26.1.5+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= From c174530d0753e691a2685a3b4817e505f03b8eff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:51:49 +0300 Subject: [PATCH 040/114] build(deps): Bump contributor-assistant/github-action (#269) Bumps [contributor-assistant/github-action](https://github.com/contributor-assistant/github-action) from 2.4.0 to 2.5.1. - [Release notes](https://github.com/contributor-assistant/github-action/releases) - [Commits](https://github.com/contributor-assistant/github-action/compare/v2.4.0...v2.5.1) --- updated-dependencies: - dependency-name: contributor-assistant/github-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cla.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 412cac6b..494dddc8 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -11,7 +11,7 @@ jobs: 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 + uses: contributor-assistant/github-action@v2.5.1 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 From abd6cf02b58b40e21d61d699cb88a54d24858269 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:57:06 +0300 Subject: [PATCH 041/114] build(deps): Bump golang.org/x/mod from 0.18.0 to 0.20.0 (#265) Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.18.0 to 0.20.0. - [Commits](https://github.com/golang/mod/compare/v0.18.0...v0.20.0) --- updated-dependencies: - dependency-name: golang.org/x/mod dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d83e1250..e4906a0e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/mod v0.18.0 + golang.org/x/mod v0.20.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) diff --git a/go.sum b/go.sum index 866fe7b5..231ca103 100644 --- a/go.sum +++ b/go.sum @@ -261,8 +261,8 @@ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXy 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From 56aacf0190db16a4773f3dc7d0566253d8bb77e2 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 28 Aug 2024 21:01:44 +0300 Subject: [PATCH 042/114] docs: add description to provider index page (#271) * docs: add description to provider index page * fix typo --- docs/index.md | 4 +++- templates/index.md.tmpl | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 859964d4..7ce115a7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,11 +3,13 @@ page_title: "Coder Provider" subcategory: "Infrastructure" description: |- - + Terraform provider for managing Coder templates, which are the underlying infrastructure for Coder workspaces. --- # Coder Provider +Terraform provider for managing Coder [templates](https://coder.com/docs/templates), which are the underlying infrastructure for Coder [workspaces](https://coder.com/docs/workspaces). + ## Example ```terraform diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index 5ca983fd..de4ec73e 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -3,11 +3,13 @@ page_title: "Coder Provider" subcategory: "Infrastructure" description: |- - + Terraform provider for managing Coder templates, which are the underlying infrastructure for Coder workspaces. --- # Coder Provider +Terraform provider for managing Coder [templates](https://coder.com/docs/templates), which are the underlying infrastructure for Coder [workspaces](https://coder.com/docs/workspaces). + ## Example {{tffile "examples/provider/provider.tf"}} From e841c7074c8b612a94bd1eba42ebe23756b4010d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 21:15:53 +0100 Subject: [PATCH 043/114] build(deps): Bump google.golang.org/grpc from 1.64.0 to 1.64.1 (#262) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.64.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.64.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index e4906a0e..17d00108 100644 --- a/go.mod +++ b/go.mod @@ -79,14 +79,14 @@ require ( go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.64.0 // indirect + google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/go.sum b/go.sum index 231ca103..95a4084f 100644 --- a/go.sum +++ b/go.sum @@ -254,8 +254,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -277,8 +277,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -307,8 +307,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -317,8 +317,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -338,8 +338,8 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 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.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= From 286841d83d118034d20035dcf5ad9def95a56834 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:14:09 +1000 Subject: [PATCH 044/114] docs: clarify `coder_metadata` usage (#267) --- docs/resources/metadata.md | 9 ++++++--- provider/metadata.go | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/resources/metadata.md b/docs/resources/metadata.md index f739db14..d8329ea9 100644 --- a/docs/resources/metadata.md +++ b/docs/resources/metadata.md @@ -3,12 +3,15 @@ page_title: "coder_metadata Resource - terraform-provider-coder" subcategory: "" description: |- - Use this resource to attach metadata to a resource. They will be displayed in the Coder dashboard. + Use this resource to attach metadata to a resource. They will be displayed in the Coder dashboard alongside the resource. The resource containing the agent, and it's metadata, will be shown by default. + Alternatively, to attach metadata to the agent, use a metadata block within a coder_agent resource. --- # coder_metadata (Resource) -Use this resource to attach metadata to a resource. They will be displayed in the Coder dashboard. +Use this resource to attach metadata to a resource. They will be displayed in the Coder dashboard alongside the resource. The resource containing the agent, and it's metadata, will be shown by default. + +Alternatively, to attach metadata to the agent, use a `metadata` block within a `coder_agent` resource. ## Example Usage @@ -82,7 +85,7 @@ Required: Optional: - `sensitive` (Boolean) Set to `true` to for items such as API keys whose values should be hidden from view by default. Note that this does not prevent metadata from being retrieved using the API, so it is not suitable for secrets that should not be exposed to workspace users. -- `value` (String) The value of this metadata item. +- `value` (String) The value of this metadata item. Supports basic Markdown, including hyperlinks. Read-Only: diff --git a/provider/metadata.go b/provider/metadata.go index abfe0a05..48a3e89d 100644 --- a/provider/metadata.go +++ b/provider/metadata.go @@ -14,7 +14,9 @@ func metadataResource() *schema.Resource { SchemaVersion: 1, Description: "Use this resource to attach metadata to a resource. They will be " + - "displayed in the Coder dashboard.", + "displayed in the Coder dashboard alongside the resource. " + + "The resource containing the agent, and it's metadata, will be shown by default. " + "\n\n" + + "Alternatively, to attach metadata to the agent, use a `metadata` block within a `coder_agent` resource.", CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { resourceData.SetId(uuid.NewString()) @@ -86,7 +88,7 @@ func metadataResource() *schema.Resource { }, "value": { Type: schema.TypeString, - Description: "The value of this metadata item.", + Description: "The value of this metadata item. Supports basic Markdown, including hyperlinks.", ForceNew: true, Optional: true, }, From 5f660a0f7182975133fa768fcea67402e34b955c Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:25:18 +1000 Subject: [PATCH 045/114] fix: optional everyone group in integration test (#278) --- integration/integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/integration_test.go b/integration/integration_test.go index 8208d20a..b7132d18 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -103,7 +103,7 @@ func TestIntegration(t *testing.T) { "workspace.transition": `start`, "workspace_owner.email": `testing@coder\.com`, "workspace_owner.full_name": `default`, - "workspace_owner.groups": `\[\]`, + "workspace_owner.groups": `\[(\"Everyone\")?\]`, "workspace_owner.id": `[a-zA-Z0-9-]+`, "workspace_owner.name": `testing`, "workspace_owner.oidc_access_token": `^$`, // TODO: test OIDC integration From f69a14d433860536d9089aea5ebdc37ff807cb5d Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Fri, 6 Sep 2024 01:28:31 +1000 Subject: [PATCH 046/114] fix: validate agent & resource metadata keys during plan (#277) --- provider/agent.go | 38 ++++++++++++++++++++++++++------------ provider/agent_test.go | 7 ++++--- provider/metadata.go | 27 +++++++++++++++++++++++++++ provider/metadata_test.go | 3 ++- provider/provider.go | 6 ------ 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/provider/agent.go b/provider/agent.go index d0ce4070..01fb5801 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -43,18 +43,6 @@ func agentResource() *schema.Resource { } } - rawPlan := resourceData.GetRawPlan() - items := rawPlan.GetAttr("metadata").AsValueSlice() - itemKeys := map[string]struct{}{} - for _, item := range items { - key := valueAsString(item.GetAttr("key")) - _, exists := itemKeys[key] - if exists { - return diag.FromErr(xerrors.Errorf("duplicate agent metadata key %q", key)) - } - itemKeys[key] = struct{}{} - } - return updateInitScript(resourceData, i) }, ReadWithoutTimeout: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { @@ -272,6 +260,32 @@ func agentResource() *schema.Resource { Optional: true, }, }, + CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i any) error { + if !rd.HasChange("metadata") { + return nil + } + + keys := map[string]bool{} + metadata, ok := rd.Get("metadata").([]any) + if !ok { + return xerrors.Errorf("unexpected type %T for metadata, expected []any", rd.Get("metadata")) + } + for _, t := range metadata { + obj, ok := t.(map[string]any) + if !ok { + return xerrors.Errorf("unexpected type %T for metadata, expected map[string]any", t) + } + key, ok := obj["key"].(string) + if !ok { + return xerrors.Errorf("unexpected type %T for metadata key, expected string", obj["key"]) + } + if keys[key] { + return xerrors.Errorf("duplicate agent metadata key %q", key) + } + keys[key] = true + } + return nil + }, } } diff --git a/provider/agent_test.go b/provider/agent_test.go index 0df2cf2d..91c708ca 100644 --- a/provider/agent_test.go +++ b/provider/agent_test.go @@ -254,6 +254,7 @@ func TestAgent_MetadataDuplicateKeys(t *testing.T) { } `, ExpectError: regexp.MustCompile("duplicate agent metadata key"), + PlanOnly: true, }}, }) } @@ -281,7 +282,7 @@ func TestAgent_DisplayApps(t *testing.T) { web_terminal = false port_forwarding_helper = false ssh_helper = false - } + } } `, Check: func(state *terraform.State) error { @@ -331,7 +332,7 @@ func TestAgent_DisplayApps(t *testing.T) { display_apps { vscode_insiders = true web_terminal = true - } + } } `, Check: func(state *terraform.State) error { @@ -426,7 +427,7 @@ func TestAgent_DisplayApps(t *testing.T) { web_terminal = false port_forwarding_helper = false ssh_helper = false - } + } } `, ExpectError: regexp.MustCompile(`An argument named "fake_app" is not expected here.`), diff --git a/provider/metadata.go b/provider/metadata.go index 48a3e89d..535c700c 100644 --- a/provider/metadata.go +++ b/provider/metadata.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "golang.org/x/xerrors" ) func metadataResource() *schema.Resource { @@ -111,5 +112,31 @@ func metadataResource() *schema.Resource { }, }, }, + CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i interface{}) error { + if !rd.HasChange("item") { + return nil + } + + keys := map[string]bool{} + metadata, ok := rd.Get("item").([]any) + if !ok { + return xerrors.Errorf("unexpected type %T for items, expected []any", rd.Get("metadata")) + } + for _, t := range metadata { + obj, ok := t.(map[string]any) + if !ok { + return xerrors.Errorf("unexpected type %T for item, expected map[string]any", t) + } + key, ok := obj["key"].(string) + if !ok { + return xerrors.Errorf("unexpected type %T for items 'key' attribute, expected string", obj["key"]) + } + if keys[key] { + return xerrors.Errorf("duplicate resource metadata key %q", key) + } + keys[key] = true + } + return nil + }, } } diff --git a/provider/metadata_test.go b/provider/metadata_test.go index 0243f48d..14cdf5bc 100644 --- a/provider/metadata_test.go +++ b/provider/metadata_test.go @@ -123,7 +123,8 @@ func TestMetadataDuplicateKeys(t *testing.T) { } } `, - ExpectError: regexp.MustCompile("duplicate metadata key"), + PlanOnly: true, + ExpectError: regexp.MustCompile("duplicate resource metadata key"), }}, }) } diff --git a/provider/provider.go b/provider/provider.go index 1a37b706..1d78f2dd 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -97,14 +97,8 @@ func populateIsNull(resourceData *schema.ResourceData) (result interface{}, err items := rawPlan.GetAttr("item").AsValueSlice() var resultItems []interface{} - itemKeys := map[string]struct{}{} for _, item := range items { key := valueAsString(item.GetAttr("key")) - _, exists := itemKeys[key] - if exists { - return nil, xerrors.Errorf("duplicate metadata key %q", key) - } - itemKeys[key] = struct{}{} resultItem := map[string]interface{}{ "key": key, "value": valueAsString(item.GetAttr("value")), From 0da135c0207017ae4c8329ee2b3fea5d1cc1b662 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 6 Sep 2024 10:48:17 +0100 Subject: [PATCH 047/114] chore(integration): cleanup on interrupt (#283) --- integration/integration_test.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/integration/integration_test.go b/integration/integration_test.go index b7132d18..c98a45eb 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -7,10 +7,12 @@ import ( "fmt" "io" "os" + "os/signal" "path/filepath" "runtime" "strconv" "strings" + "sync" "testing" "time" @@ -203,12 +205,24 @@ func setup(ctx context.Context, t *testing.T, name, coderImg, coderVersion strin require.NoError(t, err, "create test deployment") t.Logf("created container %s\n", ctr.ID) - t.Cleanup(func() { // Make sure we clean up after ourselves. - // TODO: also have this execute if you Ctrl+C! + var cleanupOnce sync.Once + removeContainer := func() { t.Logf("stopping container %s\n", ctr.ID) _ = cli.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{ Force: true, }) + } + // Ensure the container is cleaned up if you press Ctrl+C. + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + go func() { + <-sigCh + cleanupOnce.Do(removeContainer) + os.Exit(1) + }() + + t.Cleanup(func() { // Make sure we clean up after ourselves. + cleanupOnce.Do(removeContainer) }) err = cli.ContainerStart(ctx, ctr.ID, container.StartOptions{}) From cd7945d11ca71c2a0ca46abefd925c32ca153a9f Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Fri, 6 Sep 2024 10:53:19 +0100 Subject: [PATCH 048/114] feat: add 'hidden' field to 'coder_app' provider (#276) * feat: add 'hidden' field to 'coder_app' provider * fix: run 'make gen' * test: add integration test for 'coder_app.hidden' * fix: warn on 'hidden' being used with conflicting fields --- docs/resources/app.md | 1 + integration/coder-app-hidden/main.tf | 62 +++++++++++++++++++++++ integration/integration_test.go | 9 ++++ provider/app.go | 38 ++++++++++++++- provider/app_test.go | 73 ++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 integration/coder-app-hidden/main.tf diff --git a/docs/resources/app.md b/docs/resources/app.md index 1cb5d08b..ab44bede 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -63,6 +63,7 @@ resource "coder_app" "vim" { - `display_name` (String) A display name to identify the app. Defaults to the slug. - `external` (Boolean) Specifies whether `url` is opened on the client machine instead of proxied through the workspace. - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) +- `hidden` (Boolean) Determines if the app is visible in the UI. - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). - `share` (String) Determines the level which the application is shared at. Valid levels are `"owner"` (default), `"authenticated"` and `"public"`. Level `"owner"` disables sharing on the app, so only the workspace owner can access it. Level `"authenticated"` shares the app with all authenticated users. Level `"public"` shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). diff --git a/integration/coder-app-hidden/main.tf b/integration/coder-app-hidden/main.tf new file mode 100644 index 00000000..6376b25d --- /dev/null +++ b/integration/coder-app-hidden/main.tf @@ -0,0 +1,62 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + local = { + source = "hashicorp/local" + } + } +} + +data "coder_workspace" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" +} + +resource "coder_app" "hidden" { + agent_id = coder_agent.dev.id + slug = "hidden" + share = "owner" + hidden = true +} + +resource "coder_app" "visible" { + agent_id = coder_agent.dev.id + slug = "visible" + share = "owner" + hidden = false +} + +resource "coder_app" "defaulted" { + agent_id = coder_agent.dev.id + slug = "defaulted" + share = "owner" +} + +locals { + # NOTE: these must all be strings in the output + output = { + "coder_app.hidden.hidden" = tostring(coder_app.hidden.hidden) + "coder_app.visible.hidden" = tostring(coder_app.visible.hidden) + "coder_app.defaulted.hidden" = tostring(coder_app.defaulted.hidden) + } +} + +variable "output_path" { + type = string +} + +resource "local_file" "output" { + filename = var.output_path + content = jsonencode(local.output) +} + +output "output" { + value = local.output + sensitive = true +} + diff --git a/integration/integration_test.go b/integration/integration_test.go index c98a45eb..26b9544a 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -114,6 +114,15 @@ func TestIntegration(t *testing.T) { "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, }, }, + { + name: "coder-app-hidden", + minVersion: "v0.0.0", + expectedOutput: map[string]string{ + "coder_app.hidden.hidden": "true", + "coder_app.visible.hidden": "false", + "coder_app.defaulted.hidden": "false", + }, + }, } { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/provider/app.go b/provider/app.go index 754b0e2a..10f946c7 100644 --- a/provider/app.go +++ b/provider/app.go @@ -30,7 +30,36 @@ func appResource() *schema.Resource { Description: "Use this resource to define shortcuts to access applications in a workspace.", CreateContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { resourceData.SetId(uuid.NewString()) - return nil + + diags := diag.Diagnostics{} + + hiddenData := resourceData.Get("hidden") + if hidden, ok := hiddenData.(bool); !ok { + return diag.Errorf("hidden should be a bool") + } else if hidden { + if _, ok := resourceData.GetOk("display_name"); ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "`display_name` set when app is hidden", + }) + } + + if _, ok := resourceData.GetOk("icon"); ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "`icon` set when app is hidden", + }) + } + + if _, ok := resourceData.GetOk("order"); ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "`order` set when app is hidden", + }) + } + } + + return diags }, ReadContext: func(c context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { return nil @@ -187,6 +216,13 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, + "hidden": { + Type: schema.TypeBool, + Description: "Determines if the app is visible in the UI.", + Default: false, + ForceNew: true, + Optional: true, + }, }, } } diff --git a/provider/app_test.go b/provider/app_test.go index f17513e1..80f1df14 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -45,6 +45,7 @@ func TestApp(t *testing.T) { threshold = 6 } order = 4 + hidden = false } `, Check: func(state *terraform.State) error { @@ -66,6 +67,7 @@ func TestApp(t *testing.T) { "healthcheck.0.interval", "healthcheck.0.threshold", "order", + "hidden", } { value := resource.Primary.Attributes[key] t.Logf("%q = %q", key, value) @@ -246,4 +248,75 @@ func TestApp(t *testing.T) { }) } }) + + t.Run("Hidden", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + config string + hidden bool + }{{ + name: "Is Hidden", + config: ` + provider "coder" {} + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "test" { + agent_id = coder_agent.dev.id + slug = "test" + display_name = "Testing" + url = "https://google.com" + external = true + hidden = true + } + `, + hidden: true, + }, { + name: "Is Not Hidden", + config: ` + provider "coder" {} + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "test" { + agent_id = coder_agent.dev.id + slug = "test" + display_name = "Testing" + url = "https://google.com" + external = true + hidden = false + } + `, + hidden: false, + }} + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + resource.Test(t, resource.TestCase{ + Providers: map[string]*schema.Provider{ + "coder": provider.New(), + }, + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: tc.config, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 2) + resource := state.Modules[0].Resources["coder_app.test"] + require.NotNil(t, resource) + require.Equal(t, strconv.FormatBool(tc.hidden), resource.Primary.Attributes["hidden"]) + return nil + }, + ExpectError: nil, + }}, + }) + }) + } + }) + } From 332f8ae7845eb0399151ffa4d27a6dc493840ae0 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Mon, 9 Sep 2024 13:10:06 +0100 Subject: [PATCH 049/114] docs: document minimum version required for 'hidden' attribute (#285) * docs: document minimum version required for 'hidden' attribute * fix: capitalize coder Co-authored-by: Cian Johnston * fix: apply 'make gen' --------- Co-authored-by: Cian Johnston --- docs/resources/app.md | 2 +- provider/app.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/app.md b/docs/resources/app.md index ab44bede..6b8e99f4 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -63,7 +63,7 @@ resource "coder_app" "vim" { - `display_name` (String) A display name to identify the app. Defaults to the slug. - `external` (Boolean) Specifies whether `url` is opened on the client machine instead of proxied through the workspace. - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) -- `hidden` (Boolean) Determines if the app is visible in the UI. +- `hidden` (Boolean) Determines if the app is visible in the UI (minimum Coder version: v2.16). - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). - `share` (String) Determines the level which the application is shared at. Valid levels are `"owner"` (default), `"authenticated"` and `"public"`. Level `"owner"` disables sharing on the app, so only the workspace owner can access it. Level `"authenticated"` shares the app with all authenticated users. Level `"public"` shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). diff --git a/provider/app.go b/provider/app.go index 10f946c7..3fd71692 100644 --- a/provider/app.go +++ b/provider/app.go @@ -218,7 +218,7 @@ func appResource() *schema.Resource { }, "hidden": { Type: schema.TypeBool, - Description: "Determines if the app is visible in the UI.", + Description: "Determines if the app is visible in the UI (minimum Coder version: v2.16).", Default: false, ForceNew: true, Optional: true, From 2598aa7c653b2e8c512ce5789106f07d0a51251f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20L=C3=B3pez=20Mareque?= Date: Wed, 11 Sep 2024 15:04:22 +0200 Subject: [PATCH 050/114] Add login_type to coder_workspace_owner data source #235 (#287) * feat: add login_type to coder_workspace_owner data source * feat: remove types check * feat: update integration test * feat: run linter * feat: set empty string as the default value for the login type * feat: update integration test * feat: add a warning when the CODER_WORKSPACE_OWNER_LOGIN_TYPE is not set * feat: create a new diags variable * feat: add missing comma * feat: add missing parenthesis * feat: fix typo in summary field Co-authored-by: Cian Johnston --------- Co-authored-by: Cian Johnston --- docs/data-sources/workspace_owner.md | 1 + integration/integration_test.go | 1 + integration/workspace-owner/main.tf | 1 + provider/workspace_owner.go | 17 ++++++++++++++++- provider/workspace_owner_test.go | 5 +++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/data-sources/workspace_owner.md b/docs/data-sources/workspace_owner.md index 1c64ea50..fbe4f205 100644 --- a/docs/data-sources/workspace_owner.md +++ b/docs/data-sources/workspace_owner.md @@ -50,6 +50,7 @@ resource "coder_env" "git_author_email" { - `full_name` (String) The full name of the user. - `groups` (List of String) The groups of which the user is a member. - `id` (String) The UUID of the workspace owner. +- `login_type` (String) The type of login the user has. - `name` (String) The username of the user. - `oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. - `session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started. diff --git a/integration/integration_test.go b/integration/integration_test.go index 26b9544a..dc3d5c98 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -112,6 +112,7 @@ func TestIntegration(t *testing.T) { "workspace_owner.session_token": `.+`, "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, + "workspace_owner.login_type": ``, }, }, { diff --git a/integration/workspace-owner/main.tf b/integration/workspace-owner/main.tf index 2be11d8e..fd923a3d 100644 --- a/integration/workspace-owner/main.tf +++ b/integration/workspace-owner/main.tf @@ -39,6 +39,7 @@ locals { "workspace_owner.session_token" : data.coder_workspace_owner.me.session_token, "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, + "workspace_owner.login_type" : data.coder_workspace_owner.me.login_type, } } diff --git a/provider/workspace_owner.go b/provider/workspace_owner.go index 13e36187..c51751b0 100644 --- a/provider/workspace_owner.go +++ b/provider/workspace_owner.go @@ -15,6 +15,8 @@ func workspaceOwnerDataSource() *schema.Resource { return &schema.Resource{ Description: "Use this data source to fetch information about the workspace owner.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { + diags := diag.Diagnostics{} + if idStr := os.Getenv("CODER_WORKSPACE_OWNER_ID"); idStr != "" { rd.SetId(idStr) } else { @@ -53,7 +55,15 @@ func workspaceOwnerDataSource() *schema.Resource { _ = rd.Set("session_token", os.Getenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN")) _ = rd.Set("oidc_access_token", os.Getenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN")) - return nil + if os.Getenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE") == "" { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "WARNING: The CODER_WORKSPACE_OWNER_LOGIN_TYPE env variable is not set", + }) + } + _ = rd.Set("login_type", os.Getenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE")) + + return diags }, Schema: map[string]*schema.Schema{ "id": { @@ -107,6 +117,11 @@ func workspaceOwnerDataSource() *schema.Resource { "This is only available if the workspace owner authenticated with OpenID Connect. " + "If a valid token cannot be obtained, this value will be an empty string.", }, + "login_type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of login the user has.", + }, }, } } diff --git a/provider/workspace_owner_test.go b/provider/workspace_owner_test.go index 90839cfc..91f47ea8 100644 --- a/provider/workspace_owner_test.go +++ b/provider/workspace_owner_test.go @@ -35,6 +35,7 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { t.Setenv("CODER_WORKSPACE_OWNER_GROUPS", `["group1", "group2"]`) t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", `supersecret`) t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", `alsosupersecret`) + t.Setenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE", `github`) resource.Test(t, resource.TestCase{ Providers: map[string]*schema.Provider{ @@ -63,6 +64,8 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { assert.Equal(t, `group2`, attrs["groups.1"]) assert.Equal(t, `supersecret`, attrs["session_token"]) assert.Equal(t, `alsosupersecret`, attrs["oidc_access_token"]) + assert.Equal(t, `github`, attrs["login_type"]) + return nil }, }}, @@ -80,6 +83,7 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { "CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", "CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY", "CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY", + "CODER_WORKSPACE_OWNER_LOGIN_TYPE", } { // https://github.com/golang/go/issues/52817 t.Setenv(v, "") os.Unsetenv(v) @@ -111,6 +115,7 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { assert.Empty(t, attrs["groups.0"]) assert.Empty(t, attrs["session_token"]) assert.Empty(t, attrs["oidc_access_token"]) + assert.Empty(t, attrs["login_type"]) return nil }, }}, From 1e8489449a22dbb1e59dcbf0209508004fc98311 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 24 Sep 2024 15:56:14 +0400 Subject: [PATCH 051/114] fix: update to terraform-plugin-sdk v2.34.0 (#290) --- go.mod | 44 ++++--- go.sum | 218 ++++++++++++------------------- provider/agent_test.go | 57 +++----- provider/app_test.go | 26 ++-- provider/env_test.go | 26 ++-- provider/examples_test.go | 8 +- provider/externalauth_test.go | 15 +-- provider/metadata_test.go | 16 +-- provider/parameter_test.go | 10 +- provider/provider_test.go | 14 +- provider/provisioner_test.go | 9 +- provider/script_test.go | 26 ++-- provider/workspace_owner_test.go | 14 +- provider/workspace_tags_test.go | 9 +- provider/workspace_test.go | 21 +-- 15 files changed, 185 insertions(+), 328 deletions(-) diff --git a/go.mod b/go.mod index 17d00108..ec54a5e1 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/docker/docker v26.1.5+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 @@ -20,15 +20,17 @@ require ( require ( github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -38,24 +40,24 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.1 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.4 // indirect + github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hc-install v0.4.0 // indirect - github.com/hashicorp/hcl/v2 v2.13.0 // indirect + github.com/hashicorp/hc-install v0.6.4 // indirect + github.com/hashicorp/hcl/v2 v2.20.1 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.17.2 // indirect - github.com/hashicorp/terraform-json v0.14.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.12.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect - github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect - github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect - github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect + github.com/hashicorp/terraform-exec v0.21.0 // indirect + github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-plugin-go v0.23.0 // indirect + github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.3 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect github.com/kr/pretty v0.3.0 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // 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 @@ -70,9 +72,9 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect - github.com/vmihailenco/tagparser v0.1.1 // indirect - github.com/zclconf/go-cty v1.10.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.14.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect @@ -81,9 +83,11 @@ require ( go.opentelemetry.io/otel/trace v1.27.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.64.1 // indirect diff --git a/go.sum b/go.sum index 95a4084f..ccbb2a1f 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,29 @@ -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +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-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -37,22 +35,19 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj 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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -62,18 +57,15 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -86,82 +78,71 @@ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= -github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= -github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 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.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= -github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= +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-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.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.4.0 h1:cZkRFr1WVa0Ty6x5fTvL1TuO1flul231rWkGH92oYYk= -github.com/hashicorp/hc-install v0.4.0/go.mod h1:5d155H8EC5ewegao9A4PUTMNPZaq+TbOzkJJZ4vrXeI= -github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= -github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= +github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0= +github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA= +github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= +github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= 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.17.2 h1:EU7i3Fh7vDUI9nNRdMATCEfnm9axzTnad8zszYZ73Go= -github.com/hashicorp/terraform-exec v0.17.2/go.mod h1:tuIbsL2l4MlwwIZx9HPM+LOV9vVyEfBYu2GsO1uH3/8= -github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= -github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= -github.com/hashicorp/terraform-plugin-go v0.12.0 h1:6wW9mT1dSs0Xq4LR6HXj1heQ5ovr5GxXNJwkErZzpJw= -github.com/hashicorp/terraform-plugin-go v0.12.0/go.mod h1:kwhmaWHNDvT1B3QiSJdAtrB/D4RaKSY/v3r2BuoWK4M= -github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= -github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 h1:+KxZULPsbjpAVoP0WNj/8aVW6EqpcX5JcUcQ5wl7Da4= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0/go.mod h1:DwGJG3KNxIPluVk6hexvDfYR/MS/eKGpiztJoT3Bbbw= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= -github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= -github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= +github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= +github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/terraform-plugin-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-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= +github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= +github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U= github.com/masterminds/semver v1.5.0/go.mod h1:s7KNT9fnd7edGzwwP7RBX4H0v/CYd5qdOLfkL1V75yg= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/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= @@ -176,17 +157,15 @@ 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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 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= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -196,39 +175,31 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 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/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= @@ -246,13 +217,9 @@ go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5 go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= @@ -263,50 +230,35 @@ golang.org/x/mod v0.3.0/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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -314,7 +266,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= @@ -326,14 +277,14 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= @@ -345,18 +296,11 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= diff --git a/provider/agent_test.go b/provider/agent_test.go index 91c708ca..d40caf56 100644 --- a/provider/agent_test.go +++ b/provider/agent_test.go @@ -6,20 +6,15 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" - - "github.com/coder/terraform-provider-coder/provider" ) func TestAgent(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -110,10 +105,8 @@ func TestAgent_StartupScriptBehavior(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: tc.Config, ExpectError: tc.ExpectError, @@ -136,10 +129,8 @@ func TestAgent_StartupScriptBehavior(t *testing.T) { func TestAgent_Instance(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -177,10 +168,8 @@ func TestAgent_Instance(t *testing.T) { func TestAgent_Metadata(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -225,10 +214,8 @@ func TestAgent_Metadata(t *testing.T) { func TestAgent_MetadataDuplicateKeys(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -263,10 +250,8 @@ func TestAgent_DisplayApps(t *testing.T) { t.Parallel() t.Run("OK", func(t *testing.T) { resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ // Test the fields with non-default values. Config: ` @@ -316,10 +301,8 @@ func TestAgent_DisplayApps(t *testing.T) { t.Run("Subset", func(t *testing.T) { resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ // Test the fields with non-default values. Config: ` @@ -363,10 +346,8 @@ func TestAgent_DisplayApps(t *testing.T) { // Assert all the defaults are set correctly. t.Run("Omitted", func(t *testing.T) { resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -408,10 +389,8 @@ func TestAgent_DisplayApps(t *testing.T) { t.Run("InvalidApp", func(t *testing.T) { resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ // Test the fields with non-default values. Config: ` diff --git a/provider/app_test.go b/provider/app_test.go index 80f1df14..6a17ca0c 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -6,9 +6,7 @@ import ( "strconv" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" ) @@ -20,10 +18,8 @@ func TestApp(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -129,10 +125,8 @@ func TestApp(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: tc.config, Check: func(state *terraform.State) error { @@ -235,10 +229,8 @@ func TestApp(t *testing.T) { } resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: config, Check: checkFn, @@ -298,10 +290,8 @@ func TestApp(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: tc.config, Check: func(state *terraform.State) error { diff --git a/provider/env_test.go b/provider/env_test.go index 66925f5e..a5892254 100644 --- a/provider/env_test.go +++ b/provider/env_test.go @@ -4,11 +4,9 @@ import ( "regexp" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/stretchr/testify/require" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) @@ -16,10 +14,8 @@ func TestEnv(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -53,10 +49,8 @@ func TestEnvEmptyValue(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -89,10 +83,8 @@ func TestEnvBadName(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -111,10 +103,8 @@ func TestEnvNoName(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { diff --git a/provider/examples_test.go b/provider/examples_test.go index ab68954b..c6931ae3 100644 --- a/provider/examples_test.go +++ b/provider/examples_test.go @@ -6,9 +6,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/require" - "github.com/coder/terraform-provider-coder/provider" ) func TestExamples(t *testing.T) { @@ -29,10 +27,8 @@ func TestExamples(t *testing.T) { func resourceTest(t *testing.T, testDir string) { resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: mustReadFile(t, fmt.Sprintf("../examples/data-sources/%s/data-source.tf", testDir)), }}, diff --git a/provider/externalauth_test.go b/provider/externalauth_test.go index 826f0a91..4c328a45 100644 --- a/provider/externalauth_test.go +++ b/provider/externalauth_test.go @@ -3,10 +3,7 @@ package provider_test import ( "testing" - "github.com/coder/terraform-provider-coder/provider" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" @@ -16,10 +13,8 @@ func TestExternalAuth(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -48,10 +43,8 @@ func TestOptionalExternalAuth(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { diff --git a/provider/metadata_test.go b/provider/metadata_test.go index 14cdf5bc..3164e65b 100644 --- a/provider/metadata_test.go +++ b/provider/metadata_test.go @@ -4,21 +4,16 @@ import ( "regexp" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" ) func TestMetadata(t *testing.T) { t.Parallel() - prov := provider.New() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": prov, - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -95,12 +90,9 @@ func TestMetadata(t *testing.T) { func TestMetadataDuplicateKeys(t *testing.T) { t.Parallel() - prov := provider.New() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": prov, - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { diff --git a/provider/parameter_test.go b/provider/parameter_test.go index 05d3604b..fc814cba 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -4,11 +4,11 @@ import ( "regexp" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" + + "github.com/coder/terraform-provider-coder/provider" ) func TestParameter(t *testing.T) { @@ -660,10 +660,8 @@ data "coder_parameter" "region" { t.Run(tc.Name, func(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: tc.Config, ExpectError: tc.ExpectError, diff --git a/provider/provider_test.go b/provider/provider_test.go index 742eb30b..3367e7f4 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -24,10 +24,8 @@ func TestProvider(t *testing.T) { func TestProviderEmpty(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" {} @@ -46,3 +44,11 @@ func TestProviderEmpty(t *testing.T) { }}, }) } + +func coderFactory() map[string]func() (*schema.Provider, error) { + return map[string]func() (*schema.Provider, error){ + "coder": func() (*schema.Provider, error) { + return provider.New(), nil + }, + } +} diff --git a/provider/provisioner_test.go b/provider/provisioner_test.go index f1521ef9..53295bcd 100644 --- a/provider/provisioner_test.go +++ b/provider/provisioner_test.go @@ -4,9 +4,7 @@ import ( "runtime" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" ) @@ -14,10 +12,8 @@ import ( func TestProvisioner(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -42,4 +38,3 @@ func TestProvisioner(t *testing.T) { }}, }) } - diff --git a/provider/script_test.go b/provider/script_test.go index 9b6bd570..37f1a819 100644 --- a/provider/script_test.go +++ b/provider/script_test.go @@ -4,11 +4,9 @@ import ( "regexp" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/stretchr/testify/require" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) @@ -16,10 +14,8 @@ func TestScript(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -55,10 +51,8 @@ func TestScriptNeverRuns(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -78,10 +72,8 @@ func TestScriptStartBlocksLoginRequiresRunOnStart(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -98,10 +90,8 @@ func TestScriptStartBlocksLoginRequiresRunOnStart(t *testing.T) { }}, }) resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { diff --git a/provider/workspace_owner_test.go b/provider/workspace_owner_test.go index 91f47ea8..ad371570 100644 --- a/provider/workspace_owner_test.go +++ b/provider/workspace_owner_test.go @@ -4,9 +4,7 @@ import ( "os" "testing" - "github.com/coder/terraform-provider-coder/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -38,10 +36,8 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { t.Setenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE", `github`) resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" {} @@ -90,10 +86,8 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { } resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" {} diff --git a/provider/workspace_tags_test.go b/provider/workspace_tags_test.go index 2d0f1c49..95949562 100644 --- a/provider/workspace_tags_test.go +++ b/provider/workspace_tags_test.go @@ -4,19 +4,14 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" - - "github.com/coder/terraform-provider-coder/provider" ) func TestWorkspaceTags(t *testing.T) { resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { diff --git a/provider/workspace_test.go b/provider/workspace_test.go index 12d5210b..e82a1005 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -5,12 +5,9 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/coder/terraform-provider-coder/provider" ) func TestWorkspace(t *testing.T) { @@ -19,10 +16,8 @@ func TestWorkspace(t *testing.T) { t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -57,10 +52,8 @@ func TestWorkspace_UndefinedOwner(t *testing.T) { t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { @@ -96,10 +89,8 @@ func TestWorkspace_MissingTemplateName(t *testing.T) { t.Setenv("CODER_WORKSPACE_TEMPLATE_VERSION", "v1.2.3") resource.Test(t, resource.TestCase{ - Providers: map[string]*schema.Provider{ - "coder": provider.New(), - }, - IsUnitTest: true, + ProviderFactories: coderFactory(), + IsUnitTest: true, Steps: []resource.TestStep{{ Config: ` provider "coder" { From 9c7b364131ca0359b4ea927ebf35df8386f6242c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:55:07 +0500 Subject: [PATCH 052/114] build(deps): Bump contributor-assistant/github-action from 2.5.1 to 2.6.0 (#289) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cla.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 494dddc8..5fdf7482 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -11,7 +11,7 @@ jobs: 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.5.1 + uses: contributor-assistant/github-action@v2.6.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 From f4d4fc111a328083be8ed6ff92fe8f1d7cf8801c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:33:49 +0500 Subject: [PATCH 053/114] build(deps): Bump contributor-assistant/github-action from 2.6.0 to 2.6.1 (#291) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cla.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 5fdf7482..71c2e905 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -11,7 +11,7 @@ jobs: 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.6.0 + uses: contributor-assistant/github-action@v2.6.1 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 From 2863913f28c633f636ed03a59c18f20da18580ff Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 14 Oct 2024 11:20:40 +0400 Subject: [PATCH 054/114] chore: add http/pprof server over unix socket for debug (#295) * chore: add http/pprof server over unix socket for debug Signed-off-by: Spike Curtis * remove old pprof file without checking if it exists Signed-off-by: Spike Curtis --------- Signed-off-by: Spike Curtis --- main.go | 1 + pprof_unix.go | 42 ++++++++++++++++++++++++++++++++++++++++++ pprof_windows.go | 6 ++++++ 3 files changed, 49 insertions(+) create mode 100644 pprof_unix.go create mode 100644 pprof_windows.go diff --git a/main.go b/main.go index 000436a3..a16c9951 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { + servePprof() plugin.Serve(&plugin.ServeOpts{ ProviderFunc: provider.New, }) diff --git a/pprof_unix.go b/pprof_unix.go new file mode 100644 index 00000000..717bc01b --- /dev/null +++ b/pprof_unix.go @@ -0,0 +1,42 @@ +//go:build !windows + +package main + +import ( + "net" + "net/http" + "net/http/pprof" + "os" +) + +// servePprof starts an HTTP server running the pprof goroutine handler on a local unix domain socket. As described in +// https://github.com/coder/coder/issues/14726 it appears this process is sometimes hanging, unable to exit cleanly, +// and this prevents additional Coder builds that try to reinstall this provider. A goroutine dump should allow us to +// determine what is hanging. +// +// This function is best-effort, and just returns early if we fail to set up the directory/listener. We don't want to +// block the normal functioning of the provider. +func servePprof() { + // Coder runs terraform in a per-build subdirectory of the work directory. The per-build subdirectory uses a + // generated name and is deleted at the end of a build, so we want to place our unix socket up one directory level + // in the provisionerd work directory, so we can connect to it from provisionerd. + err := os.Mkdir("../.coder", 0o700) + if err != nil && !os.IsExist(err) { + return + } + + // remove the old file, if it exists. It's probably from the last run of the provider + if err = os.Remove("../.coder/pprof"); err != nil && !os.IsNotExist(err) { + return + } + l, err := net.Listen("unix", "../.coder/pprof") + if err != nil { + return + } + mux := http.NewServeMux() + mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) + srv := http.Server{Handler: mux} + go srv.Serve(l) + // We just leave the server and domain socket up forever. Go programs exit when the `main()` function returns, so + // this won't block exiting, and it ensures the pprof server stays up for the entire lifetime of the provider. +} diff --git a/pprof_windows.go b/pprof_windows.go new file mode 100644 index 00000000..05cd3143 --- /dev/null +++ b/pprof_windows.go @@ -0,0 +1,6 @@ +//go:build windows + +package main + +// servePprof is not supported on Windows +func servePprof() {} From 224049576dd47cb724adac4958293bfca6222c78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:07:34 +0500 Subject: [PATCH 055/114] build(deps): Bump goreleaser/goreleaser-action from 6.0.0 to 6.1.0 (#301) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6bf1284c..0f54844a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6.0.0 + uses: goreleaser/goreleaser-action@v6.1.0 with: version: latest args: release --clean From 493a0f18a5383db9c2eaf8d1bffd8b8caf978507 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:07:49 +0500 Subject: [PATCH 056/114] build(deps): Bump crazy-max/ghaction-import-gpg from 6.1.0 to 6.2.0 (#298) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f54844a..5aa16488 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6.1.0 + uses: crazy-max/ghaction-import-gpg@v6.2.0 with: # These secrets will need to be configured for the repository: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} From 0fc111255226e66a78829a62afa6f167637e4fe9 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 26 Nov 2024 17:54:20 +0500 Subject: [PATCH 057/114] docs: v1 to v2 upgrade guide (#259) --- docs/guides/version-2-upgrade.md | 101 +++++++++++++++++++++ docs/index.md | 6 +- go.mod | 2 +- templates/guides/version-2-upgrade.md.tmpl | 101 +++++++++++++++++++++ templates/index.md.tmpl | 6 +- 5 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 docs/guides/version-2-upgrade.md create mode 100644 templates/guides/version-2-upgrade.md.tmpl diff --git a/docs/guides/version-2-upgrade.md b/docs/guides/version-2-upgrade.md new file mode 100644 index 00000000..49e262e1 --- /dev/null +++ b/docs/guides/version-2-upgrade.md @@ -0,0 +1,101 @@ +--- +page_title: "Terraform Coder Provider Version 2 Upgrade Guide" +--- + +# Terraform Coder Provider Version 2 Upgrade Guide + +Version 2.0.0 of the Coder provider for Terraform is a major release that introduces some changes that you will need to consider when upgrading. +This guide is intended to help with the process, and focuses only on the changes from version 1.X to version 2.0.0. + +!> Using Version 2.0.0 of the Coder provider requires Coder Server version [`2.18.0`](https://github.com/coder/coder/releases/tag/v2.18.0) or later. + +Upgrade topics: + +- [Provider Version Configuration](#provider-version-configuration) +- [Provider Arguments](#provider-arguments) +- [Data Source: coder_git_auth --> coder_external_auth](#data-source-coder_git_auth) +- [Data Source: coder_workspace](#data-source-coder_workspace) + +## Provider Version Configuration + +-> Before upgrading to version 2.0.0, please first upgrade to the most recent 1.X version and ensure that your environment successfully runs [`terraform plan`](https://developer.hashicorp.com/terraform/cli/commands/plan) without unexpected changes or deprecation notices. + +We highly recommend using [version constraints](https://developer.hashicorp.com/terraform/language/providers/requirements#version-constraints) when configuring Terraform providers. + + +For example, given the previous configuration: + +```terraform +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 1.0.0" + } + } +} + +provider "coder" { + feature_use_managed_variables = true +} +``` + +Update to the latest 2.X version: + +```terraform +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 2.0.0" + } + } +} + +provider "coder" {} +``` + +## Provider Arguments + +Version 2.0.0 removes the [`feature_use_managed_variables`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs#feature_use_managed_variables-1) argument from the `provider` block. + + +## Data Source: [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) + +If you are using this data source, you must replace it with the [`coder_external_auth`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/external_auth) data source. The `coder_external_auth` data source is a more generic data source that can be used to create any external authentication provider which supports OAuth2. + +For example, given the previous configuration: + +```terraform +data "coder_git_auth" "example" { + id = "example" +} +``` + +Update to the new data source: + +```terraform +data "coder_external_auth" "example" { + id = "example" +} +``` + +## Data Source: [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) + +If you are using the `owner` properties of the `coder_workspace` data source, you must remove them and use the [`coder_workspace_owner`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner) data source instead. The `coder_workspace_owner` data source provides additional properties of the workspace owner. + +Update your Terraform configuration to use the `coder_workspace_owner` data source instead and update the following attributes: + +```terraform +data "coder_workspace_owner" "me" {} +``` + +- Remove `owner_id` attribute. Use [`data.coder_workspace_owner.me.id`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#id) instead. +- Remove `owner` attribute. Use [`data.coder_workspace_owner.me.name`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#name) instead. +- Remove `owner_name` attribute. Use [`data.coder_workspace_owner.me.full_name`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#full_name) instead. +- Remove `owner_email` attribute. Use [`data.coder_workspace_owner.me.email`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#email) instead. +- Remove `owner_groups` attribute. Use [`data.coder_workspace_owner.me.groups`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#groups) instead. +- Remove `owner_oidc_access_token` attribute. Use [`data.coder_workspace_owner.me.oidc_access_token`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#oidc_access_token) instead. +- Remove `owner_session_token` attribute. Use [`data.coder_workspace_owner.me.session_token`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#session_token) instead. + +->While we do not anticipate these changes to affect existing resources, we strongly advice reviewing the plan produced by Terraform to ensure no resources are accidentally removed or altered in an undesired way. If you encounter any unexpected behavior, please report it by opening a GitHub [issue](https://github.com/coder/terraform-provider-coder/issues). \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 7ce115a7..ec7be2d8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,11 @@ description: |- # Coder Provider -Terraform provider for managing Coder [templates](https://coder.com/docs/templates), which are the underlying infrastructure for Coder [workspaces](https://coder.com/docs/workspaces). +Terraform provider for managing Coder [templates](https://coder.com/docs/admin/templates), which are the underlying infrastructure for Coder [workspaces](https://coder.com/docs/user-guides/workspace-management). + +-> Requires Coder [v2.18.0](https://github.com/coder/coder/releases/tag/v2.18.0) or later. + +!> [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) and owner related fields of [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source have been removed. Follow the [Version 2 Upgrade Guide](https://registry.terraform.io/providers/codercom/coder/latest/docs/guides/version-2-upgrade) to update your code. ## Example diff --git a/go.mod b/go.mod index ec54a5e1..f4f40b67 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/coder/terraform-provider-coder go 1.22 -toolchain go1.22.3 +toolchain go1.22.9 require ( github.com/docker/docker v26.1.5+incompatible diff --git a/templates/guides/version-2-upgrade.md.tmpl b/templates/guides/version-2-upgrade.md.tmpl new file mode 100644 index 00000000..49e262e1 --- /dev/null +++ b/templates/guides/version-2-upgrade.md.tmpl @@ -0,0 +1,101 @@ +--- +page_title: "Terraform Coder Provider Version 2 Upgrade Guide" +--- + +# Terraform Coder Provider Version 2 Upgrade Guide + +Version 2.0.0 of the Coder provider for Terraform is a major release that introduces some changes that you will need to consider when upgrading. +This guide is intended to help with the process, and focuses only on the changes from version 1.X to version 2.0.0. + +!> Using Version 2.0.0 of the Coder provider requires Coder Server version [`2.18.0`](https://github.com/coder/coder/releases/tag/v2.18.0) or later. + +Upgrade topics: + +- [Provider Version Configuration](#provider-version-configuration) +- [Provider Arguments](#provider-arguments) +- [Data Source: coder_git_auth --> coder_external_auth](#data-source-coder_git_auth) +- [Data Source: coder_workspace](#data-source-coder_workspace) + +## Provider Version Configuration + +-> Before upgrading to version 2.0.0, please first upgrade to the most recent 1.X version and ensure that your environment successfully runs [`terraform plan`](https://developer.hashicorp.com/terraform/cli/commands/plan) without unexpected changes or deprecation notices. + +We highly recommend using [version constraints](https://developer.hashicorp.com/terraform/language/providers/requirements#version-constraints) when configuring Terraform providers. + + +For example, given the previous configuration: + +```terraform +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 1.0.0" + } + } +} + +provider "coder" { + feature_use_managed_variables = true +} +``` + +Update to the latest 2.X version: + +```terraform +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 2.0.0" + } + } +} + +provider "coder" {} +``` + +## Provider Arguments + +Version 2.0.0 removes the [`feature_use_managed_variables`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs#feature_use_managed_variables-1) argument from the `provider` block. + + +## Data Source: [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) + +If you are using this data source, you must replace it with the [`coder_external_auth`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/external_auth) data source. The `coder_external_auth` data source is a more generic data source that can be used to create any external authentication provider which supports OAuth2. + +For example, given the previous configuration: + +```terraform +data "coder_git_auth" "example" { + id = "example" +} +``` + +Update to the new data source: + +```terraform +data "coder_external_auth" "example" { + id = "example" +} +``` + +## Data Source: [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) + +If you are using the `owner` properties of the `coder_workspace` data source, you must remove them and use the [`coder_workspace_owner`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner) data source instead. The `coder_workspace_owner` data source provides additional properties of the workspace owner. + +Update your Terraform configuration to use the `coder_workspace_owner` data source instead and update the following attributes: + +```terraform +data "coder_workspace_owner" "me" {} +``` + +- Remove `owner_id` attribute. Use [`data.coder_workspace_owner.me.id`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#id) instead. +- Remove `owner` attribute. Use [`data.coder_workspace_owner.me.name`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#name) instead. +- Remove `owner_name` attribute. Use [`data.coder_workspace_owner.me.full_name`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#full_name) instead. +- Remove `owner_email` attribute. Use [`data.coder_workspace_owner.me.email`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#email) instead. +- Remove `owner_groups` attribute. Use [`data.coder_workspace_owner.me.groups`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#groups) instead. +- Remove `owner_oidc_access_token` attribute. Use [`data.coder_workspace_owner.me.oidc_access_token`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#oidc_access_token) instead. +- Remove `owner_session_token` attribute. Use [`data.coder_workspace_owner.me.session_token`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#session_token) instead. + +->While we do not anticipate these changes to affect existing resources, we strongly advice reviewing the plan produced by Terraform to ensure no resources are accidentally removed or altered in an undesired way. If you encounter any unexpected behavior, please report it by opening a GitHub [issue](https://github.com/coder/terraform-provider-coder/issues). \ No newline at end of file diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index de4ec73e..be92b931 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -8,7 +8,11 @@ description: |- # Coder Provider -Terraform provider for managing Coder [templates](https://coder.com/docs/templates), which are the underlying infrastructure for Coder [workspaces](https://coder.com/docs/workspaces). +Terraform provider for managing Coder [templates](https://coder.com/docs/admin/templates), which are the underlying infrastructure for Coder [workspaces](https://coder.com/docs/user-guides/workspace-management). + +-> Requires Coder [v2.18.0](https://github.com/coder/coder/releases/tag/v2.18.0) or later. + +!> [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) and owner related fields of [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source have been removed. Follow the [Version 2 Upgrade Guide](https://registry.terraform.io/providers/codercom/coder/latest/docs/guides/version-2-upgrade) to update your code. ## Example From 701d7c888312a1867281f94f65a90777c036f1cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:02:14 +0500 Subject: [PATCH 058/114] build(deps): Bump github.com/stretchr/testify from 1.9.0 to 1.10.0 (#308) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f4f40b67..85487eb0 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 golang.org/x/mod v0.20.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 diff --git a/go.sum b/go.sum index ccbb2a1f..bc8707fc 100644 --- a/go.sum +++ b/go.sum @@ -183,8 +183,8 @@ github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= From 86ca17f876c2b701916744179fdbc0073157878d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:27:33 +0500 Subject: [PATCH 059/114] build(deps): Bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.34.0 to 2.35.0 (#299) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Muhammad Atif Ali Co-authored-by: Muhammad Atif Ali --- go.mod | 39 +++++++++++++++---------------- go.sum | 74 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index 85487eb0..8ecff3e5 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,18 @@ module github.com/coder/terraform-provider-coder -go 1.22 - -toolchain go1.22.9 +go 1.22.9 require ( github.com/docker/docker v26.1.5+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/mod v0.20.0 + golang.org/x/mod v0.21.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) @@ -40,17 +38,18 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.2 // 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.6.0 // indirect - github.com/hashicorp/hc-install v0.6.4 // indirect - github.com/hashicorp/hcl/v2 v2.20.1 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/hc-install v0.9.0 // indirect + github.com/hashicorp/hcl/v2 v2.22.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect - github.com/hashicorp/terraform-json v0.22.1 // indirect - github.com/hashicorp/terraform-plugin-go v0.23.0 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.25.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect @@ -74,24 +73,24 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/zclconf/go-cty v1.14.4 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.64.1 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index bc8707fc..0a8cc437 100644 --- a/go.sum +++ b/go.sum @@ -82,33 +82,35 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.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.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= +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-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.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0= -github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA= -github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= -github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= +github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= +github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= -github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= -github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-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-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= +github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= +github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -197,10 +199,10 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= -github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= @@ -221,30 +223,30 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 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= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -259,8 +261,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -268,8 +270,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -289,12 +291,12 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/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.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 73f18526e05e8e0ac5c556a67ba150c08b250da5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:51:55 +0500 Subject: [PATCH 060/114] build(deps): Bump golang.org/x/mod from 0.20.0 to 0.22.0 (#302) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8ecff3e5..609e1889 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/mod v0.21.0 + golang.org/x/mod v0.22.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) diff --git a/go.sum b/go.sum index 0a8cc437..524564a1 100644 --- a/go.sum +++ b/go.sum @@ -230,8 +230,8 @@ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXy 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.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= From f0607a6570a3911582547950366fe6235db349ab Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 28 Nov 2024 10:19:28 +0000 Subject: [PATCH 061/114] ci: run integration tests against oldstable (#310) * feawt(scripts/coderversion): emit OLDSTABLE version * chore: update README to better illustrate testing on tip of main * chore(integration): add test for workspace owner being set * ci: run integration tests against oldstable --- .github/workflows/test.yml | 16 ++++++ README.md | 4 +- integration/integration_test.go | 28 +++++++++++ integration/workspace-owner-filled/main.tf | 58 ++++++++++++++++++++++ scripts/coderversion/main.go | 32 ++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 integration/workspace-owner-filled/main.tf diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 57faf626..eb521f88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,14 @@ jobs: run: | go build -v . + - name: Run integration test (devel) + timeout-minutes: 10 + env: + CODER_IMAGE: "ghcr.io/coder/coder-preview" + CODER_VERSION: "latest" + run: | + go test -v ./integration + - name: Run integration test (mainline) timeout-minutes: 10 env: @@ -54,6 +62,14 @@ jobs: source <(go run ./scripts/coderversion) CODER_VERSION="${CODER_STABLE_VERSION}" go test -v ./integration + - name: Run integration test (oldstable) + timeout-minutes: 10 + env: + CODER_IMAGE: "ghcr.io/coder/coder" + run: | + source <(go run ./scripts/coderversion) + CODER_VERSION="${CODER_OLDSTABLE_VERSION}" go test -v ./integration + # run acceptance tests in a matrix with Terraform core versions test: name: Matrix Test diff --git a/README.md b/README.md index 2bceb73a..6f13efef 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,10 @@ To run these integration tests locally: 1. Pull the version of the Coder image you wish to test: ```console - docker pull ghcr.io/coder/coder:main-x.y.z-devel-abcd1234 + docker pull ghcr.io/coder/coder-preview:main-x.y.z-devel-abcd1234 ``` -1. Run `CODER_VERSION=main-x.y.z-devel-abcd1234 make test-integration`. +1. Run `CODER_IMAGE=ghcr.io/coder/coder-preview CODER_VERSION=main-x.y.z-devel-abcd1234 make test-integration`. > **Note:** you can specify `CODER_IMAGE` if the Coder image you wish to test is hosted somewhere other than `ghcr.io/coder/coder`. > For example, `CODER_IMAGE=example.com/repo/coder CODER_VERSION=foobar make test-integration`. diff --git a/integration/integration_test.go b/integration/integration_test.go index dc3d5c98..f1596eee 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -115,6 +115,34 @@ func TestIntegration(t *testing.T) { "workspace_owner.login_type": ``, }, }, + { + name: "workspace-owner-filled", + minVersion: "v2.18.0", + expectedOutput: map[string]string{ + "provisioner.arch": runtime.GOARCH, + "provisioner.id": `[a-zA-Z0-9-]+`, + "provisioner.os": runtime.GOOS, + "workspace.access_port": `\d+`, + "workspace.access_url": `https?://\D+:\d+`, + "workspace.id": `[a-zA-z0-9-]+`, + "workspace.name": ``, + "workspace.start_count": `1`, + "workspace.template_id": `[a-zA-Z0-9-]+`, + "workspace.template_name": `workspace-owner`, + "workspace.template_version": `.+`, + "workspace.transition": `start`, + "workspace_owner.email": `testing@coder\.com`, + "workspace_owner.full_name": `default`, + "workspace_owner.groups": `\[(\"Everyone\")?\]`, + "workspace_owner.id": `[a-zA-Z0-9-]+`, + "workspace_owner.name": `testing`, + "workspace_owner.oidc_access_token": `^$`, // TODO: test OIDC integration + "workspace_owner.session_token": `.+`, + "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, + "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, + "workspace_owner.login_type": `password`, + }, + }, { name: "coder-app-hidden", minVersion: "v0.0.0", diff --git a/integration/workspace-owner-filled/main.tf b/integration/workspace-owner-filled/main.tf new file mode 100644 index 00000000..fd923a3d --- /dev/null +++ b/integration/workspace-owner-filled/main.tf @@ -0,0 +1,58 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + local = { + source = "hashicorp/local" + } + } +} + +// TODO: test coder_external_auth +// data coder_external_auth "me" {} +data "coder_provisioner" "me" {} +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +locals { + # NOTE: these must all be strings in the output + output = { + "provisioner.arch" : data.coder_provisioner.me.arch, + "provisioner.id" : data.coder_provisioner.me.id, + "provisioner.os" : data.coder_provisioner.me.os, + "workspace.access_port" : tostring(data.coder_workspace.me.access_port), + "workspace.access_url" : data.coder_workspace.me.access_url, + "workspace.id" : data.coder_workspace.me.id, + "workspace.name" : data.coder_workspace.me.name, + "workspace.start_count" : tostring(data.coder_workspace.me.start_count), + "workspace.template_id" : data.coder_workspace.me.template_id, + "workspace.template_name" : data.coder_workspace.me.template_name, + "workspace.template_version" : data.coder_workspace.me.template_version, + "workspace.transition" : data.coder_workspace.me.transition, + "workspace_owner.email" : data.coder_workspace_owner.me.email, + "workspace_owner.full_name" : data.coder_workspace_owner.me.full_name, + "workspace_owner.groups" : jsonencode(data.coder_workspace_owner.me.groups), + "workspace_owner.id" : data.coder_workspace_owner.me.id, + "workspace_owner.name" : data.coder_workspace_owner.me.name, + "workspace_owner.oidc_access_token" : data.coder_workspace_owner.me.oidc_access_token, + "workspace_owner.session_token" : data.coder_workspace_owner.me.session_token, + "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, + "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, + "workspace_owner.login_type" : data.coder_workspace_owner.me.login_type, + } +} + +variable "output_path" { + type = string +} + +resource "local_file" "output" { + filename = var.output_path + content = jsonencode(local.output) +} + +output "output" { + value = local.output + sensitive = true +} diff --git a/scripts/coderversion/main.go b/scripts/coderversion/main.go index b6f57d4b..9b5f4c06 100644 --- a/scripts/coderversion/main.go +++ b/scripts/coderversion/main.go @@ -64,6 +64,38 @@ func main() { stable := fmt.Sprintf("v%d.%d.%d", stableVer.Major(), stableVer.Minor(), stableVer.Patch()) _, _ = fmt.Fprintf(os.Stdout, "CODER_STABLE_VERSION=%q\n", stable) + + expectedOldStableMinor := mainlineVer.Minor() - 2 + if expectedOldStableMinor < 0 { + expectedOldStableMinor = 0 + } + debug("expected old stable minor: %d\n", expectedStableMinor) + oldStableVer := semver.MustParse("v0.0.0") + for _, rel := range releases { + debug("check version %s\n", rel) + if rel == "" { + debug("ignoring untagged version %s\n", rel) + continue + } + + ver, err := semver.NewVersion(rel) + if err != nil { + debug("skipping invalid version %s\n", rel) + } + + if ver.Minor() != expectedOldStableMinor { + debug("skipping version %s\n", rel) + continue + } + + if ver.Compare(oldStableVer) > 0 { + oldStableVer = ver + continue + } + } + + oldStable := fmt.Sprintf("v%d.%d.%d", oldStableVer.Major(), oldStableVer.Minor(), oldStableVer.Patch()) + _, _ = fmt.Fprintf(os.Stdout, "CODER_OLDSTABLE_VERSION=%q\n", oldStable) } type release struct { From ceefe84eeb927d7650f541b1f951b44b718513c2 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 28 Nov 2024 16:07:46 +0500 Subject: [PATCH 062/114] fix: fix links in version 2 upgrade guide (#312) --- docs/guides/version-2-upgrade.md | 8 ++++---- docs/index.md | 2 +- templates/guides/version-2-upgrade.md.tmpl | 8 ++++---- templates/index.md.tmpl | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/guides/version-2-upgrade.md b/docs/guides/version-2-upgrade.md index 49e262e1..8c8233eb 100644 --- a/docs/guides/version-2-upgrade.md +++ b/docs/guides/version-2-upgrade.md @@ -1,8 +1,8 @@ --- -page_title: "Terraform Coder Provider Version 2 Upgrade Guide" +page_title: "Version 2 Upgrade Guide" --- -# Terraform Coder Provider Version 2 Upgrade Guide +# Version 2 Upgrade Guide Version 2.0.0 of the Coder provider for Terraform is a major release that introduces some changes that you will need to consider when upgrading. This guide is intended to help with the process, and focuses only on the changes from version 1.X to version 2.0.0. @@ -13,8 +13,8 @@ Upgrade topics: - [Provider Version Configuration](#provider-version-configuration) - [Provider Arguments](#provider-arguments) -- [Data Source: coder_git_auth --> coder_external_auth](#data-source-coder_git_auth) -- [Data Source: coder_workspace](#data-source-coder_workspace) +- [Data Source: [`coder_git_auth`](#data-source-coder_git_auth) +- [Data Source: [`coder_workspace`](#data-source-coder_workspace) ## Provider Version Configuration diff --git a/docs/index.md b/docs/index.md index ec7be2d8..c30f1477 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ Terraform provider for managing Coder [templates](https://coder.com/docs/admin/t -> Requires Coder [v2.18.0](https://github.com/coder/coder/releases/tag/v2.18.0) or later. -!> [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) and owner related fields of [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source have been removed. Follow the [Version 2 Upgrade Guide](https://registry.terraform.io/providers/codercom/coder/latest/docs/guides/version-2-upgrade) to update your code. +!> [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) and owner related fields of [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source have been removed. Follow the [Version 2 Upgrade Guide](https://registry.terraform.io/providers/coder/coder/latest/docs/guides/version-2-upgrade) to update your code. ## Example diff --git a/templates/guides/version-2-upgrade.md.tmpl b/templates/guides/version-2-upgrade.md.tmpl index 49e262e1..8c8233eb 100644 --- a/templates/guides/version-2-upgrade.md.tmpl +++ b/templates/guides/version-2-upgrade.md.tmpl @@ -1,8 +1,8 @@ --- -page_title: "Terraform Coder Provider Version 2 Upgrade Guide" +page_title: "Version 2 Upgrade Guide" --- -# Terraform Coder Provider Version 2 Upgrade Guide +# Version 2 Upgrade Guide Version 2.0.0 of the Coder provider for Terraform is a major release that introduces some changes that you will need to consider when upgrading. This guide is intended to help with the process, and focuses only on the changes from version 1.X to version 2.0.0. @@ -13,8 +13,8 @@ Upgrade topics: - [Provider Version Configuration](#provider-version-configuration) - [Provider Arguments](#provider-arguments) -- [Data Source: coder_git_auth --> coder_external_auth](#data-source-coder_git_auth) -- [Data Source: coder_workspace](#data-source-coder_workspace) +- [Data Source: [`coder_git_auth`](#data-source-coder_git_auth) +- [Data Source: [`coder_workspace`](#data-source-coder_workspace) ## Provider Version Configuration diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index be92b931..efdaae96 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -12,7 +12,7 @@ Terraform provider for managing Coder [templates](https://coder.com/docs/admin/t -> Requires Coder [v2.18.0](https://github.com/coder/coder/releases/tag/v2.18.0) or later. -!> [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) and owner related fields of [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source have been removed. Follow the [Version 2 Upgrade Guide](https://registry.terraform.io/providers/codercom/coder/latest/docs/guides/version-2-upgrade) to update your code. +!> [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) and owner related fields of [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source have been removed. Follow the [Version 2 Upgrade Guide](https://registry.terraform.io/providers/coder/coder/latest/docs/guides/version-2-upgrade) to update your code. ## Example From b76e23221edb705517cd9864e3126a37e57b900e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 28 Nov 2024 11:08:19 +0000 Subject: [PATCH 063/114] fix(provider): remove warning diag if CODER_WORKSPACE_OWNER_LOGIN_TYPE is not set (#311) --- provider/workspace_owner.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/provider/workspace_owner.go b/provider/workspace_owner.go index c51751b0..52b1ef8c 100644 --- a/provider/workspace_owner.go +++ b/provider/workspace_owner.go @@ -55,13 +55,9 @@ func workspaceOwnerDataSource() *schema.Resource { _ = rd.Set("session_token", os.Getenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN")) _ = rd.Set("oidc_access_token", os.Getenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN")) - if os.Getenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE") == "" { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "WARNING: The CODER_WORKSPACE_OWNER_LOGIN_TYPE env variable is not set", - }) + if loginType := os.Getenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE"); loginType != "" { + _ = rd.Set("login_type", loginType) } - _ = rd.Set("login_type", os.Getenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE")) return diags }, From 224ff28a6a88e7c3f5c63b554181dd992408e83c Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 28 Nov 2024 11:26:19 +0000 Subject: [PATCH 064/114] ci: allow go version patch upgrades (#313) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5aa16488..76523498 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.22.3 + go-version: '1.22' - name: Import GPG key id: import_gpg From 8b04ef792d81b24d1856f33c692c7b209764427f Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 28 Nov 2024 11:28:10 +0000 Subject: [PATCH 065/114] ci: create draft release (#314) * ci: create draft release * fixup! ci: create draft release --- .github/workflows/release.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76523498..cab21004 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,3 +46,17 @@ jobs: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} # GitHub sets this automatically GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ghrelease: + runs-on: ubuntu-latest + needs: ["goreleaser"] + steps: + - name: Create a draft GitHub release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create \ + --draft \ + --title "${GITHUB_REF_NAME}" \ + --notes-from-tag \ + "${GITHUB_REF_NAME}" From 0761ca8b753086f7fb565ec38e8a25e07ed76d87 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 28 Nov 2024 11:56:28 +0000 Subject: [PATCH 066/114] ci: run integration tests before release (#315) --- .github/workflows/release.yml | 52 +++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 20 ++++++++++---- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cab21004..bb023d83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,58 @@ on: tags: - "v*" jobs: + test: + name: Run Integration Tests + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v4 + + - name: Get dependencies + run: | + go mod download + + - name: Build + env: + CGO_ENABLED: "0" + run: | + go build -v . + + - name: Check Versions + id: checkversions + run: | + source <(go run ./scripts/coderversion) + echo "CODER_MAINLINE_VERSION=${CODER_MAINLINE_VERSION}" >> "${GITHUB_OUTPUT}" + echo "CODER_STABLE_VERSION=${CODER_STABLE_VERSION}" >> "${GITHUB_OUTPUT}" + echo "CODER_OLDSTABLE_VERSION=${CODER_OLDSTABLE_VERSION}" >> "${GITHUB_OUTPUT}" + + - name: Run integration test (mainline) + env: + CODER_IMAGE: "ghcr.io/coder/coder" + CODER_VERSION: ${{ steps.checkversions.outputs.CODER_MAINLINE_VERSION }} + run: | + go test -v ./integration + + - name: Run integration test (stable) + env: + CODER_IMAGE: "ghcr.io/coder/coder" + CODER_VERSION: ${{ steps.checkversions.outputs.CODER_STABLE_VERSION }} + run: | + go test -v ./integration + + - name: Run integration test (oldstable) + env: + CODER_IMAGE: "ghcr.io/coder/coder" + CODER_VERSION: ${{ steps.checkversions.outputs.CODER_OLDSTABLE_VERSION }} + run: | + go test -v ./integration + goreleaser: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb521f88..69ca7ada 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,14 @@ jobs: run: | go build -v . + - name: Check Versions + id: checkversions + run: | + source <(go run ./scripts/coderversion) + echo "CODER_MAINLINE_VERSION=${CODER_MAINLINE_VERSION}" >> "${GITHUB_OUTPUT}" + echo "CODER_STABLE_VERSION=${CODER_STABLE_VERSION}" >> "${GITHUB_OUTPUT}" + echo "CODER_OLDSTABLE_VERSION=${CODER_OLDSTABLE_VERSION}" >> "${GITHUB_OUTPUT}" + - name: Run integration test (devel) timeout-minutes: 10 env: @@ -50,25 +58,25 @@ jobs: timeout-minutes: 10 env: CODER_IMAGE: "ghcr.io/coder/coder" + CODER_VERSION: ${{ steps.checkversions.outputs.CODER_MAINLINE_VERSION }} run: | - source <(go run ./scripts/coderversion) - CODER_VERSION="${CODER_MAINLINE_VERSION}" go test -v ./integration + go test -v ./integration - name: Run integration test (stable) timeout-minutes: 10 env: CODER_IMAGE: "ghcr.io/coder/coder" + CODER_VERSION: ${{ steps.checkversions.outputs.CODER_STABLE_VERSION }} run: | - source <(go run ./scripts/coderversion) - CODER_VERSION="${CODER_STABLE_VERSION}" go test -v ./integration + go test -v ./integration - name: Run integration test (oldstable) timeout-minutes: 10 env: CODER_IMAGE: "ghcr.io/coder/coder" + CODER_VERSION: ${{ steps.checkversions.outputs.CODER_OLDSTABLE_VERSION }} run: | - source <(go run ./scripts/coderversion) - CODER_VERSION="${CODER_OLDSTABLE_VERSION}" go test -v ./integration + go test -v ./integration # run acceptance tests in a matrix with Terraform core versions test: From d15e8d19657b1e4290a18a4da5a1b4fa9ef13971 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 28 Nov 2024 12:01:32 +0000 Subject: [PATCH 067/114] ci: rm unused action (#316) --- .github/workflows/release.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb023d83..6bb2f731 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,17 +98,3 @@ jobs: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} # GitHub sets this automatically GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - ghrelease: - runs-on: ubuntu-latest - needs: ["goreleaser"] - steps: - - name: Create a draft GitHub release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create \ - --draft \ - --title "${GITHUB_REF_NAME}" \ - --notes-from-tag \ - "${GITHUB_REF_NAME}" From 9a97161c34f22ca3fedf8f5b8769931e530e4e37 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 28 Nov 2024 18:03:19 +0500 Subject: [PATCH 068/114] fix(docs): fix formatting issues in upgrade guide for version 2 (#317) --- docs/guides/version-2-upgrade.md | 14 ++++++++------ templates/guides/version-2-upgrade.md.tmpl | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/guides/version-2-upgrade.md b/docs/guides/version-2-upgrade.md index 8c8233eb..9c8063f4 100644 --- a/docs/guides/version-2-upgrade.md +++ b/docs/guides/version-2-upgrade.md @@ -13,8 +13,8 @@ Upgrade topics: - [Provider Version Configuration](#provider-version-configuration) - [Provider Arguments](#provider-arguments) -- [Data Source: [`coder_git_auth`](#data-source-coder_git_auth) -- [Data Source: [`coder_workspace`](#data-source-coder_workspace) +- [Data Source: `coder_git_auth`](#data-source-coder_git_auth) +- [Data Source: `coder_workspace`](#data-source-coder_workspace) ## Provider Version Configuration @@ -60,9 +60,9 @@ provider "coder" {} Version 2.0.0 removes the [`feature_use_managed_variables`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs#feature_use_managed_variables-1) argument from the `provider` block. -## Data Source: [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) +## Data Source: `coder_git_auth` -If you are using this data source, you must replace it with the [`coder_external_auth`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/external_auth) data source. The `coder_external_auth` data source is a more generic data source that can be used to create any external authentication provider which supports OAuth2. +If you are using the [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) data source, you must replace it with the [`coder_external_auth`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/external_auth) data source. The `coder_external_auth` data source is a more generic data source that can be used to create any external authentication provider which supports OAuth2. For example, given the previous configuration: @@ -80,14 +80,16 @@ data "coder_external_auth" "example" { } ``` -## Data Source: [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) +## Data Source: `coder_workspace` -If you are using the `owner` properties of the `coder_workspace` data source, you must remove them and use the [`coder_workspace_owner`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner) data source instead. The `coder_workspace_owner` data source provides additional properties of the workspace owner. +If you are using the `owner` properties of the [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source, you must remove them and use the [`coder_workspace_owner`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner) data source instead. The `coder_workspace_owner` data source provides additional properties of the workspace owner. Update your Terraform configuration to use the `coder_workspace_owner` data source instead and update the following attributes: ```terraform + data "coder_workspace_owner" "me" {} + ``` - Remove `owner_id` attribute. Use [`data.coder_workspace_owner.me.id`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#id) instead. diff --git a/templates/guides/version-2-upgrade.md.tmpl b/templates/guides/version-2-upgrade.md.tmpl index 8c8233eb..9c8063f4 100644 --- a/templates/guides/version-2-upgrade.md.tmpl +++ b/templates/guides/version-2-upgrade.md.tmpl @@ -13,8 +13,8 @@ Upgrade topics: - [Provider Version Configuration](#provider-version-configuration) - [Provider Arguments](#provider-arguments) -- [Data Source: [`coder_git_auth`](#data-source-coder_git_auth) -- [Data Source: [`coder_workspace`](#data-source-coder_workspace) +- [Data Source: `coder_git_auth`](#data-source-coder_git_auth) +- [Data Source: `coder_workspace`](#data-source-coder_workspace) ## Provider Version Configuration @@ -60,9 +60,9 @@ provider "coder" {} Version 2.0.0 removes the [`feature_use_managed_variables`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs#feature_use_managed_variables-1) argument from the `provider` block. -## Data Source: [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) +## Data Source: `coder_git_auth` -If you are using this data source, you must replace it with the [`coder_external_auth`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/external_auth) data source. The `coder_external_auth` data source is a more generic data source that can be used to create any external authentication provider which supports OAuth2. +If you are using the [`coder_git_auth`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/git_auth) data source, you must replace it with the [`coder_external_auth`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/external_auth) data source. The `coder_external_auth` data source is a more generic data source that can be used to create any external authentication provider which supports OAuth2. For example, given the previous configuration: @@ -80,14 +80,16 @@ data "coder_external_auth" "example" { } ``` -## Data Source: [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) +## Data Source: `coder_workspace` -If you are using the `owner` properties of the `coder_workspace` data source, you must remove them and use the [`coder_workspace_owner`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner) data source instead. The `coder_workspace_owner` data source provides additional properties of the workspace owner. +If you are using the `owner` properties of the [`coder_workspace`](https://registry.terraform.io/providers/coder/coder/1.0.4/docs/data-sources/workspace) data source, you must remove them and use the [`coder_workspace_owner`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner) data source instead. The `coder_workspace_owner` data source provides additional properties of the workspace owner. Update your Terraform configuration to use the `coder_workspace_owner` data source instead and update the following attributes: ```terraform + data "coder_workspace_owner" "me" {} + ``` - Remove `owner_id` attribute. Use [`data.coder_workspace_owner.me.id`](https://registry.terraform.io/providers/coder/coder/2.0.0/docs/data-sources/workspace_owner#id) instead. From 2b9f8032198210af11ba50cf64bb8e091c7e5a2c Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 29 Nov 2024 22:00:26 +0500 Subject: [PATCH 069/114] ci: generate release notes automatically (#318) --- .goreleaser.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 3866324a..69029533 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -57,4 +57,7 @@ release: # If you want to manually examine the release before its live, uncomment this line: # draft: true changelog: - disable: true + # see https://goreleaser.com/customization/changelog/ + use: github-native + sort: asc + abbrev: 0 From 3d46b7343232c606a18b4ffb0632cebcfb3a5615 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 4 Dec 2024 01:25:46 +0500 Subject: [PATCH 070/114] fix(.goreleaser.yml): use semver to sort git tags (#319) --- .goreleaser.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index 69029533..89118d2c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -61,3 +61,5 @@ changelog: use: github-native sort: asc abbrev: 0 +git: + tag_sort: semver From c9dbd6f7639765999b3e84c2f966b3a171d31a4d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 4 Dec 2024 01:34:11 +0500 Subject: [PATCH 071/114] Revert "fix(.goreleaser.yml): use semver to sort git tags" (#320) --- .goreleaser.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 89118d2c..69029533 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -61,5 +61,3 @@ changelog: use: github-native sort: asc abbrev: 0 -git: - tag_sort: semver From 8349a69f9512f67805f8544c311b4fa8a70dd4f5 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 16 Dec 2024 13:50:06 +0000 Subject: [PATCH 072/114] chore: update golang.org/x/crypto to v0.31.0 (#323) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 609e1889..9087a1a7 100644 --- a/go.mod +++ b/go.mod @@ -80,11 +80,11 @@ require ( go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 524564a1..1922dad3 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -245,8 +245,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -261,8 +261,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -270,8 +270,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From d933a7149b87898dba196342be5dd9df3fc07bcf Mon Sep 17 00:00:00 2001 From: Vincent Vielle Date: Thu, 19 Dec 2024 16:35:25 +0100 Subject: [PATCH 073/114] add open_in option to coder_app (#321) * add open_in option to coder_app * work on tests * add missing example * rename test * lint * generate docs --- docs/resources/app.md | 2 + examples/resources/coder_app/resource.tf | 1 + integration/coder-app-open-in/main.tf | 62 +++++++++++++ integration/integration_test.go | 9 ++ provider/app.go | 22 +++++ provider/app_test.go | 111 +++++++++++++++++++++++ 6 files changed, 207 insertions(+) create mode 100644 integration/coder-app-open-in/main.tf diff --git a/docs/resources/app.md b/docs/resources/app.md index 6b8e99f4..e2bbe435 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -33,6 +33,7 @@ resource "coder_app" "code-server" { url = "http://localhost:13337" share = "owner" subdomain = false + open_in = "window" healthcheck { url = "http://localhost:13337/healthz" interval = 5 @@ -65,6 +66,7 @@ resource "coder_app" "vim" { - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) - `hidden` (Boolean) Determines if the app is visible in the UI (minimum Coder version: v2.16). - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. +- `open_in` (String) Determines where the app will be opened. Valid values are `"tab"`, `"window"`, and `"slim-window" (default)`. `"tab"` opens in a new tab in the same browser window. `"window"` opens a fresh browser window with navigation options. `"slim-window"` opens a new browser window without navigation controls. - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). - `share` (String) Determines the level which the application is shared at. Valid levels are `"owner"` (default), `"authenticated"` and `"public"`. Level `"owner"` disables sharing on the app, so only the workspace owner can access it. Level `"authenticated"` shares the app with all authenticated users. Level `"public"` shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with `subdomain` set to `true` will not be accessible. Defaults to `false`. diff --git a/examples/resources/coder_app/resource.tf b/examples/resources/coder_app/resource.tf index 9345dfc5..8aea7b99 100644 --- a/examples/resources/coder_app/resource.tf +++ b/examples/resources/coder_app/resource.tf @@ -18,6 +18,7 @@ resource "coder_app" "code-server" { url = "http://localhost:13337" share = "owner" subdomain = false + open_in = "window" healthcheck { url = "http://localhost:13337/healthz" interval = 5 diff --git a/integration/coder-app-open-in/main.tf b/integration/coder-app-open-in/main.tf new file mode 100644 index 00000000..529d9bef --- /dev/null +++ b/integration/coder-app-open-in/main.tf @@ -0,0 +1,62 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + local = { + source = "hashicorp/local" + } + } +} + +data "coder_workspace" "me" {} + +resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + dir = "/workspace" +} + +resource "coder_app" "window" { + agent_id = coder_agent.dev.id + slug = "window" + share = "owner" + open_in = "window" +} + +resource "coder_app" "slim-window" { + agent_id = coder_agent.dev.id + slug = "slim-window" + share = "owner" + open_in = "slim-window" +} + +resource "coder_app" "defaulted" { + agent_id = coder_agent.dev.id + slug = "defaulted" + share = "owner" +} + +locals { + # NOTE: these must all be strings in the output + output = { + "coder_app.window.open_in" = tostring(coder_app.window.open_in) + "coder_app.slim-window.open_in" = tostring(coder_app.slim-window.open_in) + "coder_app.defaulted.open_in" = tostring(coder_app.defaulted.open_in) + } +} + +variable "output_path" { + type = string +} + +resource "local_file" "output" { + filename = var.output_path + content = jsonencode(local.output) +} + +output "output" { + value = local.output + sensitive = true +} + diff --git a/integration/integration_test.go b/integration/integration_test.go index f1596eee..50ef71b5 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -143,6 +143,15 @@ func TestIntegration(t *testing.T) { "workspace_owner.login_type": `password`, }, }, + { + name: "coder-app-open-in", + minVersion: "v2.19.0", + expectedOutput: map[string]string{ + "coder_app.window.open_in": "window", + "coder_app.slim-window.open_in": "slim-window", + "coder_app.defaulted.open_in": "slim-window", + }, + }, { name: "coder-app-hidden", minVersion: "v0.0.0", diff --git a/provider/app.go b/provider/app.go index 3fd71692..391bdfc3 100644 --- a/provider/app.go +++ b/provider/app.go @@ -223,6 +223,28 @@ func appResource() *schema.Resource { ForceNew: true, Optional: true, }, + "open_in": { + Type: schema.TypeString, + Description: "Determines where the app will be opened. Valid values are `\"tab\"`, `\"window\"`, and `\"slim-window\" (default)`. " + + "`\"tab\"` opens in a new tab in the same browser window. `\"window\"` opens a fresh browser window with navigation options. " + + "`\"slim-window\"` opens a new browser window without navigation controls.", + ForceNew: true, + Optional: true, + Default: "slim-window", + ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics { + valStr, ok := val.(string) + if !ok { + return diag.Errorf("expected string, got %T", val) + } + + switch valStr { + case "tab", "window", "slim-window": + return nil + } + + return diag.Errorf(`invalid "coder_app" open_in value, must be one of "tab", "window", "slim-window": %q`, valStr) + }, + }, }, } } diff --git a/provider/app_test.go b/provider/app_test.go index 6a17ca0c..aaa4f631 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -42,6 +42,7 @@ func TestApp(t *testing.T) { } order = 4 hidden = false + open_in = "slim-window" } `, Check: func(state *terraform.State) error { @@ -64,6 +65,7 @@ func TestApp(t *testing.T) { "healthcheck.0.threshold", "order", "hidden", + "open_in", } { value := resource.Primary.Attributes[key] t.Logf("%q = %q", key, value) @@ -98,6 +100,7 @@ func TestApp(t *testing.T) { display_name = "Testing" url = "https://google.com" external = true + open_in = "slim-window" } `, external: true, @@ -116,6 +119,7 @@ func TestApp(t *testing.T) { url = "https://google.com" external = true subdomain = true + open_in = "slim-window" } `, expectError: regexp.MustCompile("conflicts with subdomain"), @@ -209,6 +213,7 @@ func TestApp(t *testing.T) { interval = 5 threshold = 6 } + open_in = "slim-window" } `, sharingLine) @@ -241,6 +246,106 @@ func TestApp(t *testing.T) { } }) + t.Run("OpenIn", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + value string + expectValue string + expectError *regexp.Regexp + }{ + { + name: "default", + value: "", // default + expectValue: "slim-window", + }, + { + name: "InvalidValue", + value: "nonsense", + expectError: regexp.MustCompile(`invalid "coder_app" open_in value, must be one of "tab", "window", "slim-window": "nonsense"`), + }, + { + name: "ExplicitWindow", + value: "window", + expectValue: "window", + }, + { + name: "ExplicitSlimWindow", + value: "slim-window", + expectValue: "slim-window", + }, + { + name: "ExplicitTab", + value: "tab", + expectValue: "tab", + }, + } + + for _, c := range cases { + c := c + + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + config := ` + provider "coder" { + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "code-server" { + agent_id = coder_agent.dev.id + slug = "code-server" + display_name = "code-server" + icon = "builtin:vim" + url = "http://localhost:13337" + healthcheck { + url = "http://localhost:13337/healthz" + interval = 5 + threshold = 6 + }` + + if c.value != "" { + config += fmt.Sprintf(` + open_in = %q + `, c.value) + } + + config += ` + } + ` + + checkFn := func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 2) + resource := state.Modules[0].Resources["coder_app.code-server"] + require.NotNil(t, resource) + + // Read share and ensure it matches the expected + // value. + value := resource.Primary.Attributes["open_in"] + require.Equal(t, c.expectValue, value) + return nil + } + if c.expectError != nil { + checkFn = nil + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: config, + Check: checkFn, + ExpectError: c.expectError, + }}, + }) + }) + } + }) + t.Run("Hidden", func(t *testing.T) { t.Parallel() @@ -248,6 +353,7 @@ func TestApp(t *testing.T) { name string config string hidden bool + openIn string }{{ name: "Is Hidden", config: ` @@ -263,9 +369,11 @@ func TestApp(t *testing.T) { url = "https://google.com" external = true hidden = true + open_in = "slim-window" } `, hidden: true, + openIn: "slim-window", }, { name: "Is Not Hidden", config: ` @@ -281,9 +389,11 @@ func TestApp(t *testing.T) { url = "https://google.com" external = true hidden = false + open_in = "window" } `, hidden: false, + openIn: "window", }} for _, tc := range cases { tc := tc @@ -300,6 +410,7 @@ func TestApp(t *testing.T) { resource := state.Modules[0].Resources["coder_app.test"] require.NotNil(t, resource) require.Equal(t, strconv.FormatBool(tc.hidden), resource.Primary.Attributes["hidden"]) + require.Equal(t, tc.openIn, resource.Primary.Attributes["open_in"]) return nil }, ExpectError: nil, From b7ab1bb824ae90b447f6183c671cb0ca5580bb8d Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 20 Dec 2024 13:47:58 +0000 Subject: [PATCH 074/114] chore: update paths to /v2 (#324) --- go.mod | 2 +- main.go | 2 +- provider/agent.go | 2 +- provider/decode_test.go | 2 +- provider/externalauth.go | 2 +- provider/parameter_test.go | 2 +- provider/provider_test.go | 2 +- provider/workspace.go | 2 +- scripts/docsgen/main.go | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9087a1a7..f88cd289 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/coder/terraform-provider-coder +module github.com/coder/terraform-provider-coder/v2 go 1.22.9 diff --git a/main.go b/main.go index a16c9951..2eaa5dc5 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - "github.com/coder/terraform-provider-coder/provider" + "github.com/coder/terraform-provider-coder/v2/provider" ) // Run the docs generation tool, check its repository for more information on how it works and how docs diff --git a/provider/agent.go b/provider/agent.go index 01fb5801..ac012bf1 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "golang.org/x/xerrors" - "github.com/coder/terraform-provider-coder/provider/helpers" + "github.com/coder/terraform-provider-coder/v2/provider/helpers" ) func agentResource() *schema.Resource { diff --git a/provider/decode_test.go b/provider/decode_test.go index a5e20b14..947ebf79 100644 --- a/provider/decode_test.go +++ b/provider/decode_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/coder/terraform-provider-coder/provider" + "github.com/coder/terraform-provider-coder/v2/provider" ) func TestDecode(t *testing.T) { diff --git a/provider/externalauth.go b/provider/externalauth.go index a11a67c4..915a21a9 100644 --- a/provider/externalauth.go +++ b/provider/externalauth.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/coder/terraform-provider-coder/provider/helpers" + "github.com/coder/terraform-provider-coder/v2/provider/helpers" ) // externalAuthDataSource returns a schema for an external authentication data source. diff --git a/provider/parameter_test.go b/provider/parameter_test.go index fc814cba..b1f164a0 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" - "github.com/coder/terraform-provider-coder/provider" + "github.com/coder/terraform-provider-coder/v2/provider" ) func TestParameter(t *testing.T) { diff --git a/provider/provider_test.go b/provider/provider_test.go index 3367e7f4..4bf98b32 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" - "github.com/coder/terraform-provider-coder/provider" + "github.com/coder/terraform-provider-coder/v2/provider" ) func TestProvider(t *testing.T) { diff --git a/provider/workspace.go b/provider/workspace.go index 575fd60f..fde742b6 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/coder/terraform-provider-coder/provider/helpers" + "github.com/coder/terraform-provider-coder/v2/provider/helpers" ) func workspaceDataSource() *schema.Resource { diff --git a/scripts/docsgen/main.go b/scripts/docsgen/main.go index d83cf123..53b43ca4 100644 --- a/scripts/docsgen/main.go +++ b/scripts/docsgen/main.go @@ -9,7 +9,7 @@ import ( "regexp" "strings" - "github.com/coder/terraform-provider-coder/provider" + "github.com/coder/terraform-provider-coder/v2/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "golang.org/x/xerrors" ) From 464d613873cab9b1424c0c5a4d5c74ae3bf52927 Mon Sep 17 00:00:00 2001 From: Vincent Vielle Date: Mon, 20 Jan 2025 13:59:24 +0100 Subject: [PATCH 075/114] fix: remove window option from open_in parameter (#327) * remove window option * fix missing integration test * fix tests * run linter --- docs/resources/app.md | 2 +- integration/coder-app-open-in/main.tf | 18 +++++------------- integration/integration_test.go | 5 ++--- provider/app.go | 8 ++++---- provider/app_test.go | 11 +++-------- 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/docs/resources/app.md b/docs/resources/app.md index e2bbe435..b3ac728f 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -66,7 +66,7 @@ resource "coder_app" "vim" { - `healthcheck` (Block Set, Max: 1) HTTP health checking to determine the application readiness. (see [below for nested schema](#nestedblock--healthcheck)) - `hidden` (Boolean) Determines if the app is visible in the UI (minimum Coder version: v2.16). - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons here: https://github.com/coder/coder/tree/main/site/static/icon. Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. -- `open_in` (String) Determines where the app will be opened. Valid values are `"tab"`, `"window"`, and `"slim-window" (default)`. `"tab"` opens in a new tab in the same browser window. `"window"` opens a fresh browser window with navigation options. `"slim-window"` opens a new browser window without navigation controls. +- `open_in` (String) Determines where the app will be opened. Valid values are `"tab"` and `"slim-window" (default)`. `"tab"` opens in a new tab in the same browser window. `"slim-window"` opens a new browser window without navigation controls. - `order` (Number) The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order). - `share` (String) Determines the level which the application is shared at. Valid levels are `"owner"` (default), `"authenticated"` and `"public"`. Level `"owner"` disables sharing on the app, so only the workspace owner can access it. Level `"authenticated"` shares the app with all authenticated users. Level `"public"` shares it with any user, including unauthenticated users. Permitted application sharing levels can be configured site-wide via a flag on `coder server` (Enterprise only). - `subdomain` (Boolean) Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. If wildcards have not been setup by the administrator then apps with `subdomain` set to `true` will not be accessible. Defaults to `false`. diff --git a/integration/coder-app-open-in/main.tf b/integration/coder-app-open-in/main.tf index 529d9bef..f06947ae 100644 --- a/integration/coder-app-open-in/main.tf +++ b/integration/coder-app-open-in/main.tf @@ -17,18 +17,11 @@ resource "coder_agent" "dev" { dir = "/workspace" } -resource "coder_app" "window" { +resource "coder_app" "tab" { agent_id = coder_agent.dev.id - slug = "window" + slug = "tab" share = "owner" - open_in = "window" -} - -resource "coder_app" "slim-window" { - agent_id = coder_agent.dev.id - slug = "slim-window" - share = "owner" - open_in = "slim-window" + open_in = "tab" } resource "coder_app" "defaulted" { @@ -40,9 +33,8 @@ resource "coder_app" "defaulted" { locals { # NOTE: these must all be strings in the output output = { - "coder_app.window.open_in" = tostring(coder_app.window.open_in) - "coder_app.slim-window.open_in" = tostring(coder_app.slim-window.open_in) - "coder_app.defaulted.open_in" = tostring(coder_app.defaulted.open_in) + "coder_app.tab.open_in" = tostring(coder_app.tab.open_in) + "coder_app.defaulted.open_in" = tostring(coder_app.defaulted.open_in) } } diff --git a/integration/integration_test.go b/integration/integration_test.go index 50ef71b5..8ac68f28 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -147,9 +147,8 @@ func TestIntegration(t *testing.T) { name: "coder-app-open-in", minVersion: "v2.19.0", expectedOutput: map[string]string{ - "coder_app.window.open_in": "window", - "coder_app.slim-window.open_in": "slim-window", - "coder_app.defaulted.open_in": "slim-window", + "coder_app.tab.open_in": "tab", + "coder_app.defaulted.open_in": "slim-window", }, }, { diff --git a/provider/app.go b/provider/app.go index 391bdfc3..cd64db54 100644 --- a/provider/app.go +++ b/provider/app.go @@ -225,8 +225,8 @@ func appResource() *schema.Resource { }, "open_in": { Type: schema.TypeString, - Description: "Determines where the app will be opened. Valid values are `\"tab\"`, `\"window\"`, and `\"slim-window\" (default)`. " + - "`\"tab\"` opens in a new tab in the same browser window. `\"window\"` opens a fresh browser window with navigation options. " + + Description: "Determines where the app will be opened. Valid values are `\"tab\"` and `\"slim-window\" (default)`. " + + "`\"tab\"` opens in a new tab in the same browser window. " + "`\"slim-window\"` opens a new browser window without navigation controls.", ForceNew: true, Optional: true, @@ -238,11 +238,11 @@ func appResource() *schema.Resource { } switch valStr { - case "tab", "window", "slim-window": + case "tab", "slim-window": return nil } - return diag.Errorf(`invalid "coder_app" open_in value, must be one of "tab", "window", "slim-window": %q`, valStr) + return diag.Errorf(`invalid "coder_app" open_in value, must be one of "tab", "slim-window": %q`, valStr) }, }, }, diff --git a/provider/app_test.go b/provider/app_test.go index aaa4f631..005e8377 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -263,12 +263,7 @@ func TestApp(t *testing.T) { { name: "InvalidValue", value: "nonsense", - expectError: regexp.MustCompile(`invalid "coder_app" open_in value, must be one of "tab", "window", "slim-window": "nonsense"`), - }, - { - name: "ExplicitWindow", - value: "window", - expectValue: "window", + expectError: regexp.MustCompile(`invalid "coder_app" open_in value, must be one of "tab", "slim-window": "nonsense"`), }, { name: "ExplicitSlimWindow", @@ -389,11 +384,11 @@ func TestApp(t *testing.T) { url = "https://google.com" external = true hidden = false - open_in = "window" + open_in = "tab" } `, hidden: false, - openIn: "window", + openIn: "tab", }} for _, tc := range cases { tc := tc From 3c23e37fe329803dcb5763acc035402bc1dd8d34 Mon Sep 17 00:00:00 2001 From: Vincent Vielle Date: Wed, 22 Jan 2025 14:19:14 +0100 Subject: [PATCH 076/114] doc: add release section (#328) * update readme release section * Update README.md Co-authored-by: Muhammad Atif Ali --------- Co-authored-by: Muhammad Atif Ali --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 6f13efef..cf012c46 100644 --- a/README.md +++ b/README.md @@ -77,3 +77,18 @@ To run these integration tests locally: > **Note:** you can specify `CODER_IMAGE` if the Coder image you wish to test is hosted somewhere other than `ghcr.io/coder/coder`. > For example, `CODER_IMAGE=example.com/repo/coder CODER_VERSION=foobar make test-integration`. + +### How to create a new release +> **Warning:** Before creating a new release, make sure you have pulled the latest commit from the main branch i.e. `git pull origin main` + +1. Create a new tag with a version number (following semantic versioning): + ```console + git tag -a v2.1.2 -m "v2.1.2" + ``` + +2. Push the tag to the remote repository: + ```console + git push origin tag v2.1.2 + ``` + +A GitHub Actions workflow named "Release" will automatically trigger, run integration tests, and publish the new release. \ No newline at end of file From 054e9bca452d7715f169c48d1c169314151118e2 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 22 Jan 2025 23:38:54 +0500 Subject: [PATCH 077/114] chore: Prettify README.md (#329) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cf012c46..b8ee8840 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ to setup your local Terraform to use your local version rather than the registry To run Terraform acceptance tests, run `make testacc`. This will test the provider against the locally installed version of Terraform. -> **Note:** our [CI workflow](./github/workflows/test.yml) runs a test matrix against multiple Terraform versions. +> [!Note] +> Our [CI workflow](./github/workflows/test.yml) runs a test matrix against multiple Terraform versions. #### Integration Tests @@ -75,11 +76,13 @@ To run these integration tests locally: 1. Run `CODER_IMAGE=ghcr.io/coder/coder-preview CODER_VERSION=main-x.y.z-devel-abcd1234 make test-integration`. -> **Note:** you can specify `CODER_IMAGE` if the Coder image you wish to test is hosted somewhere other than `ghcr.io/coder/coder`. +> [!Note] +> You can specify `CODER_IMAGE` if the Coder image you wish to test is hosted somewhere other than `ghcr.io/coder/coder`. > For example, `CODER_IMAGE=example.com/repo/coder CODER_VERSION=foobar make test-integration`. ### How to create a new release -> **Warning:** Before creating a new release, make sure you have pulled the latest commit from the main branch i.e. `git pull origin main` +> [!Warning] +> Before creating a new release, make sure you have pulled the latest commit from the main branch i.e. `git pull origin main` 1. Create a new tag with a version number (following semantic versioning): ```console @@ -91,4 +94,4 @@ To run these integration tests locally: git push origin tag v2.1.2 ``` -A GitHub Actions workflow named "Release" will automatically trigger, run integration tests, and publish the new release. \ No newline at end of file +A GitHub Actions workflow named "Release" will automatically trigger, run integration tests, and publish the new release. From d7d2b705fc00673bfd1a4f55efe97dfa7f89da84 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Tue, 4 Feb 2025 10:26:06 +0200 Subject: [PATCH 078/114] feat: add a workspace preset datasource (#332) * add a workspace preset datasource * add workspace preset type * add validation and tests for coder_workspace_presets * make -B gen * make -B gen * idiomatic tests and review notes * make fmt * make -B gen * add integration tests * make gen fmt --- docs/data-sources/workspace_preset.md | 42 ++++++ .../coder_workspace_preset/data-source.tf | 14 ++ integration/integration_test.go | 45 ++++-- integration/test-data-source/main.tf | 17 +++ integration/test-data-source/parameters.yaml | 1 + provider/provider.go | 13 +- provider/workspace_preset.go | 70 ++++++++++ provider/workspace_preset_test.go | 128 ++++++++++++++++++ 8 files changed, 311 insertions(+), 19 deletions(-) create mode 100644 docs/data-sources/workspace_preset.md create mode 100644 examples/data-sources/coder_workspace_preset/data-source.tf create mode 100644 integration/test-data-source/parameters.yaml create mode 100644 provider/workspace_preset.go create mode 100644 provider/workspace_preset_test.go diff --git a/docs/data-sources/workspace_preset.md b/docs/data-sources/workspace_preset.md new file mode 100644 index 00000000..28f90faa --- /dev/null +++ b/docs/data-sources/workspace_preset.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coder_workspace_preset Data Source - terraform-provider-coder" +subcategory: "" +description: |- + Use this data source to predefine common configurations for workspaces. +--- + +# coder_workspace_preset (Data Source) + +Use this data source to predefine common configurations for workspaces. + +## Example Usage + +```terraform +provider "coder" {} + +# presets can be used to predefine common configurations for workspaces +# Parameters are referenced by their name. Each parameter must be defined in the preset. +# Values defined by the preset must pass validation for the parameter. +# See the coder_parameter data source's documentation for examples of how to define +# parameters like the ones used below. +data "coder_workspace_preset" "example" { + name = "example" + parameters = { + (data.coder_parameter.example.name) = "us-central1-a" + (data.coder_parameter.ami.name) = "ami-xxxxxxxx" + } +} +``` + + +## Schema + +### Required + +- `name` (String) Name of the workspace preset. +- `parameters` (Map of String) Parameters of the workspace preset. + +### Read-Only + +- `id` (String) ID of the workspace preset. diff --git a/examples/data-sources/coder_workspace_preset/data-source.tf b/examples/data-sources/coder_workspace_preset/data-source.tf new file mode 100644 index 00000000..4f29a199 --- /dev/null +++ b/examples/data-sources/coder_workspace_preset/data-source.tf @@ -0,0 +1,14 @@ +provider "coder" {} + +# presets can be used to predefine common configurations for workspaces +# Parameters are referenced by their name. Each parameter must be defined in the preset. +# Values defined by the preset must pass validation for the parameter. +# See the coder_parameter data source's documentation for examples of how to define +# parameters like the ones used below. +data "coder_workspace_preset" "example" { + name = "example" + parameters = { + (data.coder_parameter.example.name) = "us-central1-a" + (data.coder_parameter.ami.name) = "ami-xxxxxxxx" + } +} diff --git a/integration/integration_test.go b/integration/integration_test.go index 8ac68f28..bbbd5587 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -73,18 +73,27 @@ func TestIntegration(t *testing.T) { name: "test-data-source", minVersion: "v0.0.0", expectedOutput: map[string]string{ - "provisioner.arch": runtime.GOARCH, - "provisioner.id": `[a-zA-Z0-9-]+`, - "provisioner.os": runtime.GOOS, - "workspace.access_port": `\d+`, - "workspace.access_url": `https?://\D+:\d+`, - "workspace.id": `[a-zA-z0-9-]+`, - "workspace.name": `test-data-source`, - "workspace.start_count": `1`, - "workspace.template_id": `[a-zA-Z0-9-]+`, - "workspace.template_name": `test-data-source`, - "workspace.template_version": `.+`, - "workspace.transition": `start`, + "provisioner.arch": runtime.GOARCH, + "provisioner.id": `[a-zA-Z0-9-]+`, + "provisioner.os": runtime.GOOS, + "workspace.access_port": `\d+`, + "workspace.access_url": `https?://\D+:\d+`, + "workspace.id": `[a-zA-z0-9-]+`, + "workspace.name": `test-data-source`, + "workspace.start_count": `1`, + "workspace.template_id": `[a-zA-Z0-9-]+`, + "workspace.template_name": `test-data-source`, + "workspace.template_version": `.+`, + "workspace.transition": `start`, + "workspace_parameter.name": `param`, + "workspace_parameter.description": `param description`, + // TODO (sasswart): the cli doesn't support presets yet. + // once it does, the value for workspace_parameter.value + // will be the preset value. + "workspace_parameter.value": `param value`, + "workspace_parameter.icon": `param icon`, + "workspace_preset.name": `preset`, + "workspace_preset.parameters.param": `preset param value`, }, }, { @@ -179,8 +188,18 @@ func TestIntegration(t *testing.T) { } _, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`coder templates %s %s --directory /src/integration/%s --var output_path=/tmp/%s.json --yes`, templateCreateCmd, tt.name, tt.name, tt.name)) require.Equal(t, 0, rc) + + // Check if parameters.yaml exists + _, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`stat /src/integration/%s/parameters.yaml 2>/dev/null > /dev/null`, tt.name)) + hasParameters := rc == 0 + var includeParameters string + if hasParameters { + // If it exists, include it in the create command + includeParameters = fmt.Sprintf(`--rich-parameter-file /src/integration/%s/parameters.yaml`, tt.name) + } + // Create a workspace - _, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`coder create %s -t %s --yes`, tt.name, tt.name)) + _, rc = execContainer(ctx, t, ctrID, fmt.Sprintf(`coder create %s -t %s %s --yes`, tt.name, tt.name, includeParameters)) require.Equal(t, 0, rc) // Fetch the output created by the template out, rc := execContainer(ctx, t, ctrID, fmt.Sprintf(`cat /tmp/%s.json`, tt.name)) diff --git a/integration/test-data-source/main.tf b/integration/test-data-source/main.tf index 6d4b85cd..5fb2e0e6 100644 --- a/integration/test-data-source/main.tf +++ b/integration/test-data-source/main.tf @@ -14,6 +14,17 @@ terraform { data "coder_provisioner" "me" {} data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} +data "coder_parameter" "param" { + name = "param" + description = "param description" + icon = "param icon" +} +data "coder_workspace_preset" "preset" { + name = "preset" + parameters = { + (data.coder_parameter.param.name) = "preset param value" + } +} locals { # NOTE: these must all be strings in the output @@ -30,6 +41,12 @@ locals { "workspace.template_name" : data.coder_workspace.me.template_name, "workspace.template_version" : data.coder_workspace.me.template_version, "workspace.transition" : data.coder_workspace.me.transition, + "workspace_parameter.name" : data.coder_parameter.param.name, + "workspace_parameter.description" : data.coder_parameter.param.description, + "workspace_parameter.value" : data.coder_parameter.param.value, + "workspace_parameter.icon" : data.coder_parameter.param.icon, + "workspace_preset.name" : data.coder_workspace_preset.preset.name, + "workspace_preset.parameters.param" : data.coder_workspace_preset.preset.parameters.param, } } diff --git a/integration/test-data-source/parameters.yaml b/integration/test-data-source/parameters.yaml new file mode 100644 index 00000000..0e75c133 --- /dev/null +++ b/integration/test-data-source/parameters.yaml @@ -0,0 +1 @@ +param: "param value" \ No newline at end of file diff --git a/provider/provider.go b/provider/provider.go index 1d78f2dd..d9780d76 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -61,12 +61,13 @@ func New() *schema.Provider { }, nil }, DataSourcesMap: map[string]*schema.Resource{ - "coder_workspace": workspaceDataSource(), - "coder_workspace_tags": workspaceTagDataSource(), - "coder_provisioner": provisionerDataSource(), - "coder_parameter": parameterDataSource(), - "coder_external_auth": externalAuthDataSource(), - "coder_workspace_owner": workspaceOwnerDataSource(), + "coder_workspace": workspaceDataSource(), + "coder_workspace_tags": workspaceTagDataSource(), + "coder_provisioner": provisionerDataSource(), + "coder_parameter": parameterDataSource(), + "coder_external_auth": externalAuthDataSource(), + "coder_workspace_owner": workspaceOwnerDataSource(), + "coder_workspace_preset": workspacePresetDataSource(), }, ResourcesMap: map[string]*schema.Resource{ "coder_agent": agentResource(), diff --git a/provider/workspace_preset.go b/provider/workspace_preset.go new file mode 100644 index 00000000..cd56c980 --- /dev/null +++ b/provider/workspace_preset.go @@ -0,0 +1,70 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/mitchellh/mapstructure" +) + +type WorkspacePreset struct { + Name string `mapstructure:"name"` + Parameters map[string]string `mapstructure:"parameters"` +} + +func workspacePresetDataSource() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + Description: "Use this data source to predefine common configurations for workspaces.", + ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { + var preset WorkspacePreset + err := mapstructure.Decode(struct { + Name interface{} + Parameters interface{} + }{ + Name: rd.Get("name"), + Parameters: rd.Get("parameters"), + }, &preset) + if err != nil { + return diag.Errorf("decode workspace preset: %s", err) + } + + // MinItems doesn't work with maps, so we need to check the length + // of the map manually. All other validation is handled by the + // schema. + if len(preset.Parameters) == 0 { + return diag.Errorf("expected \"parameters\" to not be an empty map") + } + + rd.SetId(preset.Name) + + return nil + }, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Description: "ID of the workspace preset.", + Computed: true, + }, + "name": { + Type: schema.TypeString, + Description: "Name of the workspace preset.", + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "parameters": { + Type: schema.TypeMap, + Description: "Parameters of the workspace preset.", + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + } +} diff --git a/provider/workspace_preset_test.go b/provider/workspace_preset_test.go new file mode 100644 index 00000000..876e2044 --- /dev/null +++ b/provider/workspace_preset_test.go @@ -0,0 +1,128 @@ +package provider_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/require" +) + +func TestWorkspacePreset(t *testing.T) { + t.Parallel() + type testcase struct { + Name string + Config string + ExpectError *regexp.Regexp + Check func(state *terraform.State) error + } + testcases := []testcase{ + { + Name: "Happy Path", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = "preset_1" + parameters = { + "region" = "us-east1-a" + } + }`, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + resource := state.Modules[0].Resources["data.coder_workspace_preset.preset_1"] + require.NotNil(t, resource) + attrs := resource.Primary.Attributes + require.Equal(t, attrs["name"], "preset_1") + require.Equal(t, attrs["parameters.region"], "us-east1-a") + return nil + }, + }, + { + Name: "Name field is not provided", + Config: ` + data "coder_workspace_preset" "preset_1" { + parameters = { + "region" = "us-east1-a" + } + }`, + // This validation is done by Terraform, but it could still break if we misconfigure the schema. + // So we test it here to make sure we don't regress. + ExpectError: regexp.MustCompile("The argument \"name\" is required, but no definition was found"), + }, + { + Name: "Name field is empty", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = "" + parameters = { + "region" = "us-east1-a" + } + }`, + // This validation is done by Terraform, but it could still break if we misconfigure the schema. + // So we test it here to make sure we don't regress. + ExpectError: regexp.MustCompile("expected \"name\" to not be an empty string"), + }, + { + Name: "Name field is not a string", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = [1, 2, 3] + parameters = { + "region" = "us-east1-a" + } + }`, + // This validation is done by Terraform, but it could still break if we misconfigure the schema. + // So we test it here to make sure we don't regress. + ExpectError: regexp.MustCompile("Incorrect attribute value type"), + }, + { + Name: "Parameters field is not provided", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = "preset_1" + }`, + // This validation is done by Terraform, but it could still break if we misconfigure the schema. + // So we test it here to make sure we don't regress. + ExpectError: regexp.MustCompile("The argument \"parameters\" is required, but no definition was found"), + }, + { + Name: "Parameters field is empty", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = "preset_1" + parameters = {} + }`, + // This validation is *not* done by Terraform, because MinItems doesn't work with maps. + // We've implemented the validation in ReadContext, so we test it here to make sure we don't regress. + ExpectError: regexp.MustCompile("expected \"parameters\" to not be an empty map"), + }, + { + Name: "Parameters field is not a map", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = "preset_1" + parameters = "not a map" + }`, + // This validation is done by Terraform, but it could still break if we misconfigure the schema. + // So we test it here to make sure we don't regress. + ExpectError: regexp.MustCompile("Inappropriate value for attribute \"parameters\": map of string required"), + }, + } + + for _, testcase := range testcases { + t.Run(testcase.Name, func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: testcase.Config, + ExpectError: testcase.ExpectError, + Check: testcase.Check, + }}, + }) + }) + } +} From b73223c16edef7c407eacbded79e8a0b99a8f0a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:24:29 +0000 Subject: [PATCH 079/114] build(deps): Bump golang.org/x/mod from 0.22.0 to 0.23.0 (#337) Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.22.0 to 0.23.0. - [Commits](https://github.com/golang/mod/compare/v0.22.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/mod dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f88cd289..f4f8d4b0 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/mod v0.22.0 + golang.org/x/mod v0.23.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) diff --git a/go.sum b/go.sum index 1922dad3..ab31763e 100644 --- a/go.sum +++ b/go.sum @@ -230,8 +230,8 @@ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXy 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.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= From ec48bcb742656c13ec21fd3b2be15b1138565425 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:25:06 +0000 Subject: [PATCH 080/114] build(deps): Bump github.com/hashicorp/terraform-plugin-sdk/v2 (#338) Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.35.0 to 2.36.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.35.0...v2.36.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 37 +++++++++++------------ go.sum | 94 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/go.mod b/go.mod index f4f8d4b0..c1033b5e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/docker/docker v26.1.5+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0 github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 @@ -19,7 +19,7 @@ require ( require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect @@ -30,7 +30,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -44,14 +44,14 @@ require ( 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.9.0 // indirect - github.com/hashicorp/hcl/v2 v2.22.0 // indirect + github.com/hashicorp/hc-install v0.9.1 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.21.0 // indirect - github.com/hashicorp/terraform-json v0.23.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.25.0 // indirect + github.com/hashicorp/terraform-exec v0.22.0 // indirect + github.com/hashicorp/terraform-json v0.24.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect - github.com/hashicorp/terraform-registry-address v0.2.3 // indirect + github.com/hashicorp/terraform-registry-address v0.2.4 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/kr/pretty v0.3.0 // indirect @@ -73,24 +73,23 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/zclconf/go-cty v1.15.0 // indirect + github.com/zclconf/go-cty v1.16.2 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect - go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.28.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index ab31763e..a8819092 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= -github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= @@ -22,8 +22,8 @@ github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBS github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= +github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -44,13 +44,13 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= +github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= +github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= @@ -95,24 +95,24 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= -github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= -github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= -github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ= +github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= -github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= -github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= -github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= -github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= -github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= +github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64= +github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ= +github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= +github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= +github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= +github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= -github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= -github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0 h1:7/iejAPyCRBhqAg3jOx+4UcAhY0A+Sg8B+0+d/GxSfM= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0/go.mod h1:TiQwXAjFrgBf5tg5rvBRz8/ubPULpU0HjSaVi5UoJf8= +github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA= +github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= @@ -181,8 +181,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= -github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -199,32 +199,34 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= -github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= +github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= 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/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -238,8 +240,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 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= @@ -261,8 +263,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -291,12 +293,12 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 5e4c5966ba27873ec498ba83139d0cfde142899f Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Fri, 7 Feb 2025 15:49:10 +0500 Subject: [PATCH 081/114] fix(docs): fix parameter options icons (#339) --- docs/data-sources/parameter.md | 10 +++++----- examples/data-sources/coder_parameter/data-source.tf | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/data-sources/parameter.md b/docs/data-sources/parameter.md index f40a9c83..4da9dac2 100644 --- a/docs/data-sources/parameter.md +++ b/docs/data-sources/parameter.md @@ -20,16 +20,16 @@ data "coder_parameter" "example" { description = "Specify a region to place your workspace." mutable = false type = "string" - default = "asia-central1-a" + default = "us-central1-a" option { value = "us-central1-a" name = "US Central" - icon = "/icon/usa.svg" + icon = "/icons/1f1fa-1f1f8.png" } option { - value = "asia-central1-a" - name = "Asia" - icon = "/icon/asia.svg" + value = "asia-southeast1-a" + name = "Singapore" + icon = "/icons/1f1f8-1f1ec.png" } } diff --git a/examples/data-sources/coder_parameter/data-source.tf b/examples/data-sources/coder_parameter/data-source.tf index 4efc3320..ac0de7cb 100644 --- a/examples/data-sources/coder_parameter/data-source.tf +++ b/examples/data-sources/coder_parameter/data-source.tf @@ -5,16 +5,16 @@ data "coder_parameter" "example" { description = "Specify a region to place your workspace." mutable = false type = "string" - default = "asia-central1-a" + default = "us-central1-a" option { value = "us-central1-a" name = "US Central" - icon = "/icon/usa.svg" + icon = "/icons/1f1fa-1f1f8.png" } option { - value = "asia-central1-a" - name = "Asia" - icon = "/icon/asia.svg" + value = "asia-southeast1-a" + name = "Singapore" + icon = "/icons/1f1f8-1f1ec.png" } } From c9b396f1997db20188ce674dd6335e8b6ac7b6f3 Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Fri, 7 Feb 2025 15:54:08 +0500 Subject: [PATCH 082/114] ci: drop support for EOL terraform version (#340) --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 69ca7ada..88ccde05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,11 +88,8 @@ jobs: fail-fast: false matrix: terraform: - - "1.5.*" - - "1.6.*" - - "1.7.*" - - "1.8.*" - "1.9.*" + - "1.10.*" steps: - name: Set up Go uses: actions/setup-go@v5 From 1f958079cf6996d72fbdd1d8b0ecff0815b4680f Mon Sep 17 00:00:00 2001 From: Vincent Vielle Date: Fri, 7 Feb 2025 14:58:34 +0100 Subject: [PATCH 083/114] feat: add resources_monitoring field to agent (#331) * add resources_monitoring logic * add resources_monitoring logic * improve testing logic * improve testing logic * gen doc * improved testing and validation * improved testing and validation * improved testing and validation * regenerate documentation with validation * add more test cases * add validation methods * improve test verbose --- docs/resources/agent.md | 28 ++ .../coder_resources_monitoring/data-source.tf | 26 ++ provider/agent.go | 143 ++++++++- provider/agent_test.go | 296 ++++++++++++++++++ provider/examples_test.go | 1 + 5 files changed, 479 insertions(+), 15 deletions(-) create mode 100644 examples/data-sources/coder_resources_monitoring/data-source.tf diff --git a/docs/resources/agent.md b/docs/resources/agent.md index 8c786d6e..7c28b1f4 100644 --- a/docs/resources/agent.md +++ b/docs/resources/agent.md @@ -79,6 +79,7 @@ resource "kubernetes_pod" "dev" { - `metadata` (Block List) Each `metadata` block defines a single item consisting of a key/value pair. This feature is in alpha and may break in future releases. (see [below for nested schema](#nestedblock--metadata)) - `motd_file` (String) The path to a file within the workspace containing a message to display to users when they login via SSH. A typical value would be `"/etc/motd"`. - `order` (Number) The order determines the position of agents in the UI presentation. The lowest order is shown first and agents with equal order are sorted by name (ascending order). +- `resources_monitoring` (Block Set, Max: 1) The resources monitoring configuration for this agent. (see [below for nested schema](#nestedblock--resources_monitoring)) - `shutdown_script` (String) A script to run before the agent is stopped. The script should exit when it is done to signal that the workspace can be stopped. This option is an alias for defining a `coder_script` resource with `run_on_stop` set to `true`. - `startup_script` (String) A script to run after the agent starts. The script should exit when it is done to signal that the agent is ready. This option is an alias for defining a `coder_script` resource with `run_on_start` set to `true`. - `startup_script_behavior` (String) This option sets the behavior of the `startup_script`. When set to `"blocking"`, the `startup_script` must exit before the workspace is ready. When set to `"non-blocking"`, the `startup_script` may run in the background and the workspace will be ready immediately. Default is `"non-blocking"`, although `"blocking"` is recommended. This option is an alias for defining a `coder_script` resource with `start_blocks_login` set to `true` (blocking). @@ -116,3 +117,30 @@ Optional: - `display_name` (String) The user-facing name of this value. - `order` (Number) The order determines the position of agent metadata in the UI presentation. The lowest order is shown first and metadata with equal order are sorted by key (ascending order). - `timeout` (Number) The maximum time the command is allowed to run in seconds. + + + +### Nested Schema for `resources_monitoring` + +Optional: + +- `memory` (Block Set, Max: 1) The memory monitoring configuration for this agent. (see [below for nested schema](#nestedblock--resources_monitoring--memory)) +- `volume` (Block Set) The volumes monitoring configuration for this agent. (see [below for nested schema](#nestedblock--resources_monitoring--volume)) + + +### Nested Schema for `resources_monitoring.memory` + +Required: + +- `enabled` (Boolean) Enable memory monitoring for this agent. +- `threshold` (Number) The memory usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100. + + + +### Nested Schema for `resources_monitoring.volume` + +Required: + +- `enabled` (Boolean) Enable volume monitoring for this agent. +- `path` (String) The path of the volume to monitor. +- `threshold` (Number) The volume usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100. diff --git a/examples/data-sources/coder_resources_monitoring/data-source.tf b/examples/data-sources/coder_resources_monitoring/data-source.tf new file mode 100644 index 00000000..94bc55ed --- /dev/null +++ b/examples/data-sources/coder_resources_monitoring/data-source.tf @@ -0,0 +1,26 @@ +provider "coder" {} + +data "coder_provisioner" "dev" {} + +data "coder_workspace" "dev" {} + +resource "coder_agent" "main" { + arch = data.coder_provisioner.dev.arch + os = data.coder_provisioner.dev.os + resources_monitoring { + memory { + enabled = true + threshold = 80 + } + volume { + path = "/volume1" + enabled = true + threshold = 80 + } + volume { + path = "/volume2" + enabled = true + threshold = 100 + } + } +} \ No newline at end of file diff --git a/provider/agent.go b/provider/agent.go index ac012bf1..3ddae235 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -3,10 +3,12 @@ package provider import ( "context" "fmt" + "path/filepath" "reflect" "strings" "github.com/google/uuid" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -259,31 +261,142 @@ func agentResource() *schema.Resource { ForceNew: true, Optional: true, }, + "resources_monitoring": { + Type: schema.TypeSet, + Description: "The resources monitoring configuration for this agent.", + ForceNew: true, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "memory": { + Type: schema.TypeSet, + Description: "The memory monitoring configuration for this agent.", + ForceNew: true, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Description: "Enable memory monitoring for this agent.", + ForceNew: true, + Required: true, + }, + "threshold": { + Type: schema.TypeInt, + Description: "The memory usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100.", + ForceNew: true, + Required: true, + ValidateFunc: validation.IntBetween(0, 100), + }, + }, + }, + }, + "volume": { + Type: schema.TypeSet, + Description: "The volumes monitoring configuration for this agent.", + ForceNew: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Description: "The path of the volume to monitor.", + ForceNew: true, + Required: true, + ValidateDiagFunc: func(i interface{}, s cty.Path) diag.Diagnostics { + path, ok := i.(string) + if !ok { + return diag.Errorf("volume path must be a string") + } + if path == "" { + return diag.Errorf("volume path must not be empty") + } + + if !filepath.IsAbs(i.(string)) { + return diag.Errorf("volume path must be an absolute path") + } + + return nil + }, + }, + "enabled": { + Type: schema.TypeBool, + Description: "Enable volume monitoring for this agent.", + ForceNew: true, + Required: true, + }, + "threshold": { + Type: schema.TypeInt, + Description: "The volume usage threshold in percentage at which to trigger an alert. Value should be between 0 and 100.", + ForceNew: true, + Required: true, + ValidateFunc: validation.IntBetween(0, 100), + }, + }, + }, + }, + }, + }, + }, }, CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i any) error { - if !rd.HasChange("metadata") { - return nil + if rd.HasChange("metadata") { + keys := map[string]bool{} + metadata, ok := rd.Get("metadata").([]any) + if !ok { + return xerrors.Errorf("unexpected type %T for metadata, expected []any", rd.Get("metadata")) + } + for _, t := range metadata { + obj, ok := t.(map[string]any) + if !ok { + return xerrors.Errorf("unexpected type %T for metadata, expected map[string]any", t) + } + key, ok := obj["key"].(string) + if !ok { + return xerrors.Errorf("unexpected type %T for metadata key, expected string", obj["key"]) + } + if keys[key] { + return xerrors.Errorf("duplicate agent metadata key %q", key) + } + keys[key] = true + } } - keys := map[string]bool{} - metadata, ok := rd.Get("metadata").([]any) - if !ok { - return xerrors.Errorf("unexpected type %T for metadata, expected []any", rd.Get("metadata")) - } - for _, t := range metadata { - obj, ok := t.(map[string]any) + if rd.HasChange("resources_monitoring") { + monitors, ok := rd.Get("resources_monitoring").(*schema.Set) if !ok { - return xerrors.Errorf("unexpected type %T for metadata, expected map[string]any", t) + return xerrors.Errorf("unexpected type %T for resources_monitoring.0.volume, expected []any", rd.Get("resources_monitoring.0.volume")) } - key, ok := obj["key"].(string) + + monitor := monitors.List()[0].(map[string]any) + + volumes, ok := monitor["volume"].(*schema.Set) if !ok { - return xerrors.Errorf("unexpected type %T for metadata key, expected string", obj["key"]) + return xerrors.Errorf("unexpected type %T for resources_monitoring.0.volume, expected []any", monitor["volume"]) } - if keys[key] { - return xerrors.Errorf("duplicate agent metadata key %q", key) + + paths := map[string]bool{} + for _, volume := range volumes.List() { + obj, ok := volume.(map[string]any) + if !ok { + return xerrors.Errorf("unexpected type %T for volume, expected map[string]any", volume) + } + + // print path for debug purpose + + path, ok := obj["path"].(string) + if !ok { + return xerrors.Errorf("unexpected type %T for volume path, expected string", obj["path"]) + } + if paths[path] { + return xerrors.Errorf("duplicate volume path %q", path) + } + paths[path] = true } - keys[key] = true } + return nil }, } diff --git a/provider/agent_test.go b/provider/agent_test.go index d40caf56..a45ac86a 100644 --- a/provider/agent_test.go +++ b/provider/agent_test.go @@ -211,6 +211,302 @@ func TestAgent_Metadata(t *testing.T) { }) } +func TestAgent_ResourcesMonitoring(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + memory { + enabled = true + threshold = 80 + } + volume { + path = "/volume1" + enabled = true + threshold = 80 + } + volume { + path = "/volume2" + enabled = true + threshold = 100 + } + } + }`, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + + resource := state.Modules[0].Resources["coder_agent.dev"] + require.NotNil(t, resource) + + attr := resource.Primary.Attributes + require.Equal(t, "1", attr["resources_monitoring.#"]) + require.Equal(t, "1", attr["resources_monitoring.0.memory.#"]) + require.Equal(t, "2", attr["resources_monitoring.0.volume.#"]) + require.Equal(t, "80", attr["resources_monitoring.0.memory.0.threshold"]) + require.Equal(t, "/volume1", attr["resources_monitoring.0.volume.0.path"]) + require.Equal(t, "100", attr["resources_monitoring.0.volume.1.threshold"]) + require.Equal(t, "/volume2", attr["resources_monitoring.0.volume.1.path"]) + return nil + }, + }}, + }) + }) + + t.Run("OnlyMemory", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + memory { + enabled = true + threshold = 80 + } + } + }`, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + + resource := state.Modules[0].Resources["coder_agent.dev"] + require.NotNil(t, resource) + + attr := resource.Primary.Attributes + require.Equal(t, "1", attr["resources_monitoring.#"]) + require.Equal(t, "1", attr["resources_monitoring.0.memory.#"]) + require.Equal(t, "80", attr["resources_monitoring.0.memory.0.threshold"]) + return nil + }, + }}, + }) + }) + t.Run("MultipleMemory", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + memory { + enabled = true + threshold = 80 + } + memory { + enabled = true + threshold = 90 + } + } + }`, + ExpectError: regexp.MustCompile(`No more than 1 "memory" blocks are allowed`), + }}, + }) + }) + + t.Run("InvalidThreshold", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + memory { + enabled = true + threshold = 101 + } + } + }`, + Check: nil, + ExpectError: regexp.MustCompile(`expected resources_monitoring\.0\.memory\.0\.threshold to be in the range \(0 - 100\), got 101`), + }}, + }) + }) + + t.Run("DuplicatePaths", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + volume { + path = "/volume1" + enabled = true + threshold = 80 + } + volume { + path = "/volume1" + enabled = true + threshold = 100 + } + } + }`, + ExpectError: regexp.MustCompile("duplicate volume path"), + }}, + }) + }) + + t.Run("NoPath", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + volume { + enabled = true + threshold = 80 + } + } + }`, + ExpectError: regexp.MustCompile(`The argument "path" is required, but no definition was found.`), + }}, + }) + }) + + t.Run("NonAbsPath", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + volume { + path = "tmp" + enabled = true + threshold = 80 + } + } + }`, + Check: nil, + ExpectError: regexp.MustCompile(`volume path must be an absolute path`), + }}, + }) + }) + + t.Run("EmptyPath", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + volume { + path = "" + enabled = true + threshold = 80 + } + } + }`, + Check: nil, + ExpectError: regexp.MustCompile(`volume path must not be empty`), + }}, + }) + }) + + t.Run("ThresholdMissing", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + volume { + path = "/volume1" + enabled = true + } + } + }`, + Check: nil, + ExpectError: regexp.MustCompile(`The argument "threshold" is required, but no definition was found.`), + }}, + }) + }) + t.Run("EnabledMissing", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + url = "https://example.com" + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + resources_monitoring { + memory { + threshold = 80 + } + } + }`, + Check: nil, + ExpectError: regexp.MustCompile(`The argument "enabled" is required, but no definition was found.`), + }}, + }) + }) +} + func TestAgent_MetadataDuplicateKeys(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ diff --git a/provider/examples_test.go b/provider/examples_test.go index c6931ae3..1d17b1ba 100644 --- a/provider/examples_test.go +++ b/provider/examples_test.go @@ -15,6 +15,7 @@ func TestExamples(t *testing.T) { for _, testDir := range []string{ "coder_parameter", "coder_workspace_tags", + "coder_resources_monitoring", } { t.Run(testDir, func(t *testing.T) { testDir := testDir From aef62206d070e05bd7770cedba532592cb9e42e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:18:37 +0000 Subject: [PATCH 084/114] build(deps): Bump goreleaser/goreleaser-action from 6.1.0 to 6.2.1 (#342) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 6.1.0 to 6.2.1. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v6.1.0...v6.2.1) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6bb2f731..d85ab5e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,7 +90,7 @@ jobs: passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6.1.0 + uses: goreleaser/goreleaser-action@v6.2.1 with: version: latest args: release --clean From 4b3fc653e348c324d680c817eceeeccf06c9b877 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 13 Feb 2025 12:32:48 +0100 Subject: [PATCH 085/114] fix: app display name validation (#344) --- provider/app.go | 13 ++++++++++ provider/app_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/provider/app.go b/provider/app.go index cd64db54..2d0d6b09 100644 --- a/provider/app.go +++ b/provider/app.go @@ -23,6 +23,8 @@ var ( appSlugRegex = regexp.MustCompile(`^[a-z0-9](-?[a-z0-9])*$`) ) +const appDisplayNameMaxLength = 64 // database column limit + func appResource() *schema.Resource { return &schema.Resource{ SchemaVersion: 1, @@ -124,6 +126,17 @@ func appResource() *schema.Resource { Description: "A display name to identify the app. Defaults to the slug.", ForceNew: true, Optional: true, + ValidateDiagFunc: func(val interface{}, c cty.Path) diag.Diagnostics { + valStr, ok := val.(string) + if !ok { + return diag.Errorf("expected string, got %T", val) + } + + if len(valStr) > appDisplayNameMaxLength { + return diag.Errorf("display name is too long (max %d characters)", appDisplayNameMaxLength) + } + return nil + }, }, "subdomain": { Type: schema.TypeBool, diff --git a/provider/app_test.go b/provider/app_test.go index 005e8377..444b6b0d 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -415,4 +415,66 @@ func TestApp(t *testing.T) { } }) + t.Run("DisplayName", func(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + displayName string + expectValue string + expectError *regexp.Regexp + }{ + { + name: "Empty", + displayName: "", + }, + { + name: "Regular", + displayName: "Regular Application", + }, + { + name: "DisplayNameStillOK", + displayName: "0123456789012345678901234567890123456789012345678901234567890123", + }, + { + name: "DisplayNameTooLong", + displayName: "01234567890123456789012345678901234567890123456789012345678901234", + expectError: regexp.MustCompile("display name is too long"), + }, + } + + for _, c := range cases { + c := c + + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + config := fmt.Sprintf(` + provider "coder" { + } + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "code-server" { + agent_id = coder_agent.dev.id + slug = "code-server" + display_name = "%s" + url = "http://localhost:13337" + open_in = "slim-window" + } + `, c.displayName) + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: config, + ExpectError: c.expectError, + }}, + }) + }) + } + }) + } From a9b64aab99d7815addbe25498a161c29ac1e62da Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 13 Feb 2025 13:42:47 +0100 Subject: [PATCH 086/114] fix: support unlimited parameter options (#345) --- docs/data-sources/parameter.md | 2 +- provider/parameter.go | 1 - provider/parameter_test.go | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/parameter.md b/docs/data-sources/parameter.md index 4da9dac2..e46cf86d 100644 --- a/docs/data-sources/parameter.md +++ b/docs/data-sources/parameter.md @@ -147,7 +147,7 @@ data "coder_parameter" "home_volume_size" { - `ephemeral` (Boolean) The value of an ephemeral parameter will not be preserved between consecutive workspace builds. - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. - `mutable` (Boolean) Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution! -- `option` (Block List, Max: 64) Each `option` block defines a value for a user to select from. (see [below for nested schema](#nestedblock--option)) +- `option` (Block List) Each `option` block defines a value for a user to select from. (see [below for nested schema](#nestedblock--option)) - `order` (Number) The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order). - `type` (String) The type of this parameter. Must be one of: `"number"`, `"string"`, `"bool"`, or `"list(string)"`. - `validation` (Block List, Max: 1) Validate the input of a parameter. (see [below for nested schema](#nestedblock--validation)) diff --git a/provider/parameter.go b/provider/parameter.go index 00dd5f34..1345f4d6 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -237,7 +237,6 @@ func parameterDataSource() *schema.Resource { Description: "Each `option` block defines a value for a user to select from.", ForceNew: true, Optional: true, - MaxItems: 64, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { diff --git a/provider/parameter_test.go b/provider/parameter_test.go index b1f164a0..7bcea8fd 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -1,7 +1,9 @@ package provider_test import ( + "fmt" "regexp" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -816,3 +818,43 @@ func TestValueValidatesType(t *testing.T) { }) } } + +func TestParameterWithManyOptions(t *testing.T) { + t.Parallel() + + const maxItemsInTest = 1024 + + var options strings.Builder + for i := 0; i < maxItemsInTest; i++ { + _, _ = options.WriteString(fmt.Sprintf(`option { + name = "%d" + value = "%d" + } +`, i, i)) + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: fmt.Sprintf(`data "coder_parameter" "region" { + name = "Region" + type = "string" + %s + }`, options.String()), + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + param := state.Modules[0].Resources["data.coder_parameter.region"] + + for i := 0; i < maxItemsInTest; i++ { + name, _ := param.Primary.Attributes[fmt.Sprintf("option.%d.name", i)] + value, _ := param.Primary.Attributes[fmt.Sprintf("option.%d.value", i)] + require.Equal(t, fmt.Sprintf("%d", i), name) + require.Equal(t, fmt.Sprintf("%d", i), value) + } + return nil + }, + }}, + }) +} From 249863dcc1977f6ee8869e50a12929787e3e3305 Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Wed, 19 Feb 2025 17:55:06 +0500 Subject: [PATCH 087/114] chore: test auto prerelease (#347) --- .goreleaser.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index 69029533..31a6b24c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -56,6 +56,8 @@ release: name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' # If you want to manually examine the release before its live, uncomment this line: # draft: true + prerelease: auto + make_latest: {{ not .Prerelease }} changelog: # see https://goreleaser.com/customization/changelog/ use: github-native From 87755fcac79b787db7c5b82593e66fb29db58fe0 Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Wed, 19 Feb 2025 18:45:52 +0500 Subject: [PATCH 088/114] chore: set GoReleaser version to 2 in config (#348) --- .github/workflows/release.yml | 2 +- .goreleaser.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d85ab5e7..82a7bb3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -92,7 +92,7 @@ jobs: - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6.2.1 with: - version: latest + version: '~> v2' args: release --clean env: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 31a6b24c..c17bc698 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,5 +1,6 @@ # Visit https://goreleaser.com for documentation on how to customize this # behavior. +version: 2 before: hooks: # this is just an example and not a requirement for provider building/publishing From 72bed805debb8863657e98dae77073a0ea87f4ed Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Wed, 19 Feb 2025 19:26:26 +0500 Subject: [PATCH 089/114] chore: fix GoReleaser config for version 2 (#349) --- .goreleaser.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index c17bc698..34b92f3f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -31,7 +31,7 @@ builds: goarch: '386' binary: '{{ .ProjectName }}_v{{ .Version }}' archives: -- format: zip +- formats: [ zip ] name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' checksum: extra_files: @@ -58,7 +58,7 @@ release: # If you want to manually examine the release before its live, uncomment this line: # draft: true prerelease: auto - make_latest: {{ not .Prerelease }} + make_latest: '{{ not .Prerelease }}' changelog: # see https://goreleaser.com/customization/changelog/ use: github-native From f95a77f8993ffb01bb90c051f38a5ef4ed93716f Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Wed, 5 Mar 2025 13:49:15 +0500 Subject: [PATCH 090/114] chore: do not use GitHub prereleases (#355) --- .goreleaser.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 34b92f3f..658a715c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -55,10 +55,6 @@ release: extra_files: - glob: 'terraform-registry-manifest.json' name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' - # If you want to manually examine the release before its live, uncomment this line: - # draft: true - prerelease: auto - make_latest: '{{ not .Prerelease }}' changelog: # see https://goreleaser.com/customization/changelog/ use: github-native From 68f7cf0346c636298844b947ef2d7b5b09d8a0f2 Mon Sep 17 00:00:00 2001 From: M Atif Ali Date: Wed, 5 Mar 2025 14:04:42 +0500 Subject: [PATCH 091/114] chore: include terraform 1.11.* in tests (#356) --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 88ccde05..8e4df55d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -90,6 +90,7 @@ jobs: terraform: - "1.9.*" - "1.10.*" + - "1.11.*" steps: - name: Set up Go uses: actions/setup-go@v5 From ea1c3d7bf71e739a01a4655897cde23d5d8b4c8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 09:13:59 +0000 Subject: [PATCH 092/114] build(deps): Bump github.com/hashicorp/terraform-plugin-sdk/v2 (#351) Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.36.0 to 2.36.1. - [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.36.0...v2.36.1) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index c1033b5e..02ea7137 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/docker/docker v26.1.5+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 @@ -79,11 +79,11 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.33.0 // indirect golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index a8819092..2b5ad9c5 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,8 @@ github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiy github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0 h1:7/iejAPyCRBhqAg3jOx+4UcAhY0A+Sg8B+0+d/GxSfM= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.0/go.mod h1:TiQwXAjFrgBf5tg5rvBRz8/ubPULpU0HjSaVi5UoJf8= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4= github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA= github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -225,8 +225,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -247,8 +247,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -263,8 +263,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -272,8 +272,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 552eb5e9a1a564f19cd3ba3745f2abb5d205e74c Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 5 Mar 2025 11:18:00 +0200 Subject: [PATCH 093/114] chore: only release if tests pass (#357) We've generally been lucky that builds are slower than integration tests, but we need explicit ordering here Signed-off-by: Danny Kopping --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82a7bb3b..33f17b8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,6 +69,7 @@ jobs: goreleaser: runs-on: ubuntu-latest + needs: test steps: - name: Checkout uses: actions/checkout@v4 From eab8698d5b34d712a15cbaa99addf3698aa87577 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Fri, 14 Mar 2025 14:40:14 -0400 Subject: [PATCH 094/114] feat: Add rbac_roles to coder_workspace_owner data source (#330) --- docs/data-sources/workspace_owner.md | 1 + integration/integration_test.go | 2 ++ integration/workspace-owner-filled/main.tf | 1 + integration/workspace-owner/main.tf | 1 + provider/workspace_owner.go | 27 ++++++++++++++++++++++ provider/workspace_owner_test.go | 6 ++++- 6 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/data-sources/workspace_owner.md b/docs/data-sources/workspace_owner.md index fbe4f205..71e39ed1 100644 --- a/docs/data-sources/workspace_owner.md +++ b/docs/data-sources/workspace_owner.md @@ -53,6 +53,7 @@ resource "coder_env" "git_author_email" { - `login_type` (String) The type of login the user has. - `name` (String) The username of the user. - `oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. +- `rbac_roles` (List of Map) The RBAC roles and associated org ids of which the user is assigned. - `session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started. - `ssh_private_key` (String, Sensitive) The user's generated SSH private key. - `ssh_public_key` (String) The user's generated SSH public key. diff --git a/integration/integration_test.go b/integration/integration_test.go index bbbd5587..89fdda7e 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -122,6 +122,7 @@ func TestIntegration(t *testing.T) { "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, "workspace_owner.login_type": ``, + "workspace_owner.rbac_roles": `\[\]`, }, }, { @@ -150,6 +151,7 @@ func TestIntegration(t *testing.T) { "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, "workspace_owner.login_type": `password`, + "workspace_owner.rbac_roles": `\[\]`, }, }, { diff --git a/integration/workspace-owner-filled/main.tf b/integration/workspace-owner-filled/main.tf index fd923a3d..d2de5661 100644 --- a/integration/workspace-owner-filled/main.tf +++ b/integration/workspace-owner-filled/main.tf @@ -40,6 +40,7 @@ locals { "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, "workspace_owner.login_type" : data.coder_workspace_owner.me.login_type, + "workspace_owner.rbac_roles" : jsonencode(data.coder_workspace_owner.me.rbac_roles), } } diff --git a/integration/workspace-owner/main.tf b/integration/workspace-owner/main.tf index fd923a3d..d2de5661 100644 --- a/integration/workspace-owner/main.tf +++ b/integration/workspace-owner/main.tf @@ -40,6 +40,7 @@ locals { "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, "workspace_owner.login_type" : data.coder_workspace_owner.me.login_type, + "workspace_owner.rbac_roles" : jsonencode(data.coder_workspace_owner.me.rbac_roles), } } diff --git a/provider/workspace_owner.go b/provider/workspace_owner.go index 52b1ef8c..078047ff 100644 --- a/provider/workspace_owner.go +++ b/provider/workspace_owner.go @@ -59,6 +59,14 @@ func workspaceOwnerDataSource() *schema.Resource { _ = rd.Set("login_type", loginType) } + var rbacRoles []map[string]string + if rolesRaw, ok := os.LookupEnv("CODER_WORKSPACE_OWNER_RBAC_ROLES"); ok { + if err := json.NewDecoder(strings.NewReader(rolesRaw)).Decode(&rbacRoles); err != nil { + return diag.Errorf("invalid user rbac roles: %s", err.Error()) + } + } + _ = rd.Set("rbac_roles", rbacRoles) + return diags }, Schema: map[string]*schema.Schema{ @@ -118,6 +126,25 @@ func workspaceOwnerDataSource() *schema.Resource { Computed: true, Description: "The type of login the user has.", }, + "rbac_roles": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the RBAC role.", + }, + "org_id": { + Type: schema.TypeString, + Computed: true, + Description: "The organization ID associated with the RBAC role.", + }, + }, + }, + Computed: true, + Description: "The RBAC roles of which the user is assigned.", + }, }, } } diff --git a/provider/workspace_owner_test.go b/provider/workspace_owner_test.go index ad371570..de23b3e7 100644 --- a/provider/workspace_owner_test.go +++ b/provider/workspace_owner_test.go @@ -34,6 +34,7 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { t.Setenv("CODER_WORKSPACE_OWNER_SESSION_TOKEN", `supersecret`) t.Setenv("CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN", `alsosupersecret`) t.Setenv("CODER_WORKSPACE_OWNER_LOGIN_TYPE", `github`) + t.Setenv("CODER_WORKSPACE_OWNER_RBAC_ROLES", `[{"name":"member","org_id":"00000000-0000-0000-0000-000000000000"}]`) resource.Test(t, resource.TestCase{ ProviderFactories: coderFactory(), @@ -61,7 +62,8 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { assert.Equal(t, `supersecret`, attrs["session_token"]) assert.Equal(t, `alsosupersecret`, attrs["oidc_access_token"]) assert.Equal(t, `github`, attrs["login_type"]) - + assert.Equal(t, `member`, attrs["rbac_roles.0.name"]) + assert.Equal(t, `00000000-0000-0000-0000-000000000000`, attrs["rbac_roles.0.org_id"]) return nil }, }}, @@ -80,6 +82,7 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { "CODER_WORKSPACE_OWNER_SSH_PUBLIC_KEY", "CODER_WORKSPACE_OWNER_SSH_PRIVATE_KEY", "CODER_WORKSPACE_OWNER_LOGIN_TYPE", + "CODER_WORKSPACE_OWNER_RBAC_ROLES", } { // https://github.com/golang/go/issues/52817 t.Setenv(v, "") os.Unsetenv(v) @@ -110,6 +113,7 @@ func TestWorkspaceOwnerDatasource(t *testing.T) { assert.Empty(t, attrs["session_token"]) assert.Empty(t, attrs["oidc_access_token"]) assert.Empty(t, attrs["login_type"]) + assert.Empty(t, attrs["rbac_roles.0"]) return nil }, }}, From f75fb0b273851decf878920d04d12f16e7e9d743 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 19 Mar 2025 14:53:20 +0000 Subject: [PATCH 095/114] chore(integration): fix failing tests referencing workspace_owner.rbac_roles (#369) --- docs/data-sources/workspace_owner.md | 10 +++- integration/integration_test.go | 33 ++++++++++- .../workspace-owner-rbac-roles/main.tf | 57 +++++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 integration/workspace-owner-rbac-roles/main.tf diff --git a/docs/data-sources/workspace_owner.md b/docs/data-sources/workspace_owner.md index 71e39ed1..2a912e1f 100644 --- a/docs/data-sources/workspace_owner.md +++ b/docs/data-sources/workspace_owner.md @@ -53,7 +53,15 @@ resource "coder_env" "git_author_email" { - `login_type` (String) The type of login the user has. - `name` (String) The username of the user. - `oidc_access_token` (String) A valid OpenID Connect access token of the workspace owner. This is only available if the workspace owner authenticated with OpenID Connect. If a valid token cannot be obtained, this value will be an empty string. -- `rbac_roles` (List of Map) The RBAC roles and associated org ids of which the user is assigned. +- `rbac_roles` (List of Object) The RBAC roles of which the user is assigned. (see [below for nested schema](#nestedatt--rbac_roles)) - `session_token` (String) Session token for authenticating with a Coder deployment. It is regenerated every time a workspace is started. - `ssh_private_key` (String, Sensitive) The user's generated SSH private key. - `ssh_public_key` (String) The user's generated SSH public key. + + +### Nested Schema for `rbac_roles` + +Read-Only: + +- `name` (String) +- `org_id` (String) diff --git a/integration/integration_test.go b/integration/integration_test.go index 89fdda7e..65aa5aed 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -122,7 +122,7 @@ func TestIntegration(t *testing.T) { "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, "workspace_owner.login_type": ``, - "workspace_owner.rbac_roles": `\[\]`, + "workspace_owner.rbac_roles": ``, }, }, { @@ -151,7 +151,36 @@ func TestIntegration(t *testing.T) { "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, "workspace_owner.login_type": `password`, - "workspace_owner.rbac_roles": `\[\]`, + "workspace_owner.rbac_roles": ``, + }, + }, + { + name: "workspace-owner-rbac-roles", + minVersion: "v2.21.0", // anticipated version, update as required + expectedOutput: map[string]string{ + "provisioner.arch": runtime.GOARCH, + "provisioner.id": `[a-zA-Z0-9-]+`, + "provisioner.os": runtime.GOOS, + "workspace.access_port": `\d+`, + "workspace.access_url": `https?://\D+:\d+`, + "workspace.id": `[a-zA-z0-9-]+`, + "workspace.name": ``, + "workspace.start_count": `1`, + "workspace.template_id": `[a-zA-Z0-9-]+`, + "workspace.template_name": `workspace-owner`, + "workspace.template_version": `.+`, + "workspace.transition": `start`, + "workspace_owner.email": `testing@coder\.com`, + "workspace_owner.full_name": `default`, + "workspace_owner.groups": `\[(\"Everyone\")?\]`, + "workspace_owner.id": `[a-zA-Z0-9-]+`, + "workspace_owner.name": `testing`, + "workspace_owner.oidc_access_token": `^$`, // TODO: test OIDC integration + "workspace_owner.session_token": `.+`, + "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, + "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, + "workspace_owner.login_type": `password`, + "workspace_owner.rbac_roles": `(?is)\[(\{"name":"[a-z0-9-:]+","org_id":"[a-f0-9-]+"\},?)+\]`, }, }, { diff --git a/integration/workspace-owner-rbac-roles/main.tf b/integration/workspace-owner-rbac-roles/main.tf new file mode 100644 index 00000000..66b79282 --- /dev/null +++ b/integration/workspace-owner-rbac-roles/main.tf @@ -0,0 +1,57 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + } + local = { + source = "hashicorp/local" + } + } +} + +data "coder_provisioner" "me" {} +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +locals { + # NOTE: these must all be strings in the output + output = { + "provisioner.arch" : data.coder_provisioner.me.arch, + "provisioner.id" : data.coder_provisioner.me.id, + "provisioner.os" : data.coder_provisioner.me.os, + "workspace.access_port" : tostring(data.coder_workspace.me.access_port), + "workspace.access_url" : data.coder_workspace.me.access_url, + "workspace.id" : data.coder_workspace.me.id, + "workspace.name" : data.coder_workspace.me.name, + "workspace.start_count" : tostring(data.coder_workspace.me.start_count), + "workspace.template_id" : data.coder_workspace.me.template_id, + "workspace.template_name" : data.coder_workspace.me.template_name, + "workspace.template_version" : data.coder_workspace.me.template_version, + "workspace.transition" : data.coder_workspace.me.transition, + "workspace_owner.email" : data.coder_workspace_owner.me.email, + "workspace_owner.full_name" : data.coder_workspace_owner.me.full_name, + "workspace_owner.groups" : jsonencode(data.coder_workspace_owner.me.groups), + "workspace_owner.id" : data.coder_workspace_owner.me.id, + "workspace_owner.name" : data.coder_workspace_owner.me.name, + "workspace_owner.oidc_access_token" : data.coder_workspace_owner.me.oidc_access_token, + "workspace_owner.session_token" : data.coder_workspace_owner.me.session_token, + "workspace_owner.ssh_private_key" : data.coder_workspace_owner.me.ssh_private_key, + "workspace_owner.ssh_public_key" : data.coder_workspace_owner.me.ssh_public_key, + "workspace_owner.login_type" : data.coder_workspace_owner.me.login_type, + "workspace_owner.rbac_roles" : jsonencode(data.coder_workspace_owner.me.rbac_roles), + } +} + +variable "output_path" { + type = string +} + +resource "local_file" "output" { + filename = var.output_path + content = jsonencode(local.output) +} + +output "output" { + value = local.output + sensitive = true +} From 9a745586b23a9cb5de2f65a2dcac12e48b134ffa Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 20 Mar 2025 19:09:06 +0200 Subject: [PATCH 096/114] feat: add `coder_devcontainer` resource (#368) * feat: add `coder_devcontainer` resource This change will allow autostarting Dev Containers. This is implemented as a resource instead of a `schema.TypeSet` on the agent resource to allow definition via Coder modules. Updates coder/coder#16423 --- docs/resources/devcontainer.md | 29 ++++++++++ provider/devcontainer.go | 46 ++++++++++++++++ provider/devcontainer_test.go | 98 ++++++++++++++++++++++++++++++++++ provider/provider.go | 1 + 4 files changed, 174 insertions(+) create mode 100644 docs/resources/devcontainer.md create mode 100644 provider/devcontainer.go create mode 100644 provider/devcontainer_test.go diff --git a/docs/resources/devcontainer.md b/docs/resources/devcontainer.md new file mode 100644 index 00000000..93d5724b --- /dev/null +++ b/docs/resources/devcontainer.md @@ -0,0 +1,29 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coder_devcontainer Resource - terraform-provider-coder" +subcategory: "" +description: |- + Define a Dev Container the agent should know of and attempt to autostart (minimum Coder version: v2.21). +--- + +# coder_devcontainer (Resource) + +Define a Dev Container the agent should know of and attempt to autostart (minimum Coder version: v2.21). + + + + +## Schema + +### Required + +- `agent_id` (String) The `id` property of a `coder_agent` resource to associate with. +- `workspace_folder` (String) The workspace folder to for the Dev Container. + +### Optional + +- `config_path` (String) The path to the Dev Container configuration file (devcontainer.json). + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/provider/devcontainer.go b/provider/devcontainer.go new file mode 100644 index 00000000..7d1fe0a4 --- /dev/null +++ b/provider/devcontainer.go @@ -0,0 +1,46 @@ +package provider + +import ( + "context" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func devcontainerResource() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + Description: "Define a Dev Container the agent should know of and attempt to autostart (minimum Coder version: v2.21).", + CreateContext: func(_ context.Context, rd *schema.ResourceData, _ interface{}) diag.Diagnostics { + rd.SetId(uuid.NewString()) + + return nil + }, + ReadContext: schema.NoopContext, + DeleteContext: schema.NoopContext, + Schema: map[string]*schema.Schema{ + "agent_id": { + Type: schema.TypeString, + Description: "The `id` property of a `coder_agent` resource to associate with.", + ForceNew: true, + Required: true, + }, + "workspace_folder": { + Type: schema.TypeString, + Description: "The workspace folder to for the Dev Container.", + ForceNew: true, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "config_path": { + Type: schema.TypeString, + Description: "The path to the Dev Container configuration file (devcontainer.json).", + ForceNew: true, + Optional: true, + }, + }, + } +} diff --git a/provider/devcontainer_test.go b/provider/devcontainer_test.go new file mode 100644 index 00000000..784cfb0d --- /dev/null +++ b/provider/devcontainer_test.go @@ -0,0 +1,98 @@ +package provider_test + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestDevcontainer(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + resource "coder_devcontainer" "example" { + agent_id = "king" + workspace_folder = "/workspace" + config_path = "/workspace/devcontainer.json" + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + script := state.Modules[0].Resources["coder_devcontainer.example"] + require.NotNil(t, script) + t.Logf("script attributes: %#v", script.Primary.Attributes) + for key, expected := range map[string]string{ + "agent_id": "king", + "workspace_folder": "/workspace", + "config_path": "/workspace/devcontainer.json", + } { + require.Equal(t, expected, script.Primary.Attributes[key]) + } + return nil + }, + }}, + }) +} + +func TestDevcontainerNoConfigPath(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + resource "coder_devcontainer" "example" { + agent_id = "king" + workspace_folder = "/workspace" + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + script := state.Modules[0].Resources["coder_devcontainer.example"] + require.NotNil(t, script) + t.Logf("script attributes: %#v", script.Primary.Attributes) + for key, expected := range map[string]string{ + "agent_id": "king", + "workspace_folder": "/workspace", + } { + require.Equal(t, expected, script.Primary.Attributes[key]) + } + return nil + }, + }}, + }) +} + +func TestDevcontainerNoWorkspaceFolder(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + resource "coder_devcontainer" "example" { + agent_id = "" + } + `, + ExpectError: regexp.MustCompile(`The argument "workspace_folder" is required, but no definition was found.`), + }}, + }) +} diff --git a/provider/provider.go b/provider/provider.go index d9780d76..cc2644ef 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -76,6 +76,7 @@ func New() *schema.Provider { "coder_metadata": metadataResource(), "coder_script": scriptResource(), "coder_env": envResource(), + "coder_devcontainer": devcontainerResource(), }, } } From ef92eea9d88a63ecda165a3f17f891845d3a7da2 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Mon, 7 Apr 2025 08:20:00 +0200 Subject: [PATCH 097/114] feat: allow presets to define prebuilds (#373) * feat: allow presets to define prebuilds * document prebuild parameters * remove todo * make gen * Add integration testing * Allow presets to define 0 parameters * additional documentation for prebuilds --- README.md | 2 +- docs/data-sources/workspace.md | 2 ++ docs/data-sources/workspace_preset.md | 21 +++++++++--- integration/integration_test.go | 9 ++--- integration/test-data-source/main.tf | 5 +++ provider/workspace.go | 22 +++++++++++++ provider/workspace_preset.go | 47 ++++++++++++++++++++------- provider/workspace_preset_test.go | 40 +++++++++++++++++++++-- 8 files changed, 124 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b8ee8840..f055961e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ to setup your local Terraform to use your local version rather than the registry } ``` 2. Run `terraform init` and observe a warning like `Warning: Provider development overrides are in effect` -4. Run `go build -o terraform-provider-coder` to build the provider binary, which Terraform will try locate and execute +4. Run `make build` to build the provider binary, which Terraform will try locate and execute 5. All local Terraform runs will now use your local provider! 6. _**NOTE**: we vendor in this provider into `github.com/coder/coder`, so if you're testing with a local clone then you should also run `go mod edit -replace github.com/coder/terraform-provider-coder=/path/to/terraform-provider-coder` in your clone._ diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index 26396ba1..29fd9179 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -69,7 +69,9 @@ resource "docker_container" "workspace" { - `access_port` (Number) The access port of the Coder deployment provisioning this workspace. - `access_url` (String) The access URL of the Coder deployment provisioning this workspace. - `id` (String) UUID of the workspace. +- `is_prebuild` (Boolean) Similar to `prebuild_count`, but a boolean value instead of a count. This is set to true if the workspace is a currently unassigned prebuild. Once the workspace is assigned, this value will be false. - `name` (String) Name of the workspace. +- `prebuild_count` (Number) A computed count, equal to 1 if the workspace is a currently unassigned prebuild. Use this to conditionally act on the status of a prebuild. Actions that do not require user identity can be taken when this value is set to 1. Actions that should only be taken once the workspace has been assigned to a user may be taken when this value is set to 0. - `start_count` (Number) A computed count based on `transition` state. If `start`, count will equal 1. - `template_id` (String) ID of the workspace's template. - `template_name` (String) Name of the workspace's template. diff --git a/docs/data-sources/workspace_preset.md b/docs/data-sources/workspace_preset.md index 28f90faa..edd61f18 100644 --- a/docs/data-sources/workspace_preset.md +++ b/docs/data-sources/workspace_preset.md @@ -3,12 +3,12 @@ page_title: "coder_workspace_preset Data Source - terraform-provider-coder" subcategory: "" description: |- - Use this data source to predefine common configurations for workspaces. + Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are not defined by the preset will still be configurable when creating a workspace. --- # coder_workspace_preset (Data Source) -Use this data source to predefine common configurations for workspaces. +Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are not defined by the preset will still be configurable when creating a workspace. ## Example Usage @@ -34,9 +34,20 @@ data "coder_workspace_preset" "example" { ### Required -- `name` (String) Name of the workspace preset. -- `parameters` (Map of String) Parameters of the workspace preset. +- `name` (String) The name of the workspace preset. + +### Optional + +- `parameters` (Map of String) Workspace parameters that will be set by the workspace preset. For simple templates that only need prebuilds, you may define a preset with zero parameters. Because workspace parameters may change between Coder template versions, preset parameters are allowed to define values for parameters that do not exist in the current template version. +- `prebuilds` (Block Set, Max: 1) Prebuilt workspace configuration related to this workspace preset. Coder will build and maintain workspaces in reserve based on this configuration. When a user creates a new workspace using a preset, they will be assigned a prebuilt workspace, instead of waiting for a new workspace to build. (see [below for nested schema](#nestedblock--prebuilds)) ### Read-Only -- `id` (String) ID of the workspace preset. +- `id` (String) The preset ID is automatically generated and may change between runs. It is recommended to use the `name` attribute to identify the preset. + + +### Nested Schema for `prebuilds` + +Required: + +- `instances` (Number) The number of workspaces to keep in reserve for this preset. diff --git a/integration/integration_test.go b/integration/integration_test.go index 65aa5aed..9803aa41 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -90,10 +90,11 @@ func TestIntegration(t *testing.T) { // TODO (sasswart): the cli doesn't support presets yet. // once it does, the value for workspace_parameter.value // will be the preset value. - "workspace_parameter.value": `param value`, - "workspace_parameter.icon": `param icon`, - "workspace_preset.name": `preset`, - "workspace_preset.parameters.param": `preset param value`, + "workspace_parameter.value": `param value`, + "workspace_parameter.icon": `param icon`, + "workspace_preset.name": `preset`, + "workspace_preset.parameters.param": `preset param value`, + "workspace_preset.prebuilds.instances": `1`, }, }, { diff --git a/integration/test-data-source/main.tf b/integration/test-data-source/main.tf index 5fb2e0e6..f18fa347 100644 --- a/integration/test-data-source/main.tf +++ b/integration/test-data-source/main.tf @@ -24,6 +24,10 @@ data "coder_workspace_preset" "preset" { parameters = { (data.coder_parameter.param.name) = "preset param value" } + + prebuilds { + instances = 1 + } } locals { @@ -47,6 +51,7 @@ locals { "workspace_parameter.icon" : data.coder_parameter.param.icon, "workspace_preset.name" : data.coder_workspace_preset.preset.name, "workspace_preset.parameters.param" : data.coder_workspace_preset.preset.parameters.param, + "workspace_preset.prebuilds.instances" : tostring(one(data.coder_workspace_preset.preset.prebuilds).instances), } } diff --git a/provider/workspace.go b/provider/workspace.go index fde742b6..5ddd3ee8 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -27,6 +27,14 @@ func workspaceDataSource() *schema.Resource { } _ = rd.Set("start_count", count) + prebuild := helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) + prebuildCount := 0 + if prebuild == "true" { + prebuildCount = 1 + _ = rd.Set("is_prebuild", true) + } + _ = rd.Set("prebuild_count", prebuildCount) + name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default") rd.Set("name", name) @@ -83,6 +91,11 @@ func workspaceDataSource() *schema.Resource { Computed: true, Description: "The access port of the Coder deployment provisioning this workspace.", }, + "prebuild_count": { + Type: schema.TypeInt, + Computed: true, + Description: "A computed count, equal to 1 if the workspace is a currently unassigned prebuild. Use this to conditionally act on the status of a prebuild. Actions that do not require user identity can be taken when this value is set to 1. Actions that should only be taken once the workspace has been assigned to a user may be taken when this value is set to 0.", + }, "start_count": { Type: schema.TypeInt, Computed: true, @@ -98,6 +111,11 @@ func workspaceDataSource() *schema.Resource { Computed: true, Description: "UUID of the workspace.", }, + "is_prebuild": { + Type: schema.TypeBool, + Computed: true, + Description: "Similar to `prebuild_count`, but a boolean value instead of a count. This is set to true if the workspace is a currently unassigned prebuild. Once the workspace is assigned, this value will be false.", + }, "name": { Type: schema.TypeString, Computed: true, @@ -121,3 +139,7 @@ func workspaceDataSource() *schema.Resource { }, } } + +func IsPrebuildEnvironmentVariable() string { + return "CODER_WORKSPACE_IS_PREBUILD" +} diff --git a/provider/workspace_preset.go b/provider/workspace_preset.go index cd56c980..004489e7 100644 --- a/provider/workspace_preset.go +++ b/provider/workspace_preset.go @@ -12,33 +12,39 @@ import ( type WorkspacePreset struct { Name string `mapstructure:"name"` Parameters map[string]string `mapstructure:"parameters"` + Prebuilds WorkspacePrebuild `mapstructure:"prebuilds"` +} + +type WorkspacePrebuild struct { + Instances int `mapstructure:"instances"` } func workspacePresetDataSource() *schema.Resource { return &schema.Resource{ SchemaVersion: 1, - Description: "Use this data source to predefine common configurations for workspaces.", + Description: "Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are not defined by the preset will still be configurable when creating a workspace.", ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { var preset WorkspacePreset err := mapstructure.Decode(struct { Name interface{} Parameters interface{} + Prebuilds struct { + Instances interface{} + } }{ Name: rd.Get("name"), Parameters: rd.Get("parameters"), + Prebuilds: struct { + Instances interface{} + }{ + Instances: rd.Get("prebuilds.0.instances"), + }, }, &preset) if err != nil { return diag.Errorf("decode workspace preset: %s", err) } - // MinItems doesn't work with maps, so we need to check the length - // of the map manually. All other validation is handled by the - // schema. - if len(preset.Parameters) == 0 { - return diag.Errorf("expected \"parameters\" to not be an empty map") - } - rd.SetId(preset.Name) return nil @@ -46,25 +52,42 @@ func workspacePresetDataSource() *schema.Resource { Schema: map[string]*schema.Schema{ "id": { Type: schema.TypeString, - Description: "ID of the workspace preset.", + Description: "The preset ID is automatically generated and may change between runs. It is recommended to use the `name` attribute to identify the preset.", Computed: true, }, "name": { Type: schema.TypeString, - Description: "Name of the workspace preset.", + Description: "The name of the workspace preset.", Required: true, ValidateFunc: validation.StringIsNotEmpty, }, "parameters": { Type: schema.TypeMap, - Description: "Parameters of the workspace preset.", - Required: true, + Description: "Workspace parameters that will be set by the workspace preset. For simple templates that only need prebuilds, you may define a preset with zero parameters. Because workspace parameters may change between Coder template versions, preset parameters are allowed to define values for parameters that do not exist in the current template version.", + Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, Required: true, ValidateFunc: validation.StringIsNotEmpty, }, }, + "prebuilds": { + Type: schema.TypeSet, + Description: "Prebuilt workspace configuration related to this workspace preset. Coder will build and maintain workspaces in reserve based on this configuration. When a user creates a new workspace using a preset, they will be assigned a prebuilt workspace, instead of waiting for a new workspace to build.", + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instances": { + Type: schema.TypeInt, + Description: "The number of workspaces to keep in reserve for this preset.", + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + }, + }, + }, + }, }, } } diff --git a/provider/workspace_preset_test.go b/provider/workspace_preset_test.go index 876e2044..aa1ca0ce 100644 --- a/provider/workspace_preset_test.go +++ b/provider/workspace_preset_test.go @@ -84,7 +84,7 @@ func TestWorkspacePreset(t *testing.T) { }`, // This validation is done by Terraform, but it could still break if we misconfigure the schema. // So we test it here to make sure we don't regress. - ExpectError: regexp.MustCompile("The argument \"parameters\" is required, but no definition was found"), + ExpectError: nil, }, { Name: "Parameters field is empty", @@ -95,7 +95,7 @@ func TestWorkspacePreset(t *testing.T) { }`, // This validation is *not* done by Terraform, because MinItems doesn't work with maps. // We've implemented the validation in ReadContext, so we test it here to make sure we don't regress. - ExpectError: regexp.MustCompile("expected \"parameters\" to not be an empty map"), + ExpectError: nil, }, { Name: "Parameters field is not a map", @@ -108,6 +108,42 @@ func TestWorkspacePreset(t *testing.T) { // So we test it here to make sure we don't regress. ExpectError: regexp.MustCompile("Inappropriate value for attribute \"parameters\": map of string required"), }, + { + Name: "Prebuilds is set, but not its required fields", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = "preset_1" + parameters = { + "region" = "us-east1-a" + } + prebuilds {} + }`, + ExpectError: regexp.MustCompile("The argument \"instances\" is required, but no definition was found."), + }, + { + Name: "Prebuilds is set, and so are its required fields", + Config: ` + data "coder_workspace_preset" "preset_1" { + name = "preset_1" + parameters = { + "region" = "us-east1-a" + } + prebuilds { + instances = 1 + } + }`, + ExpectError: nil, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + resource := state.Modules[0].Resources["data.coder_workspace_preset.preset_1"] + require.NotNil(t, resource) + attrs := resource.Primary.Attributes + require.Equal(t, attrs["name"], "preset_1") + require.Equal(t, attrs["prebuilds.0.instances"], "1") + return nil + }, + }, } for _, testcase := range testcases { From 3a2c18dab13ebd36ddab31a97e2c566696b1d3e3 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Mon, 7 Apr 2025 09:55:38 +0200 Subject: [PATCH 098/114] Correct the prebuilds type (#376) --- provider/workspace_preset.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/provider/workspace_preset.go b/provider/workspace_preset.go index 004489e7..2fab0b8a 100644 --- a/provider/workspace_preset.go +++ b/provider/workspace_preset.go @@ -12,7 +12,12 @@ import ( type WorkspacePreset struct { Name string `mapstructure:"name"` Parameters map[string]string `mapstructure:"parameters"` - Prebuilds WorkspacePrebuild `mapstructure:"prebuilds"` + // There should always be only one prebuild block, but Terraform's type system + // still parses them as a slice, so we need to handle it as such. We could use + // an anonymous type and rd.Get to avoid a slice here, but that would not be possible + // for utilities that parse our terraform output using this type. To remain compatible + // with those cases, we use a slice here. + Prebuilds []WorkspacePrebuild `mapstructure:"prebuilds"` } type WorkspacePrebuild struct { @@ -27,19 +32,9 @@ func workspacePresetDataSource() *schema.Resource { ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { var preset WorkspacePreset err := mapstructure.Decode(struct { - Name interface{} - Parameters interface{} - Prebuilds struct { - Instances interface{} - } + Name interface{} }{ - Name: rd.Get("name"), - Parameters: rd.Get("parameters"), - Prebuilds: struct { - Instances interface{} - }{ - Instances: rd.Get("prebuilds.0.instances"), - }, + Name: rd.Get("name"), }, &preset) if err != nil { return diag.Errorf("decode workspace preset: %s", err) From 8804c44f070b366bb29e0e4b8d44b6eba9473b0c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 8 Apr 2025 13:04:02 -0500 Subject: [PATCH 099/114] feat: `form_type` and `styling` metadata arguments added (#375) Adds `form_type` as a field to parameters. By default, legacy behavior is maintained by deducing the default `form_type` value from the existing inputs. `styling` argument also added for cosmetic usage on the frontend form --- docs/data-sources/parameter.md | 2 + provider/formtype.go | 148 ++++++++++++ provider/formtype_test.go | 428 +++++++++++++++++++++++++++++++++ provider/parameter.go | 94 ++++++-- provider/parameter_test.go | 17 +- 5 files changed, 664 insertions(+), 25 deletions(-) create mode 100644 provider/formtype.go create mode 100644 provider/formtype_test.go diff --git a/docs/data-sources/parameter.md b/docs/data-sources/parameter.md index e46cf86d..934ae77a 100644 --- a/docs/data-sources/parameter.md +++ b/docs/data-sources/parameter.md @@ -145,10 +145,12 @@ data "coder_parameter" "home_volume_size" { - `description` (String) Describe what this parameter does. - `display_name` (String) The displayed name of the parameter as it will appear in the interface. - `ephemeral` (Boolean) The value of an ephemeral parameter will not be preserved between consecutive workspace builds. +- `form_type` (String) The type of this parameter. Must be one of: [radio, slider, input, dropdown, checkbox, switch, multi-select, tag-select, textarea, error]. - `icon` (String) A URL to an icon that will display in the dashboard. View built-in icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a built-in icon with `"${data.coder_workspace.me.access_url}/icon/"`. - `mutable` (Boolean) Whether this value can be changed after workspace creation. This can be destructive for values like region, so use with caution! - `option` (Block List) Each `option` block defines a value for a user to select from. (see [below for nested schema](#nestedblock--option)) - `order` (Number) The order determines the position of a template parameter in the UI/CLI presentation. The lowest order is shown first and parameters with equal order are sorted by name (ascending order). +- `styling` (String) JSON encoded string containing the metadata for controlling the appearance of this parameter in the UI. This option is purely cosmetic and does not affect the function of the parameter in terraform. - `type` (String) The type of this parameter. Must be one of: `"number"`, `"string"`, `"bool"`, or `"list(string)"`. - `validation` (Block List, Max: 1) Validate the input of a parameter. (see [below for nested schema](#nestedblock--validation)) diff --git a/provider/formtype.go b/provider/formtype.go new file mode 100644 index 00000000..48fbeed3 --- /dev/null +++ b/provider/formtype.go @@ -0,0 +1,148 @@ +package provider + +import ( + "slices" + + "golang.org/x/xerrors" +) + +// OptionType is a type of option that can be used in the 'type' argument of +// a parameter. These should match types as defined in terraform: +// +// https://developer.hashicorp.com/terraform/language/expressions/types +// +// The value have to be string literals, as type constraint keywords are not +// supported in providers. +type OptionType string + +const ( + OptionTypeString OptionType = "string" + OptionTypeNumber OptionType = "number" + OptionTypeBoolean OptionType = "bool" + OptionTypeListString OptionType = "list(string)" +) + +func OptionTypes() []OptionType { + return []OptionType{ + OptionTypeString, + OptionTypeNumber, + OptionTypeBoolean, + OptionTypeListString, + } +} + +// ParameterFormType is the list of supported form types for display in +// the Coder "create workspace" form. These form types are functional as well +// as cosmetic. Refer to `formTypeTruthTable` for the allowed pairings. +// For example, "multi-select" has the type "list(string)" but the option +// values are "string". +type ParameterFormType string + +const ( + ParameterFormTypeDefault ParameterFormType = "" + ParameterFormTypeRadio ParameterFormType = "radio" + ParameterFormTypeSlider ParameterFormType = "slider" + ParameterFormTypeInput ParameterFormType = "input" + ParameterFormTypeDropdown ParameterFormType = "dropdown" + ParameterFormTypeCheckbox ParameterFormType = "checkbox" + ParameterFormTypeSwitch ParameterFormType = "switch" + ParameterFormTypeMultiSelect ParameterFormType = "multi-select" + ParameterFormTypeTagSelect ParameterFormType = "tag-select" + ParameterFormTypeTextArea ParameterFormType = "textarea" + ParameterFormTypeError ParameterFormType = "error" +) + +// ParameterFormTypes should be kept in sync with the enum list above. +func ParameterFormTypes() []ParameterFormType { + return []ParameterFormType{ + // Intentionally omit "ParameterFormTypeDefault" from this set. + // It is a valid enum, but will always be mapped to a real value when + // being used. + ParameterFormTypeRadio, + ParameterFormTypeSlider, + ParameterFormTypeInput, + ParameterFormTypeDropdown, + ParameterFormTypeCheckbox, + ParameterFormTypeSwitch, + ParameterFormTypeMultiSelect, + ParameterFormTypeTagSelect, + ParameterFormTypeTextArea, + ParameterFormTypeError, + } +} + +// formTypeTruthTable is a map of [`type`][`optionCount` > 0] to `form_type`. +// The first value in the slice is the default value assuming `form_type` is +// not specified. +// +// The boolean key indicates whether the `options` field is specified. +// | Type | Options | Specified Form Type | form_type | Notes | +// |-------------------|---------|---------------------|----------------|--------------------------------| +// | `string` `number` | Y | | `radio` | | +// | `string` `number` | Y | `dropdown` | `dropdown` | | +// | `string` `number` | N | | `input` | | +// | `string` | N | 'textarea' | `textarea` | | +// | `number` | N | 'slider' | `slider` | min/max validation | +// | `bool` | Y | | `radio` | | +// | `bool` | N | | `checkbox` | | +// | `bool` | N | `switch` | `switch` | | +// | `list(string)` | Y | | `radio` | | +// | `list(string)` | N | | `tag-select` | | +// | `list(string)` | Y | `multi-select` | `multi-select` | Option values will be `string` | +var formTypeTruthTable = map[OptionType]map[bool][]ParameterFormType{ + OptionTypeString: { + true: {ParameterFormTypeRadio, ParameterFormTypeDropdown}, + false: {ParameterFormTypeInput, ParameterFormTypeTextArea}, + }, + OptionTypeNumber: { + true: {ParameterFormTypeRadio, ParameterFormTypeDropdown}, + false: {ParameterFormTypeInput, ParameterFormTypeSlider}, + }, + OptionTypeBoolean: { + true: {ParameterFormTypeRadio}, + false: {ParameterFormTypeCheckbox, ParameterFormTypeSwitch}, + }, + OptionTypeListString: { + true: {ParameterFormTypeRadio, ParameterFormTypeMultiSelect}, + false: {ParameterFormTypeTagSelect}, + }, +} + +// ValidateFormType handles the truth table for the valid set of `type` and +// `form_type` options. +// The OptionType is also returned because it is possible the 'type' of the +// 'value' & 'default' fields is different from the 'type' of the options. +// The use case is when using multi-select. The options are 'string' and the +// value is 'list(string)'. +func ValidateFormType(paramType OptionType, optionCount int, specifiedFormType ParameterFormType) (OptionType, ParameterFormType, error) { + allowed, ok := formTypeTruthTable[paramType][optionCount > 0] + if !ok || len(allowed) == 0 { + return paramType, specifiedFormType, xerrors.Errorf("value type %q is not supported for 'form_types'", paramType) + } + + if specifiedFormType == ParameterFormTypeDefault { + // handle the default case + specifiedFormType = allowed[0] + } + + if !slices.Contains(allowed, specifiedFormType) { + return paramType, specifiedFormType, xerrors.Errorf("value type %q is not supported for 'form_types'", specifiedFormType) + } + + // This is the only current special case. If 'multi-select' is selected, the type + // of 'value' and an options 'value' are different. The type of the parameter is + // `list(string)` but the type of the individual options is `string`. + if paramType == OptionTypeListString && specifiedFormType == ParameterFormTypeMultiSelect { + return OptionTypeString, ParameterFormTypeMultiSelect, nil + } + + return paramType, specifiedFormType, nil +} + +func toStrings[A ~string](l []A) []string { + var r []string + for _, v := range l { + r = append(r, string(v)) + } + return r +} diff --git a/provider/formtype_test.go b/provider/formtype_test.go new file mode 100644 index 00000000..599c2e41 --- /dev/null +++ b/provider/formtype_test.go @@ -0,0 +1,428 @@ +package provider_test + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/coder/terraform-provider-coder/v2/provider" +) + +// formTypeTestCase is the config for a single test case. +type formTypeTestCase struct { + name string + config formTypeCheck + assert paramAssert + expectError *regexp.Regexp +} + +// paramAssert is asserted on the provider's parsed terraform state. +type paramAssert struct { + FormType provider.ParameterFormType + Type provider.OptionType + Styling json.RawMessage +} + +// formTypeCheck is a struct that helps build the terraform config +type formTypeCheck struct { + formType provider.ParameterFormType + optionType provider.OptionType + options bool + + // optional to inform the assert + customOptions []string + defValue string + styling json.RawMessage +} + +func (c formTypeCheck) String() string { + return fmt.Sprintf("%s_%s_%t", c.formType, c.optionType, c.options) +} + +func TestValidateFormType(t *testing.T) { + t.Parallel() + + // formTypesChecked keeps track of all checks run. It will be used to + // ensure all combinations of form_type and option_type are tested. + // All untested options are assumed to throw an error. + formTypesChecked := make(map[string]struct{}) + + expectType := func(expected provider.ParameterFormType, opts formTypeCheck) formTypeTestCase { + ftname := opts.formType + if ftname == "" { + ftname = "default" + } + + if opts.styling == nil { + // Try passing arbitrary data in, as anything should be accepted + opts.styling, _ = json.Marshal(map[string]any{ + "foo": "bar", + "disabled": true, + "nested": map[string]any{ + "foo": "bar", + }, + }) + } + + return formTypeTestCase{ + name: fmt.Sprintf("%s_%s_%t", + ftname, + opts.optionType, + opts.options, + ), + config: opts, + assert: paramAssert{ + FormType: expected, + Type: opts.optionType, + Styling: opts.styling, + }, + expectError: nil, + } + } + + // expectSameFormType just assumes the FormType in the check is the expected + // FormType. Using `expectType` these fields can differ + expectSameFormType := func(opts formTypeCheck) formTypeTestCase { + return expectType(opts.formType, opts) + } + + cases := []formTypeTestCase{ + { + // When nothing is specified + name: "defaults", + config: formTypeCheck{}, + assert: paramAssert{ + FormType: provider.ParameterFormTypeInput, + Type: provider.OptionTypeString, + Styling: []byte("{}"), + }, + }, + // All default behaviors. Essentially legacy behavior. + // String + expectType(provider.ParameterFormTypeRadio, formTypeCheck{ + options: true, + optionType: provider.OptionTypeString, + }), + expectType(provider.ParameterFormTypeInput, formTypeCheck{ + options: false, + optionType: provider.OptionTypeString, + }), + // Number + expectType(provider.ParameterFormTypeRadio, formTypeCheck{ + options: true, + optionType: provider.OptionTypeNumber, + }), + expectType(provider.ParameterFormTypeInput, formTypeCheck{ + options: false, + optionType: provider.OptionTypeNumber, + }), + // Boolean + expectType(provider.ParameterFormTypeRadio, formTypeCheck{ + options: true, + optionType: provider.OptionTypeBoolean, + }), + expectType(provider.ParameterFormTypeCheckbox, formTypeCheck{ + options: false, + optionType: provider.OptionTypeBoolean, + }), + // List(string) + expectType(provider.ParameterFormTypeRadio, formTypeCheck{ + options: true, + optionType: provider.OptionTypeListString, + }), + expectType(provider.ParameterFormTypeTagSelect, formTypeCheck{ + options: false, + optionType: provider.OptionTypeListString, + }), + + // ---- New Behavior + // String + expectSameFormType(formTypeCheck{ + options: true, + optionType: provider.OptionTypeString, + formType: provider.ParameterFormTypeDropdown, + }), + expectSameFormType(formTypeCheck{ + options: true, + optionType: provider.OptionTypeString, + formType: provider.ParameterFormTypeRadio, + }), + expectSameFormType(formTypeCheck{ + options: false, + optionType: provider.OptionTypeString, + formType: provider.ParameterFormTypeInput, + }), + expectSameFormType(formTypeCheck{ + options: false, + optionType: provider.OptionTypeString, + formType: provider.ParameterFormTypeTextArea, + }), + // Number + expectSameFormType(formTypeCheck{ + options: true, + optionType: provider.OptionTypeNumber, + formType: provider.ParameterFormTypeDropdown, + }), + expectSameFormType(formTypeCheck{ + options: true, + optionType: provider.OptionTypeNumber, + formType: provider.ParameterFormTypeRadio, + }), + expectSameFormType(formTypeCheck{ + options: false, + optionType: provider.OptionTypeNumber, + formType: provider.ParameterFormTypeInput, + }), + expectSameFormType(formTypeCheck{ + options: false, + optionType: provider.OptionTypeNumber, + formType: provider.ParameterFormTypeSlider, + }), + // Boolean + expectSameFormType(formTypeCheck{ + options: true, + optionType: provider.OptionTypeBoolean, + formType: provider.ParameterFormTypeRadio, + }), + expectSameFormType(formTypeCheck{ + options: false, + optionType: provider.OptionTypeBoolean, + formType: provider.ParameterFormTypeSwitch, + }), + expectSameFormType(formTypeCheck{ + options: false, + optionType: provider.OptionTypeBoolean, + formType: provider.ParameterFormTypeCheckbox, + }), + // List(string) + expectSameFormType(formTypeCheck{ + options: true, + optionType: provider.OptionTypeListString, + formType: provider.ParameterFormTypeRadio, + }), + expectSameFormType(formTypeCheck{ + options: true, + optionType: provider.OptionTypeListString, + formType: provider.ParameterFormTypeMultiSelect, + customOptions: []string{"red", "blue", "green"}, + defValue: `["red", "blue"]`, + }), + expectSameFormType(formTypeCheck{ + options: false, + optionType: provider.OptionTypeListString, + formType: provider.ParameterFormTypeTagSelect, + }), + + // Some manual test cases + { + name: "list_string_bad_default", + config: formTypeCheck{ + formType: provider.ParameterFormTypeMultiSelect, + optionType: provider.OptionTypeListString, + customOptions: []string{"red", "blue", "green"}, + defValue: `["red", "yellow"]`, + styling: nil, + }, + expectError: regexp.MustCompile("is not a valid option"), + }, + } + + passed := t.Run("TabledTests", func(t *testing.T) { + // TabledCases runs through all the manual test cases + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + t.Parallel() + if _, ok := formTypesChecked[c.config.String()]; ok { + t.Log("Duplicated form type check, delete this extra test case") + t.Fatalf("form type %q already checked", c.config.String()) + } + + formTypesChecked[c.config.String()] = struct{}{} + formTypeTest(t, c) + }) + } + }) + + if !passed { + // Do not run additional tests and pollute the output + t.Log("Tests failed, will not run the assumed error cases") + return + } + + // AssumeErrorCases assumes any uncovered test will return an error. Not covered + // cases in the truth table are assumed to be invalid. So if the tests above + // cover all valid cases, this asserts all the invalid cases. + // + // This test consequentially ensures all valid cases are covered manually above. + t.Run("AssumeErrorCases", func(t *testing.T) { + // requiredChecks loops through all possible form_type and option_type + // combinations. + requiredChecks := make([]formTypeCheck, 0) + for _, ft := range append(provider.ParameterFormTypes(), "") { + for _, ot := range provider.OptionTypes() { + requiredChecks = append(requiredChecks, formTypeCheck{ + formType: ft, + optionType: ot, + options: false, + }) + requiredChecks = append(requiredChecks, formTypeCheck{ + formType: ft, + optionType: ot, + options: true, + }) + } + } + + for _, check := range requiredChecks { + if _, alreadyChecked := formTypesChecked[check.String()]; alreadyChecked { + continue + } + + ftName := check.formType + if ftName == "" { + ftName = "default" + } + fc := formTypeTestCase{ + name: fmt.Sprintf("%s_%s_%t", + ftName, + check.optionType, + check.options, + ), + config: check, + assert: paramAssert{}, + expectError: regexp.MustCompile("is not supported"), + } + + t.Run(fc.name, func(t *testing.T) { + t.Parallel() + + // This is just helpful log output to give the boilerplate + // to write the manual test. + tcText := fmt.Sprintf(` + expectSameFormType(%s, ezconfigOpts{ + Options: %t, + OptionType: %q, + FormType: %q, + }), + //`, "", check.options, check.optionType, check.formType) + + logDebugInfo := formTypeTest(t, fc) + if !logDebugInfo { + t.Logf("To construct this test case:\n%s", tcText) + } + }) + + } + }) +} + +// createTF converts a formTypeCheck into a terraform config string. +func createTF(paramName string, cfg formTypeCheck) (defaultValue string, tf string) { + options := cfg.customOptions + if cfg.options && len(cfg.customOptions) == 0 { + switch cfg.optionType { + case provider.OptionTypeString: + options = []string{"foo"} + defaultValue = "foo" + case provider.OptionTypeBoolean: + options = []string{"true", "false"} + defaultValue = "true" + case provider.OptionTypeNumber: + options = []string{"1"} + defaultValue = "1" + case provider.OptionTypeListString: + options = []string{`["red", "blue"]`} + defaultValue = `["red", "blue"]` + default: + panic(fmt.Sprintf("unknown option type %q when generating options", cfg.optionType)) + } + } + + if cfg.defValue == "" { + cfg.defValue = defaultValue + } + + var body strings.Builder + if cfg.defValue != "" { + body.WriteString(fmt.Sprintf("default = %q\n", cfg.defValue)) + } + if cfg.formType != "" { + body.WriteString(fmt.Sprintf("form_type = %q\n", cfg.formType)) + } + if cfg.optionType != "" { + body.WriteString(fmt.Sprintf("type = %q\n", cfg.optionType)) + } + if cfg.styling != nil { + body.WriteString(fmt.Sprintf("styling = %s\n", strconv.Quote(string(cfg.styling)))) + } + + for i, opt := range options { + body.WriteString("option {\n") + body.WriteString(fmt.Sprintf("name = \"val_%d\"\n", i)) + body.WriteString(fmt.Sprintf("value = %q\n", opt)) + body.WriteString("}\n") + } + + return cfg.defValue, fmt.Sprintf(` + provider "coder" { + } + data "coder_parameter" "%s" { + name = "%s" + %s + } + `, paramName, paramName, body.String()) +} + +func formTypeTest(t *testing.T, c formTypeTestCase) bool { + t.Helper() + const paramName = "test_param" + // logDebugInfo is just a guess used for logging. It's not important. It cannot + // determine for sure if the test passed because the terraform test runner is a + // black box. It does not indicate if the test passed or failed. Since this is + // just used for logging, this is good enough. + logDebugInfo := true + + def, tf := createTF(paramName, c.config) + checkFn := func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + + key := strings.Join([]string{"data", "coder_parameter", paramName}, ".") + param := state.Modules[0].Resources[key] + + logDebugInfo = logDebugInfo && assert.Equal(t, def, param.Primary.Attributes["default"], "default value") + logDebugInfo = logDebugInfo && assert.Equal(t, string(c.assert.FormType), param.Primary.Attributes["form_type"], "form_type") + logDebugInfo = logDebugInfo && assert.Equal(t, string(c.assert.Type), param.Primary.Attributes["type"], "type") + logDebugInfo = logDebugInfo && assert.JSONEq(t, string(c.assert.Styling), param.Primary.Attributes["styling"], "styling") + + return nil + } + if c.expectError != nil { + checkFn = nil + } + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProviderFactories: coderFactory(), + Steps: []resource.TestStep{ + { + Config: tf, + Check: checkFn, + ExpectError: c.expectError, + }, + }, + }) + + if !logDebugInfo { + t.Logf("Terraform config:\n%s", tf) + } + return logDebugInfo +} diff --git a/provider/parameter.go b/provider/parameter.go index 1345f4d6..4eb8a282 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -50,7 +50,8 @@ type Parameter struct { Name string DisplayName string `mapstructure:"display_name"` Description string - Type string + Type OptionType + FormType ParameterFormType Mutable bool Default string Icon string @@ -86,6 +87,7 @@ func parameterDataSource() *schema.Resource { DisplayName interface{} Description interface{} Type interface{} + FormType interface{} Mutable interface{} Default interface{} Icon interface{} @@ -100,6 +102,7 @@ func parameterDataSource() *schema.Resource { DisplayName: rd.Get("display_name"), Description: rd.Get("description"), Type: rd.Get("type"), + FormType: rd.Get("form_type"), Mutable: rd.Get("mutable"), Default: rd.Get("default"), Icon: rd.Get("icon"), @@ -149,6 +152,20 @@ func parameterDataSource() *schema.Resource { } } + // Validate options + + // optionType might differ from parameter.Type. This is ok, and parameter.Type + // should be used for the value type, and optionType for options. + var optionType OptionType + optionType, parameter.FormType, err = ValidateFormType(parameter.Type, len(parameter.Option), parameter.FormType) + if err != nil { + return diag.FromErr(err) + } + // Set the form_type back in case the value was changed. + // Eg via a default. If a user does not specify, a default value + // is used and saved. + rd.Set("form_type", parameter.FormType) + if len(parameter.Option) > 0 { names := map[string]interface{}{} values := map[string]interface{}{} @@ -161,7 +178,7 @@ func parameterDataSource() *schema.Resource { if exists { return diag.Errorf("multiple options cannot have the same value %q", option.Value) } - err := valueIsType(parameter.Type, option.Value) + err := valueIsType(optionType, option.Value) if err != nil { return err } @@ -170,9 +187,36 @@ func parameterDataSource() *schema.Resource { } if parameter.Default != "" { - _, defaultIsValid := values[parameter.Default] - if !defaultIsValid { - return diag.Errorf("default value %q must be defined as one of options", parameter.Default) + if parameter.Type == OptionTypeListString && optionType == OptionTypeString { + // If the type is list(string) and optionType is string, we have + // to ensure all elements of the default exist as options. + var defaultValues []string + // TODO: We do this unmarshal in a few spots. It should be standardized. + err = json.Unmarshal([]byte(parameter.Default), &defaultValues) + if err != nil { + return diag.Errorf("default value %q is not a list of strings", parameter.Default) + } + + // missing is used to construct a more helpful error message + var missing []string + for _, defaultValue := range defaultValues { + _, defaultIsValid := values[defaultValue] + if !defaultIsValid { + missing = append(missing, defaultValue) + } + } + + if len(missing) > 0 { + return diag.Errorf( + "default value %q is not a valid option, values %q are missing from the option", + parameter.Default, strings.Join(missing, ", "), + ) + } + } else { + _, defaultIsValid := values[parameter.Default] + if !defaultIsValid { + return diag.Errorf("%q default value %q must be defined as one of options", parameter.FormType, parameter.Default) + } } } } @@ -203,9 +247,23 @@ func parameterDataSource() *schema.Resource { Type: schema.TypeString, Default: "string", Optional: true, - ValidateFunc: validation.StringInSlice([]string{"number", "string", "bool", "list(string)"}, false), + ValidateFunc: validation.StringInSlice(toStrings(OptionTypes()), false), Description: "The type of this parameter. Must be one of: `\"number\"`, `\"string\"`, `\"bool\"`, or `\"list(string)\"`.", }, + "form_type": { + Type: schema.TypeString, + Default: ParameterFormTypeDefault, + Optional: true, + ValidateFunc: validation.StringInSlice(toStrings(ParameterFormTypes()), false), + Description: fmt.Sprintf("The type of this parameter. Must be one of: [%s].", strings.Join(toStrings(ParameterFormTypes()), ", ")), + }, + "styling": { + Type: schema.TypeString, + Default: `{}`, + Description: "JSON encoded string containing the metadata for controlling the appearance of this parameter in the UI. " + + "This option is purely cosmetic and does not affect the function of the parameter in terraform.", + Optional: true, + }, "mutable": { Type: schema.TypeBool, Optional: true, @@ -375,25 +433,25 @@ func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (int return vArr, nil } -func valueIsType(typ, value string) diag.Diagnostics { +func valueIsType(typ OptionType, value string) diag.Diagnostics { switch typ { - case "number": + case OptionTypeNumber: _, err := strconv.ParseFloat(value, 64) if err != nil { return diag.Errorf("%q is not a number", value) } - case "bool": + case OptionTypeBoolean: _, err := strconv.ParseBool(value) if err != nil { return diag.Errorf("%q is not a bool", value) } - case "list(string)": + case OptionTypeListString: var items []string err := json.Unmarshal([]byte(value), &items) if err != nil { return diag.Errorf("%q is not an array of strings", value) } - case "string": + case OptionTypeString: // Anything is a string! default: return diag.Errorf("invalid type %q", typ) @@ -401,8 +459,8 @@ func valueIsType(typ, value string) diag.Diagnostics { return nil } -func (v *Validation) Valid(typ, value string) error { - if typ != "number" { +func (v *Validation) Valid(typ OptionType, value string) error { + if typ != OptionTypeNumber { if !v.MinDisabled { return fmt.Errorf("a min cannot be specified for a %s type", typ) } @@ -413,16 +471,16 @@ func (v *Validation) Valid(typ, value string) error { return fmt.Errorf("monotonic validation can only be specified for number types, not %s types", typ) } } - if typ != "string" && v.Regex != "" { + if typ != OptionTypeString && v.Regex != "" { return fmt.Errorf("a regex cannot be specified for a %s type", typ) } switch typ { - case "bool": + case OptionTypeBoolean: if value != "true" && value != "false" { return fmt.Errorf(`boolean value can be either "true" or "false"`) } return nil - case "string": + case OptionTypeString: if v.Regex == "" { return nil } @@ -437,7 +495,7 @@ func (v *Validation) Valid(typ, value string) error { if !matched { return fmt.Errorf("%s (value %q does not match %q)", v.Error, value, regex) } - case "number": + case OptionTypeNumber: num, err := strconv.Atoi(value) if err != nil { return takeFirstError(v.errorRendered(value), fmt.Errorf("value %q is not a number", value)) @@ -451,7 +509,7 @@ func (v *Validation) Valid(typ, value string) error { if v.Monotonic != "" && v.Monotonic != ValidationMonotonicIncreasing && v.Monotonic != ValidationMonotonicDecreasing { return fmt.Errorf("number monotonicity can be either %q or %q", ValidationMonotonicIncreasing, ValidationMonotonicDecreasing) } - case "list(string)": + case OptionTypeListString: var listOfStrings []string err := json.Unmarshal([]byte(value), &listOfStrings) if err != nil { diff --git a/provider/parameter_test.go b/provider/parameter_test.go index 7bcea8fd..f817280e 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -27,6 +27,7 @@ func TestParameter(t *testing.T) { name = "region" display_name = "Region" type = "string" + form_type = "dropdown" description = <<-EOT # Select the machine image See the [registry](https://container.registry.blah/namespace) for options. @@ -56,6 +57,7 @@ func TestParameter(t *testing.T) { "name": "region", "display_name": "Region", "type": "string", + "form_type": "dropdown", "description": "# Select the machine image\nSee the [registry](https://container.registry.blah/namespace) for options.\n", "mutable": "true", "icon": "/icon/region.svg", @@ -137,6 +139,7 @@ func TestParameter(t *testing.T) { for key, expected := range map[string]string{ "name": "Region", "type": "number", + "form_type": "input", "validation.#": "1", "default": "2", "validation.0.min": "1", @@ -686,13 +689,13 @@ data "coder_parameter" "region" { func TestValueValidatesType(t *testing.T) { t.Parallel() for _, tc := range []struct { - Name, - Type, - Value, - Regex, - RegexError string - Min, - Max int + Name string + Type provider.OptionType + Value string + Regex string + RegexError string + Min int + Max int MinDisabled, MaxDisabled bool Monotonic string Error *regexp.Regexp From d90c1817de41ff9768ce7be861a66ea02773c2ee Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Apr 2025 09:04:53 -0500 Subject: [PATCH 100/114] chore: change OptionType to alias for usage as string (#377) To be backwards compatible using this repo as a library, use a string alias. This allows using the 'type' as a `string` or the new enum. Since enum validation is quite weak in Go, this is baiscally functionally equvialent. --- provider/formtype.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/formtype.go b/provider/formtype.go index 48fbeed3..753f8945 100644 --- a/provider/formtype.go +++ b/provider/formtype.go @@ -13,7 +13,7 @@ import ( // // The value have to be string literals, as type constraint keywords are not // supported in providers. -type OptionType string +type OptionType = string const ( OptionTypeString OptionType = "string" From 53a68cd7496371d6f325f9c7bd8c6808069c4664 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 10 Apr 2025 11:52:25 +0200 Subject: [PATCH 101/114] feat: add the option to debug the coder terraform provider (#378) Setting the debug flag runs the provider as a standalone process and provides an environment variable that can be set for Terraform to use it. One can then attach a debugger to the provider process. --- main.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 2eaa5dc5..ef606a6d 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main import ( + "flag" + "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" "github.com/coder/terraform-provider-coder/v2/provider" @@ -11,8 +13,15 @@ import ( //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs func main() { - servePprof() - plugin.Serve(&plugin.ServeOpts{ + debug := flag.Bool("debug", false, "Enable debug mode for the provider") + flag.Parse() + + opts := &plugin.ServeOpts{ + Debug: *debug, + ProviderAddr: "registry.terraform.io/coder/coder", ProviderFunc: provider.New, - }) + } + + servePprof() + plugin.Serve(opts) } From f66adaca2adfb6b19108200483855c4023fcc982 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 14 Apr 2025 09:05:16 -0500 Subject: [PATCH 102/114] chore: enhance parameter validation error messages (#379) * chore: implement validation in an exported function * chore: enhance parameter validation error messages --- provider/formtype.go | 28 +++++- provider/parameter.go | 197 +++++++++++++++++++++++++------------ provider/parameter_test.go | 4 +- 3 files changed, 159 insertions(+), 70 deletions(-) diff --git a/provider/formtype.go b/provider/formtype.go index 753f8945..75d32c46 100644 --- a/provider/formtype.go +++ b/provider/formtype.go @@ -115,9 +115,11 @@ var formTypeTruthTable = map[OptionType]map[bool][]ParameterFormType{ // The use case is when using multi-select. The options are 'string' and the // value is 'list(string)'. func ValidateFormType(paramType OptionType, optionCount int, specifiedFormType ParameterFormType) (OptionType, ParameterFormType, error) { - allowed, ok := formTypeTruthTable[paramType][optionCount > 0] + optionsExist := optionCount > 0 + allowed, ok := formTypeTruthTable[paramType][optionsExist] if !ok || len(allowed) == 0 { - return paramType, specifiedFormType, xerrors.Errorf("value type %q is not supported for 'form_types'", paramType) + // This error should really never be hit, as the provider sdk does an enum validation. + return paramType, specifiedFormType, xerrors.Errorf("\"type\" attribute=%q is not supported, choose one of %v", paramType, OptionTypes()) } if specifiedFormType == ParameterFormTypeDefault { @@ -126,7 +128,27 @@ func ValidateFormType(paramType OptionType, optionCount int, specifiedFormType P } if !slices.Contains(allowed, specifiedFormType) { - return paramType, specifiedFormType, xerrors.Errorf("value type %q is not supported for 'form_types'", specifiedFormType) + optionMsg := "" + opposite := formTypeTruthTable[paramType][!optionsExist] + + // This extra message tells a user if they are using a valid form_type + // for a 'type', but it is invalid because options do/do-not exist. + // It serves as a more helpful error message. + // + // Eg: form_type=slider is valid for type=number, but invalid if options exist. + // And this error message is more accurate than just saying "form_type=slider is + // not valid for type=number". + if slices.Contains(opposite, specifiedFormType) { + if optionsExist { + optionMsg = " when options exist" + } else { + optionMsg = " when options do not exist" + } + } + return paramType, specifiedFormType, + xerrors.Errorf("\"form_type\" attribute=%q is not supported for \"type\"=%q%s, choose one of %v", + specifiedFormType, paramType, + optionMsg, toStrings(allowed)) } // This is the only current special case. If 'multi-select' is selected, the type diff --git a/provider/parameter.go b/provider/parameter.go index 4eb8a282..2f7dc662 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -124,7 +124,7 @@ func parameterDataSource() *schema.Resource { } var value string if parameter.Default != "" { - err := valueIsType(parameter.Type, parameter.Default) + err := valueIsType(parameter.Type, parameter.Default, cty.Path{cty.GetAttrStep{Name: "default"}}) if err != nil { return err } @@ -144,6 +144,8 @@ func parameterDataSource() *schema.Resource { return diag.Errorf("ephemeral parameter requires the default property") } + // TODO: Should we move this into the Valid() function on + // Parameter? if len(parameter.Validation) == 1 { validation := ¶meter.Validation[0] err = validation.Valid(parameter.Type, value) @@ -153,73 +155,27 @@ func parameterDataSource() *schema.Resource { } // Validate options - - // optionType might differ from parameter.Type. This is ok, and parameter.Type - // should be used for the value type, and optionType for options. - var optionType OptionType - optionType, parameter.FormType, err = ValidateFormType(parameter.Type, len(parameter.Option), parameter.FormType) + _, parameter.FormType, err = ValidateFormType(parameter.Type, len(parameter.Option), parameter.FormType) if err != nil { - return diag.FromErr(err) + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "Invalid form_type for parameter", + Detail: err.Error(), + AttributePath: cty.Path{cty.GetAttrStep{Name: "form_type"}}, + }, + } } // Set the form_type back in case the value was changed. // Eg via a default. If a user does not specify, a default value // is used and saved. rd.Set("form_type", parameter.FormType) - if len(parameter.Option) > 0 { - names := map[string]interface{}{} - values := map[string]interface{}{} - for _, option := range parameter.Option { - _, exists := names[option.Name] - if exists { - return diag.Errorf("multiple options cannot have the same name %q", option.Name) - } - _, exists = values[option.Value] - if exists { - return diag.Errorf("multiple options cannot have the same value %q", option.Value) - } - err := valueIsType(optionType, option.Value) - if err != nil { - return err - } - values[option.Value] = nil - names[option.Name] = nil - } - - if parameter.Default != "" { - if parameter.Type == OptionTypeListString && optionType == OptionTypeString { - // If the type is list(string) and optionType is string, we have - // to ensure all elements of the default exist as options. - var defaultValues []string - // TODO: We do this unmarshal in a few spots. It should be standardized. - err = json.Unmarshal([]byte(parameter.Default), &defaultValues) - if err != nil { - return diag.Errorf("default value %q is not a list of strings", parameter.Default) - } - - // missing is used to construct a more helpful error message - var missing []string - for _, defaultValue := range defaultValues { - _, defaultIsValid := values[defaultValue] - if !defaultIsValid { - missing = append(missing, defaultValue) - } - } - - if len(missing) > 0 { - return diag.Errorf( - "default value %q is not a valid option, values %q are missing from the option", - parameter.Default, strings.Join(missing, ", "), - ) - } - } else { - _, defaultIsValid := values[parameter.Default] - if !defaultIsValid { - return diag.Errorf("%q default value %q must be defined as one of options", parameter.FormType, parameter.Default) - } - } - } + diags := parameter.Valid() + if diags.HasError() { + return diags } + return nil }, Schema: map[string]*schema.Schema{ @@ -433,7 +389,7 @@ func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (int return vArr, nil } -func valueIsType(typ OptionType, value string) diag.Diagnostics { +func valueIsType(typ OptionType, value string, attrPath cty.Path) diag.Diagnostics { switch typ { case OptionTypeNumber: _, err := strconv.ParseFloat(value, 64) @@ -446,10 +402,9 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics { return diag.Errorf("%q is not a bool", value) } case OptionTypeListString: - var items []string - err := json.Unmarshal([]byte(value), &items) - if err != nil { - return diag.Errorf("%q is not an array of strings", value) + _, diags := valueIsListString(value, attrPath) + if diags.HasError() { + return diags } case OptionTypeString: // Anything is a string! @@ -459,6 +414,102 @@ func valueIsType(typ OptionType, value string) diag.Diagnostics { return nil } +func (v *Parameter) Valid() diag.Diagnostics { + // optionType might differ from parameter.Type. This is ok, and parameter.Type + // should be used for the value type, and optionType for options. + optionType, _, err := ValidateFormType(v.Type, len(v.Option), v.FormType) + if err != nil { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "Invalid form_type for parameter", + Detail: err.Error(), + AttributePath: cty.Path{cty.GetAttrStep{Name: "form_type"}}, + }, + } + } + + optionNames := map[string]any{} + optionValues := map[string]any{} + if len(v.Option) > 0 { + for _, option := range v.Option { + _, exists := optionNames[option.Name] + if exists { + return diag.Diagnostics{{ + Severity: diag.Error, + Summary: "Option names must be unique.", + Detail: fmt.Sprintf("multiple options found with the same name %q", option.Name), + }, + } + } + _, exists = optionValues[option.Value] + if exists { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "Option values must be unique.", + Detail: fmt.Sprintf("multiple options found with the same value %q", option.Value), + }, + } + } + diags := valueIsType(optionType, option.Value, cty.Path{}) + if diags.HasError() { + return diags + } + optionValues[option.Value] = nil + optionNames[option.Name] = nil + } + } + + if v.Default != "" && len(v.Option) > 0 { + if v.Type == OptionTypeListString && optionType == OptionTypeString { + // If the type is list(string) and optionType is string, we have + // to ensure all elements of the default exist as options. + defaultValues, diags := valueIsListString(v.Default, cty.Path{cty.GetAttrStep{Name: "default"}}) + if diags.HasError() { + return diags + } + + // missing is used to construct a more helpful error message + var missing []string + for _, defaultValue := range defaultValues { + _, defaultIsValid := optionValues[defaultValue] + if !defaultIsValid { + missing = append(missing, defaultValue) + } + } + + if len(missing) > 0 { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "Default values must be a valid option", + Detail: fmt.Sprintf( + "default value %q is not a valid option, values %q are missing from the options", + v.Default, strings.Join(missing, ", "), + ), + AttributePath: cty.Path{cty.GetAttrStep{Name: "default"}}, + }, + } + } + } else { + _, defaultIsValid := optionValues[v.Default] + if !defaultIsValid { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "Default value must be a valid option", + Detail: fmt.Sprintf("the value %q must be defined as one of options", v.Default), + AttributePath: cty.Path{cty.GetAttrStep{Name: "default"}}, + }, + } + } + } + } + + return nil +} + func (v *Validation) Valid(typ OptionType, value string) error { if typ != OptionTypeNumber { if !v.MinDisabled { @@ -519,6 +570,22 @@ func (v *Validation) Valid(typ OptionType, value string) error { return nil } +func valueIsListString(value string, path cty.Path) ([]string, diag.Diagnostics) { + var items []string + err := json.Unmarshal([]byte(value), &items) + if err != nil { + return nil, diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "When using list(string) type, value must be a json encoded list of strings", + Detail: fmt.Sprintf("value %q is not a valid list of strings", value), + AttributePath: path, + }, + } + } + return items, nil +} + // ParameterEnvironmentVariable returns the environment variable to specify for // a parameter by it's name. It's hashed because spaces and special characters // can be used in parameter names that may not be valid in env vars. diff --git a/provider/parameter_test.go b/provider/parameter_test.go index f817280e..4b52d943 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -291,7 +291,7 @@ func TestParameter(t *testing.T) { } } `, - ExpectError: regexp.MustCompile("cannot have the same name"), + ExpectError: regexp.MustCompile("Option names must be unique"), }, { Name: "DuplicateOptionValue", Config: ` @@ -308,7 +308,7 @@ func TestParameter(t *testing.T) { } } `, - ExpectError: regexp.MustCompile("cannot have the same value"), + ExpectError: regexp.MustCompile("Option values must be unique"), }, { Name: "RequiredParameterNoDefault", Config: ` From e40c9b9278ad0a7574bbeb067e2119f850b0df5c Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 15 Apr 2025 10:54:10 +0100 Subject: [PATCH 103/114] chore: update to go 1.24.2 (#382) Co-authored-by: Danny Kopping --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 02ea7137..993ba093 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/coder/terraform-provider-coder/v2 -go 1.22.9 +go 1.24.2 require ( github.com/docker/docker v26.1.5+incompatible From e51ae3aff8c4c0c3c0559841fabb59eafea1f86a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 10:59:50 +0100 Subject: [PATCH 104/114] build(deps): Bump golang.org/x/crypto from 0.33.0 to 0.35.0 (#380) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.33.0 to 0.35.0. - [Commits](https://github.com/golang/crypto/compare/v0.33.0...v0.35.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.35.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 993ba093..cb415c2b 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect - golang.org/x/crypto v0.33.0 // indirect + golang.org/x/crypto v0.35.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 2b5ad9c5..870d770d 100644 --- a/go.sum +++ b/go.sum @@ -225,8 +225,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= From c86bb5c3ddcd0735fefe644b264c04f9ecafbd4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:02:58 +0100 Subject: [PATCH 105/114] build(deps): Bump golang.org/x/net from 0.34.0 to 0.38.0 (#383) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.34.0 to 0.38.0. - [Commits](https://github.com/golang/net/compare/v0.34.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index cb415c2b..848423f4 100644 --- a/go.mod +++ b/go.mod @@ -79,11 +79,11 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect - golang.org/x/crypto v0.35.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 870d770d..77c61771 100644 --- a/go.sum +++ b/go.sum @@ -225,8 +225,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -240,15 +240,15 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 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= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -263,8 +263,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -272,8 +272,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From f871a43ee9a2615a195da72821148a2045ff0d6d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 23 Apr 2025 08:44:20 -0500 Subject: [PATCH 106/114] test: rbac role test assertion to handle site wide roles (#385) Site wide roles have empty string org_ids --- integration/integration_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/integration_test.go b/integration/integration_test.go index 9803aa41..a5019635 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -181,7 +181,8 @@ func TestIntegration(t *testing.T) { "workspace_owner.ssh_private_key": `(?s)^.+?BEGIN OPENSSH PRIVATE KEY.+?END OPENSSH PRIVATE KEY.+?$`, "workspace_owner.ssh_public_key": `(?s)^ssh-ed25519.+$`, "workspace_owner.login_type": `password`, - "workspace_owner.rbac_roles": `(?is)\[(\{"name":"[a-z0-9-:]+","org_id":"[a-f0-9-]+"\},?)+\]`, + // org_id will either be a uuid or an empty string for site wide roles. + "workspace_owner.rbac_roles": `(?is)\[(\{"name":"[a-z0-9-:]+","org_id":"[a-f0-9-]*"\},?)+\]`, }, }, { From ac2f9bf28b20a387661cf9f45444a5ceda0dc689 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 30 Apr 2025 14:30:36 -0500 Subject: [PATCH 107/114] test: unit test to document validation behavior of parameters (#387) Documenting validation behavior with a unit test --- provider/parameter_test.go | 232 +++++++++++++++++++++++++++ provider/testdata/parameter_table.md | 70 ++++++++ 2 files changed, 302 insertions(+) create mode 100644 provider/testdata/parameter_table.md diff --git a/provider/parameter_test.go b/provider/parameter_test.go index 4b52d943..37a46796 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -2,7 +2,9 @@ package provider_test import ( "fmt" + "os" "regexp" + "strconv" "strings" "testing" @@ -686,6 +688,217 @@ data "coder_parameter" "region" { } } +// TestParameterValidationEnforcement tests various parameter states and the +// validation enforcement that should be applied to them. The table is described +// by a markdown table. This is done so that the test cases can be more easily +// edited and read. +// +// Copy and paste the table to https://www.tablesgenerator.com/markdown_tables for easier editing +// +//nolint:paralleltest,tparallel // Parameters load values from env vars +func TestParameterValidationEnforcement(t *testing.T) { + // Some interesting observations: + // - Validation logic does not apply to the value of 'options' + // - [NumDefInvOpt] So an invalid option can be present and selected, but would fail + // - Validation logic does not apply to the default if a value is given + // - [NumIns/DefInv] So the default can be invalid if an input value is valid. + // The value is therefore not really optional, but it is marked as such. + // - [NumInsNotOptsVal | NumsInsNotOpts] values do not need to be in the option set? + table, err := os.ReadFile("testdata/parameter_table.md") + require.NoError(t, err) + + type row struct { + Name string + Types []string + InputValue string + Default string + Options []string + Validation *provider.Validation + OutputValue string + Optional bool + Error *regexp.Regexp + } + + rows := make([]row, 0) + lines := strings.Split(string(table), "\n") + validMinMax := regexp.MustCompile("^[0-9]*-[0-9]*$") + for _, line := range lines[2:] { + columns := strings.Split(line, "|") + columns = columns[1 : len(columns)-1] + for i := range columns { + // Trim the whitespace from all columns + columns[i] = strings.TrimSpace(columns[i]) + } + + if columns[0] == "" { + continue // Skip rows with empty names + } + + optional, err := strconv.ParseBool(columns[8]) + if columns[8] != "" { + // Value does not matter if not specified + require.NoError(t, err) + } + + var rerr *regexp.Regexp + if columns[9] != "" { + rerr, err = regexp.Compile(columns[9]) + if err != nil { + t.Fatalf("failed to parse error column %q: %v", columns[9], err) + } + } + var options []string + if columns[4] != "" { + options = strings.Split(columns[4], ",") + } + + var validation *provider.Validation + if columns[5] != "" { + // Min-Max validation should look like: + // 1-10 :: min=1, max=10 + // -10 :: max=10 + // 1- :: min=1 + if validMinMax.MatchString(columns[5]) { + parts := strings.Split(columns[5], "-") + min, _ := strconv.ParseInt(parts[0], 10, 64) + max, _ := strconv.ParseInt(parts[1], 10, 64) + validation = &provider.Validation{ + Min: int(min), + MinDisabled: parts[0] == "", + Max: int(max), + MaxDisabled: parts[1] == "", + Monotonic: "", + Regex: "", + Error: "{min} < {value} < {max}", + } + } else { + validation = &provider.Validation{ + Min: 0, + MinDisabled: true, + Max: 0, + MaxDisabled: true, + Monotonic: "", + Regex: columns[5], + Error: "regex error", + } + } + } + + rows = append(rows, row{ + Name: columns[0], + Types: strings.Split(columns[1], ","), + InputValue: columns[2], + Default: columns[3], + Options: options, + Validation: validation, + OutputValue: columns[7], + Optional: optional, + Error: rerr, + }) + } + + stringLiteral := func(s string) string { + if s == "" { + return `""` + } + return fmt.Sprintf("%q", s) + } + + for rowIndex, row := range rows { + for _, rt := range row.Types { + //nolint:paralleltest,tparallel // Parameters load values from env vars + t.Run(fmt.Sprintf("%d|%s:%s", rowIndex, row.Name, rt), func(t *testing.T) { + if row.InputValue != "" { + t.Setenv(provider.ParameterEnvironmentVariable("parameter"), row.InputValue) + } + + if row.Error != nil { + if row.OutputValue != "" { + t.Errorf("output value %q should not be set if error is set", row.OutputValue) + } + } + + var cfg strings.Builder + cfg.WriteString("data \"coder_parameter\" \"parameter\" {\n") + cfg.WriteString("\tname = \"parameter\"\n") + if rt == "multi-select" || rt == "tag-select" { + cfg.WriteString(fmt.Sprintf("\ttype = \"%s\"\n", "list(string)")) + cfg.WriteString(fmt.Sprintf("\tform_type = \"%s\"\n", rt)) + } else { + cfg.WriteString(fmt.Sprintf("\ttype = \"%s\"\n", rt)) + } + if row.Default != "" { + cfg.WriteString(fmt.Sprintf("\tdefault = %s\n", stringLiteral(row.Default))) + } + + for _, opt := range row.Options { + cfg.WriteString("\toption {\n") + cfg.WriteString(fmt.Sprintf("\t\tname = %s\n", stringLiteral(opt))) + cfg.WriteString(fmt.Sprintf("\t\tvalue = %s\n", stringLiteral(opt))) + cfg.WriteString("\t}\n") + } + + if row.Validation != nil { + cfg.WriteString("\tvalidation {\n") + if !row.Validation.MinDisabled { + cfg.WriteString(fmt.Sprintf("\t\tmin = %d\n", row.Validation.Min)) + } + if !row.Validation.MaxDisabled { + cfg.WriteString(fmt.Sprintf("\t\tmax = %d\n", row.Validation.Max)) + } + if row.Validation.Monotonic != "" { + cfg.WriteString(fmt.Sprintf("\t\tmonotonic = \"%s\"\n", row.Validation.Monotonic)) + } + if row.Validation.Regex != "" { + cfg.WriteString(fmt.Sprintf("\t\tregex = %q\n", row.Validation.Regex)) + } + cfg.WriteString(fmt.Sprintf("\t\terror = %q\n", row.Validation.Error)) + cfg.WriteString("\t}\n") + } + + cfg.WriteString("}\n") + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: cfg.String(), + ExpectError: row.Error, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + param := state.Modules[0].Resources["data.coder_parameter.parameter"] + require.NotNil(t, param) + + if row.Default == "" { + _, ok := param.Primary.Attributes["default"] + require.False(t, ok, "default should not be set") + } else { + require.Equal(t, strings.Trim(row.Default, `"`), param.Primary.Attributes["default"]) + } + + if row.OutputValue == "" { + _, ok := param.Primary.Attributes["value"] + require.False(t, ok, "output value should not be set") + } else { + require.Equal(t, strings.Trim(row.OutputValue, `"`), param.Primary.Attributes["value"]) + } + + for key, expected := range map[string]string{ + "optional": strconv.FormatBool(row.Optional), + } { + require.Equal(t, expected, param.Primary.Attributes[key], "optional") + } + + return nil + }, + }}, + }) + }) + } + } +} + func TestValueValidatesType(t *testing.T) { t.Parallel() for _, tc := range []struct { @@ -798,6 +1011,25 @@ func TestValueValidatesType(t *testing.T) { Value: `[]`, MinDisabled: true, MaxDisabled: true, + }, { + Name: "ValidListOfStrings", + Type: "list(string)", + Value: `["first","second","third"]`, + MinDisabled: true, + MaxDisabled: true, + }, { + Name: "InvalidListOfStrings", + Type: "list(string)", + Value: `["first","second","third"`, + MinDisabled: true, + MaxDisabled: true, + Error: regexp.MustCompile("is not valid list of strings"), + }, { + Name: "EmptyListOfStrings", + Type: "list(string)", + Value: `[]`, + MinDisabled: true, + MaxDisabled: true, }} { tc := tc t.Run(tc.Name, func(t *testing.T) { diff --git a/provider/testdata/parameter_table.md b/provider/testdata/parameter_table.md new file mode 100644 index 00000000..4c9ee458 --- /dev/null +++ b/provider/testdata/parameter_table.md @@ -0,0 +1,70 @@ +| Name | Type | Input | Default | Options | Validation | -> | Output | Optional | Error | +|----------------------|---------------|-----------|---------|-------------------|------------|----|--------|----------|--------------| +| | Empty Vals | | | | | | | | | +| Empty | string,number | | | | | | "" | false | | +| EmptyList | list(string) | | | | | | "" | false | | +| EmptyMulti | tag-select | | | | | | "" | false | | +| EmptyOpts | string,number | | | 1,2,3 | | | "" | false | | +| EmptyRegex | string | | | | world | | | | regex error | +| EmptyMin | number | | | | 1-10 | | | | 1 < < 10 | +| EmptyMinOpt | number | | | 1,2,3 | 2-5 | | | | 2 < < 5 | +| EmptyRegexOpt | string | | | "hello","goodbye" | goodbye | | | | regex error | +| EmptyRegexOk | string | | | | .* | | "" | false | | +| | | | | | | | | | | +| | Default Set | No inputs | | | | | | | | +| NumDef | number | | 5 | | | | 5 | true | | +| NumDefVal | number | | 5 | | 3-7 | | 5 | true | | +| NumDefInv | number | | 5 | | 10- | | | | 10 < 5 < 0 | +| NumDefOpts | number | | 5 | 1,3,5,7 | 2-6 | | 5 | true | | +| NumDefNotOpts | number | | 5 | 1,3,7,9 | 2-6 | | | | valid option | +| NumDefInvOpt | number | | 5 | 1,3,5,7 | 6-10 | | | | 6 < 5 < 10 | +| | | | | | | | | | | +| StrDef | string | | hello | | | | hello | true | | +| StrDefInv | string | | hello | | world | | | | regex error | +| StrDefOpts | string | | a | a,b,c | | | a | true | | +| StrDefNotOpts | string | | a | b,c,d | | | | | valid option | +| StrDefValOpts | string | | a | a,b,c,d,e,f | [a-c] | | a | true | | +| StrDefInvOpt | string | | d | a,b,c,d,e,f | [a-c] | | | | regex error | +| | | | | | | | | | | +| LStrDef | list(string) | | ["a"] | | | | ["a"] | true | | +| LStrDefOpts | list(string) | | ["a"] | ["a"], ["b"] | | | ["a"] | true | | +| LStrDefNotOpts | list(string) | | ["a"] | ["b"], ["c"] | | | | | valid option | +| | | | | | | | | | | +| MulDef | tag-select | | ["a"] | | | | ["a"] | true | | +| MulDefOpts | multi-select | | ["a"] | a,b | | | ["a"] | true | | +| MulDefNotOpts | multi-select | | ["a"] | b,c | | | | | valid option | +| | | | | | | | | | | +| | Input Vals | | | | | | | | | +| NumIns | number | 3 | | | | | 3 | false | | +| NumInsDef | number | 3 | 5 | | | | 3 | true | | +| NumIns/DefInv | number | 3 | 5 | | 1-3 | | 3 | true | | +| NumIns=DefInv | number | 5 | 5 | | 1-3 | | | | 1 < 5 < 3 | +| NumInsOpts | number | 3 | 5 | 1,2,3,4,5 | 1-3 | | 3 | true | | +| NumInsNotOptsVal | number | 3 | 5 | 1,2,4,5 | 1-3 | | 3 | true | | +| NumInsNotOptsInv | number | 3 | 5 | 1,2,4,5 | 1-2 | | | true | 1 < 3 < 2 | +| NumInsNotOpts | number | 3 | 5 | 1,2,4,5 | | | 3 | true | | +| NumInsNotOpts/NoDef | number | 3 | | 1,2,4,5 | | | 3 | false | | +| | | | | | | | | | | +| StrIns | string | c | | | | | c | false | | +| StrInsDef | string | c | e | | | | c | true | | +| StrIns/DefInv | string | c | e | | [a-c] | | c | true | | +| StrIns=DefInv | string | e | e | | [a-c] | | | | regex error | +| StrInsOpts | string | c | e | a,b,c,d,e | [a-c] | | c | true | | +| StrInsNotOptsVal | string | c | e | a,b,d,e | [a-c] | | c | true | | +| StrInsNotOptsInv | string | c | e | a,b,d,e | [a-b] | | | | regex error | +| StrInsNotOpts | string | c | e | a,b,d,e | | | c | true | | +| StrInsNotOpts/NoDef | string | c | | a,b,d,e | | | c | false | | +| StrInsBadVal | string | c | | a,b,c,d,e | 1-10 | | | | min cannot | +| | | | | | | | | | | +| | list(string) | | | | | | | | | +| LStrIns | list(string) | ["c"] | | | | | ["c"] | false | | +| LStrInsDef | list(string) | ["c"] | ["e"] | | | | ["c"] | true | | +| LStrIns/DefInv | list(string) | ["c"] | ["e"] | | [a-c] | | | | regex cannot | +| LStrInsOpts | list(string) | ["c"] | ["e"] | ["c"],["d"],["e"] | | | ["c"] | true | | +| LStrInsNotOpts | list(string) | ["c"] | ["e"] | ["d"],["e"] | | | ["c"] | true | | +| LStrInsNotOpts/NoDef | list(string) | ["c"] | | ["d"],["e"] | | | ["c"] | false | | +| | | | | | | | | | | +| MulInsOpts | multi-select | ["c"] | ["e"] | c,d,e | | | ["c"] | true | | +| MulInsNotOpts | multi-select | ["c"] | ["e"] | d,e | | | ["c"] | true | | +| MulInsNotOpts/NoDef | multi-select | ["c"] | | d,e | | | ["c"] | false | | +| MulInsInvOpts | multi-select | ["c"] | ["e"] | c,d,e | [a-c] | | | | regex cannot | \ No newline at end of file From 4e7da25b24d24b43a0540296a2c50b80002b722a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 30 Apr 2025 16:30:22 -0500 Subject: [PATCH 108/114] test: add more validation test cases (#388) Some of these cases you would expect it to be an error, but the validation passes --- provider/parameter_test.go | 3 +++ provider/testdata/parameter_table.md | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/provider/parameter_test.go b/provider/parameter_test.go index 37a46796..32877c2b 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -704,6 +704,9 @@ func TestParameterValidationEnforcement(t *testing.T) { // - [NumIns/DefInv] So the default can be invalid if an input value is valid. // The value is therefore not really optional, but it is marked as such. // - [NumInsNotOptsVal | NumsInsNotOpts] values do not need to be in the option set? + // - [NumInsNotNum] number params do not require the value to be a number + // - [LStrInsNotList] list(string) do not require the value to be a list(string) + // - Same with [MulInsNotListOpts] table, err := os.ReadFile("testdata/parameter_table.md") require.NoError(t, err) diff --git a/provider/testdata/parameter_table.md b/provider/testdata/parameter_table.md index 4c9ee458..f7645efa 100644 --- a/provider/testdata/parameter_table.md +++ b/provider/testdata/parameter_table.md @@ -2,7 +2,9 @@ |----------------------|---------------|-----------|---------|-------------------|------------|----|--------|----------|--------------| | | Empty Vals | | | | | | | | | | Empty | string,number | | | | | | "" | false | | +| EmptyDupeOps | string,number | | | 1,1,1 | | | | | unique | | EmptyList | list(string) | | | | | | "" | false | | +| EmptyListDupeOpts | list(string) | | | ["a"],["a"] | | | | | unique | | EmptyMulti | tag-select | | | | | | "" | false | | | EmptyOpts | string,number | | | 1,2,3 | | | "" | false | | | EmptyRegex | string | | | | world | | | | regex error | @@ -18,6 +20,8 @@ | NumDefOpts | number | | 5 | 1,3,5,7 | 2-6 | | 5 | true | | | NumDefNotOpts | number | | 5 | 1,3,7,9 | 2-6 | | | | valid option | | NumDefInvOpt | number | | 5 | 1,3,5,7 | 6-10 | | | | 6 < 5 < 10 | +| NumDefNotNum | number | | a | | | | | | a number | +| NumDefOptsNotNum | number | | 1 | 1,a,2 | | | | | a number | | | | | | | | | | | | | StrDef | string | | hello | | | | hello | true | | | StrDefInv | string | | hello | | world | | | | regex error | @@ -36,6 +40,8 @@ | | | | | | | | | | | | | Input Vals | | | | | | | | | | NumIns | number | 3 | | | | | 3 | false | | +| NumInsNotNum | number | a | | | | | a | false | | +| NumInsNotNumInv | number | a | | | 1-3 | | | | 1 < a < 3 | | NumInsDef | number | 3 | 5 | | | | 3 | true | | | NumIns/DefInv | number | 3 | 5 | | 1-3 | | 3 | true | | | NumIns=DefInv | number | 5 | 5 | | 1-3 | | | | 1 < 5 < 3 | @@ -46,6 +52,7 @@ | NumInsNotOpts/NoDef | number | 3 | | 1,2,4,5 | | | 3 | false | | | | | | | | | | | | | | StrIns | string | c | | | | | c | false | | +| StrInsDupeOpts | string | c | | a,b,c,c | | | | | unique | | StrInsDef | string | c | e | | | | c | true | | | StrIns/DefInv | string | c | e | | [a-c] | | c | true | | | StrIns=DefInv | string | e | e | | [a-c] | | | | regex error | @@ -58,6 +65,7 @@ | | | | | | | | | | | | | list(string) | | | | | | | | | | LStrIns | list(string) | ["c"] | | | | | ["c"] | false | | +| LStrInsNotList | list(string) | c | | | | | c | false | | | LStrInsDef | list(string) | ["c"] | ["e"] | | | | ["c"] | true | | | LStrIns/DefInv | list(string) | ["c"] | ["e"] | | [a-c] | | | | regex cannot | | LStrInsOpts | list(string) | ["c"] | ["e"] | ["c"],["d"],["e"] | | | ["c"] | true | | @@ -65,6 +73,7 @@ | LStrInsNotOpts/NoDef | list(string) | ["c"] | | ["d"],["e"] | | | ["c"] | false | | | | | | | | | | | | | | MulInsOpts | multi-select | ["c"] | ["e"] | c,d,e | | | ["c"] | true | | +| MulInsNotListOpts | multi-select | c | ["e"] | c,d,e | | | c | true | | | MulInsNotOpts | multi-select | ["c"] | ["e"] | d,e | | | ["c"] | true | | | MulInsNotOpts/NoDef | multi-select | ["c"] | | d,e | | | ["c"] | false | | | MulInsInvOpts | multi-select | ["c"] | ["e"] | c,d,e | [a-c] | | | | regex cannot | \ No newline at end of file From 5648efbf6db0e02bd7cf79386650ade6cf923681 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Fri, 2 May 2025 08:57:05 +0200 Subject: [PATCH 109/114] feat: reuse agent tokens when a prebuilt agent reinitializes (#374) * feat: allow presets to define prebuilds * document prebuild parameters * remove todo * make gen * feat: reuse agent tokens when a prebuilt agent reinitializes * WIP: get agent.go ready to be merged with support for prebuilds * fix: ensure the agent token is reused for prebuilds * lint and make gen * simplify function * test: rbac role test assertion to handle site wide roles Site wide roles have empty string org_ids --------- Co-authored-by: Steven Masley --- go.mod | 2 +- provider/agent.go | 51 ++++++++++++++++++++++++++++++++++++++----- provider/workspace.go | 28 +++++++++++++++++++----- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 848423f4..2d3db5d0 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/docker/docker v26.1.5+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 + github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 @@ -50,7 +51,6 @@ require ( github.com/hashicorp/terraform-exec v0.22.0 // indirect github.com/hashicorp/terraform-json v0.24.0 // indirect github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.4 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect diff --git a/provider/agent.go b/provider/agent.go index 3ddae235..ad264030 100644 --- a/provider/agent.go +++ b/provider/agent.go @@ -2,6 +2,8 @@ package provider import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" "path/filepath" "reflect" @@ -9,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -22,10 +25,12 @@ func agentResource() *schema.Resource { SchemaVersion: 1, Description: "Use this resource to associate an agent.", - CreateContext: func(_ context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { - // This should be a real authentication token! - resourceData.SetId(uuid.NewString()) - err := resourceData.Set("token", uuid.NewString()) + CreateContext: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { + agentID := uuid.NewString() + resourceData.SetId(agentID) + + token := agentAuthToken(ctx, "") + err := resourceData.Set("token", token) if err != nil { return diag.FromErr(err) } @@ -48,10 +53,12 @@ func agentResource() *schema.Resource { return updateInitScript(resourceData, i) }, ReadWithoutTimeout: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics { - err := resourceData.Set("token", uuid.NewString()) + token := agentAuthToken(ctx, "") + err := resourceData.Set("token", token) if err != nil { return diag.FromErr(err) } + if _, ok := resourceData.GetOk("display_apps"); !ok { err = resourceData.Set("display_apps", []interface{}{ map[string]bool{ @@ -469,3 +476,37 @@ func updateInitScript(resourceData *schema.ResourceData, i interface{}) diag.Dia } return nil } + +func agentAuthToken(ctx context.Context, agentID string) string { + existingToken := helpers.OptionalEnv(RunningAgentTokenEnvironmentVariable(agentID)) + if existingToken == "" { + // Most of the time, we will generate a new token for the agent. + // In the case of a prebuilt workspace being claimed, we will override with + // an existing token provided below. + token := uuid.NewString() + return token + } + + // An existing token was provided for this agent. That means that this + // is a prebuilt workspace in the process of being claimed. + // We should reuse the token. + tflog.Info(ctx, "using provided agent token for prebuild", map[string]interface{}{ + "agent_id": agentID, + }) + return existingToken +} + +// RunningAgentTokenEnvironmentVariable returns the name of an environment variable +// that contains the token to use for the running agent. This is used for prebuilds, +// where we want to reuse the same token for the next iteration of a workspace agent +// before and after the workspace was claimed by a user. +// +// By reusing an existing token, we can avoid the need to change a value that may have been +// used immutably. Thus, allowing us to avoid reprovisioning resources that may take a long time +// to replace. +// +// agentID is unused for now, but will be used as soon as we support multiple agents. +func RunningAgentTokenEnvironmentVariable(agentID string) string { + sum := sha256.Sum256([]byte(agentID)) + return "CODER_RUNNING_WORKSPACE_AGENT_TOKEN_" + hex.EncodeToString(sum[:]) +} diff --git a/provider/workspace.go b/provider/workspace.go index 5ddd3ee8..c477fad6 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -27,13 +27,13 @@ func workspaceDataSource() *schema.Resource { } _ = rd.Set("start_count", count) - prebuild := helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) - prebuildCount := 0 - if prebuild == "true" { - prebuildCount = 1 + if isPrebuiltWorkspace() { + _ = rd.Set("prebuild_count", 1) _ = rd.Set("is_prebuild", true) + } else { + _ = rd.Set("prebuild_count", 0) + _ = rd.Set("is_prebuild", false) } - _ = rd.Set("prebuild_count", prebuildCount) name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default") rd.Set("name", name) @@ -140,6 +140,24 @@ func workspaceDataSource() *schema.Resource { } } +// isPrebuiltWorkspace returns true if the workspace is an unclaimed prebuilt workspace. +func isPrebuiltWorkspace() bool { + return helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) == "true" +} + +// IsPrebuildEnvironmentVariable returns the name of the environment variable that +// indicates whether the workspace is an unclaimed prebuilt workspace. +// +// Knowing whether the workspace is an unclaimed prebuilt workspace allows template +// authors to conditionally execute code in the template based on whether the workspace +// has been assigned to a user or not. This allows identity specific configuration to +// be applied only after the workspace is claimed, while the rest of the workspace can +// be pre-configured. +// +// The value of this environment variable should be set to "true" if the workspace is prebuilt +// and it has not yet been claimed by a user. Any other values, including "false" +// and "" will be interpreted to mean that the workspace is not prebuilt, or was +// prebuilt but has since been claimed by a user. func IsPrebuildEnvironmentVariable() string { return "CODER_WORKSPACE_IS_PREBUILD" } From 21147bf3e4f814ce804be47204501d06740fa7dd Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 2 May 2025 10:08:37 -0500 Subject: [PATCH 110/114] test: fix concurrent map write of TestValidateFormType (#390) --- provider/formtype_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/provider/formtype_test.go b/provider/formtype_test.go index 599c2e41..eaf7b587 100644 --- a/provider/formtype_test.go +++ b/provider/formtype_test.go @@ -6,6 +6,7 @@ import ( "regexp" "strconv" "strings" + "sync" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -53,7 +54,7 @@ func TestValidateFormType(t *testing.T) { // formTypesChecked keeps track of all checks run. It will be used to // ensure all combinations of form_type and option_type are tested. // All untested options are assumed to throw an error. - formTypesChecked := make(map[string]struct{}) + var formTypesChecked sync.Map expectType := func(expected provider.ParameterFormType, opts formTypeCheck) formTypeTestCase { ftname := opts.formType @@ -240,12 +241,12 @@ func TestValidateFormType(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { t.Parallel() - if _, ok := formTypesChecked[c.config.String()]; ok { + if _, ok := formTypesChecked.Load(c.config.String()); ok { t.Log("Duplicated form type check, delete this extra test case") t.Fatalf("form type %q already checked", c.config.String()) } - formTypesChecked[c.config.String()] = struct{}{} + formTypesChecked.Store(c.config.String(), struct{}{}) formTypeTest(t, c) }) } @@ -282,7 +283,7 @@ func TestValidateFormType(t *testing.T) { } for _, check := range requiredChecks { - if _, alreadyChecked := formTypesChecked[check.String()]; alreadyChecked { + if _, alreadyChecked := formTypesChecked.Load(check.String()); alreadyChecked { continue } From 3c748041033c3f80effb41fc7c9120f01003d5bd Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 7 May 2025 08:01:02 -0500 Subject: [PATCH 111/114] feat: parameter validation refactored to single function (#381) Validation bugs resolved: - Number parameters require valid number strings - Input values must be in the option set --- provider/parameter.go | 298 +++++++++++++++++---------- provider/parameter_test.go | 187 +++++++++++++++-- provider/testdata/parameter_table.md | 159 +++++++------- 3 files changed, 440 insertions(+), 204 deletions(-) diff --git a/provider/parameter.go b/provider/parameter.go index 2f7dc662..fd484578 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -21,6 +21,10 @@ import ( "golang.org/x/xerrors" ) +var ( + defaultValuePath = cty.Path{cty.GetAttrStep{Name: "default"}} +) + type Option struct { Name string Description string @@ -46,14 +50,13 @@ const ( ) type Parameter struct { - Value string Name string DisplayName string `mapstructure:"display_name"` Description string Type OptionType FormType ParameterFormType Mutable bool - Default string + Default *string Icon string Option []Option Validation []Validation @@ -82,7 +85,6 @@ func parameterDataSource() *schema.Resource { var parameter Parameter err = mapstructure.Decode(struct { - Value interface{} Name interface{} DisplayName interface{} Description interface{} @@ -97,17 +99,22 @@ func parameterDataSource() *schema.Resource { Order interface{} Ephemeral interface{} }{ - Value: rd.Get("value"), Name: rd.Get("name"), DisplayName: rd.Get("display_name"), Description: rd.Get("description"), Type: rd.Get("type"), FormType: rd.Get("form_type"), Mutable: rd.Get("mutable"), - Default: rd.Get("default"), - Icon: rd.Get("icon"), - Option: rd.Get("option"), - Validation: fixedValidation, + Default: func() *string { + if rd.GetRawConfig().AsValueMap()["default"].IsNull() { + return nil + } + val, _ := rd.Get("default").(string) + return &val + }(), + Icon: rd.Get("icon"), + Option: rd.Get("option"), + Validation: fixedValidation, Optional: func() bool { // This hack allows for checking if the "default" field is present in the .tf file. // If "default" is missing or is "null", then it means that this field is required, @@ -122,19 +129,6 @@ func parameterDataSource() *schema.Resource { if err != nil { return diag.Errorf("decode parameter: %s", err) } - var value string - if parameter.Default != "" { - err := valueIsType(parameter.Type, parameter.Default, cty.Path{cty.GetAttrStep{Name: "default"}}) - if err != nil { - return err - } - value = parameter.Default - } - envValue, ok := os.LookupEnv(ParameterEnvironmentVariable(parameter.Name)) - if ok { - value = envValue - } - rd.Set("value", value) if !parameter.Mutable && parameter.Ephemeral { return diag.Errorf("parameter can't be immutable and ephemeral") @@ -144,38 +138,25 @@ func parameterDataSource() *schema.Resource { return diag.Errorf("ephemeral parameter requires the default property") } - // TODO: Should we move this into the Valid() function on - // Parameter? - if len(parameter.Validation) == 1 { - validation := ¶meter.Validation[0] - err = validation.Valid(parameter.Type, value) - if err != nil { - return diag.FromErr(err) - } - } - - // Validate options - _, parameter.FormType, err = ValidateFormType(parameter.Type, len(parameter.Option), parameter.FormType) - if err != nil { - return diag.Diagnostics{ - { - Severity: diag.Error, - Summary: "Invalid form_type for parameter", - Detail: err.Error(), - AttributePath: cty.Path{cty.GetAttrStep{Name: "form_type"}}, - }, - } + var input *string + envValue, ok := os.LookupEnv(ParameterEnvironmentVariable(parameter.Name)) + if ok { + input = &envValue } - // Set the form_type back in case the value was changed. - // Eg via a default. If a user does not specify, a default value - // is used and saved. - rd.Set("form_type", parameter.FormType) - diags := parameter.Valid() + value, diags := parameter.ValidateInput(input) if diags.HasError() { return diags } + // Always set back the value, as it can be sourced from the default + rd.Set("value", value) + + // Set the form_type, as if it was unset, a default form_type will be updated on + // the parameter struct. Always set back the updated form_type to be more + // specific than the default empty string. + rd.Set("form_type", parameter.FormType) + return nil }, Schema: map[string]*schema.Schema{ @@ -389,37 +370,49 @@ func fixValidationResourceData(rawConfig cty.Value, validation interface{}) (int return vArr, nil } -func valueIsType(typ OptionType, value string, attrPath cty.Path) diag.Diagnostics { +func valueIsType(typ OptionType, value string) error { switch typ { case OptionTypeNumber: _, err := strconv.ParseFloat(value, 64) if err != nil { - return diag.Errorf("%q is not a number", value) + return fmt.Errorf("%q is not a number", value) } case OptionTypeBoolean: _, err := strconv.ParseBool(value) if err != nil { - return diag.Errorf("%q is not a bool", value) + return fmt.Errorf("%q is not a bool", value) } case OptionTypeListString: - _, diags := valueIsListString(value, attrPath) - if diags.HasError() { - return diags + _, err := valueIsListString(value) + if err != nil { + return err } case OptionTypeString: // Anything is a string! default: - return diag.Errorf("invalid type %q", typ) + return fmt.Errorf("invalid type %q", typ) } return nil } -func (v *Parameter) Valid() diag.Diagnostics { +func (v *Parameter) ValidateInput(input *string) (string, diag.Diagnostics) { + var err error + var optionType OptionType + + valuePath := cty.Path{} + value := input + if input == nil { + value = v.Default + if v.Default != nil { + valuePath = defaultValuePath + } + } + // optionType might differ from parameter.Type. This is ok, and parameter.Type // should be used for the value type, and optionType for options. - optionType, _, err := ValidateFormType(v.Type, len(v.Option), v.FormType) + optionType, v.FormType, err = ValidateFormType(v.Type, len(v.Option), v.FormType) if err != nil { - return diag.Diagnostics{ + return "", diag.Diagnostics{ { Severity: diag.Error, Summary: "Invalid form_type for parameter", @@ -429,53 +422,120 @@ func (v *Parameter) Valid() diag.Diagnostics { } } - optionNames := map[string]any{} - optionValues := map[string]any{} - if len(v.Option) > 0 { - for _, option := range v.Option { - _, exists := optionNames[option.Name] - if exists { - return diag.Diagnostics{{ - Severity: diag.Error, - Summary: "Option names must be unique.", - Detail: fmt.Sprintf("multiple options found with the same name %q", option.Name), - }, - } - } - _, exists = optionValues[option.Value] - if exists { - return diag.Diagnostics{ - { - Severity: diag.Error, - Summary: "Option values must be unique.", - Detail: fmt.Sprintf("multiple options found with the same value %q", option.Value), - }, - } - } - diags := valueIsType(optionType, option.Value, cty.Path{}) - if diags.HasError() { - return diags - } - optionValues[option.Value] = nil - optionNames[option.Name] = nil + optionValues, diags := v.ValidOptions(optionType) + if diags.HasError() { + return "", diags + } + + // TODO: This is a bit of a hack. The current behavior states if validation + // is given, then apply validation to unset values. + // value == nil should not be accepted in the first place. + // To fix this, value should be coerced to an empty string + // if it is nil. Then let the validation logic always apply. + if len(v.Validation) == 0 && value == nil { + return "", nil + } + + // forcedValue ensures the value is not-nil. + var forcedValue string + if value != nil { + forcedValue = *value + } + + d := v.validValue(forcedValue, optionType, optionValues, valuePath) + if d.HasError() { + return "", d + } + + err = valueIsType(v.Type, forcedValue) + if err != nil { + return "", diag.Diagnostics{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("Parameter value is not of type %q", v.Type), + Detail: err.Error(), + }, } } - if v.Default != "" && len(v.Option) > 0 { + return forcedValue, nil +} + +func (v *Parameter) ValidOptions(optionType OptionType) (map[string]struct{}, diag.Diagnostics) { + optionNames := map[string]struct{}{} + optionValues := map[string]struct{}{} + + var diags diag.Diagnostics + for _, option := range v.Option { + _, exists := optionNames[option.Name] + if exists { + return nil, diag.Diagnostics{{ + Severity: diag.Error, + Summary: "Option names must be unique.", + Detail: fmt.Sprintf("multiple options found with the same name %q", option.Name), + }} + } + + _, exists = optionValues[option.Value] + if exists { + return nil, diag.Diagnostics{{ + Severity: diag.Error, + Summary: "Option values must be unique.", + Detail: fmt.Sprintf("multiple options found with the same value %q", option.Value), + }} + } + + err := valueIsType(optionType, option.Value) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("Option %q with value=%q is not of type %q", option.Name, option.Value, optionType), + Detail: err.Error(), + }) + continue + } + optionValues[option.Value] = struct{}{} + optionNames[option.Name] = struct{}{} + + // Option values are assumed to be valid. Do not call validValue on them. + } + + if diags != nil && diags.HasError() { + return nil, diags + } + return optionValues, nil +} + +func (v *Parameter) validValue(value string, optionType OptionType, optionValues map[string]struct{}, path cty.Path) diag.Diagnostics { + // name is used for constructing more precise error messages. + name := "Value" + if path.Equals(defaultValuePath) { + name = "Default value" + } + + // First validate if the value is a valid option + if len(optionValues) > 0 { if v.Type == OptionTypeListString && optionType == OptionTypeString { // If the type is list(string) and optionType is string, we have - // to ensure all elements of the default exist as options. - defaultValues, diags := valueIsListString(v.Default, cty.Path{cty.GetAttrStep{Name: "default"}}) - if diags.HasError() { - return diags + // to ensure all elements of the value exist as options. + listValues, err := valueIsListString(value) + if err != nil { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "When using list(string) type, value must be a json encoded list of strings", + Detail: err.Error(), + AttributePath: path, + }, + } } // missing is used to construct a more helpful error message var missing []string - for _, defaultValue := range defaultValues { - _, defaultIsValid := optionValues[defaultValue] - if !defaultIsValid { - missing = append(missing, defaultValue) + for _, listValue := range listValues { + _, isValid := optionValues[listValue] + if !isValid { + missing = append(missing, listValue) } } @@ -483,30 +543,49 @@ func (v *Parameter) Valid() diag.Diagnostics { return diag.Diagnostics{ { Severity: diag.Error, - Summary: "Default values must be a valid option", + Summary: fmt.Sprintf("%ss must be a valid option", name), Detail: fmt.Sprintf( - "default value %q is not a valid option, values %q are missing from the options", - v.Default, strings.Join(missing, ", "), + "%s %q is not a valid option, values %q are missing from the options", + name, value, strings.Join(missing, ", "), ), - AttributePath: cty.Path{cty.GetAttrStep{Name: "default"}}, + AttributePath: path, }, } } } else { - _, defaultIsValid := optionValues[v.Default] - if !defaultIsValid { + _, isValid := optionValues[value] + if !isValid { + extra := "" + if value == "" { + extra = ". The value is empty, did you forget to set it with a default or from user input?" + } return diag.Diagnostics{ { Severity: diag.Error, - Summary: "Default value must be a valid option", - Detail: fmt.Sprintf("the value %q must be defined as one of options", v.Default), - AttributePath: cty.Path{cty.GetAttrStep{Name: "default"}}, + Summary: fmt.Sprintf("%s must be a valid option%s", name, extra), + Detail: fmt.Sprintf("the value %q must be defined as one of options", value), + AttributePath: path, }, } } } } + if len(v.Validation) == 1 { + validCheck := &v.Validation[0] + err := validCheck.Valid(v.Type, value) + if err != nil { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("Invalid parameter %s according to 'validation' block", strings.ToLower(name)), + Detail: err.Error(), + AttributePath: path, + }, + } + } + } + return nil } @@ -570,18 +649,11 @@ func (v *Validation) Valid(typ OptionType, value string) error { return nil } -func valueIsListString(value string, path cty.Path) ([]string, diag.Diagnostics) { +func valueIsListString(value string) ([]string, error) { var items []string err := json.Unmarshal([]byte(value), &items) if err != nil { - return nil, diag.Diagnostics{ - { - Severity: diag.Error, - Summary: "When using list(string) type, value must be a json encoded list of strings", - Detail: fmt.Sprintf("value %q is not a valid list of strings", value), - AttributePath: path, - }, - } + return nil, fmt.Errorf("value %q is not a valid list of strings", value) } return items, nil } diff --git a/provider/parameter_test.go b/provider/parameter_test.go index 32877c2b..21842b6a 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/coder/terraform-provider-coder/v2/provider" @@ -84,6 +85,7 @@ func TestParameter(t *testing.T) { data "coder_parameter" "region" { name = "Region" type = "number" + default = 1 option { name = "1" value = "1" @@ -101,6 +103,7 @@ func TestParameter(t *testing.T) { data "coder_parameter" "region" { name = "Region" type = "string" + default = "1" option { name = "1" value = "1" @@ -688,6 +691,168 @@ data "coder_parameter" "region" { } } +func TestParameterValidation(t *testing.T) { + t.Parallel() + opts := func(vals ...string) []provider.Option { + options := make([]provider.Option, 0, len(vals)) + for _, val := range vals { + options = append(options, provider.Option{ + Name: val, + Value: val, + }) + } + return options + } + + for _, tc := range []struct { + Name string + Parameter provider.Parameter + Value string + ExpectError *regexp.Regexp + }{ + { + Name: "ValidStringParameter", + Parameter: provider.Parameter{ + Type: "string", + }, + Value: "alpha", + }, + // Test invalid states + { + Name: "InvalidFormType", + Parameter: provider.Parameter{ + Type: "string", + Option: opts("alpha", "bravo", "charlie"), + FormType: provider.ParameterFormTypeSlider, + }, + Value: "alpha", + ExpectError: regexp.MustCompile("Invalid form_type for parameter"), + }, + { + Name: "NotInOptions", + Parameter: provider.Parameter{ + Type: "string", + Option: opts("alpha", "bravo", "charlie"), + }, + Value: "delta", // not in option set + ExpectError: regexp.MustCompile("Value must be a valid option"), + }, + { + Name: "NumberNotInOptions", + Parameter: provider.Parameter{ + Type: "number", + Option: opts("1", "2", "3"), + }, + Value: "0", // not in option set + ExpectError: regexp.MustCompile("Value must be a valid option"), + }, + { + Name: "NonUniqueOptionNames", + Parameter: provider.Parameter{ + Type: "string", + Option: opts("alpha", "alpha"), + }, + Value: "alpha", + ExpectError: regexp.MustCompile("Option names must be unique"), + }, + { + Name: "NonUniqueOptionValues", + Parameter: provider.Parameter{ + Type: "string", + Option: []provider.Option{ + {Name: "Alpha", Value: "alpha"}, + {Name: "AlphaAgain", Value: "alpha"}, + }, + }, + Value: "alpha", + ExpectError: regexp.MustCompile("Option values must be unique"), + }, + { + Name: "IncorrectValueTypeOption", + Parameter: provider.Parameter{ + Type: "number", + Option: opts("not-a-number"), + }, + Value: "5", + ExpectError: regexp.MustCompile("is not a number"), + }, + { + Name: "IncorrectValueType", + Parameter: provider.Parameter{ + Type: "number", + }, + Value: "not-a-number", + ExpectError: regexp.MustCompile("Parameter value is not of type \"number\""), + }, + { + Name: "NotListStringDefault", + Parameter: provider.Parameter{ + Type: "list(string)", + Default: ptr("not-a-list"), + }, + ExpectError: regexp.MustCompile("not a valid list of strings"), + }, + { + Name: "NotListStringDefault", + Parameter: provider.Parameter{ + Type: "list(string)", + }, + Value: "not-a-list", + ExpectError: regexp.MustCompile("not a valid list of strings"), + }, + { + Name: "DefaultListStringNotInOptions", + Parameter: provider.Parameter{ + Type: "list(string)", + Default: ptr(`["red", "yellow", "black"]`), + Option: opts("red", "blue", "green"), + FormType: provider.ParameterFormTypeMultiSelect, + }, + Value: `["red", "yellow", "black"]`, + ExpectError: regexp.MustCompile("is not a valid option, values \"yellow, black\" are missing from the options"), + }, + { + Name: "ListStringNotInOptions", + Parameter: provider.Parameter{ + Type: "list(string)", + Default: ptr(`["red"]`), + Option: opts("red", "blue", "green"), + FormType: provider.ParameterFormTypeMultiSelect, + }, + Value: `["red", "yellow", "black"]`, + ExpectError: regexp.MustCompile("is not a valid option, values \"yellow, black\" are missing from the options"), + }, + { + Name: "InvalidMiniumum", + Parameter: provider.Parameter{ + Type: "number", + Default: ptr("5"), + Validation: []provider.Validation{{ + Min: 10, + Error: "must be greater than 10", + }}, + }, + ExpectError: regexp.MustCompile("must be greater than 10"), + }, + } { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + value := &tc.Value + _, diags := tc.Parameter.ValidateInput(value) + if tc.ExpectError != nil { + require.True(t, diags.HasError()) + errMsg := fmt.Sprintf("%+v", diags[0]) // close enough + require.Truef(t, tc.ExpectError.MatchString(errMsg), "got: %s", errMsg) + } else { + if !assert.False(t, diags.HasError()) { + t.Logf("got: %+v", diags[0]) + } + } + }) + } +} + // TestParameterValidationEnforcement tests various parameter states and the // validation enforcement that should be applied to them. The table is described // by a markdown table. This is done so that the test cases can be more easily @@ -703,10 +868,6 @@ func TestParameterValidationEnforcement(t *testing.T) { // - Validation logic does not apply to the default if a value is given // - [NumIns/DefInv] So the default can be invalid if an input value is valid. // The value is therefore not really optional, but it is marked as such. - // - [NumInsNotOptsVal | NumsInsNotOpts] values do not need to be in the option set? - // - [NumInsNotNum] number params do not require the value to be a number - // - [LStrInsNotList] list(string) do not require the value to be a list(string) - // - Same with [MulInsNotListOpts] table, err := os.ReadFile("testdata/parameter_table.md") require.NoError(t, err) @@ -719,7 +880,7 @@ func TestParameterValidationEnforcement(t *testing.T) { Validation *provider.Validation OutputValue string Optional bool - Error *regexp.Regexp + CreateError *regexp.Regexp } rows := make([]row, 0) @@ -750,6 +911,7 @@ func TestParameterValidationEnforcement(t *testing.T) { t.Fatalf("failed to parse error column %q: %v", columns[9], err) } } + var options []string if columns[4] != "" { options = strings.Split(columns[4], ",") @@ -796,7 +958,7 @@ func TestParameterValidationEnforcement(t *testing.T) { Validation: validation, OutputValue: columns[7], Optional: optional, - Error: rerr, + CreateError: rerr, }) } @@ -815,10 +977,8 @@ func TestParameterValidationEnforcement(t *testing.T) { t.Setenv(provider.ParameterEnvironmentVariable("parameter"), row.InputValue) } - if row.Error != nil { - if row.OutputValue != "" { - t.Errorf("output value %q should not be set if error is set", row.OutputValue) - } + if row.CreateError != nil && row.OutputValue != "" { + t.Errorf("output value %q should not be set if both errors are set", row.OutputValue) } var cfg strings.Builder @@ -860,13 +1020,12 @@ func TestParameterValidationEnforcement(t *testing.T) { } cfg.WriteString("}\n") - resource.Test(t, resource.TestCase{ ProviderFactories: coderFactory(), IsUnitTest: true, Steps: []resource.TestStep{{ Config: cfg.String(), - ExpectError: row.Error, + ExpectError: row.CreateError, Check: func(state *terraform.State) error { require.Len(t, state.Modules, 1) require.Len(t, state.Modules[0].Resources, 1) @@ -1096,3 +1255,7 @@ func TestParameterWithManyOptions(t *testing.T) { }}, }) } + +func ptr[T any](v T) *T { + return &v +} diff --git a/provider/testdata/parameter_table.md b/provider/testdata/parameter_table.md index f7645efa..3df16f06 100644 --- a/provider/testdata/parameter_table.md +++ b/provider/testdata/parameter_table.md @@ -1,79 +1,80 @@ -| Name | Type | Input | Default | Options | Validation | -> | Output | Optional | Error | -|----------------------|---------------|-----------|---------|-------------------|------------|----|--------|----------|--------------| -| | Empty Vals | | | | | | | | | -| Empty | string,number | | | | | | "" | false | | -| EmptyDupeOps | string,number | | | 1,1,1 | | | | | unique | -| EmptyList | list(string) | | | | | | "" | false | | -| EmptyListDupeOpts | list(string) | | | ["a"],["a"] | | | | | unique | -| EmptyMulti | tag-select | | | | | | "" | false | | -| EmptyOpts | string,number | | | 1,2,3 | | | "" | false | | -| EmptyRegex | string | | | | world | | | | regex error | -| EmptyMin | number | | | | 1-10 | | | | 1 < < 10 | -| EmptyMinOpt | number | | | 1,2,3 | 2-5 | | | | 2 < < 5 | -| EmptyRegexOpt | string | | | "hello","goodbye" | goodbye | | | | regex error | -| EmptyRegexOk | string | | | | .* | | "" | false | | -| | | | | | | | | | | -| | Default Set | No inputs | | | | | | | | -| NumDef | number | | 5 | | | | 5 | true | | -| NumDefVal | number | | 5 | | 3-7 | | 5 | true | | -| NumDefInv | number | | 5 | | 10- | | | | 10 < 5 < 0 | -| NumDefOpts | number | | 5 | 1,3,5,7 | 2-6 | | 5 | true | | -| NumDefNotOpts | number | | 5 | 1,3,7,9 | 2-6 | | | | valid option | -| NumDefInvOpt | number | | 5 | 1,3,5,7 | 6-10 | | | | 6 < 5 < 10 | -| NumDefNotNum | number | | a | | | | | | a number | -| NumDefOptsNotNum | number | | 1 | 1,a,2 | | | | | a number | -| | | | | | | | | | | -| StrDef | string | | hello | | | | hello | true | | -| StrDefInv | string | | hello | | world | | | | regex error | -| StrDefOpts | string | | a | a,b,c | | | a | true | | -| StrDefNotOpts | string | | a | b,c,d | | | | | valid option | -| StrDefValOpts | string | | a | a,b,c,d,e,f | [a-c] | | a | true | | -| StrDefInvOpt | string | | d | a,b,c,d,e,f | [a-c] | | | | regex error | -| | | | | | | | | | | -| LStrDef | list(string) | | ["a"] | | | | ["a"] | true | | -| LStrDefOpts | list(string) | | ["a"] | ["a"], ["b"] | | | ["a"] | true | | -| LStrDefNotOpts | list(string) | | ["a"] | ["b"], ["c"] | | | | | valid option | -| | | | | | | | | | | -| MulDef | tag-select | | ["a"] | | | | ["a"] | true | | -| MulDefOpts | multi-select | | ["a"] | a,b | | | ["a"] | true | | -| MulDefNotOpts | multi-select | | ["a"] | b,c | | | | | valid option | -| | | | | | | | | | | -| | Input Vals | | | | | | | | | -| NumIns | number | 3 | | | | | 3 | false | | -| NumInsNotNum | number | a | | | | | a | false | | -| NumInsNotNumInv | number | a | | | 1-3 | | | | 1 < a < 3 | -| NumInsDef | number | 3 | 5 | | | | 3 | true | | -| NumIns/DefInv | number | 3 | 5 | | 1-3 | | 3 | true | | -| NumIns=DefInv | number | 5 | 5 | | 1-3 | | | | 1 < 5 < 3 | -| NumInsOpts | number | 3 | 5 | 1,2,3,4,5 | 1-3 | | 3 | true | | -| NumInsNotOptsVal | number | 3 | 5 | 1,2,4,5 | 1-3 | | 3 | true | | -| NumInsNotOptsInv | number | 3 | 5 | 1,2,4,5 | 1-2 | | | true | 1 < 3 < 2 | -| NumInsNotOpts | number | 3 | 5 | 1,2,4,5 | | | 3 | true | | -| NumInsNotOpts/NoDef | number | 3 | | 1,2,4,5 | | | 3 | false | | -| | | | | | | | | | | -| StrIns | string | c | | | | | c | false | | -| StrInsDupeOpts | string | c | | a,b,c,c | | | | | unique | -| StrInsDef | string | c | e | | | | c | true | | -| StrIns/DefInv | string | c | e | | [a-c] | | c | true | | -| StrIns=DefInv | string | e | e | | [a-c] | | | | regex error | -| StrInsOpts | string | c | e | a,b,c,d,e | [a-c] | | c | true | | -| StrInsNotOptsVal | string | c | e | a,b,d,e | [a-c] | | c | true | | -| StrInsNotOptsInv | string | c | e | a,b,d,e | [a-b] | | | | regex error | -| StrInsNotOpts | string | c | e | a,b,d,e | | | c | true | | -| StrInsNotOpts/NoDef | string | c | | a,b,d,e | | | c | false | | -| StrInsBadVal | string | c | | a,b,c,d,e | 1-10 | | | | min cannot | -| | | | | | | | | | | -| | list(string) | | | | | | | | | -| LStrIns | list(string) | ["c"] | | | | | ["c"] | false | | -| LStrInsNotList | list(string) | c | | | | | c | false | | -| LStrInsDef | list(string) | ["c"] | ["e"] | | | | ["c"] | true | | -| LStrIns/DefInv | list(string) | ["c"] | ["e"] | | [a-c] | | | | regex cannot | -| LStrInsOpts | list(string) | ["c"] | ["e"] | ["c"],["d"],["e"] | | | ["c"] | true | | -| LStrInsNotOpts | list(string) | ["c"] | ["e"] | ["d"],["e"] | | | ["c"] | true | | -| LStrInsNotOpts/NoDef | list(string) | ["c"] | | ["d"],["e"] | | | ["c"] | false | | -| | | | | | | | | | | -| MulInsOpts | multi-select | ["c"] | ["e"] | c,d,e | | | ["c"] | true | | -| MulInsNotListOpts | multi-select | c | ["e"] | c,d,e | | | c | true | | -| MulInsNotOpts | multi-select | ["c"] | ["e"] | d,e | | | ["c"] | true | | -| MulInsNotOpts/NoDef | multi-select | ["c"] | | d,e | | | ["c"] | false | | -| MulInsInvOpts | multi-select | ["c"] | ["e"] | c,d,e | [a-c] | | | | regex cannot | \ No newline at end of file +| Name | Type | Input | Default | Options | Validation | -> | Output | Optional | ErrorCreate | +|----------------------|---------------|-----------|---------|-------------------|------------|----|--------|----------|-----------------| +| | Empty Vals | | | | | | | | | +| Empty | string,number | | | | | | "" | false | | +| EmptyDupeOps | string,number | | | 1,1,1 | | | | | unique | +| EmptyList | list(string) | | | | | | "" | false | | +| EmptyListDupeOpts | list(string) | | | ["a"],["a"] | | | | | unique | +| EmptyMulti | tag-select | | | | | | "" | false | | +| EmptyOpts | string,number | | | 1,2,3 | | | "" | false | | +| EmptyRegex | string | | | | world | | | | regex error | +| EmptyMin | number | | | | 1-10 | | | | 1 < < 10 | +| EmptyMinOpt | number | | | 1,2,3 | 2-5 | | | | valid option | +| EmptyRegexOpt | string | | | "hello","goodbye" | goodbye | | | | valid option | +| EmptyRegexOk | string | | | | .* | | "" | false | | +| | | | | | | | | | | +| | Default Set | No inputs | | | | | | | | +| NumDef | number | | 5 | | | | 5 | true | | +| NumDefVal | number | | 5 | | 3-7 | | 5 | true | | +| NumDefInv | number | | 5 | | 10- | | | | 10 < 5 < 0 | +| NumDefOpts | number | | 5 | 1,3,5,7 | 2-6 | | 5 | true | | +| NumDefNotOpts | number | | 5 | 1,3,7,9 | 2-6 | | | | valid option | +| NumDefInvOpt | number | | 5 | 1,3,5,7 | 6-10 | | | | 6 < 5 < 10 | +| NumDefNotNum | number | | a | | | | | | type "number" | +| NumDefOptsNotNum | number | | 1 | 1,a,2 | | | | | type "number" | +| | | | | | | | | | | +| StrDef | string | | hello | | | | hello | true | | +| StrDefInv | string | | hello | | world | | | | regex error | +| StrDefOpts | string | | a | a,b,c | | | a | true | | +| StrDefNotOpts | string | | a | b,c,d | | | | | valid option | +| StrDefValOpts | string | | a | a,b,c,d,e,f | [a-c] | | a | true | | +| StrDefInvOpt | string | | d | a,b,c,d,e,f | [a-c] | | | | regex error | +| | | | | | | | | | | +| LStrDef | list(string) | | ["a"] | | | | ["a"] | true | | +| LStrDefOpts | list(string) | | ["a"] | ["a"], ["b"] | | | ["a"] | true | | +| LStrDefNotOpts | list(string) | | ["a"] | ["b"], ["c"] | | | | | valid option | +| | | | | | | | | | | +| MulDef | tag-select | | ["a"] | | | | ["a"] | true | | +| MulDefOpts | multi-select | | ["a"] | a,b | | | ["a"] | true | | +| MulDefNotOpts | multi-select | | ["a"] | b,c | | | | | valid option | +| | | | | | | | | | | +| | Input Vals | | | | | | | | | +| NumIns | number | 3 | | | | | 3 | false | | +| NumInsOptsNaN | number | 3 | 5 | a,1,2,3,4,5 | 1-3 | | | | type "number" | +| NumInsNotNum | number | a | | | | | | | type "number" | +| NumInsNotNumInv | number | a | | | 1-3 | | | | 1 < a < 3 | +| NumInsDef | number | 3 | 5 | | | | 3 | true | | +| NumIns/DefInv | number | 3 | 5 | | 1-3 | | 3 | true | | +| NumIns=DefInv | number | 5 | 5 | | 1-3 | | | | 1 < 5 < 3 | +| NumInsOpts | number | 3 | 5 | 1,2,3,4,5 | 1-3 | | 3 | true | | +| NumInsNotOptsVal | number | 3 | 5 | 1,2,4,5 | 1-3 | | | | valid option | +| NumInsNotOptsInv | number | 3 | 5 | 1,2,4,5 | 1-2 | | | true | valid option | +| NumInsNotOpts | number | 3 | 5 | 1,2,4,5 | | | | | valid option | +| NumInsNotOpts/NoDef | number | 3 | | 1,2,4,5 | | | | | valid option | +| | | | | | | | | | | +| StrIns | string | c | | | | | c | false | | +| StrInsDupeOpts | string | c | | a,b,c,c | | | | | unique | +| StrInsDef | string | c | e | | | | c | true | | +| StrIns/DefInv | string | c | e | | [a-c] | | c | true | | +| StrIns=DefInv | string | e | e | | [a-c] | | | | regex error | +| StrInsOpts | string | c | e | a,b,c,d,e | [a-c] | | c | true | | +| StrInsNotOptsVal | string | c | e | a,b,d,e | [a-c] | | | | valid option | +| StrInsNotOptsInv | string | c | e | a,b,d,e | [a-b] | | | | valid option | +| StrInsNotOpts | string | c | e | a,b,d,e | | | | | valid option | +| StrInsNotOpts/NoDef | string | c | | a,b,d,e | | | | | valid option | +| StrInsBadVal | string | c | | a,b,c,d,e | 1-10 | | | | min cannot | +| | | | | | | | | | | +| | list(string) | | | | | | | | | +| LStrIns | list(string) | ["c"] | | | | | ["c"] | false | | +| LStrInsNotList | list(string) | c | | | | | | | list of strings | +| LStrInsDef | list(string) | ["c"] | ["e"] | | | | ["c"] | true | | +| LStrIns/DefInv | list(string) | ["c"] | ["e"] | | [a-c] | | | | regex cannot | +| LStrInsOpts | list(string) | ["c"] | ["e"] | ["c"],["d"],["e"] | | | ["c"] | true | | +| LStrInsNotOpts | list(string) | ["c"] | ["e"] | ["d"],["e"] | | | | | valid option | +| LStrInsNotOpts/NoDef | list(string) | ["c"] | | ["d"],["e"] | | | | | valid option | +| | | | | | | | | | | +| MulInsOpts | multi-select | ["c"] | ["e"] | c,d,e | | | ["c"] | true | | +| MulInsNotListOpts | multi-select | c | ["e"] | c,d,e | | | | | json encoded | +| MulInsNotOpts | multi-select | ["c"] | ["e"] | d,e | | | | | valid option | +| MulInsNotOpts/NoDef | multi-select | ["c"] | | d,e | | | | | valid option | +| MulInsInvOpts | multi-select | ["c"] | ["e"] | c,d,e | [a-c] | | | | regex cannot | \ No newline at end of file From 8692a56f55acb7a10f74c16f7f00b896c33ce02b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 8 May 2025 09:36:57 -0500 Subject: [PATCH 112/114] feat: enforce monotonicity in terraform provider (#392) Previous value must come from env var. --- provider/parameter.go | 55 ++++++++- provider/parameter_test.go | 120 ++++++++++++++---- provider/testdata/parameter_table.md | 175 +++++++++++++++------------ 3 files changed, 241 insertions(+), 109 deletions(-) diff --git a/provider/parameter.go b/provider/parameter.go index fd484578..f0a26c99 100644 --- a/provider/parameter.go +++ b/provider/parameter.go @@ -144,7 +144,13 @@ func parameterDataSource() *schema.Resource { input = &envValue } - value, diags := parameter.ValidateInput(input) + var previous *string + envPreviousValue, ok := os.LookupEnv(ParameterEnvironmentVariablePrevious(parameter.Name)) + if ok { + previous = &envPreviousValue + } + + value, diags := parameter.ValidateInput(input, previous) if diags.HasError() { return diags } @@ -395,7 +401,7 @@ func valueIsType(typ OptionType, value string) error { return nil } -func (v *Parameter) ValidateInput(input *string) (string, diag.Diagnostics) { +func (v *Parameter) ValidateInput(input *string, previous *string) (string, diag.Diagnostics) { var err error var optionType OptionType @@ -442,7 +448,7 @@ func (v *Parameter) ValidateInput(input *string) (string, diag.Diagnostics) { forcedValue = *value } - d := v.validValue(forcedValue, optionType, optionValues, valuePath) + d := v.validValue(forcedValue, previous, optionType, optionValues, valuePath) if d.HasError() { return "", d } @@ -506,7 +512,7 @@ func (v *Parameter) ValidOptions(optionType OptionType) (map[string]struct{}, di return optionValues, nil } -func (v *Parameter) validValue(value string, optionType OptionType, optionValues map[string]struct{}, path cty.Path) diag.Diagnostics { +func (v *Parameter) validValue(value string, previous *string, optionType OptionType, optionValues map[string]struct{}, path cty.Path) diag.Diagnostics { // name is used for constructing more precise error messages. name := "Value" if path.Equals(defaultValuePath) { @@ -573,7 +579,7 @@ func (v *Parameter) validValue(value string, optionType OptionType, optionValues if len(v.Validation) == 1 { validCheck := &v.Validation[0] - err := validCheck.Valid(v.Type, value) + err := validCheck.Valid(v.Type, value, previous) if err != nil { return diag.Diagnostics{ { @@ -589,7 +595,7 @@ func (v *Parameter) validValue(value string, optionType OptionType, optionValues return nil } -func (v *Validation) Valid(typ OptionType, value string) error { +func (v *Validation) Valid(typ OptionType, value string, previous *string) error { if typ != OptionTypeNumber { if !v.MinDisabled { return fmt.Errorf("a min cannot be specified for a %s type", typ) @@ -639,6 +645,34 @@ func (v *Validation) Valid(typ OptionType, value string) error { if v.Monotonic != "" && v.Monotonic != ValidationMonotonicIncreasing && v.Monotonic != ValidationMonotonicDecreasing { return fmt.Errorf("number monotonicity can be either %q or %q", ValidationMonotonicIncreasing, ValidationMonotonicDecreasing) } + + switch v.Monotonic { + case "": + // No monotonicity check + case ValidationMonotonicIncreasing, ValidationMonotonicDecreasing: + if previous != nil { // Only check if previous value exists + previousNum, err := strconv.Atoi(*previous) + if err != nil { + // Do not throw an error for the previous value not being a number. Throwing an + // error here would cause an unrepairable state for the user. This is + // unfortunate, but there is not much we can do at this point. + // TODO: Maybe we should enforce this, and have the calling coderd + // do something to resolve it. Such as doing this check before calling + // terraform apply. + break + } + + if v.Monotonic == ValidationMonotonicIncreasing && !(num >= previousNum) { + return fmt.Errorf("parameter value '%d' must be equal or greater than previous value: %d", num, previousNum) + } + + if v.Monotonic == ValidationMonotonicDecreasing && !(num <= previousNum) { + return fmt.Errorf("parameter value '%d' must be equal or lower than previous value: %d", num, previousNum) + } + } + default: + return fmt.Errorf("number monotonicity can be either %q or %q", ValidationMonotonicIncreasing, ValidationMonotonicDecreasing) + } case OptionTypeListString: var listOfStrings []string err := json.Unmarshal([]byte(value), &listOfStrings) @@ -666,6 +700,15 @@ func ParameterEnvironmentVariable(name string) string { return "CODER_PARAMETER_" + hex.EncodeToString(sum[:]) } +// ParameterEnvironmentVariablePrevious returns the environment variable to +// specify for a parameter's previous value. This is used for workspace +// subsequent builds after the first. Primarily to validate monotonicity in the +// `validation` block. +func ParameterEnvironmentVariablePrevious(name string) string { + sum := sha256.Sum256([]byte(name)) + return "CODER_PARAMETER_PREVIOUS_" + hex.EncodeToString(sum[:]) +} + func takeFirstError(errs ...error) error { for _, err := range errs { if err != nil { diff --git a/provider/parameter_test.go b/provider/parameter_test.go index 21842b6a..9b5e76f1 100644 --- a/provider/parameter_test.go +++ b/provider/parameter_test.go @@ -839,7 +839,7 @@ func TestParameterValidation(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { t.Parallel() value := &tc.Value - _, diags := tc.Parameter.ValidateInput(value) + _, diags := tc.Parameter.ValidateInput(value, nil) if tc.ExpectError != nil { require.True(t, diags.HasError()) errMsg := fmt.Sprintf("%+v", diags[0]) // close enough @@ -881,6 +881,7 @@ func TestParameterValidationEnforcement(t *testing.T) { OutputValue string Optional bool CreateError *regexp.Regexp + Previous *string } rows := make([]row, 0) @@ -898,33 +899,44 @@ func TestParameterValidationEnforcement(t *testing.T) { continue // Skip rows with empty names } - optional, err := strconv.ParseBool(columns[8]) - if columns[8] != "" { + cname, ctype, cprev, cinput, cdefault, coptions, cvalidation, _, coutput, coptional, cerr := + columns[0], columns[1], columns[2], columns[3], columns[4], columns[5], columns[6], columns[7], columns[8], columns[9], columns[10] + + optional, err := strconv.ParseBool(coptional) + if coptional != "" { // Value does not matter if not specified require.NoError(t, err) } var rerr *regexp.Regexp - if columns[9] != "" { - rerr, err = regexp.Compile(columns[9]) + if cerr != "" { + rerr, err = regexp.Compile(cerr) if err != nil { - t.Fatalf("failed to parse error column %q: %v", columns[9], err) + t.Fatalf("failed to parse error column %q: %v", cerr, err) } } var options []string - if columns[4] != "" { - options = strings.Split(columns[4], ",") + if coptions != "" { + options = strings.Split(coptions, ",") } var validation *provider.Validation - if columns[5] != "" { - // Min-Max validation should look like: - // 1-10 :: min=1, max=10 - // -10 :: max=10 - // 1- :: min=1 - if validMinMax.MatchString(columns[5]) { - parts := strings.Split(columns[5], "-") + if cvalidation != "" { + switch { + case cvalidation == provider.ValidationMonotonicIncreasing || cvalidation == provider.ValidationMonotonicDecreasing: + validation = &provider.Validation{ + MinDisabled: true, + MaxDisabled: true, + Monotonic: cvalidation, + Error: "monotonicity", + } + case validMinMax.MatchString(cvalidation): + // Min-Max validation should look like: + // 1-10 :: min=1, max=10 + // -10 :: max=10 + // 1- :: min=1 + parts := strings.Split(cvalidation, "-") min, _ := strconv.ParseInt(parts[0], 10, 64) max, _ := strconv.ParseInt(parts[1], 10, 64) validation = &provider.Validation{ @@ -936,29 +948,37 @@ func TestParameterValidationEnforcement(t *testing.T) { Regex: "", Error: "{min} < {value} < {max}", } - } else { + default: validation = &provider.Validation{ Min: 0, MinDisabled: true, Max: 0, MaxDisabled: true, Monotonic: "", - Regex: columns[5], + Regex: cvalidation, Error: "regex error", } } } + var prev *string + if cprev != "" { + prev = ptr(cprev) + if cprev == `""` { + prev = ptr("") + } + } rows = append(rows, row{ - Name: columns[0], - Types: strings.Split(columns[1], ","), - InputValue: columns[2], - Default: columns[3], + Name: cname, + Types: strings.Split(ctype, ","), + InputValue: cinput, + Default: cdefault, Options: options, Validation: validation, - OutputValue: columns[7], + OutputValue: coutput, Optional: optional, CreateError: rerr, + Previous: prev, }) } @@ -976,6 +996,9 @@ func TestParameterValidationEnforcement(t *testing.T) { if row.InputValue != "" { t.Setenv(provider.ParameterEnvironmentVariable("parameter"), row.InputValue) } + if row.Previous != nil { + t.Setenv(provider.ParameterEnvironmentVariablePrevious("parameter"), *row.Previous) + } if row.CreateError != nil && row.OutputValue != "" { t.Errorf("output value %q should not be set if both errors are set", row.OutputValue) @@ -1067,6 +1090,7 @@ func TestValueValidatesType(t *testing.T) { Name string Type provider.OptionType Value string + Previous *string Regex string RegexError string Min int @@ -1154,6 +1178,56 @@ func TestValueValidatesType(t *testing.T) { Min: 0, Max: 2, Monotonic: "decreasing", + }, { + Name: "IncreasingMonotonicityEqual", + Type: "number", + Previous: ptr("1"), + Value: "1", + Monotonic: "increasing", + MinDisabled: true, + MaxDisabled: true, + }, { + Name: "DecreasingMonotonicityEqual", + Type: "number", + Value: "1", + Previous: ptr("1"), + Monotonic: "decreasing", + MinDisabled: true, + MaxDisabled: true, + }, { + Name: "IncreasingMonotonicityGreater", + Type: "number", + Previous: ptr("0"), + Value: "1", + Monotonic: "increasing", + MinDisabled: true, + MaxDisabled: true, + }, { + Name: "DecreasingMonotonicityGreater", + Type: "number", + Value: "1", + Previous: ptr("0"), + Monotonic: "decreasing", + MinDisabled: true, + MaxDisabled: true, + Error: regexp.MustCompile("must be equal or"), + }, { + Name: "IncreasingMonotonicityLesser", + Type: "number", + Previous: ptr("2"), + Value: "1", + Monotonic: "increasing", + MinDisabled: true, + MaxDisabled: true, + Error: regexp.MustCompile("must be equal or"), + }, { + Name: "DecreasingMonotonicityLesser", + Type: "number", + Value: "1", + Previous: ptr("2"), + Monotonic: "decreasing", + MinDisabled: true, + MaxDisabled: true, }, { Name: "ValidListOfStrings", Type: "list(string)", @@ -1205,7 +1279,7 @@ func TestValueValidatesType(t *testing.T) { Regex: tc.Regex, Error: tc.RegexError, } - err := v.Valid(tc.Type, tc.Value) + err := v.Valid(tc.Type, tc.Value, tc.Previous) if tc.Error != nil { require.Error(t, err) require.True(t, tc.Error.MatchString(err.Error()), "got: %s", err.Error()) diff --git a/provider/testdata/parameter_table.md b/provider/testdata/parameter_table.md index 3df16f06..6087460f 100644 --- a/provider/testdata/parameter_table.md +++ b/provider/testdata/parameter_table.md @@ -1,80 +1,95 @@ -| Name | Type | Input | Default | Options | Validation | -> | Output | Optional | ErrorCreate | -|----------------------|---------------|-----------|---------|-------------------|------------|----|--------|----------|-----------------| -| | Empty Vals | | | | | | | | | -| Empty | string,number | | | | | | "" | false | | -| EmptyDupeOps | string,number | | | 1,1,1 | | | | | unique | -| EmptyList | list(string) | | | | | | "" | false | | -| EmptyListDupeOpts | list(string) | | | ["a"],["a"] | | | | | unique | -| EmptyMulti | tag-select | | | | | | "" | false | | -| EmptyOpts | string,number | | | 1,2,3 | | | "" | false | | -| EmptyRegex | string | | | | world | | | | regex error | -| EmptyMin | number | | | | 1-10 | | | | 1 < < 10 | -| EmptyMinOpt | number | | | 1,2,3 | 2-5 | | | | valid option | -| EmptyRegexOpt | string | | | "hello","goodbye" | goodbye | | | | valid option | -| EmptyRegexOk | string | | | | .* | | "" | false | | -| | | | | | | | | | | -| | Default Set | No inputs | | | | | | | | -| NumDef | number | | 5 | | | | 5 | true | | -| NumDefVal | number | | 5 | | 3-7 | | 5 | true | | -| NumDefInv | number | | 5 | | 10- | | | | 10 < 5 < 0 | -| NumDefOpts | number | | 5 | 1,3,5,7 | 2-6 | | 5 | true | | -| NumDefNotOpts | number | | 5 | 1,3,7,9 | 2-6 | | | | valid option | -| NumDefInvOpt | number | | 5 | 1,3,5,7 | 6-10 | | | | 6 < 5 < 10 | -| NumDefNotNum | number | | a | | | | | | type "number" | -| NumDefOptsNotNum | number | | 1 | 1,a,2 | | | | | type "number" | -| | | | | | | | | | | -| StrDef | string | | hello | | | | hello | true | | -| StrDefInv | string | | hello | | world | | | | regex error | -| StrDefOpts | string | | a | a,b,c | | | a | true | | -| StrDefNotOpts | string | | a | b,c,d | | | | | valid option | -| StrDefValOpts | string | | a | a,b,c,d,e,f | [a-c] | | a | true | | -| StrDefInvOpt | string | | d | a,b,c,d,e,f | [a-c] | | | | regex error | -| | | | | | | | | | | -| LStrDef | list(string) | | ["a"] | | | | ["a"] | true | | -| LStrDefOpts | list(string) | | ["a"] | ["a"], ["b"] | | | ["a"] | true | | -| LStrDefNotOpts | list(string) | | ["a"] | ["b"], ["c"] | | | | | valid option | -| | | | | | | | | | | -| MulDef | tag-select | | ["a"] | | | | ["a"] | true | | -| MulDefOpts | multi-select | | ["a"] | a,b | | | ["a"] | true | | -| MulDefNotOpts | multi-select | | ["a"] | b,c | | | | | valid option | -| | | | | | | | | | | -| | Input Vals | | | | | | | | | -| NumIns | number | 3 | | | | | 3 | false | | -| NumInsOptsNaN | number | 3 | 5 | a,1,2,3,4,5 | 1-3 | | | | type "number" | -| NumInsNotNum | number | a | | | | | | | type "number" | -| NumInsNotNumInv | number | a | | | 1-3 | | | | 1 < a < 3 | -| NumInsDef | number | 3 | 5 | | | | 3 | true | | -| NumIns/DefInv | number | 3 | 5 | | 1-3 | | 3 | true | | -| NumIns=DefInv | number | 5 | 5 | | 1-3 | | | | 1 < 5 < 3 | -| NumInsOpts | number | 3 | 5 | 1,2,3,4,5 | 1-3 | | 3 | true | | -| NumInsNotOptsVal | number | 3 | 5 | 1,2,4,5 | 1-3 | | | | valid option | -| NumInsNotOptsInv | number | 3 | 5 | 1,2,4,5 | 1-2 | | | true | valid option | -| NumInsNotOpts | number | 3 | 5 | 1,2,4,5 | | | | | valid option | -| NumInsNotOpts/NoDef | number | 3 | | 1,2,4,5 | | | | | valid option | -| | | | | | | | | | | -| StrIns | string | c | | | | | c | false | | -| StrInsDupeOpts | string | c | | a,b,c,c | | | | | unique | -| StrInsDef | string | c | e | | | | c | true | | -| StrIns/DefInv | string | c | e | | [a-c] | | c | true | | -| StrIns=DefInv | string | e | e | | [a-c] | | | | regex error | -| StrInsOpts | string | c | e | a,b,c,d,e | [a-c] | | c | true | | -| StrInsNotOptsVal | string | c | e | a,b,d,e | [a-c] | | | | valid option | -| StrInsNotOptsInv | string | c | e | a,b,d,e | [a-b] | | | | valid option | -| StrInsNotOpts | string | c | e | a,b,d,e | | | | | valid option | -| StrInsNotOpts/NoDef | string | c | | a,b,d,e | | | | | valid option | -| StrInsBadVal | string | c | | a,b,c,d,e | 1-10 | | | | min cannot | -| | | | | | | | | | | -| | list(string) | | | | | | | | | -| LStrIns | list(string) | ["c"] | | | | | ["c"] | false | | -| LStrInsNotList | list(string) | c | | | | | | | list of strings | -| LStrInsDef | list(string) | ["c"] | ["e"] | | | | ["c"] | true | | -| LStrIns/DefInv | list(string) | ["c"] | ["e"] | | [a-c] | | | | regex cannot | -| LStrInsOpts | list(string) | ["c"] | ["e"] | ["c"],["d"],["e"] | | | ["c"] | true | | -| LStrInsNotOpts | list(string) | ["c"] | ["e"] | ["d"],["e"] | | | | | valid option | -| LStrInsNotOpts/NoDef | list(string) | ["c"] | | ["d"],["e"] | | | | | valid option | -| | | | | | | | | | | -| MulInsOpts | multi-select | ["c"] | ["e"] | c,d,e | | | ["c"] | true | | -| MulInsNotListOpts | multi-select | c | ["e"] | c,d,e | | | | | json encoded | -| MulInsNotOpts | multi-select | ["c"] | ["e"] | d,e | | | | | valid option | -| MulInsNotOpts/NoDef | multi-select | ["c"] | | d,e | | | | | valid option | -| MulInsInvOpts | multi-select | ["c"] | ["e"] | c,d,e | [a-c] | | | | regex cannot | \ No newline at end of file +| Name | Type | Previous | Input | Default | Options | Validation | -> | Output | Optional | ErrorCreate | +|----------------------|---------------|----------|-----------|---------|-------------------|------------|----|--------|----------|-----------------| +| | Empty Vals | | | | | | | | | | +| Empty | string,number | | | | | | | "" | false | | +| EmptyDupeOps | string,number | | | | 1,1,1 | | | | | unique | +| EmptyList | list(string) | | | | | | | "" | false | | +| EmptyListDupeOpts | list(string) | | | | ["a"],["a"] | | | | | unique | +| EmptyMulti | tag-select | | | | | | | "" | false | | +| EmptyOpts | string,number | | | | 1,2,3 | | | "" | false | | +| EmptyRegex | string | | | | | world | | | | regex error | +| EmptyMin | number | | | | | 1-10 | | | | 1 < < 10 | +| EmptyMinOpt | number | | | | 1,2,3 | 2-5 | | | | valid option | +| EmptyRegexOpt | string | | | | "hello","goodbye" | goodbye | | | | valid option | +| EmptyRegexOk | string | | | | | .* | | "" | false | | +| | | | | | | | | | | | +| | Default Set | | No inputs | | | | | | | | +| NumDef | number | | | 5 | | | | 5 | true | | +| NumDefVal | number | | | 5 | | 3-7 | | 5 | true | | +| NumDefInv | number | | | 5 | | 10- | | | | 10 < 5 < 0 | +| NumDefOpts | number | | | 5 | 1,3,5,7 | 2-6 | | 5 | true | | +| NumDefNotOpts | number | | | 5 | 1,3,7,9 | 2-6 | | | | valid option | +| NumDefInvOpt | number | | | 5 | 1,3,5,7 | 6-10 | | | | 6 < 5 < 10 | +| NumDefNotNum | number | | | a | | | | | | type "number" | +| NumDefOptsNotNum | number | | | 1 | 1,a,2 | | | | | type "number" | +| NumDefInc | number | 4 | | 5 | | increasing | | 5 | true | | +| NumDefIncBad | number | 6 | | 5 | | increasing | | | | greater | +| NumDefDec | number | 6 | | 5 | | decreasing | | 5 | true | | +| NumDefDecBad | number | 4 | | 5 | | decreasing | | | | lower | +| NumDefDecEq | number | 5 | | 5 | | decreasing | | 5 | true | | +| NumDefIncEq | number | 5 | | 5 | | increasing | | 5 | true | | +| NumDefIncNaN | number | a | | 5 | | increasing | | 5 | true | | +| NumDefDecNaN | number | b | | 5 | | decreasing | | 5 | true | | +| | | | | | | | | | | | +| StrDef | string | | | hello | | | | hello | true | | +| StrMonotonicity | string | | | hello | | increasing | | | | monotonic | +| StrDefInv | string | | | hello | | world | | | | regex error | +| StrDefOpts | string | | | a | a,b,c | | | a | true | | +| StrDefNotOpts | string | | | a | b,c,d | | | | | valid option | +| StrDefValOpts | string | | | a | a,b,c,d,e,f | [a-c] | | a | true | | +| StrDefInvOpt | string | | | d | a,b,c,d,e,f | [a-c] | | | | regex error | +| | | | | | | | | | | | +| LStrDef | list(string) | | | ["a"] | | | | ["a"] | true | | +| LStrDefOpts | list(string) | | | ["a"] | ["a"], ["b"] | | | ["a"] | true | | +| LStrDefNotOpts | list(string) | | | ["a"] | ["b"], ["c"] | | | | | valid option | +| | | | | | | | | | | | +| MulDef | tag-select | | | ["a"] | | | | ["a"] | true | | +| MulDefOpts | multi-select | | | ["a"] | a,b | | | ["a"] | true | | +| MulDefNotOpts | multi-select | | | ["a"] | b,c | | | | | valid option | +| | | | | | | | | | | | +| | Input Vals | | | | | | | | | | +| NumIns | number | | 3 | | | | | 3 | false | | +| NumInsOptsNaN | number | | 3 | 5 | a,1,2,3,4,5 | 1-3 | | | | type "number" | +| NumInsNotNum | number | | a | | | | | | | type "number" | +| NumInsNotNumInv | number | | a | | | 1-3 | | | | 1 < a < 3 | +| NumInsDef | number | | 3 | 5 | | | | 3 | true | | +| NumIns/DefInv | number | | 3 | 5 | | 1-3 | | 3 | true | | +| NumIns=DefInv | number | | 5 | 5 | | 1-3 | | | | 1 < 5 < 3 | +| NumInsOpts | number | | 3 | 5 | 1,2,3,4,5 | 1-3 | | 3 | true | | +| NumInsNotOptsVal | number | | 3 | 5 | 1,2,4,5 | 1-3 | | | | valid option | +| NumInsNotOptsInv | number | | 3 | 5 | 1,2,4,5 | 1-2 | | | true | valid option | +| NumInsNotOpts | number | | 3 | 5 | 1,2,4,5 | | | | | valid option | +| NumInsNotOpts/NoDef | number | | 3 | | 1,2,4,5 | | | | | valid option | +| NumInsInc | number | 4 | 5 | 3 | | increasing | | 5 | true | | +| NumInsIncBad | number | 6 | 5 | 7 | | increasing | | | | greater | +| NumInsDec | number | 6 | 5 | 7 | | decreasing | | 5 | true | | +| NumInsDecBad | number | 4 | 5 | 3 | | decreasing | | | | lower | +| NumInsDecEq | number | 5 | 5 | 5 | | decreasing | | 5 | true | | +| NumInsIncEq | number | 5 | 5 | 5 | | increasing | | 5 | true | | +| | | | | | | | | | | | +| StrIns | string | | c | | | | | c | false | | +| StrInsDupeOpts | string | | c | | a,b,c,c | | | | | unique | +| StrInsDef | string | | c | e | | | | c | true | | +| StrIns/DefInv | string | | c | e | | [a-c] | | c | true | | +| StrIns=DefInv | string | | e | e | | [a-c] | | | | regex error | +| StrInsOpts | string | | c | e | a,b,c,d,e | [a-c] | | c | true | | +| StrInsNotOptsVal | string | | c | e | a,b,d,e | [a-c] | | | | valid option | +| StrInsNotOptsInv | string | | c | e | a,b,d,e | [a-b] | | | | valid option | +| StrInsNotOpts | string | | c | e | a,b,d,e | | | | | valid option | +| StrInsNotOpts/NoDef | string | | c | | a,b,d,e | | | | | valid option | +| StrInsBadVal | string | | c | | a,b,c,d,e | 1-10 | | | | min cannot | +| | | | | | | | | | | | +| | list(string) | | | | | | | | | | +| LStrIns | list(string) | | ["c"] | | | | | ["c"] | false | | +| LStrInsNotList | list(string) | | c | | | | | | | list of strings | +| LStrInsDef | list(string) | | ["c"] | ["e"] | | | | ["c"] | true | | +| LStrIns/DefInv | list(string) | | ["c"] | ["e"] | | [a-c] | | | | regex cannot | +| LStrInsOpts | list(string) | | ["c"] | ["e"] | ["c"],["d"],["e"] | | | ["c"] | true | | +| LStrInsNotOpts | list(string) | | ["c"] | ["e"] | ["d"],["e"] | | | | | valid option | +| LStrInsNotOpts/NoDef | list(string) | | ["c"] | | ["d"],["e"] | | | | | valid option | +| | | | | | | | | | | | +| MulInsOpts | multi-select | | ["c"] | ["e"] | c,d,e | | | ["c"] | true | | +| MulInsNotListOpts | multi-select | | c | ["e"] | c,d,e | | | | | json encoded | +| MulInsNotOpts | multi-select | | ["c"] | ["e"] | d,e | | | | | valid option | +| MulInsNotOpts/NoDef | multi-select | | ["c"] | | d,e | | | | | valid option | +| MulInsInvOpts | multi-select | | ["c"] | ["e"] | c,d,e | [a-c] | | | | regex cannot | \ No newline at end of file From 4126ff41a540cd3c34dbf034fa1e4b269f38da70 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Fri, 9 May 2025 09:41:58 +0200 Subject: [PATCH 113/114] docs: link to additional documentation for prebuilt workspaces (#395) * docs: link to additional documentation for prebuilt workspaces * improve wording --- docs/data-sources/workspace_preset.md | 6 +++--- provider/workspace_preset.go | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/data-sources/workspace_preset.md b/docs/data-sources/workspace_preset.md index edd61f18..14e235f9 100644 --- a/docs/data-sources/workspace_preset.md +++ b/docs/data-sources/workspace_preset.md @@ -3,12 +3,12 @@ page_title: "coder_workspace_preset Data Source - terraform-provider-coder" subcategory: "" description: |- - Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are not defined by the preset will still be configurable when creating a workspace. + Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are defined by the template but not defined by the preset will still be configurable when creating a workspace. --- # coder_workspace_preset (Data Source) -Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are not defined by the preset will still be configurable when creating a workspace. +Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are defined by the template but not defined by the preset will still be configurable when creating a workspace. ## Example Usage @@ -39,7 +39,7 @@ data "coder_workspace_preset" "example" { ### Optional - `parameters` (Map of String) Workspace parameters that will be set by the workspace preset. For simple templates that only need prebuilds, you may define a preset with zero parameters. Because workspace parameters may change between Coder template versions, preset parameters are allowed to define values for parameters that do not exist in the current template version. -- `prebuilds` (Block Set, Max: 1) Prebuilt workspace configuration related to this workspace preset. Coder will build and maintain workspaces in reserve based on this configuration. When a user creates a new workspace using a preset, they will be assigned a prebuilt workspace, instead of waiting for a new workspace to build. (see [below for nested schema](#nestedblock--prebuilds)) +- `prebuilds` (Block Set, Max: 1) Configuration for prebuilt workspaces associated with this preset. Coder will maintain a pool of standby workspaces based on this configuration. When a user creates a workspace using this preset, they are assigned a prebuilt workspace instead of waiting for a new one to build. See prebuilt workspace documentation [here](https://coder.com/docs/admin/templates/extending-templates/prebuilt-workspaces.md) (see [below for nested schema](#nestedblock--prebuilds)) ### Read-Only diff --git a/provider/workspace_preset.go b/provider/workspace_preset.go index 2fab0b8a..78466c10 100644 --- a/provider/workspace_preset.go +++ b/provider/workspace_preset.go @@ -28,7 +28,8 @@ func workspacePresetDataSource() *schema.Resource { return &schema.Resource{ SchemaVersion: 1, - Description: "Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are not defined by the preset will still be configurable when creating a workspace.", + Description: "Use this data source to predefine common configurations for coder workspaces. Users will have the option to select a defined preset, which will automatically apply the selected configuration. Any parameters defined in the preset will be applied to the workspace. Parameters that are defined by the template but not defined by the preset will still be configurable when creating a workspace.", + ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { var preset WorkspacePreset err := mapstructure.Decode(struct { @@ -68,7 +69,7 @@ func workspacePresetDataSource() *schema.Resource { }, "prebuilds": { Type: schema.TypeSet, - Description: "Prebuilt workspace configuration related to this workspace preset. Coder will build and maintain workspaces in reserve based on this configuration. When a user creates a new workspace using a preset, they will be assigned a prebuilt workspace, instead of waiting for a new workspace to build.", + Description: "Configuration for prebuilt workspaces associated with this preset. Coder will maintain a pool of standby workspaces based on this configuration. When a user creates a workspace using this preset, they are assigned a prebuilt workspace instead of waiting for a new one to build. See prebuilt workspace documentation [here](https://coder.com/docs/admin/templates/extending-templates/prebuilt-workspaces.md)", Optional: true, MaxItems: 1, Elem: &schema.Resource{ From 50ed1c120bb89a31c90881522fde10929f8a0004 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Fri, 9 May 2025 18:44:08 +0200 Subject: [PATCH 114/114] feat: expose `is_prebuild_claim` attribute (#396) * feat: expose is_prebuild_claim attribute Signed-off-by: Danny Kopping * fix: string comparison hardening Signed-off-by: Danny Kopping --------- Signed-off-by: Danny Kopping --- docs/data-sources/workspace.md | 1 + provider/workspace.go | 44 +++++++++++- provider/workspace_test.go | 120 +++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md index 29fd9179..4dacdfc3 100644 --- a/docs/data-sources/workspace.md +++ b/docs/data-sources/workspace.md @@ -70,6 +70,7 @@ resource "docker_container" "workspace" { - `access_url` (String) The access URL of the Coder deployment provisioning this workspace. - `id` (String) UUID of the workspace. - `is_prebuild` (Boolean) Similar to `prebuild_count`, but a boolean value instead of a count. This is set to true if the workspace is a currently unassigned prebuild. Once the workspace is assigned, this value will be false. +- `is_prebuild_claim` (Boolean) Indicates whether a prebuilt workspace has just been claimed and this is the first `apply` after that occurrence. - `name` (String) Name of the workspace. - `prebuild_count` (Number) A computed count, equal to 1 if the workspace is a currently unassigned prebuild. Use this to conditionally act on the status of a prebuild. Actions that do not require user identity can be taken when this value is set to 1. Actions that should only be taken once the workspace has been assigned to a user may be taken when this value is set to 0. - `start_count` (Number) A computed count based on `transition` state. If `start`, count will equal 1. diff --git a/provider/workspace.go b/provider/workspace.go index c477fad6..58100a88 100644 --- a/provider/workspace.go +++ b/provider/workspace.go @@ -4,6 +4,7 @@ import ( "context" "reflect" "strconv" + "strings" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -30,10 +31,23 @@ func workspaceDataSource() *schema.Resource { if isPrebuiltWorkspace() { _ = rd.Set("prebuild_count", 1) _ = rd.Set("is_prebuild", true) + + // A claim can only take place AFTER a prebuild, so it's not logically consistent to have this set to any other value. + _ = rd.Set("is_prebuild_claim", false) } else { _ = rd.Set("prebuild_count", 0) _ = rd.Set("is_prebuild", false) } + if isPrebuiltWorkspaceClaim() { + // Indicate that a prebuild claim has taken place. + _ = rd.Set("is_prebuild_claim", true) + + // A claim can only take place AFTER a prebuild, so it's not logically consistent to have these set to any other values. + _ = rd.Set("prebuild_count", 0) + _ = rd.Set("is_prebuild", false) + } else { + _ = rd.Set("is_prebuild_claim", false) + } name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default") rd.Set("name", name) @@ -116,6 +130,11 @@ func workspaceDataSource() *schema.Resource { Computed: true, Description: "Similar to `prebuild_count`, but a boolean value instead of a count. This is set to true if the workspace is a currently unassigned prebuild. Once the workspace is assigned, this value will be false.", }, + "is_prebuild_claim": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether a prebuilt workspace has just been claimed and this is the first `apply` after that occurrence.", + }, "name": { Type: schema.TypeString, Computed: true, @@ -142,7 +161,12 @@ func workspaceDataSource() *schema.Resource { // isPrebuiltWorkspace returns true if the workspace is an unclaimed prebuilt workspace. func isPrebuiltWorkspace() bool { - return helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) == "true" + return strings.EqualFold(helpers.OptionalEnv(IsPrebuildEnvironmentVariable()), "true") +} + +// isPrebuiltWorkspaceClaim returns true if the workspace is a prebuilt workspace which has just been claimed. +func isPrebuiltWorkspaceClaim() bool { + return strings.EqualFold(helpers.OptionalEnv(IsPrebuildClaimEnvironmentVariable()), "true") } // IsPrebuildEnvironmentVariable returns the name of the environment variable that @@ -161,3 +185,21 @@ func isPrebuiltWorkspace() bool { func IsPrebuildEnvironmentVariable() string { return "CODER_WORKSPACE_IS_PREBUILD" } + +// IsPrebuildClaimEnvironmentVariable returns the name of the environment variable that +// indicates whether the workspace is a prebuilt workspace which has just been claimed, and this is the first Terraform +// apply after that occurrence. +// +// Knowing whether the workspace is a claimed prebuilt workspace allows template +// authors to conditionally execute code in the template based on whether the workspace +// has been assigned to a user or not. This allows identity specific configuration to +// be applied only after the workspace is claimed, while the rest of the workspace can +// be pre-configured. +// +// The value of this environment variable should be set to "true" if the workspace is prebuilt +// and it has just been claimed by a user. Any other values, including "false" +// and "" will be interpreted to mean that the workspace is not prebuilt, or was +// prebuilt but has not been claimed by a user. +func IsPrebuildClaimEnvironmentVariable() string { + return "CODER_WORKSPACE_IS_PREBUILD_CLAIM" +} diff --git a/provider/workspace_test.go b/provider/workspace_test.go index e82a1005..17dfabd2 100644 --- a/provider/workspace_test.go +++ b/provider/workspace_test.go @@ -4,6 +4,7 @@ import ( "regexp" "testing" + "github.com/coder/terraform-provider-coder/v2/provider" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/assert" @@ -102,3 +103,122 @@ func TestWorkspace_MissingTemplateName(t *testing.T) { }}, }) } + +// TestWorkspace_PrebuildEnv validates that our handling of input environment variables is correct. +func TestWorkspace_PrebuildEnv(t *testing.T) { + cases := []struct { + name string + envs map[string]string + check func(state *terraform.State, resource *terraform.ResourceState) error + }{ + { + name: "unused", + envs: map[string]string{}, + check: func(state *terraform.State, resource *terraform.ResourceState) error { + attribs := resource.Primary.Attributes + assert.Equal(t, "false", attribs["is_prebuild"]) + assert.Equal(t, "0", attribs["prebuild_count"]) + assert.Equal(t, "false", attribs["is_prebuild_claim"]) + return nil + }, + }, + { + name: "prebuild=true", + envs: map[string]string{ + provider.IsPrebuildEnvironmentVariable(): "true", + }, + check: func(state *terraform.State, resource *terraform.ResourceState) error { + attribs := resource.Primary.Attributes + assert.Equal(t, "true", attribs["is_prebuild"]) + assert.Equal(t, "1", attribs["prebuild_count"]) + assert.Equal(t, "false", attribs["is_prebuild_claim"]) + return nil + }, + }, + { + name: "prebuild=false", + envs: map[string]string{ + provider.IsPrebuildEnvironmentVariable(): "false", + }, + check: func(state *terraform.State, resource *terraform.ResourceState) error { + attribs := resource.Primary.Attributes + assert.Equal(t, "false", attribs["is_prebuild"]) + assert.Equal(t, "0", attribs["prebuild_count"]) + assert.Equal(t, "false", attribs["is_prebuild_claim"]) + return nil + }, + }, + { + name: "prebuild_claim=true", + envs: map[string]string{ + provider.IsPrebuildClaimEnvironmentVariable(): "true", + }, + check: func(state *terraform.State, resource *terraform.ResourceState) error { + attribs := resource.Primary.Attributes + assert.Equal(t, "false", attribs["is_prebuild"]) + assert.Equal(t, "0", attribs["prebuild_count"]) + assert.Equal(t, "true", attribs["is_prebuild_claim"]) + return nil + }, + }, + { + name: "prebuild_claim=false", + envs: map[string]string{ + provider.IsPrebuildClaimEnvironmentVariable(): "false", + }, + check: func(state *terraform.State, resource *terraform.ResourceState) error { + attribs := resource.Primary.Attributes + assert.Equal(t, "false", attribs["is_prebuild"]) + assert.Equal(t, "0", attribs["prebuild_count"]) + assert.Equal(t, "false", attribs["is_prebuild_claim"]) + return nil + }, + }, + { + // Should not ever happen, but let's ensure our defensive check is activated. We can't ever have both flags + // being true. + name: "prebuild=true,prebuild_claim=true", + envs: map[string]string{ + provider.IsPrebuildEnvironmentVariable(): "true", + provider.IsPrebuildClaimEnvironmentVariable(): "true", + }, + check: func(state *terraform.State, resource *terraform.ResourceState) error { + attribs := resource.Primary.Attributes + assert.Equal(t, "false", attribs["is_prebuild"]) + assert.Equal(t, "0", attribs["prebuild_count"]) + assert.Equal(t, "true", attribs["is_prebuild_claim"]) + return nil + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envs { + t.Setenv(k, v) + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` +provider "coder" { + url = "https://example.com:8080" +} +data "coder_workspace" "me" { +}`, + Check: func(state *terraform.State) error { + // Baseline checks + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + resource := state.Modules[0].Resources["data.coder_workspace.me"] + require.NotNil(t, resource) + + return tc.check(state, resource) + }, + }}, + }) + }) + } +}