Skip to content

Commit 3a0284f

Browse files
Merge remote-tracking branch 'origin/main' into 17432-limit-prebuild-failure-cost
2 parents 906ceb9 + 87a1ebc commit 3a0284f

File tree

187 files changed

+3492
-2104
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

187 files changed

+3492
-2104
lines changed

.github/actions/setup-go/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ runs:
4242

4343
- name: Install gotestsum
4444
shell: bash
45-
run: go install gotest.tools/gotestsum@3f7ff0ec4aeb6f95f5d67c998b71f272aa8a8b41 # v1.12.1
45+
run: go install gotest.tools/gotestsum@0d9599e513d70e5792bb9334869f82f6e8b53d4d # main as of 2025-05-15
4646

4747
# It isn't necessary that we ever do this, but it helps
4848
# separate the "setup" from the "run" times.

.github/workflows/ci.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ jobs:
336336
# a separate repository to allow its use before actions/checkout.
337337
- name: Setup RAM Disks
338338
if: runner.os == 'Windows'
339-
uses: coder/setup-ramdisk-action@79dacfe70c47ad6d6c0dd7f45412368802641439
339+
uses: coder/setup-ramdisk-action@81c5c441bda00c6c3d6bcee2e5a33ed4aadbbcc1
340340

341341
- name: Checkout
342342
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -613,7 +613,7 @@ jobs:
613613
# c.f. discussion on https://github.com/coder/coder/pull/15106
614614
- name: Run Tests
615615
run: |
616-
gotestsum --junitfile="gotests.xml" -- -race -parallel 4 -p 4 ./...
616+
gotestsum --junitfile="gotests.xml" --packages="./..." --rerun-fails=2 --rerun-fails-abort-on-data-race -- -race -parallel 4 -p 4
617617
618618
- name: Upload Test Cache
619619
uses: ./.github/actions/test-cache/upload
@@ -665,7 +665,7 @@ jobs:
665665
POSTGRES_VERSION: "16"
666666
run: |
667667
make test-postgres-docker
668-
DB=ci gotestsum --junitfile="gotests.xml" -- -race -parallel 4 -p 4 ./...
668+
DB=ci gotestsum --junitfile="gotests.xml" --packages="./..." --rerun-fails=2 --rerun-fails-abort-on-data-race -- -race -parallel 4 -p 4
669669
670670
- name: Upload Test Cache
671671
uses: ./.github/actions/test-cache/upload

.github/workflows/release.yaml

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -924,55 +924,3 @@ jobs:
924924
continue-on-error: true
925925
run: |
926926
make sqlc-push
927-
928-
update-calendar:
929-
name: "Update release calendar in docs"
930-
runs-on: "ubuntu-latest"
931-
needs: [release, publish-homebrew, publish-winget, publish-sqlc]
932-
if: ${{ !inputs.dry_run }}
933-
permissions:
934-
contents: write
935-
pull-requests: write
936-
steps:
937-
- name: Harden Runner
938-
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
939-
with:
940-
egress-policy: audit
941-
942-
- name: Checkout repository
943-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
944-
with:
945-
fetch-depth: 0 # Needed to get all tags for version calculation
946-
947-
- name: Set up Git
948-
run: |
949-
git config user.name "Coder CI"
950-
git config user.email "cdrci@coder.com"
951-
952-
- name: Run update script
953-
run: |
954-
./scripts/update-release-calendar.sh
955-
make fmt/markdown
956-
957-
- name: Check for changes
958-
id: check_changes
959-
run: |
960-
if git diff --quiet docs/install/releases/index.md; then
961-
echo "No changes detected in release calendar."
962-
echo "changes=false" >> $GITHUB_OUTPUT
963-
else
964-
echo "Changes detected in release calendar."
965-
echo "changes=true" >> $GITHUB_OUTPUT
966-
fi
967-
968-
- name: Create Pull Request
969-
if: steps.check_changes.outputs.changes == 'true'
970-
uses: peter-evans/create-pull-request@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
971-
with:
972-
commit-message: "docs: update release calendar"
973-
title: "docs: update release calendar"
974-
body: |
975-
This PR automatically updates the release calendar in the docs.
976-
branch: bot/update-release-calendar
977-
delete-branch: true
978-
labels: docs

agent/agent.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ type Options struct {
8989
ServiceBannerRefreshInterval time.Duration
9090
BlockFileTransfer bool
9191
Execer agentexec.Execer
92-
SubAgent bool
9392

9493
ExperimentalDevcontainersEnabled bool
9594
ContainerAPIOptions []agentcontainers.Option // Enable ExperimentalDevcontainersEnabled for these to be effective.
@@ -191,8 +190,6 @@ func New(options Options) Agent {
191190
metrics: newAgentMetrics(prometheusRegistry),
192191
execer: options.Execer,
193192

194-
subAgent: options.SubAgent,
195-
196193
experimentalDevcontainersEnabled: options.ExperimentalDevcontainersEnabled,
197194
containerAPIOptions: options.ContainerAPIOptions,
198195
}
@@ -275,8 +272,6 @@ type agent struct {
275272
metrics *agentMetrics
276273
execer agentexec.Execer
277274

278-
subAgent bool
279-
280275
experimentalDevcontainersEnabled bool
281276
containerAPIOptions []agentcontainers.Option
282277
containerAPI atomic.Pointer[agentcontainers.API] // Set by apiHandler.

agent/agent_test.go

Lines changed: 176 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,10 +1262,6 @@ func TestAgent_SSHConnectionLoginVars(t *testing.T) {
12621262
key: "LOGNAME",
12631263
want: u.Username,
12641264
},
1265-
{
1266-
key: "HOME",
1267-
want: u.HomeDir,
1268-
},
12691265
{
12701266
key: "SHELL",
12711267
want: shell,
@@ -1502,7 +1498,7 @@ func TestAgent_Lifecycle(t *testing.T) {
15021498

15031499
_, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
15041500
Scripts: []codersdk.WorkspaceAgentScript{{
1505-
Script: "true",
1501+
Script: "echo foo",
15061502
Timeout: 30 * time.Second,
15071503
RunOnStart: true,
15081504
}},
@@ -1935,8 +1931,6 @@ func TestAgent_ReconnectingPTYContainer(t *testing.T) {
19351931
t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
19361932
}
19371933

1938-
ctx := testutil.Context(t, testutil.WaitLong)
1939-
19401934
pool, err := dockertest.NewPool("")
19411935
require.NoError(t, err, "Could not connect to docker")
19421936
ct, err := pool.RunWithOptions(&dockertest.RunOptions{
@@ -1948,10 +1942,10 @@ func TestAgent_ReconnectingPTYContainer(t *testing.T) {
19481942
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
19491943
})
19501944
require.NoError(t, err, "Could not start container")
1951-
t.Cleanup(func() {
1945+
defer func() {
19521946
err := pool.Purge(ct)
19531947
require.NoError(t, err, "Could not stop container")
1954-
})
1948+
}()
19551949
// Wait for container to start
19561950
require.Eventually(t, func() bool {
19571951
ct, ok := pool.ContainerByName(ct.Container.Name)
@@ -1962,6 +1956,7 @@ func TestAgent_ReconnectingPTYContainer(t *testing.T) {
19621956
conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0, func(_ *agenttest.Client, o *agent.Options) {
19631957
o.ExperimentalDevcontainersEnabled = true
19641958
})
1959+
ctx := testutil.Context(t, testutil.WaitLong)
19651960
ac, err := conn.ReconnectingPTY(ctx, uuid.New(), 80, 80, "/bin/sh", func(arp *workspacesdk.AgentReconnectingPTYInit) {
19661961
arp.Container = ct.Container.ID
19671962
})
@@ -2005,9 +2000,6 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
20052000
t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
20062001
}
20072002

2008-
ctx := testutil.Context(t, testutil.WaitLong)
2009-
2010-
// Connect to Docker
20112003
pool, err := dockertest.NewPool("")
20122004
require.NoError(t, err, "Could not connect to docker")
20132005

@@ -2051,7 +2043,7 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
20512043
},
20522044
},
20532045
}
2054-
// nolint: dogsled
2046+
//nolint:dogsled
20552047
conn, _, _, _, _ := setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) {
20562048
o.ExperimentalDevcontainersEnabled = true
20572049
})
@@ -2079,8 +2071,7 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
20792071

20802072
return false
20812073
}, testutil.WaitSuperLong, testutil.IntervalMedium, "no container with workspace folder label found")
2082-
2083-
t.Cleanup(func() {
2074+
defer func() {
20842075
// We can't rely on pool here because the container is not
20852076
// managed by it (it is managed by @devcontainer/cli).
20862077
err := pool.Client.RemoveContainer(docker.RemoveContainerOptions{
@@ -2089,13 +2080,15 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
20892080
Force: true,
20902081
})
20912082
assert.NoError(t, err, "remove container")
2092-
})
2083+
}()
20932084

