Skip to content

Commit bfcf6f4

Browse files
committed
feat: Update Terraform provider to support "dir" in "coder_agent" (#1219)
This allows users to specify a starting directory for shell sessions.
1 parent bae35c0 commit bfcf6f4

File tree

17 files changed

+233
-145
lines changed

17 files changed

+233
-145
lines changed

agent/agent.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type Metadata struct {
4949
OwnerUsername string `json:"owner_username"`
5050
EnvironmentVariables map[string]string `json:"environment_variables"`
5151
StartupScript string `json:"startup_script"`
52+
Directory string `json:"directory"`
5253
}
5354

5455
type Dialer func(ctx context.Context, logger slog.Logger) (Metadata, *peerbroker.Listener, error)
@@ -340,6 +341,11 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
340341
caller = "/c"
341342
}
342343
cmd := exec.CommandContext(ctx, shell, caller, command)
344+
cmd.Dir = metadata.Directory
345+
if cmd.Dir == "" {
346+
// Default to $HOME if a directory is not set!
347+
cmd.Dir = os.Getenv("HOME")
348+
}
343349
cmd.Env = append(os.Environ(), env...)
344350
executablePath, err := os.Executable()
345351
if err != nil {

coderd/database/databasefake/databasefake.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,7 @@ func (q *fakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.Inser
11731173
Name: arg.Name,
11741174
Architecture: arg.Architecture,
11751175
OperatingSystem: arg.OperatingSystem,
1176+
Directory: arg.Directory,
11761177
StartupScript: arg.StartupScript,
11771178
InstanceMetadata: arg.InstanceMetadata,
11781179
ResourceMetadata: arg.ResourceMetadata,

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE ONLY workspace_agents
2+
DROP COLUMN IF EXISTS directory;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE ONLY workspace_agents
2+
-- UNIX paths are a maximum length of 4096.
3+
ADD COLUMN IF NOT EXISTS directory varchar(4096) DEFAULT '' NOT NULL;

coderd/database/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 13 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaceagents.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ INSERT INTO
4848
environment_variables,
4949
operating_system,
5050
startup_script,
51+
directory,
5152
instance_metadata,
5253
resource_metadata
5354
)
5455
VALUES
55-
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *;
56+
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *;
5657

5758
-- name: UpdateWorkspaceAgentConnectionByID :exec
5859
UPDATE

coderd/provisionerdaemons.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
243243
WorkspaceTransition: transition,
244244
WorkspaceName: workspace.Name,
245245
WorkspaceOwner: owner.Username,
246+
WorkspaceId: workspace.ID.String(),
247+
WorkspaceOwnerId: owner.ID.String(),
246248
},
247249
},
248250
}
@@ -633,6 +635,7 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
633635
AuthInstanceID: instanceID,
634636
Architecture: agent.Architecture,
635637
EnvironmentVariables: env,
638+
Directory: agent.Directory,
636639
OperatingSystem: agent.OperatingSystem,
637640
StartupScript: sql.NullString{
638641
String: agent.StartupScript,

coderd/workspaceagents.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ func (api *api) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
132132
OwnerUsername: owner.Username,
133133
EnvironmentVariables: apiAgent.EnvironmentVariables,
134134
StartupScript: apiAgent.StartupScript,
135+
Directory: apiAgent.Directory,
135136
})
136137
}
137138

@@ -469,6 +470,7 @@ func convertWorkspaceAgent(dbAgent database.WorkspaceAgent, agentUpdateFrequency
469470
OperatingSystem: dbAgent.OperatingSystem,
470471
StartupScript: dbAgent.StartupScript.String,
471472
EnvironmentVariables: envs,
473+
Directory: dbAgent.Directory,
472474
}
473475
if dbAgent.FirstConnectedAt.Valid {
474476
workspaceAgent.FirstConnectedAt = &dbAgent.FirstConnectedAt.Time

coderd/workspaceagents_test.go

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package coderd_test
33
import (
44
"context"
55
"encoding/json"
6+
"runtime"
67
"strings"
78
"testing"
89

@@ -22,40 +23,45 @@ import (
2223

2324
func TestWorkspaceAgent(t *testing.T) {
2425
t.Parallel()
25-
client := coderdtest.New(t, nil)
26-
user := coderdtest.CreateFirstUser(t, client)
27-
daemonCloser := coderdtest.NewProvisionerDaemon(t, client)
28-
authToken := uuid.NewString()
29-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
30-
Parse: echo.ParseComplete,
31-
ProvisionDryRun: echo.ProvisionComplete,
32-
Provision: []*proto.Provision_Response{{
33-
Type: &proto.Provision_Response_Complete{
34-
Complete: &proto.Provision_Complete{
35-
Resources: []*proto.Resource{{
36-
Name: "example",
37-
Type: "aws_instance",
38-
Agents: []*proto.Agent{{
39-
Id: uuid.NewString(),
40-
Auth: &proto.Agent_Token{
41-
Token: authToken,
42-
},
26+
t.Run("Connect", func(t *testing.T) {
27+
t.Parallel()
28+
client := coderdtest.New(t, nil)
29+
user := coderdtest.CreateFirstUser(t, client)
30+
daemonCloser := coderdtest.NewProvisionerDaemon(t, client)
31+
authToken := uuid.NewString()
32+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
33+
Parse: echo.ParseComplete,
34+
ProvisionDryRun: echo.ProvisionComplete,
35+
Provision: []*proto.Provision_Response{{
36+
Type: &proto.Provision_Response_Complete{
37+
Complete: &proto.Provision_Complete{
38+
Resources: []*proto.Resource{{
39+
Name: "example",
40+
Type: "aws_instance",
41+
Agents: []*proto.Agent{{
42+
Id: uuid.NewString(),
43+
Directory: "/tmp",
44+
Auth: &proto.Agent_Token{
45+
Token: authToken,
46+
},
47+
}},
4348
}},
44-
}},
49+
},
4550
},
46-
},
47-
}},
51+
}},
52+
})
53+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
54+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
55+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
56+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
57+
daemonCloser.Close()
58+
59+
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
60+
require.NoError(t, err)
61+
require.Equal(t, "/tmp", resources[0].Agents[0].Directory)
62+
_, err = client.WorkspaceAgent(context.Background(), resources[0].Agents[0].ID)
63+
require.NoError(t, err)
4864
})
49-
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
50-
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
51-
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
52-
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
53-
daemonCloser.Close()
54-
55-
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
56-
require.NoError(t, err)
57-
_, err = client.WorkspaceAgent(context.Background(), resources[0].Agents[0].ID)
58-
require.NoError(t, err)
5965
}
6066

6167
func TestWorkspaceAgentListen(t *testing.T) {
@@ -165,6 +171,12 @@ func TestWorkspaceAgentTURN(t *testing.T) {
165171

166172
func TestWorkspaceAgentPTY(t *testing.T) {
167173
t.Parallel()
174+
if runtime.GOOS == "windows" {
175+
// This might be our implementation, or ConPTY itself.
176+
// It's difficult to find extensive tests for it, so
177+
// it seems like it could be either.
178+
t.Skip("ConPTY appears to be inconsistent on Windows.")
179+
}
168180
client := coderdtest.New(t, nil)
169181
user := coderdtest.CreateFirstUser(t, client)
170182
daemonCloser := coderdtest.NewProvisionerDaemon(t, client)

codersdk/workspaceresources.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type WorkspaceAgent struct {
4545
EnvironmentVariables map[string]string `json:"environment_variables"`
4646
OperatingSystem string `json:"operating_system"`
4747
StartupScript string `json:"startup_script,omitempty"`
48+
Directory string `json:"directory,omitempty"`
4849
}
4950

5051
type WorkspaceAgentResourceMetadata struct {

provisioner/terraform/provision.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
124124
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(start.Metadata.WorkspaceTransition.String()),
125125
"CODER_WORKSPACE_NAME="+start.Metadata.WorkspaceName,
126126
"CODER_WORKSPACE_OWNER="+start.Metadata.WorkspaceOwner,
127+
"CODER_WORKSPACE_ID="+start.Metadata.WorkspaceId,
128+
"CODER_WORKSPACE_OWNER_ID="+start.Metadata.WorkspaceOwnerId,
127129
)
128130
for key, value := range provisionersdk.AgentScriptEnv() {
129131
env = append(env, key+"="+value)
@@ -330,6 +332,12 @@ func parseTerraformPlan(ctx context.Context, terraform *tfexec.Terraform, planfi
330332
agent.StartupScript = startupScript
331333
}
332334
}
335+
if directoryRaw, has := resource.Expressions["dir"]; has {
336+
dir, ok := directoryRaw.ConstantValue.(string)
337+
if ok {
338+
agent.Directory = dir
339+
}
340+
}
333341

334342
agents[resource.Address] = agent
335343
}
@@ -381,6 +389,7 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state
381389
Auth string `mapstructure:"auth"`
382390
OperatingSystem string `mapstructure:"os"`
383391
Architecture string `mapstructure:"arch"`
392+
Directory string `mapstructure:"dir"`
384393
ID string `mapstructure:"id"`
385394
Token string `mapstructure:"token"`
386395
Env map[string]string `mapstructure:"env"`
@@ -405,6 +414,7 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state
405414
StartupScript: attrs.StartupScript,
406415
OperatingSystem: attrs.OperatingSystem,
407416
Architecture: attrs.Architecture,
417+
Directory: attrs.Directory,
408418
}
409419
switch attrs.Auth {
410420
case "token":

provisioner/terraform/provision_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ provider "coder" {
160160
resource "coder_agent" "A" {
161161
os = "windows"
162162
arch = "arm64"
163+
dir = "C:\\System32"
163164
}
164165
resource "null_resource" "A" {
165166
depends_on = [
@@ -184,6 +185,7 @@ provider "coder" {
184185
Name: "A",
185186
OperatingSystem: "windows",
186187
Architecture: "arm64",
188+
Directory: "C:\\System32",
187189
Auth: &proto.Agent_Token{
188190
Token: "",
189191
},

0 commit comments

Comments
 (0)