Skip to content

Commit 234fc25

Browse files
committed
test
1 parent 7954f8d commit 234fc25

File tree

3 files changed

+261
-27
lines changed

3 files changed

+261
-27
lines changed

agent/agent.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -1121,11 +1121,9 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
11211121
)
11221122
if a.experimentalDevcontainersEnabled {
11231123
var dcScripts []codersdk.WorkspaceAgentScript
1124-
scripts, dcScripts = agentcontainers.ExtractDevcontainerScripts(a.logger, expandPathToAbs, manifest.Devcontainers, scripts)
1125-
// The post-start scripts are used to autostart Dev Containers
1126-
// after the start scripts have completed. This is necessary
1127-
// because the Dev Container may depend on the workspace being
1128-
// initialized (git clone, etc).
1124+
scripts, dcScripts = agentcontainers.ExtractAndInitializeDevcontainerScripts(a.logger, expandPathToAbs, manifest.Devcontainers, scripts)
1125+
// See ExtractAndInitializeDevcontainerScripts for motivation
1126+
// behind running dcScripts as post start scripts.
11291127
scriptRunnerOpts = append(scriptRunnerOpts, agentscripts.WithPostStartScripts(dcScripts...))
11301128
}
11311129
err = a.scriptRunner.Init(scripts, aAPI.ScriptCompleted, scriptRunnerOpts...)

agent/agentcontainers/devcontainer.go

