Skip to content

Commit 3bb5db0

Browse files
authored
Merge branch 'main' into fix/multi-network-old-daemon-compat
2 parents 7895a5a + 2b9f60b commit 3bb5db0

11 files changed

Lines changed: 221 additions & 52 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
make ${{ matrix.target }}
4646
4747
binary:
48-
uses: docker/github-builder/.github/workflows/bake.yml@v1
48+
uses: docker/github-builder/.github/workflows/bake.yml@v1.4.0
4949
permissions:
5050
contents: read # same as global permission
5151
id-token: write # for signing attestation(s) with GitHub OIDC Token
@@ -111,7 +111,7 @@ jobs:
111111

112112
bin-image-test:
113113
if: github.event_name == 'pull_request'
114-
uses: docker/github-builder/.github/workflows/bake.yml@v1
114+
uses: docker/github-builder/.github/workflows/bake.yml@v1.4.0
115115
with:
116116
runner: amd64
117117
target: image-cross

.github/workflows/merge.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
- run: echo "Exposing env vars for reusable workflow"
8484

8585
bin-image:
86-
uses: docker/github-builder/.github/workflows/bake.yml@v1
86+
uses: docker/github-builder/.github/workflows/bake.yml@v1.4.0
8787
needs:
8888
- bin-image-prepare
8989
permissions:

.github/workflows/pr-review.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: PR Review
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, ready_for_review]
6+
issue_comment:
7+
types: [created]
8+
pull_request_review_comment:
9+
types: [created]
10+
11+
# Serialize reviews per PR; do not cancel in-progress runs
12+
# so no review is silently dropped mid-execution.
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number }}
15+
cancel-in-progress: false
16+
17+
jobs:
18+
review:
19+
# Only run on the upstream repo (not forks) to prevent credential leaks.
20+
# Skip draft PRs (ready_for_review will fire when promoted).
21+
# Skip bot actors to avoid reviewing Dependabot and automation PRs.
22+
# Require collaborator-level access for comment-triggered events.
23+
# Only trigger on PR comments, not plain issue comments.
24+
if: >-
25+
github.repository == 'docker/compose' &&
26+
(github.event_name != 'pull_request_target' || github.event.pull_request.draft == false) &&
27+
(github.event_name == 'pull_request_target' ||
28+
(github.event_name == 'issue_comment' &&
29+
github.event.issue.pull_request &&
30+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
31+
(github.event_name == 'pull_request_review_comment' &&
32+
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))) &&
33+
!endsWith(github.actor, '[bot]')
34+
uses: docker/cagent-action/.github/workflows/review-pr.yml@3a12dbd0c6cd7dda3d4e05f24f0143c9701456de # v1.2.13
35+
secrets:
36+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
37+
CAGENT_ORG_MEMBERSHIP_TOKEN: ${{ secrets.CAGENT_ORG_MEMBERSHIP_TOKEN }}
38+
CAGENT_REVIEWER_APP_ID: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
39+
CAGENT_REVIEWER_APP_PRIVATE_KEY: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}
40+
permissions:
41+
contents: read # to fetch code
42+
pull-requests: write # to post review comments
43+
issues: write # to reply to issue/PR comments
44+
checks: write # to update check statuses

CLAUDE.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Project: Docker Compose
2+
3+
## Build & Test
4+
5+
- Build: `make build`
6+
- Test all: `make test`
7+
- Test unit: `go test ./pkg/...`
8+
- Test single: `go test ./pkg/compose/ -run TestFunctionName`
9+
- E2E tests: `go test -tags e2e ./pkg/e2e/ -run TestName`
10+
11+
## Lint
12+
13+
- Linter: golangci-lint v2 (config in `.golangci.yml`)
14+
- Run: `golangci-lint run --build-tags "e2e" ./...`
15+
- **After modifying any Go code, ALWAYS run the linter and fix all reported issues before considering the task complete.**
16+
- Lint is also run via Docker: `docker buildx bake lint` (uses version pinned in `Dockerfile`)
17+
18+
## Code Style
19+
20+
- Formatting is enforced by golangci-lint (gofumpt + gci)
21+
- Import order: stdlib, third-party, local module (enforced by gci)
22+
- Max line length: 200 chars
23+
- Max cyclomatic complexity: 16
24+
- No `io/ioutil`, `github.com/pkg/errors`, `gopkg.in/yaml.v2`, `golang.org/x/exp/maps`, `golang.org/x/exp/slices`
25+
- Use `github.com/containerd/errdefs` instead of `github.com/docker/docker/errdefs`
26+
- In tests: use `t.Context()` instead of `context.Background()` or `context.TODO()`
27+
- Prefer `fmt.Fprintf` over `WriteString(fmt.Sprintf(...))`

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
ARG GO_VERSION=1.25.8
1919
ARG XX_VERSION=1.9.0
20-
ARG GOLANGCI_LINT_VERSION=v2.8.0
20+
ARG GOLANGCI_LINT_VERSION=v2.11.3
2121
ARG ADDLICENSE_VERSION=v1.0.0
2222

