Skip to content

Commit 5648efb

Browse files
SasSwartEmyrk
andauthored
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 <[email protected]>
1 parent 4e7da25 commit 5648efb

File tree

3 files changed

+70
-11
lines changed

3 files changed

+70
-11
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/docker/docker v26.1.5+incompatible
77
github.com/google/uuid v1.6.0
88
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
9+
github.com/hashicorp/terraform-plugin-log v0.9.0
910
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1
1011
github.com/masterminds/semver v1.5.0
1112
github.com/mitchellh/mapstructure v1.5.0
@@ -50,7 +51,6 @@ require (
5051
github.com/hashicorp/terraform-exec v0.22.0 // indirect
5152
github.com/hashicorp/terraform-json v0.24.0 // indirect
5253
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
53-
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
5454
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
5555
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
5656
github.com/hashicorp/yamux v0.1.1 // indirect

provider/agent.go

+46-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package provider
22

33
import (
44
"context"
5+
"crypto/sha256"
6+
"encoding/hex"
57
"fmt"
68
"path/filepath"
79
"reflect"
810
"strings"
911

1012
"github.com/google/uuid"
1113
"github.com/hashicorp/go-cty/cty"
14+
"github.com/hashicorp/terraform-plugin-log/tflog"
1215
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1316
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1417
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
@@ -22,10 +25,12 @@ func agentResource() *schema.Resource {
2225
SchemaVersion: 1,
2326

2427
Description: "Use this resource to associate an agent.",
25-
CreateContext: func(_ context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
26-
// This should be a real authentication token!
27-
resourceData.SetId(uuid.NewString())
28-
err := resourceData.Set("token", uuid.NewString())
28+
CreateContext: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
29+
agentID := uuid.NewString()
30+
resourceData.SetId(agentID)
31+
32+
token := agentAuthToken(ctx, "")
33+
err := resourceData.Set("token", token)
2934
if err != nil {
3035
return diag.FromErr(err)
3136
}
@@ -48,10 +53,12 @@ func agentResource() *schema.Resource {
4853
return updateInitScript(resourceData, i)
4954
},
5055
ReadWithoutTimeout: func(ctx context.Context, resourceData *schema.ResourceData, i interface{}) diag.Diagnostics {
51-
err := resourceData.Set("token", uuid.NewString())
56+
token := agentAuthToken(ctx, "")
57+
err := resourceData.Set("token", token)
5258
if err != nil {
5359
return diag.FromErr(err)
5460
}
61+
5562
if _, ok := resourceData.GetOk("display_apps"); !ok {
5663
err = resourceData.Set("display_apps", []interface{}{
5764
map[string]bool{
@@ -469,3 +476,37 @@ func updateInitScript(resourceData *schema.ResourceData, i interface{}) diag.Dia
469476
}
470477
return nil
471478
}
479+
480+
func agentAuthToken(ctx context.Context, agentID string) string {
481+
existingToken := helpers.OptionalEnv(RunningAgentTokenEnvironmentVariable(agentID))
482+
if existingToken == "" {
483+
// Most of the time, we will generate a new token for the agent.
484+
// In the case of a prebuilt workspace being claimed, we will override with
485+
// an existing token provided below.
486+
token := uuid.NewString()
487+
return token
488+
}
489+
490+
// An existing token was provided for this agent. That means that this
491+
// is a prebuilt workspace in the process of being claimed.
492+
// We should reuse the token.
493+
tflog.Info(ctx, "using provided agent token for prebuild", map[string]interface{}{
494+
"agent_id": agentID,
495+
})
496+
return existingToken
497+
}
498+
499+
// RunningAgentTokenEnvironmentVariable returns the name of an environment variable
500+
// that contains the token to use for the running agent. This is used for prebuilds,
501+
// where we want to reuse the same token for the next iteration of a workspace agent
502+
// before and after the workspace was claimed by a user.
503+
//
504+
// By reusing an existing token, we can avoid the need to change a value that may have been
505+
// used immutably. Thus, allowing us to avoid reprovisioning resources that may take a long time
506+
// to replace.
507+
//
508+
// agentID is unused for now, but will be used as soon as we support multiple agents.
509+
func RunningAgentTokenEnvironmentVariable(agentID string) string {
510+
sum := sha256.Sum256([]byte(agentID))
511+
return "CODER_RUNNING_WORKSPACE_AGENT_TOKEN_" + hex.EncodeToString(sum[:])
512+
}

provider/workspace.go

+23-5
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ 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
30+
if isPrebuiltWorkspace() {
31+
_ = rd.Set("prebuild_count", 1)
3432
_ = rd.Set("is_prebuild", true)
33+
} else {
34+
_ = rd.Set("prebuild_count", 0)
35+
_ = rd.Set("is_prebuild", false)
3536
}
36-
_ = rd.Set("prebuild_count", prebuildCount)
3737

3838
name := helpers.OptionalEnvOrDefault("CODER_WORKSPACE_NAME", "default")
3939
rd.Set("name", name)
@@ -140,6 +140,24 @@ func workspaceDataSource() *schema.Resource {
140140
}
141141
}
142142

143+
// isPrebuiltWorkspace returns true if the workspace is an unclaimed prebuilt workspace.
144+
func isPrebuiltWorkspace() bool {
145+
return helpers.OptionalEnv(IsPrebuildEnvironmentVariable()) == "true"
146+
}
147+
148+
// IsPrebuildEnvironmentVariable returns the name of the environment variable that
149+
// indicates whether the workspace is an unclaimed prebuilt workspace.
150+
//
151+
// Knowing whether the workspace is an unclaimed prebuilt workspace allows template
152+
// authors to conditionally execute code in the template based on whether the workspace
153+
// has been assigned to a user or not. This allows identity specific configuration to
154+
// be applied only after the workspace is claimed, while the rest of the workspace can
155+
// be pre-configured.
156+
//
157+
// The value of this environment variable should be set to "true" if the workspace is prebuilt
158+
// and it has not yet been claimed by a user. Any other values, including "false"
159+
// and "" will be interpreted to mean that the workspace is not prebuilt, or was
160+
// prebuilt but has since been claimed by a user.
143161
func IsPrebuildEnvironmentVariable() string {
144162
return "CODER_WORKSPACE_IS_PREBUILD"
145163
}

0 commit comments

Comments
 (0)