+32-22
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,49 @@ fi
1818
devcontainer up %s
1919
`
2020

21-
// DevcontainerStartupScript returns a script that starts a devcontainer.
22-
func DevcontainerStartupScript(dc codersdk.WorkspaceAgentDevcontainer, script codersdk.WorkspaceAgentScript) codersdk.WorkspaceAgentScript {
23-
var args []string
24-
args = append(args, fmt.Sprintf("--workspace-folder %q", dc.WorkspaceFolder))
25-
if dc.ConfigPath != "" {
26-
args = append(args, fmt.Sprintf("--config %q", dc.ConfigPath))
27-
}
28-
cmd := fmt.Sprintf(devcontainerUpScriptTemplate, strings.Join(args, " "))
29-
script.RunOnStart = false
30-
script.Script = cmd
31-
return script
32-
}
33-
34-
func ExtractDevcontainerScripts(
21+
// ExtractAndInitializeDevcontainerScripts extracts devcontainer scripts from
22+
// the given scripts and devcontainers. The devcontainer scripts are removed
23+
// from the returned scripts so that they can be run separately.
24+
//
25+
// Dev Containers have an inherent dependency on start scripts, since they
26+
// initialize the workspace (e.g. git clone, npm install, etc). This is
27+
// important if e.g. a Coder module to install @devcontainer/cli is used.
28+
func ExtractAndInitializeDevcontainerScripts(
3529
logger slog.Logger,
3630
expandPath func(string) (string, error),
3731
devcontainers []codersdk.WorkspaceAgentDevcontainer,
3832
scripts []codersdk.WorkspaceAgentScript,
39-
) (other []codersdk.WorkspaceAgentScript, devcontainerScripts []codersdk.WorkspaceAgentScript) {
40-
for _, dc := range devcontainers {
41-
dc = expandDevcontainerPaths(logger, expandPath, dc)
42-
for _, script := range scripts {
33+
) (filteredScripts []codersdk.WorkspaceAgentScript, devcontainerScripts []codersdk.WorkspaceAgentScript) {
34+
ScriptLoop:
35+
for _, script := range scripts {
36+
for _, dc := range devcontainers {
4337
// The devcontainer scripts match the devcontainer ID for
4438
// identification.
4539
if script.ID == dc.ID {
46-
devcontainerScripts = append(devcontainerScripts, DevcontainerStartupScript(dc, script))
47-
} else {
48-
other = append(other, script)
40+
dc = expandDevcontainerPaths(logger, expandPath, dc)
41+
devcontainerScripts = append(devcontainerScripts, devcontainerStartupScript(dc, script))
42+
continue ScriptLoop
4943
}
5044
}
45+
46+
filteredScripts = append(filteredScripts, script)
5147
}
5248

53-
return other, devcontainerScripts
49+
return filteredScripts, devcontainerScripts
50+
}
51+
52+
func devcontainerStartupScript(dc codersdk.WorkspaceAgentDevcontainer, script codersdk.WorkspaceAgentScript) codersdk.WorkspaceAgentScript {
53+
var args []string
54+
args = append(args, fmt.Sprintf("--workspace-folder %q", dc.WorkspaceFolder))
55+
if dc.ConfigPath != "" {
56+
args = append(args, fmt.Sprintf("--config %q", dc.ConfigPath))
57+
}
58+
cmd := fmt.Sprintf(devcontainerUpScriptTemplate, strings.Join(args, " "))
59+
script.Script = cmd
60+
// Disable RunOnStart, scripts have this set so that when devcontainers
61+
// have not been enabled, a warning will be surfaced in the agent logs.
62+
script.RunOnStart = false
63+
return script
5464
}
5565

5666
func expandDevcontainerPaths(logger slog.Logger, expandPath func(string) (string, error), dc codersdk.WorkspaceAgentDevcontainer) codersdk.WorkspaceAgentDevcontainer {
+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package agentcontainers_test
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
"github.com/google/uuid"
10+
"github.com/stretchr/testify/require"
11+
12+
"cdr.dev/slog/sloggers/slogtest"
13+
"github.com/coder/coder/v2/agent/agentcontainers"
14+
"github.com/coder/coder/v2/codersdk"
15+
)
16+
17+
func TestExtractAndInitializeDevcontainerScripts(t *testing.T) {
18+
t.Parallel()
19+
20+
scriptIDs := []uuid.UUID{uuid.New(), uuid.New()}
21+
devcontainerIDs := []uuid.UUID{uuid.New(), uuid.New()}
22+
23+
type args struct {
24+
expandPath func(string) (string, error)
25+
devcontainers []codersdk.WorkspaceAgentDevcontainer
26+
scripts []codersdk.WorkspaceAgentScript
27+
}
28+
tests := []struct {
29+
name string
30+
args args
31+
wantFilteredScripts []codersdk.WorkspaceAgentScript
32+
wantDevcontainerScripts []codersdk.WorkspaceAgentScript
33+
}{
34+
{
35+
name: "no scripts",
36+
args: args{
37+
expandPath: nil,
38+
devcontainers: nil,
39+
scripts: nil,
40+
},
41+
wantFilteredScripts: nil,
42+
wantDevcontainerScripts: nil,
43+
},
44+
{
45+
name: "no devcontainers",
46+
args: args{
47+
expandPath: nil,
48+
devcontainers: nil,
49+
scripts: []codersdk.WorkspaceAgentScript{
50+
{ID: scriptIDs[0]},
51+
{ID: scriptIDs[1]},
52+
},
53+
},
54+
wantFilteredScripts: []codersdk.WorkspaceAgentScript{
55+
{ID: scriptIDs[0]},
56+
{ID: scriptIDs[1]},
57+
},
58+
wantDevcontainerScripts: nil,
59+
},
60+
{
61+
name: "no scripts match devcontainers",
62+
args: args{
63+
expandPath: nil,
64+
devcontainers: []codersdk.WorkspaceAgentDevcontainer{
65+
{ID: devcontainerIDs[0]},
66+
{ID: devcontainerIDs[1]},
67+
},
68+
scripts: []codersdk.WorkspaceAgentScript{
69+
{ID: scriptIDs[0]},
70+
{ID: scriptIDs[1]},
71+
},
72+
},
73+
wantFilteredScripts: []codersdk.WorkspaceAgentScript{
74+
{ID: scriptIDs[0]},
75+
{ID: scriptIDs[1]},
76+
},
77+
wantDevcontainerScripts: nil,
78+
},
79+
{
80+
name: "scripts match devcontainers and sets RunOnStart=false",
81+
args: args{
82+
expandPath: nil,
83+
devcontainers: []codersdk.WorkspaceAgentDevcontainer{
84+
{ID: devcontainerIDs[0], WorkspaceFolder: "workspace1"},
85+
{ID: devcontainerIDs[1], WorkspaceFolder: "workspace2"},
86+
},
87+
scripts: []codersdk.WorkspaceAgentScript{
88+
{ID: scriptIDs[0], RunOnStart: true},
89+
{ID: scriptIDs[1], RunOnStart: true},
90+
{ID: devcontainerIDs[0], RunOnStart: true},
91+
{ID: devcontainerIDs[1], RunOnStart: true},
92+
},
93+
},
94+
wantFilteredScripts: []codersdk.WorkspaceAgentScript{
95+
{ID: scriptIDs[0], RunOnStart: true},
96+
{ID: scriptIDs[1], RunOnStart: true},
97+
},
98+
wantDevcontainerScripts: []codersdk.WorkspaceAgentScript{
99+
{
100+
ID: devcontainerIDs[0],
101+
Script: "devcontainer up --workspace-folder \"workspace1\"",
102+
RunOnStart: false,
103+
},
104+
{
105+
ID: devcontainerIDs[1],
106+
Script: "devcontainer up --workspace-folder \"workspace2\"",
107+
RunOnStart: false,
108+
},
109+
},
110+
},
111+
{
112+
name: "scripts match devcontainers with config path",
113+
args: args{
114+
expandPath: nil,
115+
devcontainers: []codersdk.WorkspaceAgentDevcontainer{
116+
{
117+
ID: devcontainerIDs[0],
118+
WorkspaceFolder: "workspace1",
119+
ConfigPath: "config1",
120+
},
121+
{
122+
ID: devcontainerIDs[1],
123+
WorkspaceFolder: "workspace2",
124+
ConfigPath: "config2",
125+
},
126+
},
127+
scripts: []codersdk.WorkspaceAgentScript{
128+
{ID: devcontainerIDs[0]},
129+
{ID: devcontainerIDs[1]},
130+
},
131+
},
132+
wantFilteredScripts: []codersdk.WorkspaceAgentScript{},
133+
wantDevcontainerScripts: []codersdk.WorkspaceAgentScript{
134+
{
135+
ID: devcontainerIDs[0],
136+
Script: "devcontainer up --workspace-folder \"workspace1\" --config \"config1\"",
137+
RunOnStart: false,
138+
},
139+
{
140+
ID: devcontainerIDs[1],
141+
Script: "devcontainer up --workspace-folder \"workspace2\" --config \"config2\"",
142+
RunOnStart: false,
143+
},
144+
},
145+
},
146+
{
147+
name: "scripts match devcontainers with expand path",
148+
args: args{
149+
expandPath: func(s string) (string, error) {
150+
return "expanded/" + s, nil
151+
},
152+
devcontainers: []codersdk.WorkspaceAgentDevcontainer{
153+
{
154+
ID: devcontainerIDs[0],
155+
WorkspaceFolder: "workspace1",
156+
ConfigPath: "config1",
157+
},
158+
{
159+
ID: devcontainerIDs[1],
160+
WorkspaceFolder: "workspace2",
161+
ConfigPath: "config2",
162+
},
163+
},
164+
scripts: []codersdk.WorkspaceAgentScript{
165+
{ID: devcontainerIDs[0], RunOnStart: true},
166+
{ID: devcontainerIDs[1], RunOnStart: true},
167+
},
168+
},
169+
wantFilteredScripts: []codersdk.WorkspaceAgentScript{},
170+
wantDevcontainerScripts: []codersdk.WorkspaceAgentScript{
171+
{
172+
ID: devcontainerIDs[0],
173+
Script: "devcontainer up --workspace-folder \"expanded/workspace1\" --config \"expanded/config1\"",
174+
RunOnStart: false,
175+
},
176+
{
177+
ID: devcontainerIDs[1],
178+
Script: "devcontainer up --workspace-folder \"expanded/workspace2\" --config \"expanded/config2\"",
179+
RunOnStart: false,
180+
},
181+
},
182+
},
183+
}
184+
// nolint:foo
185+
for _, tt := range tests {
186+
t.Run(tt.name, func(t *testing.T) {
187+
t.Parallel()
188+
logger := slogtest.Make(t, nil)
189+
if tt.args.expandPath == nil {
190+
tt.args.expandPath = func(s string) (string, error) {
191+
return s, nil
192+
}
193+
}
194+
gotFilteredScripts, gotDevcontainerScripts := agentcontainers.ExtractAndInitializeDevcontainerScripts(
195+
logger,
196+
tt.args.expandPath,
197+
tt.args.devcontainers,
198+
tt.args.scripts,
199+
)
200+
201+
if diff := cmp.Diff(tt.wantFilteredScripts, gotFilteredScripts, cmpopts.EquateEmpty()); diff != "" {
202+
t.Errorf("ExtractAndInitializeDevcontainerScripts() gotFilteredScripts mismatch (-want +got):\n%s", diff)
203+
}
204+
205+
// Preprocess the devcontainer scripts to remove scripting part.
206+
for i := range gotDevcontainerScripts {
207+
gotDevcontainerScripts[i].Script = textGrep("devcontainer up", gotDevcontainerScripts[i].Script)
208+
require.NotEmpty(t, gotDevcontainerScripts[i].Script, "devcontainer up script not found")
209+
}
210+
if diff := cmp.Diff(tt.wantDevcontainerScripts, gotDevcontainerScripts); diff != "" {
211+
t.Errorf("ExtractAndInitializeDevcontainerScripts() gotDevcontainerScripts mismatch (-want +got):\n%s", diff)
212+
}
213+
})
214+
}
215+
}
216+
217+
// textGrep returns matching lines from multiline string.
218+
func textGrep(want, got string) (filtered string) {
219+
var lines []string
220+
for _, line := range strings.Split(got, "\n") {
221+
if strings.Contains(line, want) {
222+
lines = append(lines, line)
223+
}
224+
}
225+
return strings.Join(lines, "\n")
226+
}

0 commit comments

Comments
 (0)