20942085
containerInfo, err := pool.Client.InspectContainer(container.ID)
20952086
require.NoError(t, err, "inspect container")
20962087
t.Logf("Container state: status: %v", containerInfo.State.Status)
20972088
require.True(t, containerInfo.State.Running, "container should be running")
20982089

2090+
ctx := testutil.Context(t, testutil.WaitLong)
2091+
20992092
ac, err := conn.ReconnectingPTY(ctx, uuid.New(), 80, 80, "", func(opts *workspacesdk.AgentReconnectingPTYInit) {
21002093
opts.Container = container.ID
21012094
})
@@ -2124,6 +2117,173 @@ func TestAgent_DevcontainerAutostart(t *testing.T) {
21242117
require.NoError(t, err, "file should exist outside devcontainer")
21252118
}
21262119

2120+
// TestAgent_DevcontainerRecreate tests that RecreateDevcontainer
2121+
// recreates a devcontainer and emits logs.
2122+
//
2123+
// This tests end-to-end functionality of auto-starting a devcontainer.
2124+
// It runs "devcontainer up" which creates a real Docker container. As
2125+
// such, it does not run by default in CI.
2126+
//
2127+
// You can run it manually as follows:
2128+
//
2129+
// CODER_TEST_USE_DOCKER=1 go test -count=1 ./agent -run TestAgent_DevcontainerRecreate
2130+
func TestAgent_DevcontainerRecreate(t *testing.T) {
2131+
if os.Getenv("CODER_TEST_USE_DOCKER") != "1" {
2132+
t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
2133+
}
2134+
t.Parallel()
2135+
2136+
pool, err := dockertest.NewPool("")
2137+
require.NoError(t, err, "Could not connect to docker")
2138+
2139+
// Prepare temporary devcontainer for test (mywork).
2140+
devcontainerID := uuid.New()
2141+
devcontainerLogSourceID := uuid.New()
2142+
workspaceFolder := filepath.Join(t.TempDir(), "mywork")
2143+
t.Logf("Workspace folder: %s", workspaceFolder)
2144+
devcontainerPath := filepath.Join(workspaceFolder, ".devcontainer")
2145+
err = os.MkdirAll(devcontainerPath, 0o755)
2146+
require.NoError(t, err, "create devcontainer directory")
2147+
devcontainerFile := filepath.Join(devcontainerPath, "devcontainer.json")
2148+
err = os.WriteFile(devcontainerFile, []byte(`{
2149+
"name": "mywork",
2150+
"image": "busybox:latest",
2151+
"cmd": ["sleep", "infinity"]
2152+
}`), 0o600)
2153+
require.NoError(t, err, "write devcontainer.json")
2154+
2155+
manifest := agentsdk.Manifest{
2156+
// Set up pre-conditions for auto-starting a devcontainer, the
2157+
// script is used to extract the log source ID.
2158+
Devcontainers: []codersdk.WorkspaceAgentDevcontainer{
2159+
{
2160+
ID: devcontainerID,
2161+
Name: "test",
2162+
WorkspaceFolder: workspaceFolder,
2163+
},
2164+
},
2165+
Scripts: []codersdk.WorkspaceAgentScript{
2166+
{
2167+
ID: devcontainerID,
2168+
LogSourceID: devcontainerLogSourceID,
2169+
},
2170+
},
2171+
}
2172+
2173+
//nolint:dogsled
2174+
conn, client, _, _, _ := setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) {
2175+
o.ExperimentalDevcontainersEnabled = true
2176+
})
2177+
2178+
ctx := testutil.Context(t, testutil.WaitLong)
2179+
2180+
// We enabled autostart for the devcontainer, so ready is a good
2181+
// indication that the devcontainer is up and running. Importantly,
2182+
// this also means that the devcontainer startup is no longer
2183+
// producing logs that may interfere with the recreate logs.
2184+
testutil.Eventually(ctx, t, func(context.Context) bool {
2185+
states := client.GetLifecycleStates()
2186+
return slices.Contains(states, codersdk.WorkspaceAgentLifecycleReady)
2187+
}, testutil.IntervalMedium, "devcontainer not ready")
2188+
2189+
t.Logf("Looking for container with label: devcontainer.local_folder=%s", workspaceFolder)
2190+
2191+
var container docker.APIContainers
2192+
testutil.Eventually(ctx, t, func(context.Context) bool {
2193+
containers, err := pool.Client.ListContainers(docker.ListContainersOptions{All: true})
2194+
if err != nil {
2195+
t.Logf("Error listing containers: %v", err)
2196+
return false
2197+
}
2198+
for _, c := range containers {
2199+
t.Logf("Found container: %s with labels: %v", c.ID[:12], c.Labels)
2200+
if v, ok := c.Labels["devcontainer.local_folder"]; ok && v == workspaceFolder {
2201+
t.Logf("Found matching container: %s", c.ID[:12])
2202+
container = c
2203+
return true
2204+
}
2205+
}
2206+
return false
2207+
}, testutil.IntervalMedium, "no container with workspace folder label found")
2208+
defer func(container docker.APIContainers) {
2209+
// We can't rely on pool here because the container is not
2210+
// managed by it (it is managed by @devcontainer/cli).
2211+
err := pool.Client.RemoveContainer(docker.RemoveContainerOptions{
2212+
ID: container.ID,
2213+
RemoveVolumes: true,
2214+
Force: true,
2215+
})
2216+
assert.Error(t, err, "container should be removed by recreate")
2217+
}(container)
2218+
2219+
ctx = testutil.Context(t, testutil.WaitLong) // Reset context.
2220+
2221+
// Capture logs via ScriptLogger.
2222+
logsCh := make(chan *proto.BatchCreateLogsRequest, 1)
2223+
client.SetLogsChannel(logsCh)
2224+
2225+
// Invoke recreate to trigger the destruction and recreation of the
2226+
// devcontainer, we do it in a goroutine so we can process logs
2227+
// concurrently.
2228+
go func(container docker.APIContainers) {
2229+
err := conn.RecreateDevcontainer(ctx, container.ID)
2230+
assert.NoError(t, err, "recreate devcontainer should succeed")
2231+
}(container)
2232+
2233+
t.Logf("Checking recreate logs for outcome...")
2234+
2235+
// Wait for the logs to be emitted, the @devcontainer/cli up command
2236+
// will emit a log with the outcome at the end suggesting we did
2237+
// receive all the logs.
2238+
waitForOutcomeLoop:
2239+
for {
2240+
batch := testutil.RequireReceive(ctx, t, logsCh)
2241+
2242+
if bytes.Equal(batch.LogSourceId, devcontainerLogSourceID[:]) {
2243+
for _, log := range batch.Logs {
2244+
t.Logf("Received log: %s", log.Output)
2245+
if strings.Contains(log.Output, "\"outcome\"") {
2246+
break waitForOutcomeLoop
2247+
}
2248+
}
2249+
}
2250+
}
2251+
2252+
t.Logf("Checking there's a new container with label: devcontainer.local_folder=%s", workspaceFolder)
2253+
2254+
// Make sure the container exists and isn't the same as the old one.
2255+
testutil.Eventually(ctx, t, func(context.Context) bool {
2256+
containers, err := pool.Client.ListContainers(docker.ListContainersOptions{All: true})
2257+
if err != nil {
2258+
t.Logf("Error listing containers: %v", err)
2259+
return false
2260+
}
2261+
for _, c := range containers {
2262+
t.Logf("Found container: %s with labels: %v", c.ID[:12], c.Labels)
2263+
if v, ok := c.Labels["devcontainer.local_folder"]; ok && v == workspaceFolder {
2264+
if c.ID == container.ID {
2265+
t.Logf("Found same container: %s", c.ID[:12])
2266+
return false
2267+
}
2268+
t.Logf("Found new container: %s", c.ID[:12])
2269+
container = c
2270+
return true
2271+
}
2272+
}
2273+
return false
2274+
}, testutil.IntervalMedium, "new devcontainer not found")
2275+
defer func(container docker.APIContainers) {
2276+
// We can't rely on pool here because the container is not
2277+
// managed by it (it is managed by @devcontainer/cli).
2278+
err := pool.Client.RemoveContainer(docker.RemoveContainerOptions{
2279+
ID: container.ID,
2280+
RemoveVolumes: true,
2281+
Force: true,
2282+
})
2283+
assert.NoError(t, err, "remove container")
2284+
}(container)
2285+
}
2286+
21272287
func TestAgent_Dial(t *testing.T) {
21282288
t.Parallel()
21292289

0 commit comments

Comments
 (0)