Skip to content

Commit 129c295

Browse files
committed
Prebuilds
Signed-off-by: Danny Kopping <danny@coder.com>
1 parent aef6220 commit 129c295

File tree

5 files changed

+144
-6
lines changed

5 files changed

+144
-6
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ to setup your local Terraform to use your local version rather than the registry
4747
}
4848
```
4949
2. Run `terraform init` and observe a warning like `Warning: Provider development overrides are in effect`
50-
4. Run `go build -o terraform-provider-coder` to build the provider binary, which Terraform will try locate and execute
50+
4. Run `make build` to build the provider binary, which Terraform will try locate and execute
5151
5. All local Terraform runs will now use your local provider!
5252
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._
5353

provider/agent.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package provider
33
import (
44
"context"
55
"fmt"
6+
"os"
67
"path/filepath"
78
"reflect"
89
"strings"
910

1011
"github.com/google/uuid"
1112
"github.com/hashicorp/go-cty/cty"
13+
"github.com/hashicorp/terraform-plugin-log/tflog"
1214
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1315
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1416
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
@@ -22,10 +24,54 @@ func agentResource() *schema.Resource {
2224
SchemaVersion: 1,
2325

2426
Description: "Use this resource to associate an agent.",
25-
CreateContext: func(_ context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
27+
CreateContext: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
2628
// This should be a real authentication token!
2729
resourceData.SetId(uuid.NewString())
28-
err := resourceData.Set("token", uuid.NewString())
30+
31+
// CODER_RUNNING_WORKSPACE_AGENT_TOKEN is *only* used for prebuilds. We pass it down when we want to rebuild a prebuilt workspace
32+
// but not generate a new agent token. The provisionerdserver will retrieve this token and push it down to
33+
// here where it will be reused.
34+
// Context: the agent token is often used in immutable attributes of workspace resource (e.g. VM/container)
35+
// to initialize the agent, so if that value changes it will necessitate a replacement of that resource, thus
36+
// obviating the whole point of the prebuild.
37+
//
38+
// The default path is for a new token to be generated on each new resource creation.
39+
// TODO: add logging when the running token is actually used.
40+
var token string
41+
42+
isPrebuild := helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) == "true"
43+
if !isPrebuild {
44+
token = os.Getenv(RunningAgentTokenEnvironmentVariable())
45+
}
46+
47+
allEnv := make(map[string]interface{})
48+
for _, v := range os.Environ() {
49+
split := strings.Split(v, "=")
50+
var key, val string
51+
if len(split) > 0 {
52+
key = split[0]
53+
}
54+
if len(split) > 1 {
55+
val = split[1]
56+
}
57+
58+
allEnv[key] = val
59+
}
60+
61+
allEnv["is_prebuild"] = fmt.Sprintf("%v", isPrebuild)
62+
63+
if token == "" {
64+
token = uuid.NewString()
65+
if !isPrebuild {
66+
tflog.Warn(ctx, "NOT USING EXISTING AGENT TOKEN", allEnv)
67+
}
68+
} else {
69+
if !isPrebuild {
70+
tflog.Info(ctx, "IS USING EXISTING AGENT TOKEN", allEnv)
71+
}
72+
}
73+
74+
err := resourceData.Set("token", token)
2975
if err != nil {
3076
return diag.FromErr(err)
3177
}
@@ -469,3 +515,7 @@ func updateInitScript(resourceData *schema.ResourceData, i interface{}) diag.Dia
469515
}
470516
return nil
471517
}
518+
519+
func RunningAgentTokenEnvironmentVariable() string {
520+
return "CODER_RUNNING_WORKSPACE_AGENT_TOKEN"
521+
}

provider/workspace.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ func workspaceDataSource() *schema.Resource {
2727
}
2828
_ = rd.Set("start_count", count)
2929

30+
prebuild := helpers.OptionalEnv(IsPrebuildEnvironmentVariable())
31+
prebuildCount := 0
32+
if prebuild == "true" {
33+
prebuildCount = 1
34+
_ = rd.Set("is_prebuild", true)
35+
}
36+
_ = rd.Set("prebuild_count", prebuildCount)
37+
3038
name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default")
3139
rd.Set("name", name)
3240

@@ -88,6 +96,16 @@ func workspaceDataSource() *schema.Resource {
8896
Computed: true,
8997
Description: "A computed count based on `transition` state. If `start`, count will equal 1.",
9098
},
99+
"prebuild_count": {
100+
Type: schema.TypeInt,
101+
Computed: true,
102+
Description: "TODO",
103+
},
104+
"is_prebuild": {
105+
Type: schema.TypeBool,
106+
Computed: true,
107+
Description: "TODO",
108+
},
91109
"transition": {
92110
Type: schema.TypeString,
93111
Computed: true,
@@ -121,3 +139,7 @@ func workspaceDataSource() *schema.Resource {
121139
},
122140
}
123141
}
142+
143+
func IsPrebuildEnvironmentVariable() string {
144+
return "CODER_WORKSPACE_IS_PREBUILD"
145+
}

provider/workspace_preset.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ package provider
22

33
import (
44
"context"
5-
65
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
76
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
87
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
98
"github.com/mitchellh/mapstructure"
109
)
1110

1211
type WorkspacePreset struct {
13-
Name string `mapstructure:"name"`
14-
Parameters map[string]string `mapstructure:"parameters"`
12+
Name string `mapstructure:"name"`
13+
Parameters map[string]string `mapstructure:"parameters"`
14+
Prebuild []WorkspacePrebuild `mapstructure:"prebuilds"`
15+
}
16+
17+
type WorkspacePrebuild struct {
18+
Instances int `mapstructure:"instances"`
1519
}
1620

1721
func workspacePresetDataSource() *schema.Resource {
@@ -24,9 +28,19 @@ func workspacePresetDataSource() *schema.Resource {
2428
err := mapstructure.Decode(struct {
2529
Name interface{}
2630
Parameters interface{}
31+
Prebuilds []struct {
32+
Instances interface{}
33+
}
2734
}{
2835
Name: rd.Get("name"),
2936
Parameters: rd.Get("parameters"),
37+
Prebuilds: []struct {
38+
Instances interface{}
39+
}{
40+
{
41+
Instances: rd.Get("prebuilds.0.instances"),
42+
},
43+
},
3044
}, &preset)
3145
if err != nil {
3246
return diag.Errorf("decode workspace preset: %s", err)
@@ -65,6 +79,22 @@ func workspacePresetDataSource() *schema.Resource {
6579
ValidateFunc: validation.StringIsNotEmpty,
6680
},
6781
},
82+
"prebuilds": {
83+
Type: schema.TypeSet,
84+
Description: "Prebuilds of the workspace preset.",
85+
Optional: true,
86+
MaxItems: 1, // TODO: is this always true? More than 1 prebuilds config per preset?
87+
Elem: &schema.Resource{
88+
Schema: map[string]*schema.Schema{
89+
"instances": {
90+
Type: schema.TypeInt,
91+
Required: true,
92+
ForceNew: true,
93+
ValidateFunc: validation.IntAtLeast(1),
94+
},
95+
},
96+
},
97+
},
6898
},
6999
}
70100
}

provider/workspace_preset_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,42 @@ func TestWorkspacePreset(t *testing.T) {
108108
// So we test it here to make sure we don't regress.
109109
ExpectError: regexp.MustCompile("Inappropriate value for attribute \"parameters\": map of string required"),
110110
},
111+
{
112+
Name: "Prebuilds is set, but not its required fields",
113+
Config: `
114+
data "coder_workspace_preset" "preset_1" {
115+
name = "preset_1"
116+
parameters = {
117+
"region" = "us-east1-a"
118+
}
119+
prebuilds {}
120+
}`,
121+
ExpectError: regexp.MustCompile("The argument \"instances\" is required, but no definition was found."),
122+
},
123+
{
124+
Name: "Prebuilds is set, and so are its required fields",
125+
Config: `
126+
data "coder_workspace_preset" "preset_1" {
127+
name = "preset_1"
128+
parameters = {
129+
"region" = "us-east1-a"
130+
}
131+
prebuilds {
132+
instances = 1
133+
}
134+
}`,
135+
ExpectError: nil,
136+
Check: func(state *terraform.State) error {
137+
require.Len(t, state.Modules, 1)
138+
require.Len(t, state.Modules[0].Resources, 1)
139+
resource := state.Modules[0].Resources["data.coder_workspace_preset.preset_1"]
140+
require.NotNil(t, resource)
141+
attrs := resource.Primary.Attributes
142+
require.Equal(t, attrs["name"], "preset_1")
143+
require.Equal(t, attrs["prebuilds.0.instances"], "1")
144+
return nil
145+
},
146+
},
111147
}
112148

113149
for _, testcase := range testcases {

0 commit comments

Comments
 (0)