2323
ARG BUILD_TAGS="e2e"

cmd/display/tty.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,13 @@ func (w *ttyWriter) Start(ctx context.Context, operation string) {
176176

177177
func (w *ttyWriter) Done(operation string, success bool) {
178178
w.print()
179+
w.done <- true
179180
w.mtx.Lock()
180181
defer w.mtx.Unlock()
181182
if w.ticker != nil {
182183
w.ticker.Stop()
183184
}
184185
w.operation = ""
185-
w.done <- true
186186
}
187187

188188
func (w *ttyWriter) On(events ...api.Resource) {
@@ -339,6 +339,21 @@ func (w *ttyWriter) printWithDimensions(terminalWidth, terminalHeight int) {
339339
}
340340
}
341341

342+
// pad timers so they all have the same visible width
343+
for i := range lines {
344+
l := &lines[i]
345+
if l.timer == "" {
346+
continue
347+
}
348+
timerWidth := utf8.RuneCountInString(l.timer)
349+
if timerWidth < timerLen {
350+
// Left-pad so the timer's right edge stays aligned on the terminal.
351+
// This also prevents stale suffix characters from visually “sticking”
352+
// when a previously-rendered timer was wider (e.g. "10.6s" -> "0.0s").
353+
l.timer = strings.Repeat(" ", timerLen-timerWidth) + l.timer
354+
}
355+
}
356+
342357
// shorten details/taskID to fit terminal width
343358
w.adjustLineWidth(lines, timerLen, terminalWidth)
344359

cmd/display/tty_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package display
1818

1919
import (
2020
"bytes"
21+
"context"
2122
"strings"
2223
"sync"
2324
"testing"
@@ -403,6 +404,67 @@ func TestPrintWithDimensions_PulledAndPullingWithLongIDs(t *testing.T) {
403404
}
404405
}
405406

407+
func TestPrintWithDimensions_TimerIsRightAligned(t *testing.T) {
408+
w, buf := newTestWriter()
409+
410+
base := time.Unix(0, 0)
411+
412+
// Long timer: "10.6s" (length 5)
413+
longTask := &task{
414+
ID: "task-long",
415+
parents: make(map[string]struct{}),
416+
startTime: base,
417+
endTime: base.Add(10*time.Second + 600*time.Millisecond),
418+
text: "Pulled",
419+
status: api.Done,
420+
spinner: NewSpinner(),
421+
}
422+
longTask.spinner.Stop()
423+
w.tasks[longTask.ID] = longTask
424+
w.ids = append(w.ids, longTask.ID)
425+
426+
// Short timer: "0.0s" (length 4)
427+
shortTask := &task{
428+
ID: "task-short",
429+
parents: make(map[string]struct{}),
430+
startTime: base,
431+
endTime: base,
432+
text: "Pulled",
433+
status: api.Done,
434+
spinner: NewSpinner(),
435+
}
436+
shortTask.spinner.Stop()
437+
w.tasks[shortTask.ID] = shortTask
438+
w.ids = append(w.ids, shortTask.ID)
439+
440+
terminalWidth := 80
441+
w.printWithDimensions(terminalWidth, 24)
442+
443+
// Strip ANSI codes from output and split by newline
444+
stripped := stripAnsi(buf.String())
445+
lines := strings.Split(stripped, "\n")
446+
447+
var nonEmptyLines []string
448+
for _, line := range lines {
449+
if strings.TrimSpace(line) != "" {
450+
nonEmptyLines = append(nonEmptyLines, line)
451+
}
452+
}
453+
454+
// Find the line containing the shorter timer.
455+
var shortLine string
456+
for _, line := range nonEmptyLines {
457+
if strings.Contains(line, "0.0s") {
458+
shortLine = line
459+
break
460+
}
461+
}
462+
assert.Assert(t, shortLine != "", "expected to find a rendered line containing \"0.0s\"")
463+
assert.Assert(t, strings.HasSuffix(shortLine, "0.0s"),
464+
"short timer should be left-padded (no trailing spaces after the timer); got: %q",
465+
shortLine)
466+
}
467+
406468
func TestLenAnsi(t *testing.T) {
407469
testCases := []struct {
408470
input string
@@ -422,3 +484,23 @@ func TestLenAnsi(t *testing.T) {
422484
})
423485
}
424486
}
487+
488+
func TestDoneDeadlockFix(t *testing.T) {
489+
w, _ := newTestWriter()
490+
addTask(w, "test-task", "Working", "details", api.Working)
491+
ctx, cancel := context.WithCancel(t.Context())
492+
defer cancel()
493+
494+
w.Start(ctx, "test")
495+
done := make(chan bool)
496+
go func() {
497+
w.Done("test", true)
498+
done <- true
499+
}()
500+
501+
select {
502+
case <-done:
503+
case <-time.After(5 * time.Second):
504+
t.Fatal("Deadlock detected: Done() did not complete within 5 seconds")
505+
}
506+
}

