Skip to content

Commit 50ed1c1

Browse files
authored
feat: expose is_prebuild_claim attribute (#396)
* feat: expose is_prebuild_claim attribute Signed-off-by: Danny Kopping <dannykopping@gmail.com> * fix: string comparison hardening Signed-off-by: Danny Kopping <dannykopping@gmail.com> --------- Signed-off-by: Danny Kopping <dannykopping@gmail.com>
1 parent 4126ff4 commit 50ed1c1

File tree

3 files changed

+164
-1
lines changed

3 files changed

+164
-1
lines changed

docs/data-sources/workspace.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ resource "docker_container" "workspace" {
7070
- `access_url` (String) The access URL of the Coder deployment provisioning this workspace.
7171
- `id` (String) UUID of the workspace.
7272
- `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.
73+
- `is_prebuild_claim` (Boolean) Indicates whether a prebuilt workspace has just been claimed and this is the first `apply` after that occurrence.
7374
- `name` (String) Name of the workspace.
7475
- `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.
7576
- `start_count` (Number) A computed count based on `transition` state. If `start`, count will equal 1.

provider/workspace.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"reflect"
66
"strconv"
7+
"strings"
78

89
"github.com/google/uuid"
910
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -30,10 +31,23 @@ func workspaceDataSource() *schema.Resource {
3031
if isPrebuiltWorkspace() {
3132
_ = rd.Set("prebuild_count", 1)
3233
_ = rd.Set("is_prebuild", true)
34+
35+
// A claim can only take place AFTER a prebuild, so it's not logically consistent to have this set to any other value.
36+
_ = rd.Set("is_prebuild_claim", false)
3337
} else {
3438
_ = rd.Set("prebuild_count", 0)
3539
_ = rd.Set("is_prebuild", false)
3640
}
41+
if isPrebuiltWorkspaceClaim() {
42+
// Indicate that a prebuild claim has taken place.
43+
_ = rd.Set("is_prebuild_claim", true)
44+
45+
// A claim can only take place AFTER a prebuild, so it's not logically consistent to have these set to any other values.
46+
_ = rd.Set("prebuild_count", 0)
47+
_ = rd.Set("is_prebuild", false)
48+
} else {
49+
_ = rd.Set("is_prebuild_claim", false)
50+
}
3751

3852
name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default")
3953
rd.Set("name", name)
@@ -116,6 +130,11 @@ func workspaceDataSource() *schema.Resource {
116130
Computed: true,
117131
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.",
118132
},
133+
"is_prebuild_claim": {
134+
Type: schema.TypeBool,
135+
Computed: true,
136+
Description: "Indicates whether a prebuilt workspace has just been claimed and this is the first `apply` after that occurrence.",
137+
},
119138
"name": {
120139
Type: schema.TypeString,
121140
Computed: true,
@@ -142,7 +161,12 @@ func workspaceDataSource() *schema.Resource {
142161

143162
// isPrebuiltWorkspace returns true if the workspace is an unclaimed prebuilt workspace.
144163
func isPrebuiltWorkspace() bool {
145-
return helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) == "true"
164+
return strings.EqualFold(helpers.OptionalEnv(IsPrebuildEnvironmentVariable()), "true")
165+
}
166+
167+
// isPrebuiltWorkspaceClaim returns true if the workspace is a prebuilt workspace which has just been claimed.
168+
func isPrebuiltWorkspaceClaim() bool {
169+
return strings.EqualFold(helpers.OptionalEnv(IsPrebuildClaimEnvironmentVariable()), "true")
146170
}
147171

148172
// IsPrebuildEnvironmentVariable returns the name of the environment variable that
@@ -161,3 +185,21 @@ func isPrebuiltWorkspace() bool {
161185
func IsPrebuildEnvironmentVariable() string {
162186
return "CODER_WORKSPACE_IS_PREBUILD"
163187
}
188+
189+
// IsPrebuildClaimEnvironmentVariable returns the name of the environment variable that
190+
// indicates whether the workspace is a prebuilt workspace which has just been claimed, and this is the first Terraform
191+
// apply after that occurrence.
192+
//
193+
// Knowing whether the workspace is a claimed prebuilt workspace allows template
194+
// authors to conditionally execute code in the template based on whether the workspace
195+
// has been assigned to a user or not. This allows identity specific configuration to
196+
// be applied only after the workspace is claimed, while the rest of the workspace can
197+
// be pre-configured.
198+
//
199+
// The value of this environment variable should be set to "true" if the workspace is prebuilt
200+
// and it has just been claimed by a user. Any other values, including "false"
201+
// and "" will be interpreted to mean that the workspace is not prebuilt, or was
202+
// prebuilt but has not been claimed by a user.
203+
func IsPrebuildClaimEnvironmentVariable() string {
204+
return "CODER_WORKSPACE_IS_PREBUILD_CLAIM"
205+
}

provider/workspace_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"regexp"
55
"testing"
66

7+
"github.com/coder/terraform-provider-coder/v2/provider"
78
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
89
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
910
"github.com/stretchr/testify/assert"
@@ -102,3 +103,122 @@ func TestWorkspace_MissingTemplateName(t *testing.T) {
102103
}},
103104
})
104105
}
106+
107+
// TestWorkspace_PrebuildEnv validates that our handling of input environment variables is correct.
108+
func TestWorkspace_PrebuildEnv(t *testing.T) {
109+
cases := []struct {
110+
name string
111+
envs map[string]string
112+
check func(state *terraform.State, resource *terraform.ResourceState) error
113+
}{
114+
{
115+
name: "unused",
116+
envs: map[string]string{},
117+
check: func(state *terraform.State, resource *terraform.ResourceState) error {
118+
attribs := resource.Primary.Attributes
119+
assert.Equal(t, "false", attribs["is_prebuild"])
120+
assert.Equal(t, "0", attribs["prebuild_count"])
121+
assert.Equal(t, "false", attribs["is_prebuild_claim"])
122+
return nil
123+
},
124+
},
125+
{
126+
name: "prebuild=true",
127+
envs: map[string]string{
128+
provider.IsPrebuildEnvironmentVariable(): "true",
129+
},
130+
check: func(state *terraform.State, resource *terraform.ResourceState) error {
131+
attribs := resource.Primary.Attributes
132+
assert.Equal(t, "true", attribs["is_prebuild"])
133+
assert.Equal(t, "1", attribs["prebuild_count"])
134+
assert.Equal(t, "false", attribs["is_prebuild_claim"])
135+
return nil
136+
},
137+
},
138+
{
139+
name: "prebuild=false",
140+
envs: map[string]string{
141+
provider.IsPrebuildEnvironmentVariable(): "false",
142+
},
143+
check: func(state *terraform.State, resource *terraform.ResourceState) error {
144+
attribs := resource.Primary.Attributes
145+
assert.Equal(t, "false", attribs["is_prebuild"])
146+
assert.Equal(t, "0", attribs["prebuild_count"])
147+
assert.Equal(t, "false", attribs["is_prebuild_claim"])
148+
return nil
149+
},
150+
},
151+
{
152+
name: "prebuild_claim=true",
153+
envs: map[string]string{
154+
provider.IsPrebuildClaimEnvironmentVariable(): "true",
155+
},
156+
check: func(state *terraform.State, resource *terraform.ResourceState) error {
157+
attribs := resource.Primary.Attributes
158+
assert.Equal(t, "false", attribs["is_prebuild"])
159+
assert.Equal(t, "0", attribs["prebuild_count"])
160+
assert.Equal(t, "true", attribs["is_prebuild_claim"])
161+
return nil
162+
},
163+
},
164+
{
165+
name: "prebuild_claim=false",
166+
envs: map[string]string{
167+
provider.IsPrebuildClaimEnvironmentVariable(): "false",
168+
},
169+
check: func(state *terraform.State, resource *terraform.ResourceState) error {
170+
attribs := resource.Primary.Attributes
171+
assert.Equal(t, "false", attribs["is_prebuild"])
172+
assert.Equal(t, "0", attribs["prebuild_count"])
173+
assert.Equal(t, "false", attribs["is_prebuild_claim"])
174+
return nil
175+
},
176+
},
177+
{
178+
// Should not ever happen, but let's ensure our defensive check is activated. We can't ever have both flags
179+
// being true.
180+
name: "prebuild=true,prebuild_claim=true",
181+
envs: map[string]string{
182+
provider.IsPrebuildEnvironmentVariable(): "true",
183+
provider.IsPrebuildClaimEnvironmentVariable(): "true",
184+
},
185+
check: func(state *terraform.State, resource *terraform.ResourceState) error {
186+
attribs := resource.Primary.Attributes
187+
assert.Equal(t, "false", attribs["is_prebuild"])
188+
assert.Equal(t, "0", attribs["prebuild_count"])
189+
assert.Equal(t, "true", attribs["is_prebuild_claim"])
190+
return nil
191+
},
192+
},
193+
}
194+
195+
for _, tc := range cases {
196+
t.Run(tc.name, func(t *testing.T) {
197+
for k, v := range tc.envs {
198+
t.Setenv(k, v)
199+
}
200+
201+
resource.Test(t, resource.TestCase{
202+
ProviderFactories: coderFactory(),
203+
IsUnitTest: true,
204+
Steps: []resource.TestStep{{
205+
Config: `
206+
provider "coder" {
207+
url = "https://example.com:8080"
208+
}
209+
data "coder_workspace" "me" {
210+
}`,
211+
Check: func(state *terraform.State) error {
212+
// Baseline checks
213+
require.Len(t, state.Modules, 1)
214+
require.Len(t, state.Modules[0].Resources, 1)
215+
resource := state.Modules[0].Resources["data.coder_workspace.me"]
216+
require.NotNil(t, resource)
217+
218+
return tc.check(state, resource)
219+
},
220+
}},
221+
})
222+
})
223+
}
224+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy