Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb1c0b39

Browse files
authored
feat(agent): add script data dir for binaries and files (#12205)
The agent is extended with a `--script-data-dir` flag, defaulting to theOS temp dir. This dir is used for storing `coder-script-data/bin` and`coder-script/[script uuid]`. The former is a place for all scripts toplace executable binaries that will be available by other scripts, SSHsessions, etc. The latter is a place for the script to store files.Since we default to OS temp dir, files are ephemeral by default. In thefuture, we may consider adding new env vars or changing the defaultstorage location. Workspace startup speed could potentially benefit fromscripts being able to skip steps that require downloading software. Wemay also extend this with more env variables (e.g. persistent storage inHOME).Fixes#11131
1 parentab4cb66 commitb1c0b39

File tree

6 files changed

+162
-20
lines changed

6 files changed

+162
-20
lines changed

‎agent/agent.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type Options struct {
6666
Filesystem afero.Fs
6767
LogDirstring
6868
TempDirstring
69+
ScriptDataDirstring
6970
ExchangeTokenfunc(ctx context.Context) (string,error)
7071
ClientClient
7172
ReconnectingPTYTimeout time.Duration
@@ -112,9 +113,19 @@ func New(options Options) Agent {
112113
ifoptions.LogDir=="" {
113114
ifoptions.TempDir!=os.TempDir() {
114115
options.Logger.Debug(context.Background(),"log dir not set, using temp dir",slog.F("temp_dir",options.TempDir))
116+
}else {
117+
options.Logger.Debug(context.Background(),"using log dir",slog.F("log_dir",options.LogDir))
115118
}
116119
options.LogDir=options.TempDir
117120
}
121+
ifoptions.ScriptDataDir=="" {
122+
ifoptions.TempDir!=os.TempDir() {
123+
options.Logger.Debug(context.Background(),"script data dir not set, using temp dir",slog.F("temp_dir",options.TempDir))
124+
}else {
125+
options.Logger.Debug(context.Background(),"using script data dir",slog.F("script_data_dir",options.ScriptDataDir))
126+
}
127+
options.ScriptDataDir=options.TempDir
128+
}
118129
ifoptions.ExchangeToken==nil {
119130
options.ExchangeToken=func(ctx context.Context) (string,error) {
120131
return"",nil
@@ -152,6 +163,7 @@ func New(options Options) Agent {
152163
filesystem:options.Filesystem,
153164
logDir:options.LogDir,
154165
tempDir:options.TempDir,
166+
scriptDataDir:options.ScriptDataDir,
155167
lifecycleUpdate:make(chanstruct{},1),
156168
lifecycleReported:make(chan codersdk.WorkspaceAgentLifecycle,1),
157169
lifecycleStates: []agentsdk.PostLifecycleRequest{{State:codersdk.WorkspaceAgentLifecycleCreated}},
@@ -183,6 +195,7 @@ type agent struct {
183195
filesystem afero.Fs
184196
logDirstring
185197
tempDirstring
198+
scriptDataDirstring
186199
// ignorePorts tells the api handler which ports to ignore when
187200
// listing all listening ports. This is helpful to hide ports that
188201
// are used by the agent, that the user does not care about.
@@ -249,11 +262,12 @@ func (a *agent) init(ctx context.Context) {
249262
}
250263
a.sshServer=sshSrv
251264
a.scriptRunner=agentscripts.New(agentscripts.Options{
252-
LogDir:a.logDir,
253-
Logger:a.logger,
254-
SSHServer:sshSrv,
255-
Filesystem:a.filesystem,
256-
PatchLogs:a.client.PatchLogs,
265+
LogDir:a.logDir,
266+
DataDirBase:a.scriptDataDir,
267+
Logger:a.logger,
268+
SSHServer:sshSrv,
269+
Filesystem:a.filesystem,
270+
PatchLogs:a.client.PatchLogs,
257271
})
258272
// Register runner metrics. If the prom registry is nil, the metrics
259273
// will not report anywhere.
@@ -954,6 +968,13 @@ func (a *agent) updateCommandEnv(current []string) (updated []string, err error)
954968
envs[k]=v
955969
}
956970

971+
// Prepend the agent script bin directory to the PATH
972+
// (this is where Coder modules place their binaries).
973+
if_,ok:=envs["PATH"];!ok {
974+
envs["PATH"]=os.Getenv("PATH")
975+
}
976+
envs["PATH"]=fmt.Sprintf("%s%c%s",a.scriptRunner.ScriptBinDir(),filepath.ListSeparator,envs["PATH"])
977+
957978
fork,v:=rangeenvs {
958979
updated=append(updated,fmt.Sprintf("%s=%s",k,v))
959980
}

‎agent/agent_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,12 @@ func TestAgent_SessionExec(t *testing.T) {
286286
funcTestAgent_Session_EnvironmentVariables(t*testing.T) {
287287
t.Parallel()
288288

289+
tmpdir:=t.TempDir()
290+
291+
// Defined by the coder script runner, hardcoded here since we don't
292+
// have a reference to it.
293+
scriptBinDir:=filepath.Join(tmpdir,"coder-script-data","bin")
294+
289295
manifest:= agentsdk.Manifest{
290296
EnvironmentVariables:map[string]string{
291297
"MY_MANIFEST":"true",
@@ -295,6 +301,7 @@ func TestAgent_Session_EnvironmentVariables(t *testing.T) {
295301
}
296302
banner:= codersdk.ServiceBannerConfig{}
297303
session:=setupSSHSession(t,manifest,banner,nil,func(_*agenttest.Client,opts*agent.Options) {
304+
opts.ScriptDataDir=tmpdir
298305
opts.EnvironmentVariables["MY_OVERRIDE"]="true"
299306
})
300307

@@ -341,6 +348,7 @@ func TestAgent_Session_EnvironmentVariables(t *testing.T) {
341348
"MY_OVERRIDE":"true",// From the agent environment variables option, overrides manifest.
342349
"MY_SESSION_MANIFEST":"false",// From the manifest, overrides session env.
343350
"MY_SESSION":"true",// From the session.
351+
"PATH":scriptBinDir+string(filepath.ListSeparator),
344352
} {
345353
t.Run(k,func(t*testing.T) {
346354
echoEnv(t,stdin,k)

‎agent/agentscripts/agentscripts.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@ var (
4343

4444
// Options are a set of options for the runner.
4545
typeOptionsstruct {
46-
LogDirstring
47-
Logger slog.Logger
48-
SSHServer*agentssh.Server
49-
Filesystem afero.Fs
50-
PatchLogsfunc(ctx context.Context,req agentsdk.PatchLogs)error
46+
DataDirBasestring
47+
LogDirstring
48+
Logger slog.Logger
49+
SSHServer*agentssh.Server
50+
Filesystem afero.Fs
51+
PatchLogsfunc(ctx context.Context,req agentsdk.PatchLogs)error
5152
}
5253

5354
// New creates a runner for the provided scripts.
@@ -59,6 +60,7 @@ func New(opts Options) *Runner {
5960
cronCtxCancel:cronCtxCancel,
6061
cron:cron.New(cron.WithParser(parser)),
6162
closed:make(chanstruct{}),
63+
dataDir:filepath.Join(opts.DataDirBase,"coder-script-data"),
6264
scriptsExecuted:prometheus.NewCounterVec(prometheus.CounterOpts{
6365
Namespace:"agent",
6466
Subsystem:"scripts",
@@ -78,13 +80,25 @@ type Runner struct {
7880
cron*cron.Cron
7981
initialized atomic.Bool
8082
scripts []codersdk.WorkspaceAgentScript
83+
dataDirstring
8184

8285
// scriptsExecuted includes all scripts executed by the workspace agent. Agents
8386
// execute startup scripts, and scripts on a cron schedule. Both will increment
8487
// this counter.
8588
scriptsExecuted*prometheus.CounterVec
8689
}
8790

91+
// DataDir returns the directory where scripts data is stored.
92+
func (r*Runner)DataDir()string {
93+
returnr.dataDir
94+
}
95+
96+
// ScriptBinDir returns the directory where scripts can store executable
97+
// binaries.
98+
func (r*Runner)ScriptBinDir()string {
99+
returnfilepath.Join(r.dataDir,"bin")
100+
}
101+
88102
func (r*Runner)RegisterMetrics(reg prometheus.Registerer) {
89103
ifreg==nil {
90104
// If no registry, do nothing.
@@ -104,6 +118,11 @@ func (r *Runner) Init(scripts []codersdk.WorkspaceAgentScript) error {
104118
r.scripts=scripts
105119
r.Logger.Info(r.cronCtx,"initializing agent scripts",slog.F("script_count",len(scripts)),slog.F("log_dir",r.LogDir))
106120

121+
err:=r.Filesystem.MkdirAll(r.ScriptBinDir(),0o700)
122+
iferr!=nil {
123+
returnxerrors.Errorf("create script bin dir: %w",err)
124+
}
125+
107126
for_,script:=rangescripts {
108127
ifscript.Cron=="" {
109128
continue
@@ -208,7 +227,18 @@ func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript)
208227
if!filepath.IsAbs(logPath) {
209228
logPath=filepath.Join(r.LogDir,logPath)
210229
}
211-
logger:=r.Logger.With(slog.F("log_path",logPath))
230+
231+
scriptDataDir:=filepath.Join(r.DataDir(),script.LogSourceID.String())
232+
err:=r.Filesystem.MkdirAll(scriptDataDir,0o700)
233+
iferr!=nil {
234+
returnxerrors.Errorf("%s script: create script temp dir: %w",scriptDataDir,err)
235+
}
236+
237+
logger:=r.Logger.With(
238+
slog.F("log_source_id",script.LogSourceID),
239+
slog.F("log_path",logPath),
240+
slog.F("script_data_dir",scriptDataDir),
241+
)
212242
logger.Info(ctx,"running agent script",slog.F("script",script.Script))
213243

214244
fileWriter,err:=r.Filesystem.OpenFile(logPath,os.O_CREATE|os.O_RDWR,0o600)
@@ -238,6 +268,13 @@ func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript)
238268
cmd.WaitDelay=10*time.Second
239269
cmd.Cancel=cmdCancel(cmd)
240270

271+
// Expose env vars that can be used in the script for storing data
272+
// and binaries. In the future, we may want to expose more env vars
273+
// for the script to use, like CODER_SCRIPT_DATA_DIR for persistent
274+
// storage.
275+
cmd.Env=append(cmd.Env,"CODER_SCRIPT_DATA_DIR="+scriptDataDir)
276+
cmd.Env=append(cmd.Env,"CODER_SCRIPT_BIN_DIR="+r.ScriptBinDir())
277+
241278
send,flushAndClose:=agentsdk.LogsSender(script.LogSourceID,r.PatchLogs,logger)
242279
// If ctx is canceled here (or in a writer below), we may be
243280
// discarding logs, but that's okay because we're shutting down

‎agent/agentscripts/agentscripts_test.go

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package agentscripts_test
22

33
import (
44
"context"
5+
"path/filepath"
6+
"runtime"
57
"testing"
68
"time"
79

10+
"github.com/google/uuid"
811
"github.com/prometheus/client_golang/prometheus"
912
"github.com/spf13/afero"
13+
"github.com/stretchr/testify/assert"
1014
"github.com/stretchr/testify/require"
1115
"go.uber.org/goleak"
1216

@@ -15,6 +19,7 @@ import (
1519
"github.com/coder/coder/v2/agent/agentssh"
1620
"github.com/coder/coder/v2/codersdk"
1721
"github.com/coder/coder/v2/codersdk/agentsdk"
22+
"github.com/coder/coder/v2/testutil"
1823
)
1924

2025
funcTestMain(m*testing.M) {
@@ -25,12 +30,16 @@ func TestExecuteBasic(t *testing.T) {
2530
t.Parallel()
2631
logs:=make(chan agentsdk.PatchLogs,1)
2732
runner:=setup(t,func(ctx context.Context,req agentsdk.PatchLogs)error {
28-
logs<-req
33+
select {
34+
case<-ctx.Done():
35+
caselogs<-req:
36+
}
2937
returnnil
3038
})
3139
deferrunner.Close()
3240
err:=runner.Init([]codersdk.WorkspaceAgentScript{{
33-
Script:"echo hello",
41+
LogSourceID:uuid.New(),
42+
Script:"echo hello",
3443
}})
3544
require.NoError(t,err)
3645
require.NoError(t,runner.Execute(context.Background(),func(script codersdk.WorkspaceAgentScript)bool {
@@ -40,13 +49,67 @@ func TestExecuteBasic(t *testing.T) {
4049
require.Equal(t,"hello",log.Logs[0].Output)
4150
}
4251

52+
funcTestEnv(t*testing.T) {
53+
t.Parallel()
54+
logs:=make(chan agentsdk.PatchLogs,2)
55+
runner:=setup(t,func(ctx context.Context,req agentsdk.PatchLogs)error {
56+
select {
57+
case<-ctx.Done():
58+
caselogs<-req:
59+
}
60+
returnnil
61+
})
62+
deferrunner.Close()
63+
id:=uuid.New()
64+
script:="echo $CODER_SCRIPT_DATA_DIR\necho $CODER_SCRIPT_BIN_DIR\n"
65+
ifruntime.GOOS=="windows" {
66+
script=`
67+
cmd.exe /c echo %CODER_SCRIPT_DATA_DIR%
68+
cmd.exe /c echo %CODER_SCRIPT_BIN_DIR%
69+
`
70+
}
71+
err:=runner.Init([]codersdk.WorkspaceAgentScript{{
72+
LogSourceID:id,
73+
Script:script,
74+
}})
75+
require.NoError(t,err)
76+
77+
ctx:=testutil.Context(t,testutil.WaitLong)
78+
79+
testutil.Go(t,func() {
80+
err:=runner.Execute(ctx,func(script codersdk.WorkspaceAgentScript)bool {
81+
returntrue
82+
})
83+
assert.NoError(t,err)
84+
})
85+
86+
varlog []agentsdk.Log
87+
for {
88+
select {
89+
case<-ctx.Done():
90+
require.Fail(t,"timed out waiting for logs")
91+
casel:=<-logs:
92+
for_,l:=rangel.Logs {
93+
t.Logf("log: %s",l.Output)
94+
}
95+
log=append(log,l.Logs...)
96+
}
97+
iflen(log)>=2 {
98+
break
99+
}
100+
}
101+
require.Contains(t,log[0].Output,filepath.Join(runner.DataDir(),id.String()))
102+
require.Contains(t,log[1].Output,runner.ScriptBinDir())
103+
}
104+
43105
funcTestTimeout(t*testing.T) {
44106
t.Parallel()
45107
runner:=setup(t,nil)
46108
deferrunner.Close()
47109
err:=runner.Init([]codersdk.WorkspaceAgentScript{{
48-
Script:"sleep infinity",
49-
Timeout:time.Millisecond,
110+
LogSourceID:uuid.New(),
111+
Script:"sleep infinity",
112+
Timeout:time.Millisecond,
50113
}})
51114
require.NoError(t,err)
52115
require.ErrorIs(t,runner.Execute(context.Background(),nil),agentscripts.ErrTimeout)
@@ -77,10 +140,11 @@ func setup(t *testing.T, patchLogs func(ctx context.Context, req agentsdk.PatchL
77140
_=s.Close()
78141
})
79142
returnagentscripts.New(agentscripts.Options{
80-
LogDir:t.TempDir(),
81-
Logger:logger,
82-
SSHServer:s,
83-
Filesystem:fs,
84-
PatchLogs:patchLogs,
143+
LogDir:t.TempDir(),
144+
DataDirBase:t.TempDir(),
145+
Logger:logger,
146+
SSHServer:s,
147+
Filesystem:fs,
148+
PatchLogs:patchLogs,
85149
})
86150
}

‎cli/agent.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
4040
var (
4141
authstring
4242
logDirstring
43+
scriptDataDirstring
4344
pprofAddressstring
4445
noReapbool
4546
sshMaxTimeout time.Duration
@@ -289,6 +290,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
289290
Client:client,
290291
Logger:logger,
291292
LogDir:logDir,
293+
ScriptDataDir:scriptDataDir,
292294
TailnetListenPort:uint16(tailnetListenPort),
293295
ExchangeToken:func(ctx context.Context) (string,error) {
294296
ifexchangeToken==nil {
@@ -339,6 +341,13 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
339341
Env:"CODER_AGENT_LOG_DIR",
340342
Value:clibase.StringOf(&logDir),
341343
},
344+
{
345+
Flag:"script-data-dir",
346+
Default:os.TempDir(),
347+
Description:"Specify the location for storing script data.",
348+
Env:"CODER_AGENT_SCRIPT_DATA_DIR",
349+
Value:clibase.StringOf(&scriptDataDir),
350+
},
342351
{
343352
Flag:"pprof-address",
344353
Default:"127.0.0.1:6060",

‎cli/testdata/coder_agent_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ OPTIONS:
3333
--prometheus-address string, $CODER_AGENT_PROMETHEUS_ADDRESS (default: 127.0.0.1:2112)
3434
The bind address to serve Prometheus metrics.
3535

36+
--script-data-dir string, $CODER_AGENT_SCRIPT_DATA_DIR (default: /tmp)
37+
Specify the location for storing script data.
38+
3639
--ssh-max-timeout duration, $CODER_AGENT_SSH_MAX_TIMEOUT (default: 72h)
3740
Specify the max timeout for a SSH connection, it is advisable to set
3841
it to a minimum of 60s, but no more than 72h.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp