Skip to content

Commit 9d5d955

Browse files
committed
feat: expose is_prebuild_claim attribute
Signed-off-by: Danny Kopping <dannykopping@gmail.com>
1 parent 4126ff4 commit 9d5d955

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

docs/data-sources/workspace.md

+1
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

+41
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,23 @@ func workspaceDataSource() *schema.Resource {
3030
if isPrebuiltWorkspace() {
3131
_ = rd.Set("prebuild_count", 1)
3232
_ = rd.Set("is_prebuild", true)
33+
34+
// A claim can only take place AFTER a prebuild, so it's not logically consistent to have this set to any other value.
35+
_ = rd.Set("is_prebuild_claim", false)
3336
} else {
3437
_ = rd.Set("prebuild_count", 0)
3538
_ = rd.Set("is_prebuild", false)
3639
}
40+
if isPrebuiltWorkspaceClaim() {
41+
// Indicate that a prebuild claim has taken place.
42+
_ = rd.Set("is_prebuild_claim", true)
43+
44+
// A claim can only take place AFTER a prebuild, so it's not logically consistent to have these set to any other values.
45+
_ = rd.Set("prebuild_count", 0)
46+
_ = rd.Set("is_prebuild", false)
47+
} else {
48+
_ = rd.Set("is_prebuild_claim", false)
49+
}
3750

3851
name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default")
3952
rd.Set("name", name)
@@ -116,6 +129,11 @@ func workspaceDataSource() *schema.Resource {
116129
Computed: true,
117130
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.",
118131
},
132+
"is_prebuild_claim": {
133+
Type: schema.TypeBool,
134+
Computed: true,
135+
Description: "Indicates whether a prebuilt workspace has just been claimed and this is the first `apply` after that occurrence.",
136+
},
119137
"name": {
120138
Type: schema.TypeString,
121139
Computed: true,
@@ -145,6 +163,11 @@ func isPrebuiltWorkspace() bool {
145163
return helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) == "true"
146164
}
147165

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

provider/workspace_test.go

+120
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)