go.mod

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ require (
1010
github.com/buger/goterm v1.0.4
1111
github.com/compose-spec/compose-go/v2 v2.10.1
1212
github.com/containerd/console v1.0.5
13-
github.com/containerd/containerd/v2 v2.2.1
13+
github.com/containerd/containerd/v2 v2.2.2
1414
github.com/containerd/errdefs v1.0.0
15-
github.com/containerd/platforms v1.0.0-rc.2
15+
github.com/containerd/platforms v1.0.0-rc.3
1616
github.com/distribution/reference v0.6.0
1717
github.com/docker/buildx v0.31.1
1818
github.com/docker/cli v29.2.1+incompatible
@@ -31,7 +31,7 @@ require (
3131
github.com/moby/buildkit v0.27.1
3232
github.com/moby/go-archive v0.2.0
3333
github.com/moby/moby/api v1.54.0
34-
github.com/moby/moby/client v0.2.2
34+
github.com/moby/moby/client v0.3.0
3535
github.com/moby/patternmatcher v0.6.0
3636
github.com/moby/sys/atomicwriter v0.1.0
3737
github.com/morikuni/aec v1.1.0
@@ -45,25 +45,26 @@ require (
4545
github.com/stretchr/testify v1.11.1
4646
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375
4747
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
48-
go.opentelemetry.io/otel v1.38.0
48+
go.opentelemetry.io/otel v1.39.0
4949
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0
5050
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
51-
go.opentelemetry.io/otel/metric v1.38.0
52-
go.opentelemetry.io/otel/sdk v1.38.0
53-
go.opentelemetry.io/otel/trace v1.38.0
51+
go.opentelemetry.io/otel/metric v1.39.0
52+
go.opentelemetry.io/otel/sdk v1.39.0
53+
go.opentelemetry.io/otel/trace v1.39.0
5454
go.uber.org/goleak v1.3.0
5555
go.uber.org/mock v0.6.0
5656
go.yaml.in/yaml/v4 v4.0.0-rc.4
57-
golang.org/x/sync v0.19.0
57+
golang.org/x/sync v0.20.0
5858
golang.org/x/sys v0.42.0
59-
google.golang.org/grpc v1.78.0
59+
google.golang.org/grpc v1.79.3
6060
gotest.tools/v3 v3.5.2
6161
tags.cncf.io/container-device-interface v1.1.0
6262
)
6363

6464
require (
6565
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
6666
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
67+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
6768
github.com/containerd/containerd/api v1.10.0 // indirect
6869
github.com/containerd/continuity v0.4.5 // indirect
6970
github.com/containerd/errdefs/pkg v0.3.0 // indirect
@@ -131,16 +132,16 @@ require (
131132
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
132133
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect
133134
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
134-
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
135+
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
135136
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
136137
go.yaml.in/yaml/v3 v3.0.4 // indirect
137138
golang.org/x/crypto v0.46.0 // indirect
138139
golang.org/x/net v0.48.0 // indirect
139140
golang.org/x/term v0.38.0 // indirect
140141
golang.org/x/text v0.32.0 // indirect
141142
golang.org/x/time v0.14.0 // indirect
142-
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 // indirect
143-
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
143+
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
144+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
144145
google.golang.org/protobuf v1.36.11 // indirect
145146
gopkg.in/ini.v1 v1.67.0 // indirect
146147
gopkg.in/yaml.v3 v3.0.1 // indirect

0 commit comments

Comments
 (0)