From 5fb0b2404c79ff650412feb8e4e6fc5dbe17c318 Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Tue, 6 Feb 2024 17:18:20 +0100 Subject: [PATCH 01/47] add an option to use a custom progress printer for the build progress --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ pkg/api/api.go | 3 +++ pkg/compose/build.go | 35 ++++++++++++++++++++++++++++++++++- pkg/compose/create.go | 2 +- 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index b5219731b3..5c118cd1f0 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( go.opentelemetry.io/otel/trace v1.19.0 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.4.0 - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 golang.org/x/sync v0.6.0 golang.org/x/sys v0.16.0 google.golang.org/grpc v1.59.0 @@ -154,14 +154,14 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.10.0 // indirect + golang.org/x/tools v0.17.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect diff --git a/go.sum b/go.sum index d31a81011d..ab1b80a385 100644 --- a/go.sum +++ b/go.sum @@ -537,19 +537,19 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -564,8 +564,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= @@ -606,8 +606,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -626,8 +626,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/api/api.go b/pkg/api/api.go index 3b8f091611..e62b2ffba3 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -19,6 +19,7 @@ package api import ( "context" "fmt" + "io" "strings" "time" @@ -145,6 +146,8 @@ type BuildOptions struct { Memory int64 // Builder name passed in the command line Builder string + // OutPrinter used for printing the progress output + OutPrinter io.Writer } // Apply mutates project according to build options diff --git a/pkg/compose/build.go b/pkg/compose/build.go index b4ce4c80aa..03758325fa 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "github.com/containerd/console" + "io" "os" "path/filepath" @@ -127,7 +129,12 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti progressCtx, cancel := context.WithCancel(context.Background()) defer cancel() - w, err = xprogress.NewPrinter(progressCtx, os.Stdout, progressui.DisplayMode(options.Progress), + var outPrinter io.Writer = os.Stdout + if options.OutPrinter != nil { + outPrinter = options.OutPrinter + } + + w, err = xprogress.NewPrinter(progressCtx, progressPrinter(outPrinter), progressui.DisplayMode(options.Progress), xprogress.WithDesc( fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver), fmt.Sprintf("%s:%s", b.Driver, b.Name), @@ -564,3 +571,29 @@ func parsePlatforms(service types.ServiceConfig) ([]specs.Platform, error) { return ret, nil } + +type pPrinter struct { + io.Writer +} + +func (p *pPrinter) Read(_ []byte) (n int, err error) { + return 0, errors.New("not implemented") +} + +func (p *pPrinter) Close() error { + return nil +} + +func (p *pPrinter) Fd() uintptr { + return 0 +} + +func (p *pPrinter) Name() string { + return "pPrinter" +} + +func progressPrinter(w io.Writer) console.File { + return &pPrinter{ + Writer: w, + } +} diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 069db7cfc7..549b8c0243 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -275,7 +275,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context, DNSOptions: service.DNSOpts, ExtraHosts: service.ExtraHosts.AsList(":"), SecurityOpt: securityOpts, - StorageOpt: service.StorageOpt, + StorageOpt: nil, UsernsMode: container.UsernsMode(service.UserNSMode), UTSMode: container.UTSMode(service.Uts), Privileged: service.Privileged, From 76b720f9ae8bc73d0b2b22fea9bc627819618f88 Mon Sep 17 00:00:00 2001 From: jhrotko Date: Tue, 30 Jan 2024 13:27:27 +0000 Subject: [PATCH 02/47] Fix canonical container name Signed-off-by: jhrotko --- pkg/compose/compose.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index 9230e8091c..102dd86efc 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -132,7 +132,8 @@ func getCanonicalContainerName(c moby.Container) string { return name[1:] } } - return c.Names[0][1:] + + return strings.TrimPrefix(c.Names[0], "/") } func getContainerNameWithoutProject(c moby.Container) string { From d6a8116aa017e3e53e9259f7bb55eba180b52344 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:48:48 +0100 Subject: [PATCH 03/47] ci(deps): replace buildkit to fix fsutil issues on Windows (#11426) --- go.mod | 8 +++++--- go.sum | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 5c118cd1f0..6fae54b1f4 100644 --- a/go.mod +++ b/go.mod @@ -183,6 +183,8 @@ require ( tags.cncf.io/container-device-interface v0.6.2 // indirect ) -// Fix an issue with fsutil v0.0.0-20230825212630-f09800878302 on Windows -// See https://github.com/docker/buildx/issues/2207#issuecomment-1908460460 -replace github.com/tonistiigi/fsutil v0.0.0-20230825212630-f09800878302 => github.com/crazy-max/fsutil v0.0.0-20240124164449-376dc28ff40f +replace ( + // reverts https://github.com/moby/buildkit/pull/4094 to fix fsutil issues on Windows + github.com/moby/buildkit => github.com/crazy-max/buildkit v0.7.1-0.20240130133234-d9aa289bd124 // compose-957cb50df991 + github.com/tonistiigi/fsutil => github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb +) diff --git a/go.sum b/go.sum index ab1b80a385..92d4987e14 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,8 @@ github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crazy-max/fsutil v0.0.0-20240124164449-376dc28ff40f h1:f+sk5oEeSYL/2tjWraiDlR/JHJVwtqKYpGtMkfJ7MTc= -github.com/crazy-max/fsutil v0.0.0-20240124164449-376dc28ff40f/go.mod h1:9kMVqMyQ/Sx2df5LtnGG+nbrmiZzCS7V6gjW3oGHsvI= +github.com/crazy-max/buildkit v0.7.1-0.20240130133234-d9aa289bd124 h1:rTTqpfm06GSf2gjt8oo9LfUm2iGiYtx1VUDPfTHXqs4= +github.com/crazy-max/buildkit v0.7.1-0.20240130133234-d9aa289bd124/go.mod h1:eFZFY7VaoWWKmJLwkqmcWR6x0j8q+gXcngg3E4k0558= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -336,8 +336,6 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.13.0-beta1.0.20231219135447-957cb50df991 h1:r80LLQ91uOLxU1ElAvrB1o8oBsph51lPzVnr7t2b200= -github.com/moby/buildkit v0.13.0-beta1.0.20231219135447-957cb50df991/go.mod h1:6MddWPSL5jxy+W8eMMHWDOfZzzRRKWXPZqajw72YHBc= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= @@ -472,6 +470,8 @@ github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4D github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA= github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g= +github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb h1:uUe8rNyVXM8moActoBol6Xf6xX2GMr7SosR2EywMvGg= +github.com/tonistiigi/fsutil v0.0.0-20230629203738-36ef4d8c0dbb/go.mod h1:SxX/oNQ/ag6Vaoli547ipFK9J7BZn5JqJG0JE8lf8bA= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= github.com/tonistiigi/vt100 v0.0.0-20230623042737-f9a4f7ef6531 h1:Y/M5lygoNPKwVNLMPXgVfsRT40CSFKXCxuU8LoHySjs= From eab44a37d49fe27234c4fa9a3b6246ac4a1dde5c Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Tue, 30 Jan 2024 10:49:53 -0500 Subject: [PATCH 04/47] chore(e2e): fix flaky test & standalone behavior (#11382) --- pkg/e2e/cancel_test.go | 42 ++++++++++++++++++++++++------------ pkg/e2e/e2e_config_plugin.go | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/pkg/e2e/cancel_test.go b/pkg/e2e/cancel_test.go index 6e34bd8d2d..a3f4b77cda 100644 --- a/pkg/e2e/cancel_test.go +++ b/pkg/e2e/cancel_test.go @@ -37,18 +37,26 @@ func TestComposeCancel(t *testing.T) { c := NewParallelCLI(t) t.Run("metrics on cancel Compose build", func(t *testing.T) { - c.RunDockerComposeCmd(t, "ls") - buildProjectPath := "fixtures/build-infinite/compose.yaml" + const buildProjectPath = "fixtures/build-infinite/compose.yaml" + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() // require a separate groupID from the process running tests, in order to simulate ctrl+C from a terminal. // sending kill signal - stdout := &utils.SafeBuffer{} - stderr := &utils.SafeBuffer{} - cmd, err := StartWithNewGroupID(context.Background(), + var stdout, stderr utils.SafeBuffer + cmd, err := StartWithNewGroupID( + ctx, c.NewDockerComposeCmd(t, "-f", buildProjectPath, "build", "--progress", "plain"), - stdout, - stderr) + &stdout, + &stderr, + ) assert.NilError(t, err) + processDone := make(chan error, 1) + go func() { + defer close(processDone) + processDone <- cmd.Wait() + }() c.WaitForCondition(t, func() (bool, string) { out := stdout.String() @@ -58,15 +66,21 @@ func TestComposeCancel(t *testing.T) { errors) }, 30*time.Second, 1*time.Second) - err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default + // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default + err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) assert.NilError(t, err) - c.WaitForCondition(t, func() (bool, string) { - out := stdout.String() - errors := stderr.String() - return strings.Contains(out, "CANCELED"), fmt.Sprintf("'CANCELED' not found in : \n%s\nStderr: \n%s\n", out, - errors) - }, 10*time.Second, 1*time.Second) + select { + case <-ctx.Done(): + t.Fatal("test context canceled") + case err := <-processDone: + // TODO(milas): Compose should really not return exit code 130 here, + // this is an old hack for the compose-cli wrapper + assert.Error(t, err, "exit status 130", + "STDOUT:\n%s\nSTDERR:\n%s\n", stdout.String(), stderr.String()) + case <-time.After(10 * time.Second): + t.Fatal("timeout waiting for Compose exit") + } }) } diff --git a/pkg/e2e/e2e_config_plugin.go b/pkg/e2e/e2e_config_plugin.go index 5de99481f1..84ca6eabcd 100644 --- a/pkg/e2e/e2e_config_plugin.go +++ b/pkg/e2e/e2e_config_plugin.go @@ -18,4 +18,4 @@ package e2e -const composeStandaloneMode = true +const composeStandaloneMode = false From c41f3a936b306938726015e0a43f5288bc70e0c8 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 25 Jan 2024 09:26:22 +0100 Subject: [PATCH 05/47] Fix load .env from project directory when project file is set by COMPOSE_FILE Signed-off-by: Nicolas De Loof --- cmd/compose/compose.go | 4 ++-- pkg/e2e/compose_test.go | 12 ++++++++++++ pkg/e2e/fixtures/dotenv/.env | 1 + pkg/e2e/fixtures/dotenv/development/.env | 2 ++ pkg/e2e/fixtures/dotenv/development/compose.yaml | 3 +++ 5 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 pkg/e2e/fixtures/dotenv/.env create mode 100644 pkg/e2e/fixtures/dotenv/development/.env create mode 100644 pkg/e2e/fixtures/dotenv/development/compose.yaml diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 4921450b1f..17e5e071c8 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -261,10 +261,10 @@ func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj append(po, cli.WithWorkingDirectory(o.ProjectDir), cli.WithOsEnv, - cli.WithEnvFiles(o.EnvFiles...), - cli.WithDotEnv, cli.WithConfigFileEnv, cli.WithDefaultConfigPath, + cli.WithEnvFiles(o.EnvFiles...), + cli.WithDotEnv, cli.WithDefaultProfiles(o.Profiles...), cli.WithName(o.ProjectName))...) } diff --git a/pkg/e2e/compose_test.go b/pkg/e2e/compose_test.go index 6384c29c86..b37ec0f485 100644 --- a/pkg/e2e/compose_test.go +++ b/pkg/e2e/compose_test.go @@ -313,3 +313,15 @@ func TestRemoveOrphaned(t *testing.T) { res := c.RunDockerComposeCmd(t, "-f", "./fixtures/sentences/compose.yaml", "-p", projectName, "ps", "--format", "{{.Name}}") res.Assert(t, icmd.Expected{Out: fmt.Sprintf("%s-words-1", projectName)}) } + +func TestResolveDotEnv(t *testing.T) { + c := NewCLI(t) + + cmd := c.NewDockerComposeCmd(t, "config") + cmd.Dir = filepath.Join(".", "fixtures", "dotenv") + res := icmd.RunCmd(cmd) + res.Assert(t, icmd.Expected{ + ExitCode: 0, + Out: "image: backend:latest", + }) +} diff --git a/pkg/e2e/fixtures/dotenv/.env b/pkg/e2e/fixtures/dotenv/.env new file mode 100644 index 0000000000..869938aa7c --- /dev/null +++ b/pkg/e2e/fixtures/dotenv/.env @@ -0,0 +1 @@ +COMPOSE_FILE="${COMPOSE_FILE:-development/compose.yaml}" \ No newline at end of file diff --git a/pkg/e2e/fixtures/dotenv/development/.env b/pkg/e2e/fixtures/dotenv/development/.env new file mode 100644 index 0000000000..9369028779 --- /dev/null +++ b/pkg/e2e/fixtures/dotenv/development/.env @@ -0,0 +1,2 @@ +IMAGE_NAME="${IMAGE_NAME:-backend}" +IMAGE_TAG="${IMAGE_TAG:-latest}" diff --git a/pkg/e2e/fixtures/dotenv/development/compose.yaml b/pkg/e2e/fixtures/dotenv/development/compose.yaml new file mode 100644 index 0000000000..b44805e305 --- /dev/null +++ b/pkg/e2e/fixtures/dotenv/development/compose.yaml @@ -0,0 +1,3 @@ +services: + backend: + image: $IMAGE_NAME:$IMAGE_TAG From a34a367c216010ae9c4d746ce235f6023c5955e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:44:43 +0000 Subject: [PATCH 06/47] build(deps): bump github.com/docker/cli Bumps [github.com/docker/cli](https://github.com/docker/cli) from 25.0.1+incompatible to 25.0.2+incompatible. - [Commits](https://github.com/docker/cli/compare/v25.0.1...v25.0.2) --- updated-dependencies: - dependency-name: github.com/docker/cli dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6fae54b1f4..31c084dc2b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/distribution/reference v0.5.0 github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315 - github.com/docker/cli v25.0.1+incompatible + github.com/docker/cli v25.0.2+incompatible github.com/docker/cli-docs-tool v0.6.0 github.com/docker/docker v25.0.1+incompatible github.com/docker/go-connections v0.5.0 diff --git a/go.sum b/go.sum index 92d4987e14..c88fdcb8c5 100644 --- a/go.sum +++ b/go.sum @@ -124,8 +124,8 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315 h1:UZxx9xBADdf/9UmSdEUi+pdJoPKpgcf9QUAY5gEIYmY= github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315/go.mod h1:X8ZHhuW6ncwtoJ36TlU+gyaROTcBkTE01VHYmTStQCE= -github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= -github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v25.0.2+incompatible h1:6GEdvxwEA451/+Y3GtqIGn/MNjujQazUlxC6uGu8Tog= +github.com/docker/cli v25.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA= github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= From 0a1f22d0b62ffe19ae882ad50119956a88d34138 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 5 Feb 2024 08:16:45 +0100 Subject: [PATCH 07/47] CI: docker engine version matrix Signed-off-by: Nicolas De Loof --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e645d59b7f..770b665ed6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,10 +138,20 @@ jobs: mode: - plugin - standalone + engine: + - 24.0.9 + - 25.0.2 steps: - name: Checkout uses: actions/checkout@v3 + - name: Install Docker ${{ matrix.engine }} + run: | + sudo apt-get install curl + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh ./get-docker.sh --version ${{ matrix.engine }} + - name: Check Docker Version + run: docker --version - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 From f1c01ef091219e8103a672fb53af3f8e4def6efc Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 7 Feb 2024 08:38:22 +0100 Subject: [PATCH 08/47] bump compose-go to v2.0.0-rc.4 Signed-off-by: Nicolas De Loof --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 31c084dc2b..3c323949bf 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Microsoft/go-winio v0.6.1 github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.0.0-rc.3 + github.com/compose-spec/compose-go/v2 v2.0.0-rc.4 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.12 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index c88fdcb8c5..da37f60544 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.3 h1:t0qajSNkH3zR4HEN2CM+GVU7GBx5AwqiYJk5w800M7w= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.3/go.mod h1:r7CJHU0GaLtRVLm2ch8RCNkJh3GHyaqqc2rSti7VP44= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.4 h1:vVDWv1xm7Lqi7sEevU0Jk+T2huxNzf92lFhqKbn3sT8= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.4/go.mod h1:IbZsys5a7eFTsdcWvM3brnghkK7ctZwUSr7mCj5NXu0= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= From 2259da032a3065db54cde784c6eb63844a4e1b7b Mon Sep 17 00:00:00 2001 From: Laura Brehm Date: Tue, 30 Jan 2024 17:41:19 +0000 Subject: [PATCH 09/47] Include all networks in ContainerCreate call if API >= 1.44 Previously, we included the container's primary network in the ContainerCreate API call, and then connected the created container to each extra network one-by-one, by calling NetworkConnect. However, starting API version 1.44, the ContainerCreate endpoint now takes multiple EndpointsConfigs, allowing us to just include all the network configurations there and skip connecting the container to each extra network separately. Signed-off-by: Laura Brehm --- pkg/compose/compose.go | 6 +- pkg/compose/convergence.go | 35 +++--- pkg/compose/convergence_test.go | 183 ++++++++++++++++++++++++++++++++ pkg/compose/create.go | 40 +++++-- pkg/compose/create_test.go | 8 +- 5 files changed, 243 insertions(+), 29 deletions(-) diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index 102dd86efc..f1db511230 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -312,11 +312,13 @@ func (s *composeService) isSWarmEnabled(ctx context.Context) (bool, error) { return swarmEnabled.val, swarmEnabled.err } -var runtimeVersion = struct { +type runtimeVersionCache struct { once sync.Once val string err error -}{} +} + +var runtimeVersion runtimeVersionCache func (s *composeService) RuntimeVersion(ctx context.Context) (string, error) { runtimeVersion.once.Do(func() { diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index f65247b086..e4e3ad1882 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -34,6 +34,7 @@ import ( "github.com/docker/compose/v2/internal/tracing" moby "github.com/docker/docker/api/types" containerType "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/versions" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -600,19 +601,27 @@ func (s *composeService) createMobyContainer(ctx context.Context, }, } - // the highest-priority network is the primary and is included in the ContainerCreate API - // call via container.NetworkMode & network.NetworkingConfig - // any remaining networks are connected one-by-one here after creation (but before start) - serviceNetworks := service.NetworksByPriority() - for _, networkKey := range serviceNetworks { - mobyNetworkName := project.Networks[networkKey].Name - if string(cfgs.Host.NetworkMode) == mobyNetworkName { - // primary network already configured as part of ContainerCreate - continue - } - epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases) - if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil { - return created, err + apiVersion, err := s.RuntimeVersion(ctx) + if err != nil { + return created, err + } + // Starting API version 1.44, the ContainerCreate API call takes multiple networks + // so we include all the configurations there and can skip the one-by-one calls here + if versions.LessThan(apiVersion, "1.44") { + // the highest-priority network is the primary and is included in the ContainerCreate API + // call via container.NetworkMode & network.NetworkingConfig + // any remaining networks are connected one-by-one here after creation (but before start) + serviceNetworks := service.NetworksByPriority() + for _, networkKey := range serviceNetworks { + mobyNetworkName := project.Networks[networkKey].Name + if string(cfgs.Host.NetworkMode) == mobyNetworkName { + // primary network already configured as part of ContainerCreate + continue + } + epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases) + if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil { + return created, err + } } } diff --git a/pkg/compose/convergence_test.go b/pkg/compose/convergence_test.go index 32917cb661..e25ccd9f64 100644 --- a/pkg/compose/convergence_test.go +++ b/pkg/compose/convergence_test.go @@ -23,14 +23,18 @@ import ( "testing" "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/cli/cli/config/configfile" moby "github.com/docker/docker/api/types" containerType "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" + "github.com/docker/go-connections/nat" "go.uber.org/mock/gomock" "gotest.tools/v3/assert" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/mocks" + "github.com/docker/compose/v2/pkg/progress" ) func TestContainerName(t *testing.T) { @@ -251,3 +255,182 @@ func TestWaitDependencies(t *testing.T) { assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil)) }) } + +func TestCreateMobyContainer(t *testing.T) { + t.Run("connects container networks one by one if API <1.44", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + apiClient := mocks.NewMockAPIClient(mockCtrl) + cli := mocks.NewMockCli(mockCtrl) + tested := composeService{ + dockerCli: cli, + } + cli.EXPECT().Client().Return(apiClient).AnyTimes() + cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes() + apiClient.EXPECT().DaemonHost().Return("").AnyTimes() + apiClient.EXPECT().ImageInspectWithRaw(gomock.Any(), gomock.Any()).Return(moby.ImageInspect{}, nil, nil).AnyTimes() + // force `RuntimeVersion` to fetch again + runtimeVersion = runtimeVersionCache{} + apiClient.EXPECT().ServerVersion(gomock.Any()).Return(moby.Version{ + APIVersion: "1.43", + }, nil).AnyTimes() + + service := types.ServiceConfig{ + Name: "test", + Networks: map[string]*types.ServiceNetworkConfig{ + "a": { + Priority: 10, + }, + "b": { + Priority: 100, + }, + }, + } + project := types.Project{ + Name: "bork", + Services: types.Services{ + "test": service, + }, + Networks: types.Networks{ + "a": types.NetworkConfig{ + Name: "a-moby-name", + }, + "b": types.NetworkConfig{ + Name: "b-moby-name", + }, + }, + } + + var falseBool bool + apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq( + &containerType.HostConfig{ + PortBindings: nat.PortMap{}, + ExtraHosts: []string{}, + Tmpfs: map[string]string{}, + Resources: containerType.Resources{ + OomKillDisable: &falseBool, + }, + NetworkMode: "b-moby-name", + }), gomock.Eq( + &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + "b-moby-name": { + IPAMConfig: &network.EndpointIPAMConfig{}, + Aliases: []string{"bork-test-0"}, + }, + }, + }), gomock.Any(), gomock.Any()).Times(1).Return( + containerType.CreateResponse{ + ID: "an-id", + }, nil) + + apiClient.EXPECT().ContainerInspect(gomock.Any(), gomock.Eq("an-id")).Times(1).Return( + moby.ContainerJSON{ + ContainerJSONBase: &moby.ContainerJSONBase{ + ID: "an-id", + Name: "a-name", + }, + Config: &containerType.Config{}, + NetworkSettings: &moby.NetworkSettings{}, + }, nil) + + apiClient.EXPECT().NetworkConnect(gomock.Any(), "a-moby-name", "an-id", gomock.Eq( + &network.EndpointSettings{ + IPAMConfig: &network.EndpointIPAMConfig{}, + Aliases: []string{"bork-test-0"}, + })) + + _, err := tested.createMobyContainer(context.Background(), &project, service, "test", 0, nil, createOptions{ + Labels: make(types.Labels), + }, progress.ContextWriter(context.TODO())) + assert.NilError(t, err) + }) + + t.Run("includes all container networks in ContainerCreate call if API >=1.44", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + apiClient := mocks.NewMockAPIClient(mockCtrl) + cli := mocks.NewMockCli(mockCtrl) + tested := composeService{ + dockerCli: cli, + } + cli.EXPECT().Client().Return(apiClient).AnyTimes() + cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes() + apiClient.EXPECT().DaemonHost().Return("").AnyTimes() + apiClient.EXPECT().ImageInspectWithRaw(gomock.Any(), gomock.Any()).Return(moby.ImageInspect{}, nil, nil).AnyTimes() + // force `RuntimeVersion` to fetch fresh version + runtimeVersion = runtimeVersionCache{} + apiClient.EXPECT().ServerVersion(gomock.Any()).Return(moby.Version{ + APIVersion: "1.44", + }, nil).AnyTimes() + + service := types.ServiceConfig{ + Name: "test", + Networks: map[string]*types.ServiceNetworkConfig{ + "a": { + Priority: 10, + }, + "b": { + Priority: 100, + }, + }, + } + project := types.Project{ + Name: "bork", + Services: types.Services{ + "test": service, + }, + Networks: types.Networks{ + "a": types.NetworkConfig{ + Name: "a-moby-name", + }, + "b": types.NetworkConfig{ + Name: "b-moby-name", + }, + }, + } + + var falseBool bool + apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any(), gomock.Eq( + &containerType.HostConfig{ + PortBindings: nat.PortMap{}, + ExtraHosts: []string{}, + Tmpfs: map[string]string{}, + Resources: containerType.Resources{ + OomKillDisable: &falseBool, + }, + NetworkMode: "b-moby-name", + }), gomock.Eq( + &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + "a-moby-name": { + IPAMConfig: &network.EndpointIPAMConfig{}, + Aliases: []string{"bork-test-0"}, + }, + "b-moby-name": { + IPAMConfig: &network.EndpointIPAMConfig{}, + Aliases: []string{"bork-test-0"}, + }, + }, + }), gomock.Any(), gomock.Any()).Times(1).Return( + containerType.CreateResponse{ + ID: "an-id", + }, nil) + + apiClient.EXPECT().ContainerInspect(gomock.Any(), gomock.Eq("an-id")).Times(1).Return( + moby.ContainerJSON{ + ContainerJSONBase: &moby.ContainerJSONBase{ + ID: "an-id", + Name: "a-name", + }, + Config: &containerType.Config{}, + NetworkSettings: &moby.NetworkSettings{}, + }, nil) + + _, err := tested.createMobyContainer(context.Background(), &project, service, "test", 0, nil, createOptions{ + Labels: make(types.Labels), + }, progress.ContextWriter(context.TODO())) + assert.NilError(t, err) + }) + +} diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 549b8c0243..65e948ecd2 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -235,7 +235,11 @@ func (s *composeService) getCreateConfigs(ctx context.Context, if err != nil { return createConfigs{}, err } - networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases) + apiVersion, err := s.RuntimeVersion(ctx) + if err != nil { + return createConfigs{}, err + } + networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases, apiVersion) portBindings := buildContainerPortBindingOptions(service) // MISC @@ -456,6 +460,7 @@ func defaultNetworkSettings( serviceIndex int, links []string, useNetworkAliases bool, + version string, ) (container.NetworkMode, *network.NetworkingConfig) { if service.NetworkMode != "" { return container.NetworkMode(service.NetworkMode), nil @@ -465,23 +470,38 @@ func defaultNetworkSettings( return "none", nil } - var networkKey string + var primaryNetworkKey string if len(service.Networks) > 0 { - networkKey = service.NetworksByPriority()[0] + primaryNetworkKey = service.NetworksByPriority()[0] } else { - networkKey = "default" + primaryNetworkKey = "default" + } + primaryNetworkMobyNetworkName := project.Networks[primaryNetworkKey].Name + endpointsConfig := map[string]*network.EndpointSettings{ + primaryNetworkMobyNetworkName: createEndpointSettings(project, service, serviceIndex, primaryNetworkKey, links, useNetworkAliases), + } + + // Starting from API version 1.44, the Engine will take several EndpointsConfigs + // so we can pass all the extra networks we want the container to be connected to + // in the network configuration instead of connecting the container to each extra + // network individually after creation. + if versions.GreaterThanOrEqualTo(version, "1.44") && len(service.Networks) > 1 { + serviceNetworks := service.NetworksByPriority() + for _, networkKey := range serviceNetworks[1:] { + mobyNetworkName := project.Networks[networkKey].Name + epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases) + endpointsConfig[mobyNetworkName] = epSettings + } } - mobyNetworkName := project.Networks[networkKey].Name - epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases) + networkConfig := &network.NetworkingConfig{ - EndpointsConfig: map[string]*network.EndpointSettings{ - mobyNetworkName: epSettings, - }, + EndpointsConfig: endpointsConfig, } + // From the Engine API docs: // > Supported standard values are: bridge, host, none, and container:. // > Any other value is taken as a custom network's name to which this container should connect to. - return container.NetworkMode(mobyNetworkName), networkConfig + return container.NetworkMode(primaryNetworkMobyNetworkName), networkConfig } func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy { diff --git a/pkg/compose/create_test.go b/pkg/compose/create_test.go index 1a47348960..7991d09e8a 100644 --- a/pkg/compose/create_test.go +++ b/pkg/compose/create_test.go @@ -193,7 +193,7 @@ func TestDefaultNetworkSettings(t *testing.T) { }), } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true) + networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") assert.Equal(t, string(networkMode), "myProject_myNetwork2") assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1)) assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork2")) @@ -221,7 +221,7 @@ func TestDefaultNetworkSettings(t *testing.T) { }), } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true) + networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") assert.Equal(t, string(networkMode), "myProject_default") assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1)) assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_default")) @@ -238,7 +238,7 @@ func TestDefaultNetworkSettings(t *testing.T) { }, } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true) + networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") assert.Equal(t, string(networkMode), "none") assert.Check(t, cmp.Nil(networkConfig)) }) @@ -258,7 +258,7 @@ func TestDefaultNetworkSettings(t *testing.T) { }), } - networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true) + networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true, "1.43") assert.Equal(t, string(networkMode), "host") assert.Check(t, cmp.Nil(networkConfig)) }) From 240f15bb2ef5288e4b47618135c0062f5591a361 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Mon, 5 Feb 2024 17:38:15 -0500 Subject: [PATCH 10/47] chore(load): ensure context passed to load This wasn't always getting passed, so adding it to the wrapper function where it'll pass the `WithContext()` loader method at the last moment. Signed-off-by: Milas Bowman --- cmd/compose/attach.go | 2 +- cmd/compose/build.go | 2 +- cmd/compose/completion.go | 4 ++-- cmd/compose/compose.go | 34 ++++++++++++++++------------------ cmd/compose/config.go | 5 ++--- cmd/compose/cp.go | 2 +- cmd/compose/down.go | 2 +- cmd/compose/events.go | 2 +- cmd/compose/exec.go | 2 +- cmd/compose/images.go | 2 +- cmd/compose/kill.go | 2 +- cmd/compose/logs.go | 2 +- cmd/compose/pause.go | 4 ++-- cmd/compose/port.go | 2 +- cmd/compose/ps.go | 2 +- cmd/compose/publish.go | 2 +- cmd/compose/pull.go | 2 +- cmd/compose/push.go | 2 +- cmd/compose/remove.go | 2 +- cmd/compose/restart.go | 2 +- cmd/compose/run.go | 2 +- cmd/compose/scale.go | 2 +- cmd/compose/start.go | 2 +- cmd/compose/stats.go | 2 +- cmd/compose/stop.go | 2 +- cmd/compose/top.go | 2 +- cmd/compose/viz.go | 2 +- cmd/compose/wait.go | 2 +- cmd/compose/watch.go | 2 +- 29 files changed, 47 insertions(+), 50 deletions(-) diff --git a/cmd/compose/attach.go b/cmd/compose/attach.go index 1899edef11..340079cf62 100644 --- a/cmd/compose/attach.go +++ b/cmd/compose/attach.go @@ -64,7 +64,7 @@ func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service } func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Service, opts attachOpts) error { - projectName, err := opts.toProjectName(dockerCli) + projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/build.go b/cmd/compose/build.go index 5f855c9220..e96fbfb1c7 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -136,7 +136,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error { - project, err := opts.ToProject(dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution) + project, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution) if err != nil { return err } diff --git a/cmd/compose/completion.go b/cmd/compose/completion.go index 83b233f1e2..21351d2545 100644 --- a/cmd/compose/completion.go +++ b/cmd/compose/completion.go @@ -37,7 +37,7 @@ func noCompletion() validArgsFn { func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { p.Offline = true - project, err := p.ToProject(dockerCli, nil) + project, err := p.ToProject(cmd.Context(), dockerCli, nil) if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } @@ -72,7 +72,7 @@ func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []s func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { p.Offline = true - project, err := p.ToProject(dockerCli, nil) + project, err := p.ToProject(cmd.Context(), dockerCli, nil) if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 17e5e071c8..5d3d62916b 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -35,18 +35,17 @@ import ( dockercli "github.com/docker/cli/cli" "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" + "github.com/docker/compose/v2/cmd/formatter" + "github.com/docker/compose/v2/pkg/api" + "github.com/docker/compose/v2/pkg/compose" + ui "github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/remote" + "github.com/docker/compose/v2/pkg/utils" buildkit "github.com/moby/buildkit/util/progress/progressui" "github.com/morikuni/aec" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" - - "github.com/docker/compose/v2/cmd/formatter" - "github.com/docker/compose/v2/pkg/api" - "github.com/docker/compose/v2/pkg/compose" - ui "github.com/docker/compose/v2/pkg/progress" - "github.com/docker/compose/v2/pkg/utils" ) const ( @@ -140,10 +139,9 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF options := []cli.ProjectOptionsFn{ cli.WithResolvedPaths(true), cli.WithDiscardEnvFile, - cli.WithContext(ctx), } - project, err := o.ToProject(dockerCli, args, options...) + project, err := o.ToProject(ctx, dockerCli, args, options...) if err != nil { return err } @@ -164,11 +162,11 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) { _ = f.MarkHidden("workdir") } -func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string) (*types.Project, string, error) { +func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) { name := o.ProjectName var project *types.Project if len(o.ConfigPaths) > 0 || o.ProjectName == "" { - p, err := o.ToProject(dockerCli, services, cli.WithDiscardEnvFile) + p, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile) if err != nil { envProjectName := os.Getenv(ComposeProjectName) if envProjectName != "" { @@ -182,7 +180,7 @@ func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string return project, name, nil } -func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) { +func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cli) (string, error) { if o.ProjectName != "" { return o.ProjectName, nil } @@ -192,18 +190,20 @@ func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) { return envProjectName, nil } - project, err := o.ToProject(dockerCli, nil) + project, err := o.ToProject(ctx, dockerCli, nil) if err != nil { return "", err } return project.Name, nil } -func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) { +func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) { if !o.Offline { - po = o.configureRemoteLoaders(dockerCli, po) + po = append(po, o.remoteLoaders(dockerCli)...) } + po = append(po, cli.WithContext(ctx)) + options, err := o.toProjectOptions(po...) if err != nil { return nil, compose.WrapComposeError(err) @@ -248,12 +248,10 @@ func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po return project, err } -func (o *ProjectOptions) configureRemoteLoaders(dockerCli command.Cli, po []cli.ProjectOptionsFn) []cli.ProjectOptionsFn { +func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []cli.ProjectOptionsFn { git := remote.NewGitRemoteLoader(o.Offline) oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline) - - po = append(po, cli.WithResourceLoader(git), cli.WithResourceLoader(oci)) - return po + return []cli.ProjectOptionsFn{cli.WithResourceLoader(git), cli.WithResourceLoader(oci)} } func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) { diff --git a/cmd/compose/config.go b/cmd/compose/config.go index cf26832cfd..b0386be720 100644 --- a/cmd/compose/config.go +++ b/cmd/compose/config.go @@ -57,9 +57,8 @@ func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, se cli.WithNormalization(!o.noNormalize), cli.WithConsistency(!o.noConsistency), cli.WithDefaultProfiles(o.Profiles...), - cli.WithDiscardEnvFile, - cli.WithContext(ctx)) - return o.ProjectOptions.ToProject(dockerCli, services, po...) + cli.WithDiscardEnvFile) + return o.ProjectOptions.ToProject(ctx, dockerCli, services, po...) } func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { diff --git a/cmd/compose/cp.go b/cmd/compose/cp.go index 4c346b2a7b..ee7c5a9d65 100644 --- a/cmd/compose/cp.go +++ b/cmd/compose/cp.go @@ -76,7 +76,7 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Service, opts copyOptions) error { - name, err := opts.toProjectName(dockerCli) + name, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/down.go b/cmd/compose/down.go index 97215a7857..4343d7870f 100644 --- a/cmd/compose/down.go +++ b/cmd/compose/down.go @@ -78,7 +78,7 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runDown(ctx context.Context, dockerCli command.Cli, backend api.Service, opts downOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/events.go b/cmd/compose/events.go index 10ef5b4427..cfd90d03d7 100644 --- a/cmd/compose/events.go +++ b/cmd/compose/events.go @@ -52,7 +52,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service } func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service, opts eventsOpts, services []string) error { - name, err := opts.toProjectName(dockerCli) + name, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go index 12ebc70050..1fdd6e7312 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -82,7 +82,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, opts execOpts) error { - projectName, err := opts.toProjectName(dockerCli) + projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/images.go b/cmd/compose/images.go index d10700767a..fd120544ee 100644 --- a/cmd/compose/images.go +++ b/cmd/compose/images.go @@ -57,7 +57,7 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service } func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service, opts imageOptions, services []string) error { - projectName, err := opts.toProjectName(dockerCli) + projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/kill.go b/cmd/compose/kill.go index d38201cbc9..358eaec7a0 100644 --- a/cmd/compose/kill.go +++ b/cmd/compose/kill.go @@ -55,7 +55,7 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runKill(ctx context.Context, dockerCli command.Cli, backend api.Service, opts killOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go index 2df7bb59f1..62a7224c1c 100644 --- a/cmd/compose/logs.go +++ b/cmd/compose/logs.go @@ -71,7 +71,7 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, opts logsOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/pause.go b/cmd/compose/pause.go index acfc0dec51..6f34577192 100644 --- a/cmd/compose/pause.go +++ b/cmd/compose/pause.go @@ -45,7 +45,7 @@ func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pauseOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } @@ -76,7 +76,7 @@ func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic } func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts unpauseOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/port.go b/cmd/compose/port.go index 0baa875c3c..d63b03ec4d 100644 --- a/cmd/compose/port.go +++ b/cmd/compose/port.go @@ -63,7 +63,7 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, opts portOptions, service string) error { - projectName, err := opts.toProjectName(dockerCli) + projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/ps.go b/cmd/compose/ps.go index 8b3f3d34d6..5bc131df3b 100644 --- a/cmd/compose/ps.go +++ b/cmd/compose/ps.go @@ -92,7 +92,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c } func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, opts psOptions) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go index 2ef14aabf0..32a2020852 100644 --- a/cmd/compose/publish.go +++ b/cmd/compose/publish.go @@ -50,7 +50,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic } func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error { - project, err := opts.ToProject(dockerCli, nil) + project, err := opts.ToProject(ctx, dockerCli, nil) if err != nil { return err } diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index d6ce561be7..3f350ee864 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -94,7 +94,7 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types } func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error { - project, err := opts.ToProject(dockerCli, services) + project, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err } diff --git a/cmd/compose/push.go b/cmd/compose/push.go index ccc0e59917..603c2092f3 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -54,7 +54,7 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error { - project, err := opts.ToProject(dockerCli, services) + project, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err } diff --git a/cmd/compose/remove.go b/cmd/compose/remove.go index 46ac7b13fe..adcd3663c3 100644 --- a/cmd/compose/remove.go +++ b/cmd/compose/remove.go @@ -60,7 +60,7 @@ Any data which is not in a volume will be lost.`, } func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Service, opts removeOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/restart.go b/cmd/compose/restart.go index 9fe65f4615..aeb44c86c9 100644 --- a/cmd/compose/restart.go +++ b/cmd/compose/restart.go @@ -56,7 +56,7 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic } func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts restartOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli) + project, name, err := opts.projectOrName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/run.go b/cmd/compose/run.go index c2649d5f12..19db6e32ee 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -156,7 +156,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * return nil }), RunE: Adapt(func(ctx context.Context, args []string) error { - project, err := p.ToProject(dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile) + project, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile) if err != nil { return err } diff --git a/cmd/compose/scale.go b/cmd/compose/scale.go index 2ab4351277..ea852dabb9 100644 --- a/cmd/compose/scale.go +++ b/cmd/compose/scale.go @@ -61,7 +61,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error { services := maps.Keys(serviceReplicaTuples) - project, err := opts.ToProject(dockerCli, services) + project, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err } diff --git a/cmd/compose/start.go b/cmd/compose/start.go index 682a789065..6bde4b104b 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -44,7 +44,7 @@ func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runStart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts startOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/stats.go b/cmd/compose/stats.go index c6c2f7fed0..2e503fe5db 100644 --- a/cmd/compose/stats.go +++ b/cmd/compose/stats.go @@ -63,7 +63,7 @@ Refer to https://docs.docker.com/go/formatting/ for more information about forma } func runStats(ctx context.Context, dockerCli command.Cli, opts statsOptions, service []string) error { - name, err := opts.ProjectOptions.toProjectName(dockerCli) + name, err := opts.ProjectOptions.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/stop.go b/cmd/compose/stop.go index 2818ef4f33..d06cf6f229 100644 --- a/cmd/compose/stop.go +++ b/cmd/compose/stop.go @@ -54,7 +54,7 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts stopOptions, services []string) error { - project, name, err := opts.projectOrName(dockerCli, services...) + project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err } diff --git a/cmd/compose/top.go b/cmd/compose/top.go index fb7dd30d41..9d84c57a95 100644 --- a/cmd/compose/top.go +++ b/cmd/compose/top.go @@ -50,7 +50,7 @@ func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * } func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error { - projectName, err := opts.toProjectName(dockerCli) + projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err } diff --git a/cmd/compose/viz.go b/cmd/compose/viz.go index 2b4d650739..4410136f52 100644 --- a/cmd/compose/viz.go +++ b/cmd/compose/viz.go @@ -65,7 +65,7 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error { _, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL") - project, err := opts.ToProject(dockerCli, nil) + project, err := opts.ToProject(ctx, dockerCli, nil) if err != nil { return err } diff --git a/cmd/compose/wait.go b/cmd/compose/wait.go index e95818b959..88c9391400 100644 --- a/cmd/compose/wait.go +++ b/cmd/compose/wait.go @@ -61,7 +61,7 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runWait(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *waitOptions) (int64, error) { - _, name, err := opts.projectOrName(dockerCli) + _, name, err := opts.projectOrName(ctx, dockerCli) if err != nil { return 0, err } diff --git a/cmd/compose/watch.go b/cmd/compose/watch.go index 24d704570e..cf39a20751 100644 --- a/cmd/compose/watch.go +++ b/cmd/compose/watch.go @@ -63,7 +63,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, watchOpts watchOptions, buildOpts buildOptions, services []string) error { - project, err := watchOpts.ToProject(dockerCli, nil) + project, err := watchOpts.ToProject(ctx, dockerCli, nil) if err != nil { return err } From 54ba3872d396dba70dc41809db530a667e74f6c6 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Mon, 5 Feb 2024 17:38:04 -0500 Subject: [PATCH 11/47] feat(tracing): add project hash attr Hash the project config and add it as an attribute. This can be used to group multiple spans. Signed-off-by: Milas Bowman --- internal/tracing/attributes.go | 26 +++++++++++ internal/tracing/attributes_test.go | 67 +++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 internal/tracing/attributes_test.go diff --git a/internal/tracing/attributes.go b/internal/tracing/attributes.go index 1fd2dd7f91..a603ec910d 100644 --- a/internal/tracing/attributes.go +++ b/internal/tracing/attributes.go @@ -17,6 +17,9 @@ package tracing import ( + "crypto/sha256" + "encoding/json" + "fmt" "strings" "time" @@ -72,6 +75,9 @@ func ProjectOptions(proj *types.Project) SpanOptions { attribute.StringSlice("project.extensions", keys(proj.Extensions)), attribute.StringSlice("project.includes", flattenIncludeReferences(proj.IncludeReferences)), } + if projHash, ok := projectHash(proj); ok { + attrs = append(attrs, attribute.String("project.hash", projHash)) + } return []trace.SpanStartEventOption{ trace.WithAttributes(attrs...), } @@ -158,3 +164,23 @@ func flattenIncludeReferences(includeRefs map[string][]types.IncludeConfig) []st } return ret.Elements() } + +// projectHash returns a checksum from the JSON encoding of the project. +func projectHash(p *types.Project) (string, bool) { + if p == nil { + return "", false + } + // disabled services aren't included in the output, so make a copy with + // all the services active for hashing + var err error + p, err = p.WithServicesEnabled(append(p.ServiceNames(), p.DisabledServiceNames()...)...) + if err != nil { + return "", false + } + projData, err := json.Marshal(p) + if err != nil { + return "", false + } + d := sha256.Sum256(projData) + return fmt.Sprintf("%x", d), true +} diff --git a/internal/tracing/attributes_test.go b/internal/tracing/attributes_test.go new file mode 100644 index 0000000000..d4277a940a --- /dev/null +++ b/internal/tracing/attributes_test.go @@ -0,0 +1,67 @@ +/* + Copyright 2024 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package tracing + +import ( + "testing" + + "github.com/compose-spec/compose-go/v2/types" + "github.com/stretchr/testify/require" +) + +func TestProjectHash(t *testing.T) { + projA := &types.Project{ + Name: "fake-proj", + WorkingDir: "/tmp", + Services: map[string]types.ServiceConfig{ + "foo": {Image: "fake-image"}, + }, + DisabledServices: map[string]types.ServiceConfig{ + "bar": {Image: "diff-image"}, + }, + } + projB := &types.Project{ + Name: "fake-proj", + WorkingDir: "/tmp", + Services: map[string]types.ServiceConfig{ + "foo": {Image: "fake-image"}, + "bar": {Image: "diff-image"}, + }, + } + projC := &types.Project{ + Name: "fake-proj", + WorkingDir: "/tmp", + Services: map[string]types.ServiceConfig{ + "foo": {Image: "fake-image"}, + "bar": {Image: "diff-image"}, + "baz": {Image: "yet-another-image"}, + }, + } + + hashA, ok := projectHash(projA) + require.True(t, ok) + require.NotEmpty(t, hashA) + hashB, ok := projectHash(projB) + require.True(t, ok) + require.NotEmpty(t, hashB) + require.Equal(t, hashA, hashB) + + hashC, ok := projectHash(projC) + require.True(t, ok) + require.NotEmpty(t, hashC) + require.NotEqual(t, hashC, hashA) +} From 0f10e17788e821c4e095e907fb12d131be98bce5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:30:55 +0000 Subject: [PATCH 12/47] build(deps): bump github.com/opencontainers/image-spec Bumps [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec) from 1.1.0-rc5 to 1.1.0-rc6. - [Release notes](https://github.com/opencontainers/image-spec/releases) - [Changelog](https://github.com/opencontainers/image-spec/blob/main/RELEASES.md) - [Commits](https://github.com/opencontainers/image-spec/compare/v1.1.0-rc5...v1.1.0-rc6) --- updated-dependencies: - dependency-name: github.com/opencontainers/image-spec dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3c323949bf..f6bdcf6f75 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/moby/term v0.5.0 github.com/morikuni/aec v1.0.0 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc5 + github.com/opencontainers/image-spec v1.1.0-rc6 github.com/otiai10/copy v1.14.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index da37f60544..2df8f7c9dc 100644 --- a/go.sum +++ b/go.sum @@ -380,8 +380,8 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= +github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= From 0ac131803d5edde840fc724896ebc7fb3244c3f5 Mon Sep 17 00:00:00 2001 From: jhrotko Date: Thu, 8 Feb 2024 15:58:50 +0000 Subject: [PATCH 13/47] Add OTEL specs: build, depends_on, capabilities (gpu/tpu) Signed-off-by: jhrotko --- go.mod | 2 +- go.sum | 4 ++-- internal/tracing/attributes.go | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f6bdcf6f75..48e24ba0f3 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Microsoft/go-winio v0.6.1 github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.0.0-rc.4 + github.com/compose-spec/compose-go/v2 v2.0.0-rc.5 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.12 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 2df8f7c9dc..d9e35e6a48 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.4 h1:vVDWv1xm7Lqi7sEevU0Jk+T2huxNzf92lFhqKbn3sT8= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.4/go.mod h1:IbZsys5a7eFTsdcWvM3brnghkK7ctZwUSr7mCj5NXu0= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.5 h1:YoGsuVzxve1m5SdCfZqI8wJoMVZWu7SelHoqiCqb+iQ= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.5/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= diff --git a/internal/tracing/attributes.go b/internal/tracing/attributes.go index a603ec910d..5eb470c6c5 100644 --- a/internal/tracing/attributes.go +++ b/internal/tracing/attributes.go @@ -61,6 +61,7 @@ func ProjectOptions(proj *types.Project) SpanOptions { return nil } + capabilities, gpu, tpu := proj.ServicesWithCapabilities() attrs := []attribute.KeyValue{ attribute.String("project.name", proj.Name), attribute.String("project.dir", proj.WorkingDir), @@ -74,6 +75,11 @@ func ProjectOptions(proj *types.Project) SpanOptions { attribute.StringSlice("project.configs", proj.ConfigNames()), attribute.StringSlice("project.extensions", keys(proj.Extensions)), attribute.StringSlice("project.includes", flattenIncludeReferences(proj.IncludeReferences)), + attribute.StringSlice("project.services.build", proj.ServicesWithBuild()), + attribute.StringSlice("project.services.depends_on", proj.ServicesWithDependsOn()), + attribute.StringSlice("project.services.capabilities", capabilities), + attribute.StringSlice("project.services.capabilities.gpu", gpu), + attribute.StringSlice("project.services.capabilities.tpu", tpu), } if projHash, ok := projectHash(proj); ok { attrs = append(attrs, attribute.String("project.hash", projHash)) From 18e8245c965ce2c850bfcac99f57bf6b2e76c7e0 Mon Sep 17 00:00:00 2001 From: 1arp Date: Thu, 8 Feb 2024 03:45:47 +0000 Subject: [PATCH 14/47] pass All option to backend api.Service when length statuses is not equal to zero Signed-off-by: 1arp --- cmd/compose/ps.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/compose/ps.go b/cmd/compose/ps.go index 5bc131df3b..58e381a397 100644 --- a/cmd/compose/ps.go +++ b/cmd/compose/ps.go @@ -113,7 +113,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv containers, err := backend.Ps(ctx, name, api.PsOptions{ Project: project, - All: opts.All, + All: opts.All || len(opts.Status) != 0, Services: services, }) if err != nil { From 7825cc29092570df4c6558e962f7575fb590b190 Mon Sep 17 00:00:00 2001 From: Nicolas De loof Date: Mon, 12 Feb 2024 17:20:31 +0100 Subject: [PATCH 15/47] ci(deps): bump docker/cli to v25.0.3 (#11481) https://github.com/docker/cli/releases/tag/v25.0.3 https://github.com/moby/moby/releases/tag/v25.0.3 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 48e24ba0f3..24de127876 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/distribution/reference v0.5.0 github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315 - github.com/docker/cli v25.0.2+incompatible + github.com/docker/cli v25.0.3+incompatible github.com/docker/cli-docs-tool v0.6.0 github.com/docker/docker v25.0.1+incompatible github.com/docker/go-connections v0.5.0 diff --git a/go.sum b/go.sum index d9e35e6a48..0cb6c7f791 100644 --- a/go.sum +++ b/go.sum @@ -124,8 +124,8 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315 h1:UZxx9xBADdf/9UmSdEUi+pdJoPKpgcf9QUAY5gEIYmY= github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315/go.mod h1:X8ZHhuW6ncwtoJ36TlU+gyaROTcBkTE01VHYmTStQCE= -github.com/docker/cli v25.0.2+incompatible h1:6GEdvxwEA451/+Y3GtqIGn/MNjujQazUlxC6uGu8Tog= -github.com/docker/cli v25.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= +github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA= github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= From 69c916a39f2b8a75e75121eab4993bc77381bab4 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 13 Feb 2024 15:13:28 +0100 Subject: [PATCH 16/47] fix deadlock collecting large logs Signed-off-by: Nicolas De Loof --- pkg/compose/printer.go | 130 +++++++++++++++------------- pkg/e2e/fixtures/logs-test/cat.yaml | 6 ++ pkg/e2e/logs_test.go | 24 +++++ 3 files changed, 100 insertions(+), 60 deletions(-) create mode 100644 pkg/e2e/fixtures/logs-test/cat.yaml diff --git a/pkg/compose/printer.go b/pkg/compose/printer.go index dea72e27a6..e54c8c8bf6 100644 --- a/pkg/compose/printer.go +++ b/pkg/compose/printer.go @@ -32,18 +32,19 @@ type logPrinter interface { } type printer struct { - sync.Mutex queue chan api.ContainerEvent consumer api.LogConsumer - stopped bool + stopCh chan struct{} // stopCh is a signal channel for producers to stop sending events to the queue + stop sync.Once } // newLogPrinter builds a LogPrinter passing containers logs to LogConsumer func newLogPrinter(consumer api.LogConsumer) logPrinter { - queue := make(chan api.ContainerEvent) printer := printer{ consumer: consumer, - queue: queue, + queue: make(chan api.ContainerEvent), + stopCh: make(chan struct{}), + stop: sync.Once{}, } return &printer } @@ -54,24 +55,27 @@ func (p *printer) Cancel() { } func (p *printer) Stop() { - p.Lock() - defer p.Unlock() - if !p.stopped { - // only close if this is the first call to stop - p.stopped = true - close(p.queue) - } + p.stop.Do(func() { + close(p.stopCh) + for { + select { + case <-p.queue: + // purge the queue to free producers goroutines + // p.queue will be garbage collected + default: + return + } + } + }) } func (p *printer) HandleEvent(event api.ContainerEvent) { - p.Lock() - defer p.Unlock() - if p.stopped { - // prevent deadlocking, if the printer is done, there's no reader for - // queue, so this write could block indefinitely + select { + case <-p.stopCh: return + default: + p.queue <- event } - p.queue <- event } //nolint:gocyclo @@ -80,58 +84,64 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error aborting bool exitCode int ) + defer p.Stop() + containers := map[string]struct{}{} - for event := range p.queue { - container, id := event.Container, event.ID - switch event.Type { - case api.UserCancel: - aborting = true - case api.ContainerEventAttach: - if _, ok := containers[id]; ok { - continue - } - containers[id] = struct{}{} - p.consumer.Register(container) - case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated: - if !event.Restarting { - delete(containers, id) - } - if !aborting { - p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode)) - if event.Type == api.ContainerEventRecreated { - p.consumer.Status(container, "has been recreated") + for { + select { + case <-p.stopCh: + return exitCode, nil + case event := <-p.queue: + container, id := event.Container, event.ID + switch event.Type { + case api.UserCancel: + aborting = true + case api.ContainerEventAttach: + if _, ok := containers[id]; ok { + continue + } + containers[id] = struct{}{} + p.consumer.Register(container) + case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated: + if !event.Restarting { + delete(containers, id) } - } - if cascadeStop { if !aborting { - aborting = true - err := stopFn() - if err != nil { - return 0, err + p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode)) + if event.Type == api.ContainerEventRecreated { + p.consumer.Status(container, "has been recreated") } } - if event.Type == api.ContainerEventExit { - if exitCodeFrom == "" { - exitCodeFrom = event.Service + if cascadeStop { + if !aborting { + aborting = true + err := stopFn() + if err != nil { + return 0, err + } } - if exitCodeFrom == event.Service { - exitCode = event.ExitCode + if event.Type == api.ContainerEventExit { + if exitCodeFrom == "" { + exitCodeFrom = event.Service + } + if exitCodeFrom == event.Service { + exitCode = event.ExitCode + } } } - } - if len(containers) == 0 { - // Last container terminated, done - return exitCode, nil - } - case api.ContainerEventLog: - if !aborting { - p.consumer.Log(container, event.Line) - } - case api.ContainerEventErr: - if !aborting { - p.consumer.Err(container, event.Line) + if len(containers) == 0 { + // Last container terminated, done + return exitCode, nil + } + case api.ContainerEventLog: + if !aborting { + p.consumer.Log(container, event.Line) + } + case api.ContainerEventErr: + if !aborting { + p.consumer.Err(container, event.Line) + } } } } - return exitCode, nil } diff --git a/pkg/e2e/fixtures/logs-test/cat.yaml b/pkg/e2e/fixtures/logs-test/cat.yaml new file mode 100644 index 0000000000..76bd5a9ab6 --- /dev/null +++ b/pkg/e2e/fixtures/logs-test/cat.yaml @@ -0,0 +1,6 @@ +services: + test: + image: alpine + command: cat /text_file.txt + volumes: + - ${FILE}:/text_file.txt diff --git a/pkg/e2e/logs_test.go b/pkg/e2e/logs_test.go index d22347b617..6c641065eb 100644 --- a/pkg/e2e/logs_test.go +++ b/pkg/e2e/logs_test.go @@ -17,6 +17,10 @@ package e2e import ( + "fmt" + "io" + "os" + "path/filepath" "strings" "testing" "time" @@ -96,6 +100,26 @@ func TestLocalComposeLogsFollow(t *testing.T) { poll.WaitOn(t, expectOutput(res, "ping-2 "), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(20*time.Second)) } +func TestLocalComposeLargeLogs(t *testing.T) { + const projectName = "compose-e2e-large_logs" + file := filepath.Join(t.TempDir(), "large.txt") + c := NewCLI(t, WithEnv("FILE="+file)) + t.Cleanup(func() { + c.RunDockerComposeCmd(t, "--project-name", projectName, "down") + }) + + f, err := os.Create(file) + assert.NilError(t, err) + for i := 0; i < 300_000; i++ { + _, err := io.WriteString(f, fmt.Sprintf("This is line %d in a laaaarge text file\n", i)) + assert.NilError(t, err) + } + assert.NilError(t, f.Close()) + + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/logs-test/cat.yaml", "--project-name", projectName, "up", "--abort-on-container-exit") + res.Assert(t, icmd.Expected{Out: "test-1 exited with code 0"}) +} + func expectOutput(res *icmd.Result, expected string) func(t poll.LogT) poll.Result { return func(t poll.LogT) poll.Result { if strings.Contains(res.Stdout(), expected) { From 5a587b4460c2a14c0b493e2244bf5a3d71b74584 Mon Sep 17 00:00:00 2001 From: jhrotko Date: Mon, 12 Feb 2024 14:52:58 +0000 Subject: [PATCH 17/47] Use listener for file metadata Signed-off-by: jhrotko --- cmd/compose/build.go | 2 +- cmd/compose/completion.go | 4 ++-- cmd/compose/compose.go | 27 ++++++++++++++++++--------- cmd/compose/config.go | 15 ++++++++------- cmd/compose/publish.go | 2 +- cmd/compose/pull.go | 2 +- cmd/compose/push.go | 2 +- cmd/compose/run.go | 2 +- cmd/compose/scale.go | 2 +- cmd/compose/viz.go | 2 +- cmd/compose/watch.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- internal/tracing/attributes.go | 15 +++++++++++---- pkg/compose/build.go | 4 ++-- pkg/compose/scale.go | 2 +- pkg/compose/up.go | 2 +- 17 files changed, 54 insertions(+), 37 deletions(-) diff --git a/cmd/compose/build.go b/cmd/compose/build.go index e96fbfb1c7..bb1db159a4 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -136,7 +136,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error { - project, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution) + project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution) if err != nil { return err } diff --git a/cmd/compose/completion.go b/cmd/compose/completion.go index 21351d2545..71da360c14 100644 --- a/cmd/compose/completion.go +++ b/cmd/compose/completion.go @@ -37,7 +37,7 @@ func noCompletion() validArgsFn { func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { p.Offline = true - project, err := p.ToProject(cmd.Context(), dockerCli, nil) + project, _, err := p.ToProject(cmd.Context(), dockerCli, nil) if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } @@ -72,7 +72,7 @@ func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []s func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { p.Offline = true - project, err := p.ToProject(cmd.Context(), dockerCli, nil) + project, _, err := p.ToProject(cmd.Context(), dockerCli, nil) if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 5d3d62916b..6d923076b7 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -36,6 +36,7 @@ import ( "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/cmd/formatter" + "github.com/docker/compose/v2/internal/tracing" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/compose" ui "github.com/docker/compose/v2/pkg/progress" @@ -141,11 +142,13 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF cli.WithDiscardEnvFile, } - project, err := o.ToProject(ctx, dockerCli, args, options...) + project, metrics, err := o.ToProject(ctx, dockerCli, args, options...) if err != nil { return err } + ctx = context.WithValue(ctx, tracing.Metrics{}, metrics) + return fn(ctx, project, args) }) } @@ -166,7 +169,7 @@ func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cl name := o.ProjectName var project *types.Project if len(o.ConfigPaths) > 0 || o.ProjectName == "" { - p, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile) + p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile) if err != nil { envProjectName := os.Getenv(ComposeProjectName) if envProjectName != "" { @@ -190,14 +193,15 @@ func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cl return envProjectName, nil } - project, err := o.ToProject(ctx, dockerCli, nil) + project, _, err := o.ToProject(ctx, dockerCli, nil) if err != nil { return "", err } return project.Name, nil } -func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) { +func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { + var metrics tracing.Metrics if !o.Offline { po = append(po, o.remoteLoaders(dockerCli)...) } @@ -206,25 +210,30 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s options, err := o.toProjectOptions(po...) if err != nil { - return nil, compose.WrapComposeError(err) + return nil, metrics, compose.WrapComposeError(err) } + options.WithListeners(func(event string, metadata map[string]any) { + if event == "extends" { + metrics.CountExtends++ + } + }) if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) { api.Separator = "_" } project, err := cli.ProjectFromOptions(options) if err != nil { - return nil, compose.WrapComposeError(err) + return nil, metrics, compose.WrapComposeError(err) } if project.Name == "" { - return nil, errors.New("project name can't be empty. Use `--project-name` to set a valid name") + return nil, metrics, errors.New("project name can't be empty. Use `--project-name` to set a valid name") } project, err = project.WithServicesEnabled(services...) if err != nil { - return nil, err + return nil, metrics, err } for name, s := range project.Services { @@ -245,7 +254,7 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s project = project.WithoutUnnecessaryResources() project, err = project.WithSelectedServices(services) - return project, err + return project, metrics, err } func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []cli.ProjectOptionsFn { diff --git a/cmd/compose/config.go b/cmd/compose/config.go index b0386be720..c763037532 100644 --- a/cmd/compose/config.go +++ b/cmd/compose/config.go @@ -29,6 +29,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/spf13/cobra" + "github.com/docker/compose/v2/internal/tracing" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/compose" ) @@ -50,7 +51,7 @@ type configOptions struct { noConsistency bool } -func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) { +func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { po = append(po, cli.WithInterpolation(!o.noInterpolate), cli.WithResolvedPaths(!o.noResolvePath), @@ -124,7 +125,7 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, opts configOptions, services []string) error { var content []byte - project, err := opts.ToProject(ctx, dockerCli, services) + project, _, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err } @@ -154,7 +155,7 @@ func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, } func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error { - project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) + project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -166,7 +167,7 @@ func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) } func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error { - project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) + project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -181,7 +182,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err if opts.hash != "*" { services = append(services, strings.Split(opts.hash, ",")...) } - project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) + project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -217,7 +218,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error { set := map[string]struct{}{} - project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) + project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -238,7 +239,7 @@ func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, } func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error { - project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) + project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) if err != nil { return err } diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go index 32a2020852..48b0e2d587 100644 --- a/cmd/compose/publish.go +++ b/cmd/compose/publish.go @@ -50,7 +50,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic } func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error { - project, err := opts.ToProject(ctx, dockerCli, nil) + project, _, err := opts.ToProject(ctx, dockerCli, nil) if err != nil { return err } diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index 3f350ee864..3d3ef54423 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -94,7 +94,7 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types } func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error { - project, err := opts.ToProject(ctx, dockerCli, services) + project, _, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err } diff --git a/cmd/compose/push.go b/cmd/compose/push.go index 603c2092f3..177f9f2ec7 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -54,7 +54,7 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error { - project, err := opts.ToProject(ctx, dockerCli, services) + project, _, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err } diff --git a/cmd/compose/run.go b/cmd/compose/run.go index 19db6e32ee..29fcea254c 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -156,7 +156,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * return nil }), RunE: Adapt(func(ctx context.Context, args []string) error { - project, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile) + project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile) if err != nil { return err } diff --git a/cmd/compose/scale.go b/cmd/compose/scale.go index ea852dabb9..0ec4799a9c 100644 --- a/cmd/compose/scale.go +++ b/cmd/compose/scale.go @@ -61,7 +61,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error { services := maps.Keys(serviceReplicaTuples) - project, err := opts.ToProject(ctx, dockerCli, services) + project, _, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err } diff --git a/cmd/compose/viz.go b/cmd/compose/viz.go index 4410136f52..d97504e382 100644 --- a/cmd/compose/viz.go +++ b/cmd/compose/viz.go @@ -65,7 +65,7 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error { _, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL") - project, err := opts.ToProject(ctx, dockerCli, nil) + project, _, err := opts.ToProject(ctx, dockerCli, nil) if err != nil { return err } diff --git a/cmd/compose/watch.go b/cmd/compose/watch.go index cf39a20751..e4f9427263 100644 --- a/cmd/compose/watch.go +++ b/cmd/compose/watch.go @@ -63,7 +63,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, watchOpts watchOptions, buildOpts buildOptions, services []string) error { - project, err := watchOpts.ToProject(ctx, dockerCli, nil) + project, _, err := watchOpts.ToProject(ctx, dockerCli, nil) if err != nil { return err } diff --git a/go.mod b/go.mod index 24de127876..c946369ebd 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Microsoft/go-winio v0.6.1 github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.0.0-rc.5 + github.com/compose-spec/compose-go/v2 v2.0.0-rc.6 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.12 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 0cb6c7f791..9ed3efa6fb 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.5 h1:YoGsuVzxve1m5SdCfZqI8wJoMVZWu7SelHoqiCqb+iQ= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.5/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.6 h1:sv9W3U0IEYqgGqTbSDpU2c8cttWQmlbJ0D6jdt//Dv8= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.6/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= diff --git a/internal/tracing/attributes.go b/internal/tracing/attributes.go index 5eb470c6c5..23a9b13495 100644 --- a/internal/tracing/attributes.go +++ b/internal/tracing/attributes.go @@ -17,6 +17,7 @@ package tracing import ( + "context" "crypto/sha256" "encoding/json" "fmt" @@ -34,6 +35,9 @@ import ( // SpanOptions is a small helper type to make it easy to share the options helpers between // downstream functions that accept slices of trace.SpanStartOption and trace.EventOption. type SpanOptions []trace.SpanStartEventOption +type Metrics struct { + CountExtends int +} func (s SpanOptions) SpanStartOptions() []trace.SpanStartOption { out := make([]trace.SpanStartOption, len(s)) @@ -56,18 +60,15 @@ func (s SpanOptions) EventOptions() []trace.EventOption { // For convenience, it's returned as a SpanOptions object to allow it to be // passed directly to the wrapping helper methods in this package such as // SpanWrapFunc. -func ProjectOptions(proj *types.Project) SpanOptions { +func ProjectOptions(ctx context.Context, proj *types.Project) SpanOptions { if proj == nil { return nil } - capabilities, gpu, tpu := proj.ServicesWithCapabilities() attrs := []attribute.KeyValue{ attribute.String("project.name", proj.Name), attribute.String("project.dir", proj.WorkingDir), attribute.StringSlice("project.compose_files", proj.ComposeFiles), - attribute.StringSlice("project.services.active", proj.ServiceNames()), - attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()), attribute.StringSlice("project.profiles", proj.Profiles), attribute.StringSlice("project.volumes", proj.VolumeNames()), attribute.StringSlice("project.networks", proj.NetworkNames()), @@ -75,12 +76,18 @@ func ProjectOptions(proj *types.Project) SpanOptions { attribute.StringSlice("project.configs", proj.ConfigNames()), attribute.StringSlice("project.extensions", keys(proj.Extensions)), attribute.StringSlice("project.includes", flattenIncludeReferences(proj.IncludeReferences)), + attribute.StringSlice("project.services.active", proj.ServiceNames()), + attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()), attribute.StringSlice("project.services.build", proj.ServicesWithBuild()), attribute.StringSlice("project.services.depends_on", proj.ServicesWithDependsOn()), attribute.StringSlice("project.services.capabilities", capabilities), attribute.StringSlice("project.services.capabilities.gpu", gpu), attribute.StringSlice("project.services.capabilities.tpu", tpu), } + if metrics, ok := ctx.Value(Metrics{}).(Metrics); ok { + attrs = append(attrs, attribute.Int("project.services.extends", metrics.CountExtends)) + } + if projHash, ok := projectHash(proj); ok { attrs = append(attrs, attribute.String("project.hash", projHash)) } diff --git a/pkg/compose/build.go b/pkg/compose/build.go index 03758325fa..bf865a47d0 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -228,7 +228,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types. return err } - err = tracing.SpanWrapFunc("project/pull", tracing.ProjectOptions(project), + err = tracing.SpanWrapFunc("project/pull", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error { return s.pullRequiredImages(ctx, project, images, quietPull) }, @@ -238,7 +238,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types. } if buildOpts != nil { - err = tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(project), + err = tracing.SpanWrapFunc("project/build", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error { builtImages, err := s.build(ctx, project, *buildOpts, images) if err != nil { diff --git a/pkg/compose/scale.go b/pkg/compose/scale.go index 1e869ea726..985cc129d1 100644 --- a/pkg/compose/scale.go +++ b/pkg/compose/scale.go @@ -25,7 +25,7 @@ import ( ) func (s *composeService) Scale(ctx context.Context, project *types.Project, options api.ScaleOptions) error { - return progress.Run(ctx, tracing.SpanWrapFunc("project/scale", tracing.ProjectOptions(project), func(ctx context.Context) error { + return progress.Run(ctx, tracing.SpanWrapFunc("project/scale", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error { err := s.create(ctx, project, api.CreateOptions{Services: options.Services}) if err != nil { return err diff --git a/pkg/compose/up.go b/pkg/compose/up.go index be6bbdb149..c1dafb5947 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -32,7 +32,7 @@ import ( ) func (s *composeService) Up(ctx context.Context, project *types.Project, options api.UpOptions) error { //nolint:gocyclo - err := progress.Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(project), func(ctx context.Context) error { + err := progress.Run(ctx, tracing.SpanWrapFunc("project/up", tracing.ProjectOptions(ctx, project), func(ctx context.Context) error { w := progress.ContextWriter(ctx) w.HasMore(options.Start.Attach == nil) err := s.create(ctx, project, options.Create) From 22e24bc331a0b1db145216a380f48c65cdfecf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20D=C4=85browski?= Date: Wed, 14 Feb 2024 20:02:37 +0100 Subject: [PATCH 18/47] docs: unify no trailing dots in docstrings and help (#11301) --- cmd/compose/attach.go | 2 +- cmd/compose/build.go | 10 +-- cmd/compose/compose.go | 4 +- cmd/compose/config.go | 20 +++--- cmd/compose/cp.go | 8 +-- cmd/compose/create.go | 10 +-- cmd/compose/down.go | 4 +- cmd/compose/events.go | 2 +- cmd/compose/exec.go | 16 ++--- cmd/compose/images.go | 2 +- cmd/compose/kill.go | 6 +- cmd/compose/list.go | 6 +- cmd/compose/logs.go | 10 +-- cmd/compose/port.go | 4 +- cmd/compose/ps.go | 2 +- cmd/compose/publish.go | 2 +- cmd/compose/pull.go | 14 ++-- cmd/compose/restart.go | 2 +- cmd/compose/run.go | 26 +++---- cmd/compose/scale.go | 2 +- cmd/compose/up.go | 30 ++++---- cmd/compose/version.go | 2 +- docs/reference/compose.md | 68 +++++++++---------- docs/reference/compose_alpha_dry-run.md | 2 +- docs/reference/compose_alpha_publish.md | 2 +- docs/reference/compose_alpha_scale.md | 4 +- docs/reference/compose_attach.md | 3 +- docs/reference/compose_build.md | 10 +-- docs/reference/compose_config.md | 20 +++--- docs/reference/compose_cp.md | 2 +- docs/reference/compose_create.md | 10 +-- docs/reference/compose_down.md | 14 ++-- docs/reference/compose_events.md | 2 +- docs/reference/compose_exec.md | 12 ++-- docs/reference/compose_images.md | 10 +-- docs/reference/compose_kill.md | 12 ++-- docs/reference/compose_logs.md | 12 ++-- docs/reference/compose_ls.md | 16 ++--- docs/reference/compose_port.md | 6 +- docs/reference/compose_ps.md | 2 +- docs/reference/compose_pull.md | 19 +++--- docs/reference/compose_restart.md | 2 +- docs/reference/compose_run.md | 48 ++++++------- docs/reference/compose_scale.md | 2 +- docs/reference/compose_start.md | 2 +- docs/reference/compose_top.md | 2 +- docs/reference/compose_unpause.md | 2 +- docs/reference/compose_up.md | 54 +++++++-------- docs/reference/compose_version.md | 2 +- docs/reference/docker_compose.yaml | 2 +- .../docker_compose_alpha_publish.yaml | 2 +- docs/reference/docker_compose_attach.yaml | 4 +- docs/reference/docker_compose_build.yaml | 10 +-- docs/reference/docker_compose_config.yaml | 20 +++--- docs/reference/docker_compose_cp.yaml | 4 +- docs/reference/docker_compose_create.yaml | 12 ++-- docs/reference/docker_compose_down.yaml | 4 +- docs/reference/docker_compose_events.yaml | 2 +- docs/reference/docker_compose_exec.yaml | 16 ++--- docs/reference/docker_compose_images.yaml | 2 +- docs/reference/docker_compose_kill.yaml | 6 +- docs/reference/docker_compose_logs.yaml | 12 ++-- docs/reference/docker_compose_ls.yaml | 8 +-- docs/reference/docker_compose_port.yaml | 6 +- docs/reference/docker_compose_ps.yaml | 2 +- docs/reference/docker_compose_pull.yaml | 19 +++--- docs/reference/docker_compose_restart.yaml | 2 +- docs/reference/docker_compose_run.yaml | 24 +++---- docs/reference/docker_compose_scale.yaml | 2 +- docs/reference/docker_compose_start.yaml | 2 +- docs/reference/docker_compose_top.yaml | 2 +- docs/reference/docker_compose_unpause.yaml | 2 +- docs/reference/docker_compose_up.yaml | 30 ++++---- docs/reference/docker_compose_version.yaml | 2 +- pkg/compose/build.go | 2 +- 75 files changed, 360 insertions(+), 363 deletions(-) diff --git a/cmd/compose/attach.go b/cmd/compose/attach.go index 340079cf62..a4504a0aba 100644 --- a/cmd/compose/attach.go +++ b/cmd/compose/attach.go @@ -43,7 +43,7 @@ func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service } runCmd := &cobra.Command{ Use: "attach [OPTIONS] SERVICE", - Short: "Attach local standard input, output, and error streams to a service's running container.", + Short: "Attach local standard input, output, and error streams to a service's running container", Args: cobra.MinimumNArgs(1), PreRunE: Adapt(func(ctx context.Context, args []string) error { opts.service = args[0] diff --git a/cmd/compose/build.go b/cmd/compose/build.go index bb1db159a4..a204c37a00 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -111,13 +111,13 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) ValidArgsFunction: completeServiceNames(dockerCli, p), } flags := cmd.Flags() - flags.BoolVar(&opts.push, "push", false, "Push service images.") + flags.BoolVar(&opts.push, "push", false, "Push service images") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT") - flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.") - flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.") + flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image") + flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services") flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)") - flags.StringVar(&opts.builder, "builder", "", "Set builder to use.") - flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively).") + flags.StringVar(&opts.builder, "builder", "", "Set builder to use") + flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)") flags.Bool("parallel", true, "Build images in parallel. DEPRECATED") flags.MarkHidden("parallel") //nolint:errcheck diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 6d923076b7..c1799cb95d 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -157,7 +157,7 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) { f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable") f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name") f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files") - f.StringArrayVar(&o.EnvFiles, "env-file", nil, "Specify an alternate environment file.") + f.StringArrayVar(&o.EnvFiles, "env-file", nil, "Specify an alternate environment file") f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)") f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)") f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode") @@ -307,7 +307,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // ) c := &cobra.Command{ Short: "Docker Compose", - Long: "Define and run multi-container applications with Docker.", + Long: "Define and run multi-container applications with Docker", Use: PluginName, TraverseChildren: true, // By default (no Run/RunE in parent c) for typos in subcommands, cobra displays the help of parent c but exit(0) ! diff --git a/cmd/compose/config.go b/cmd/compose/config.go index c763037532..481ad776d2 100644 --- a/cmd/compose/config.go +++ b/cmd/compose/config.go @@ -106,18 +106,18 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service } flags := cmd.Flags() flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]") - flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.") - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.") - flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.") - flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.") - flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths.") + flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests") + flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything") + flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables") + flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model") + flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths") flags.BoolVar(&opts.noConsistency, "no-consistency", false, "Don't check model consistency - warning: may produce invalid Compose output") - flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.") - flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.") - flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.") - flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.") - flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.") + flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line") + flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line") + flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line") + flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line") + flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line") flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)") return cmd diff --git a/cmd/compose/cp.go b/cmd/compose/cp.go index ee7c5a9d65..9cd07e5f77 100644 --- a/cmd/compose/cp.go +++ b/cmd/compose/cp.go @@ -65,10 +65,10 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } flags := copyCmd.Flags() - flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas") - flags.BoolVar(&opts.all, "all", false, "copy to all the containers of the service.") - flags.MarkHidden("all") //nolint:errcheck - flags.MarkDeprecated("all", "by default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck + flags.IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas") + flags.BoolVar(&opts.all, "all", false, "Copy to all the containers of the service") + flags.MarkHidden("all") //nolint:errcheck + flags.MarkDeprecated("all", "By default all the containers of the service will get the source file/directory to be copied") //nolint:errcheck flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH") flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)") diff --git a/cmd/compose/create.go b/cmd/compose/create.go index a7731bae59..fbbeed7008 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -55,7 +55,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service } cmd := &cobra.Command{ Use: "create [OPTIONS] [SERVICE...]", - Short: "Creates containers for a service.", + Short: "Creates containers for a service", PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error { opts.pullChanged = cmd.Flags().Changed("pull") if opts.Build && opts.noBuild { @@ -72,12 +72,12 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service ValidArgsFunction: completeServiceNames(dockerCli, p), } flags := cmd.Flags() - flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.") - flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy.") + flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers") + flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy") flags.StringVar(&opts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never"|"build")`) - flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.") + flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed") flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.") - flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") + flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file") flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.") return cmd } diff --git a/cmd/compose/down.go b/cmd/compose/down.go index 4343d7870f..d3080ed2df 100644 --- a/cmd/compose/down.go +++ b/cmd/compose/down.go @@ -63,9 +63,9 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } flags := downCmd.Flags() removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans)) - flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.") + flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file") flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds") - flags.BoolVarP(&opts.volumes, "volumes", "v", false, `Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers.`) + flags.BoolVarP(&opts.volumes, "volumes", "v", false, `Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers`) flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`) flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { if name == "volume" { diff --git a/cmd/compose/events.go b/cmd/compose/events.go index cfd90d03d7..cade77a7fd 100644 --- a/cmd/compose/events.go +++ b/cmd/compose/events.go @@ -40,7 +40,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service } cmd := &cobra.Command{ Use: "events [OPTIONS] [SERVICE...]", - Short: "Receive real time events from containers.", + Short: "Receive real time events from containers", RunE: Adapt(func(ctx context.Context, args []string) error { return runEvents(ctx, dockerCli, backend, opts, args) }), diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go index 1fdd6e7312..aa6774d5d5 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -51,7 +51,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } runCmd := &cobra.Command{ Use: "exec [OPTIONS] SERVICE COMMAND [ARGS...]", - Short: "Execute a command in a running container.", + Short: "Execute a command in a running container", Args: cobra.MinimumNArgs(2), PreRunE: Adapt(func(ctx context.Context, args []string) error { opts.service = args[0] @@ -64,17 +64,17 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) ValidArgsFunction: completeServiceNames(dockerCli, p), } - runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.") + runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background") runCmd.Flags().StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables") - runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas") - runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.") - runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.") + runCmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas") + runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process") + runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user") runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.") - runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.") + runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command") - runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.") + runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached") runCmd.Flags().MarkHidden("interactive") //nolint:errcheck - runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.") + runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY") runCmd.Flags().MarkHidden("tty") //nolint:errcheck runCmd.Flags().SetInterspersed(false) diff --git a/cmd/compose/images.go b/cmd/compose/images.go index fd120544ee..8c68a34af3 100644 --- a/cmd/compose/images.go +++ b/cmd/compose/images.go @@ -51,7 +51,7 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service }), ValidArgsFunction: completeServiceNames(dockerCli, p), } - imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json].") + imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json]") imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs") return imgCmd } diff --git a/cmd/compose/kill.go b/cmd/compose/kill.go index 358eaec7a0..c0faa75c60 100644 --- a/cmd/compose/kill.go +++ b/cmd/compose/kill.go @@ -39,7 +39,7 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } cmd := &cobra.Command{ Use: "kill [OPTIONS] [SERVICE...]", - Short: "Force stop service containers.", + Short: "Force stop service containers", RunE: Adapt(func(ctx context.Context, args []string) error { return runKill(ctx, dockerCli, backend, opts, args) }), @@ -48,8 +48,8 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) flags := cmd.Flags() removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans)) - flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.") - flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.") + flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file") + flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container") return cmd } diff --git a/cmd/compose/list.go b/cmd/compose/list.go index e54bddb88e..bb1faeb12c 100644 --- a/cmd/compose/list.go +++ b/cmd/compose/list.go @@ -49,9 +49,9 @@ func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { Args: cobra.NoArgs, ValidArgsFunction: noCompletion(), } - lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json].") - lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs.") - lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided.") + lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json]") + lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs") + lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided") lsCmd.Flags().BoolVarP(&lsOpts.All, "all", "a", false, "Show all stopped Compose projects") return lsCmd diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go index 62a7224c1c..a6ce0a8d40 100644 --- a/cmd/compose/logs.go +++ b/cmd/compose/logs.go @@ -59,14 +59,14 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) ValidArgsFunction: completeServiceNames(dockerCli, p), } flags := logsCmd.Flags() - flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.") + flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output") flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas") flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)") flags.StringVar(&opts.until, "until", "", "Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)") - flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output.") - flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.") - flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps.") - flags.StringVarP(&opts.tail, "tail", "n", "all", "Number of lines to show from the end of the logs for each container.") + flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output") + flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs") + flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps") + flags.StringVarP(&opts.tail, "tail", "n", "all", "Number of lines to show from the end of the logs for each container") return logsCmd } diff --git a/cmd/compose/port.go b/cmd/compose/port.go index d63b03ec4d..59ea8ef1ce 100644 --- a/cmd/compose/port.go +++ b/cmd/compose/port.go @@ -41,7 +41,7 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) } cmd := &cobra.Command{ Use: "port [OPTIONS] SERVICE PRIVATE_PORT", - Short: "Print the public port for a port binding.", + Short: "Print the public port for a port binding", Args: cobra.MinimumNArgs(2), PreRunE: Adapt(func(ctx context.Context, args []string) error { port, err := strconv.ParseUint(args[1], 10, 16) @@ -58,7 +58,7 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) ValidArgsFunction: completeServiceNames(dockerCli, p), } cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp") - cmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas") + cmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas") return cmd } diff --git a/cmd/compose/ps.go b/cmd/compose/ps.go index 58e381a397..b1338837a7 100644 --- a/cmd/compose/ps.go +++ b/cmd/compose/ps.go @@ -81,7 +81,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c } flags := psCmd.Flags() flags.StringVar(&opts.Format, "format", "table", cliflags.FormatHelp) - flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).") + flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status)") flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]") flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs") flags.BoolVar(&opts.Services, "services", false, "Display services") diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go index 48b0e2d587..42b0140b58 100644 --- a/cmd/compose/publish.go +++ b/cmd/compose/publish.go @@ -44,7 +44,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic Args: cobra.ExactArgs(1), } flags := cmd.Flags() - flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.") + flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests") flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI Image/Artifact specification version (automatically determined by default)") return cmd } diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index 3d3ef54423..f003fc19e9 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -60,15 +60,15 @@ func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) ValidArgsFunction: completeServiceNames(dockerCli, p), } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information.") - cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies.") - cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel.") + flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information") + cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies") + cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel") flags.MarkHidden("parallel") //nolint:errcheck - cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling.") + cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling") flags.MarkHidden("no-parallel") //nolint:errcheck - cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures.") - cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built.") - cmd.Flags().StringVar(&opts.policy, "policy", "", `Apply pull policy ("missing"|"always").`) + cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures") + cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built") + cmd.Flags().StringVar(&opts.policy, "policy", "", `Apply pull policy ("missing"|"always")`) return cmd } diff --git a/cmd/compose/restart.go b/cmd/compose/restart.go index aeb44c86c9..718e15196f 100644 --- a/cmd/compose/restart.go +++ b/cmd/compose/restart.go @@ -50,7 +50,7 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic } flags := restartCmd.Flags() flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds") - flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services.") + flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services") return restartCmd } diff --git a/cmd/compose/run.go b/cmd/compose/run.go index 29fcea254c..1803268523 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -129,7 +129,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * } cmd := &cobra.Command{ Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]", - Short: "Run a one-off command on a service.", + Short: "Run a one-off command on a service", Args: cobra.MinimumNArgs(1), PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error { options.Service = args[0] @@ -175,24 +175,24 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables") flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label") flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits") - flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).") + flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected)") flags.StringVar(&options.name, "name", "", "Assign a name to the container") flags.StringVarP(&options.user, "user", "u", "", "Run as specified username or uid") flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container") flags.StringVar(&options.entrypoint, "entrypoint", "", "Override the entrypoint of the image") flags.Var(&options.capAdd, "cap-add", "Add Linux capabilities") flags.Var(&options.capDrop, "cap-drop", "Drop Linux capabilities") - flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services.") - flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume.") - flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.") - flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.") - flags.BoolVarP(&options.servicePorts, "service-ports", "P", false, "Run command with all service's ports enabled and mapped to the host.") - flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information.") - flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container.") - flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") - - cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.") - cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY.") + flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services") + flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume") + flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host") + flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to") + flags.BoolVarP(&options.servicePorts, "service-ports", "P", false, "Run command with all service's ports enabled and mapped to the host") + flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information") + flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container") + flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file") + + cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached") + cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY") cmd.Flags().MarkHidden("tty") //nolint:errcheck flags.SetNormalizeFunc(normalizeRunFlags) diff --git a/cmd/compose/scale.go b/cmd/compose/scale.go index 0ec4799a9c..22e8e71dd4 100644 --- a/cmd/compose/scale.go +++ b/cmd/compose/scale.go @@ -54,7 +54,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) ValidArgsFunction: completeServiceNames(dockerCli, p), } flags := scaleCmd.Flags() - flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.") + flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services") return scaleCmd } diff --git a/cmd/compose/up.go b/cmd/compose/up.go index c4919ca80e..a85acf2731 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -101,29 +101,29 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c } flags := upCmd.Flags() flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background") - flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.") - flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy.") + flags.BoolVar(&create.Build, "build", false, "Build images before starting containers") + flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy") flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`) - flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") + flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file") flags.StringArrayVar(&create.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.") - flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.") - flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.") - flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.") + flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output") + flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs") + flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed") flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.") - flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them.") + flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them") flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d") flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit") - flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running.") - flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps.") - flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services.") + flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running") + flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps") + flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services") flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.") - flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.") - flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.") + flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers") + flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information") flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.") - flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services.") - flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services.") + flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services") + flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services") flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.") - flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy.") + flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy") return upCmd } diff --git a/cmd/compose/version.go b/cmd/compose/version.go index 8ca1ed57b0..e044043478 100644 --- a/cmd/compose/version.go +++ b/cmd/compose/version.go @@ -52,7 +52,7 @@ func versionCommand(dockerCli command.Cli) *cobra.Command { // define flags for backward compatibility with com.docker.cli flags := cmd.Flags() flags.StringVarP(&opts.format, "format", "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)") - flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number.") + flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number") return cmd } diff --git a/docs/reference/compose.md b/docs/reference/compose.md index 3728a76d48..ce6c214d0d 100644 --- a/docs/reference/compose.md +++ b/docs/reference/compose.md @@ -1,42 +1,42 @@ # docker compose -Define and run multi-container applications with Docker. +Define and run multi-container applications with Docker ### Subcommands -| Name | Description | -|:--------------------------------|:-----------------------------------------------------------------------------------------| -| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container. | -| [`build`](compose_build.md) | Build or rebuild services | -| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format | -| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem | -| [`create`](compose_create.md) | Creates containers for a service. | -| [`down`](compose_down.md) | Stop and remove containers, networks | -| [`events`](compose_events.md) | Receive real time events from containers. | -| [`exec`](compose_exec.md) | Execute a command in a running container. | -| [`images`](compose_images.md) | List images used by the created containers | -| [`kill`](compose_kill.md) | Force stop service containers. | -| [`logs`](compose_logs.md) | View output from containers | -| [`ls`](compose_ls.md) | List running compose projects | -| [`pause`](compose_pause.md) | Pause services | -| [`port`](compose_port.md) | Print the public port for a port binding. | -| [`ps`](compose_ps.md) | List containers | -| [`pull`](compose_pull.md) | Pull service images | -| [`push`](compose_push.md) | Push service images | -| [`restart`](compose_restart.md) | Restart service containers | -| [`rm`](compose_rm.md) | Removes stopped service containers | -| [`run`](compose_run.md) | Run a one-off command on a service. | -| [`scale`](compose_scale.md) | Scale services | -| [`start`](compose_start.md) | Start services | -| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics | -| [`stop`](compose_stop.md) | Stop services | -| [`top`](compose_top.md) | Display the running processes | -| [`unpause`](compose_unpause.md) | Unpause services | -| [`up`](compose_up.md) | Create and start containers | -| [`version`](compose_version.md) | Show the Docker Compose version information | -| [`wait`](compose_wait.md) | Block until the first service container stops | -| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated | +| Name | Description | +|:--------------------------------|:----------------------------------------------------------------------------------------| +| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container | +| [`build`](compose_build.md) | Build or rebuild services | +| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format | +| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem | +| [`create`](compose_create.md) | Creates containers for a service | +| [`down`](compose_down.md) | Stop and remove containers, networks | +| [`events`](compose_events.md) | Receive real time events from containers | +| [`exec`](compose_exec.md) | Execute a command in a running container | +| [`images`](compose_images.md) | List images used by the created containers | +| [`kill`](compose_kill.md) | Force stop service containers | +| [`logs`](compose_logs.md) | View output from containers | +| [`ls`](compose_ls.md) | List running compose projects | +| [`pause`](compose_pause.md) | Pause services | +| [`port`](compose_port.md) | Print the public port for a port binding | +| [`ps`](compose_ps.md) | List containers | +| [`pull`](compose_pull.md) | Pull service images | +| [`push`](compose_push.md) | Push service images | +| [`restart`](compose_restart.md) | Restart service containers | +| [`rm`](compose_rm.md) | Removes stopped service containers | +| [`run`](compose_run.md) | Run a one-off command on a service | +| [`scale`](compose_scale.md) | Scale services | +| [`start`](compose_start.md) | Start services | +| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics | +| [`stop`](compose_stop.md) | Stop services | +| [`top`](compose_top.md) | Display the running processes | +| [`unpause`](compose_unpause.md) | Unpause services | +| [`up`](compose_up.md) | Create and start containers | +| [`version`](compose_version.md) | Show the Docker Compose version information | +| [`wait`](compose_wait.md) | Block until the first service container stops | +| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated | ### Options @@ -46,7 +46,7 @@ Define and run multi-container applications with Docker. | `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") | | `--compatibility` | | | Run compose in backward compatibility mode | | `--dry-run` | | | Execute command in dry run mode | -| `--env-file` | `stringArray` | | Specify an alternate environment file. | +| `--env-file` | `stringArray` | | Specify an alternate environment file | | `-f`, `--file` | `stringArray` | | Compose configuration files | | `--parallel` | `int` | `-1` | Control max parallelism, -1 for unlimited | | `--profile` | `stringArray` | | Specify a profile to enable | diff --git a/docs/reference/compose_alpha_dry-run.md b/docs/reference/compose_alpha_dry-run.md index 9e8350e2a0..7c68d94d66 100644 --- a/docs/reference/compose_alpha_dry-run.md +++ b/docs/reference/compose_alpha_dry-run.md @@ -1,7 +1,7 @@ # docker compose alpha dry-run -Dry run command allows you to test a command without applying changes. +Dry run command allows you to test a command without applying changes diff --git a/docs/reference/compose_alpha_publish.md b/docs/reference/compose_alpha_publish.md index 8424d7fbce..02516d968b 100644 --- a/docs/reference/compose_alpha_publish.md +++ b/docs/reference/compose_alpha_publish.md @@ -9,7 +9,7 @@ Publish compose application |:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------| | `--dry-run` | | | Execute command in dry run mode | | `--oci-version` | `string` | | OCI Image/Artifact specification version (automatically determined by default) | -| `--resolve-image-digests` | | | Pin image tags to digests. | +| `--resolve-image-digests` | | | Pin image tags to digests | diff --git a/docs/reference/compose_alpha_scale.md b/docs/reference/compose_alpha_scale.md index 15536b359c..f783f3335c 100644 --- a/docs/reference/compose_alpha_scale.md +++ b/docs/reference/compose_alpha_scale.md @@ -1,14 +1,14 @@ # docker compose alpha scale -Scale services. +Scale services ### Options | Name | Type | Default | Description | |:------------|:-----|:--------|:--------------------------------| | `--dry-run` | | | Execute command in dry run mode | -| `--no-deps` | | | Don't start linked services | +| `--no-deps` | | | Don't start linked services | diff --git a/docs/reference/compose_attach.md b/docs/reference/compose_attach.md index 405fed98ad..5a03388192 100644 --- a/docs/reference/compose_attach.md +++ b/docs/reference/compose_attach.md @@ -1,7 +1,7 @@ # docker compose attach -Attach local standard input, output, and error streams to a service's running container. +Attach local standard input, output, and error streams to a service's running container ### Options @@ -15,4 +15,3 @@ Attach local standard input, output, and error streams to a service's running co - diff --git a/docs/reference/compose_build.md b/docs/reference/compose_build.md index 46bcedbbe7..a0ceb050c0 100644 --- a/docs/reference/compose_build.md +++ b/docs/reference/compose_build.md @@ -7,16 +7,16 @@ Build or rebuild services | Name | Type | Default | Description | |:----------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------| -| `--build-arg` | `stringArray` | | Set build-time variables for services. | -| `--builder` | `string` | | Set builder to use. | +| `--build-arg` | `stringArray` | | Set build-time variables for services | +| `--builder` | `string` | | Set builder to use | | `--dry-run` | | | Execute command in dry run mode | | `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. | | `--no-cache` | | | Do not use cache when building the image | -| `--pull` | | | Always attempt to pull a newer version of the image. | -| `--push` | | | Push service images. | +| `--pull` | | | Always attempt to pull a newer version of the image | +| `--push` | | | Push service images | | `-q`, `--quiet` | | | Don't print anything to STDOUT | | `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) | -| `--with-dependencies` | | | Also build dependencies (transitively). | +| `--with-dependencies` | | | Also build dependencies (transitively) | diff --git a/docs/reference/compose_config.md b/docs/reference/compose_config.md index 2639cd735e..fd213b4c38 100644 --- a/docs/reference/compose_config.md +++ b/docs/reference/compose_config.md @@ -13,18 +13,18 @@ Parse, resolve and render compose file in canonical format |:--------------------------|:---------|:--------|:----------------------------------------------------------------------------| | `--dry-run` | | | Execute command in dry run mode | | `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] | -| `--hash` | `string` | | Print the service config hash, one per line. | -| `--images` | | | Print the image names, one per line. | +| `--hash` | `string` | | Print the service config hash, one per line | +| `--images` | | | Print the image names, one per line | | `--no-consistency` | | | Don't check model consistency - warning: may produce invalid Compose output | -| `--no-interpolate` | | | Don't interpolate environment variables. | -| `--no-normalize` | | | Don't normalize compose model. | -| `--no-path-resolution` | | | Don't resolve file paths. | +| `--no-interpolate` | | | Don't interpolate environment variables | +| `--no-normalize` | | | Don't normalize compose model | +| `--no-path-resolution` | | | Don't resolve file paths | | `-o`, `--output` | `string` | | Save to file (default to stdout) | -| `--profiles` | | | Print the profile names, one per line. | -| `-q`, `--quiet` | | | Only validate the configuration, don't print anything. | -| `--resolve-image-digests` | | | Pin image tags to digests. | -| `--services` | | | Print the service names, one per line. | -| `--volumes` | | | Print the volume names, one per line. | +| `--profiles` | | | Print the profile names, one per line | +| `-q`, `--quiet` | | | Only validate the configuration, don't print anything | +| `--resolve-image-digests` | | | Pin image tags to digests | +| `--services` | | | Print the service names, one per line | +| `--volumes` | | | Print the volume names, one per line | diff --git a/docs/reference/compose_cp.md b/docs/reference/compose_cp.md index 9be79443af..a60388e306 100644 --- a/docs/reference/compose_cp.md +++ b/docs/reference/compose_cp.md @@ -10,7 +10,7 @@ Copy files/folders between a service container and the local filesystem | `-a`, `--archive` | | | Archive mode (copy all uid/gid information) | | `--dry-run` | | | Execute command in dry run mode | | `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH | -| `--index` | `int` | `0` | index of the container if service has multiple replicas | +| `--index` | `int` | `0` | Index of the container if service has multiple replicas | diff --git a/docs/reference/compose_create.md b/docs/reference/compose_create.md index 149ed6fe33..386d6359ac 100644 --- a/docs/reference/compose_create.md +++ b/docs/reference/compose_create.md @@ -1,19 +1,19 @@ # docker compose create -Creates containers for a service. +Creates containers for a service ### Options | Name | Type | Default | Description | |:-------------------|:--------------|:---------|:----------------------------------------------------------------------------------------------| -| `--build` | | | Build images before starting containers. | +| `--build` | | | Build images before starting containers | | `--dry-run` | | | Execute command in dry run mode | -| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. | -| `--no-build` | | | Don't build an image, even if it's policy. | +| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed | +| `--no-build` | | | Don't build an image, even if it's policy | | `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. | | `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never"\|"build") | -| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. | +| `--remove-orphans` | | | Remove containers for services not defined in the Compose file | | `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. | diff --git a/docs/reference/compose_down.md b/docs/reference/compose_down.md index 4012b70ca4..2d7cf57271 100644 --- a/docs/reference/compose_down.md +++ b/docs/reference/compose_down.md @@ -5,13 +5,13 @@ Stop and remove containers, networks ### Options -| Name | Type | Default | Description | -|:-------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------| -| `--dry-run` | | | Execute command in dry run mode | -| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. | -| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") | -| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds | -| `-v`, `--volumes` | | | Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers. | +| Name | Type | Default | Description | +|:-------------------|:---------|:--------|:------------------------------------------------------------------------------------------------------------------------| +| `--dry-run` | | | Execute command in dry run mode | +| `--remove-orphans` | | | Remove containers for services not defined in the Compose file | +| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") | +| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds | +| `-v`, `--volumes` | | | Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers | diff --git a/docs/reference/compose_events.md b/docs/reference/compose_events.md index 86c492e843..e1db499484 100644 --- a/docs/reference/compose_events.md +++ b/docs/reference/compose_events.md @@ -1,7 +1,7 @@ # docker compose events -Receive real time events from containers. +Receive real time events from containers ### Options diff --git a/docs/reference/compose_exec.md b/docs/reference/compose_exec.md index 7c5f7d6a4f..fab8221782 100644 --- a/docs/reference/compose_exec.md +++ b/docs/reference/compose_exec.md @@ -1,20 +1,20 @@ # docker compose exec -Execute a command in a running container. +Execute a command in a running container ### Options | Name | Type | Default | Description | |:------------------|:--------------|:--------|:---------------------------------------------------------------------------------| -| `-d`, `--detach` | | | Detached mode: Run command in the background. | +| `-d`, `--detach` | | | Detached mode: Run command in the background | | `--dry-run` | | | Execute command in dry run mode | | `-e`, `--env` | `stringArray` | | Set environment variables | -| `--index` | `int` | `0` | index of the container if service has multiple replicas | +| `--index` | `int` | `0` | Index of the container if service has multiple replicas | | `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. | -| `--privileged` | | | Give extended privileges to the process. | -| `-u`, `--user` | `string` | | Run the command as this user. | -| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. | +| `--privileged` | | | Give extended privileges to the process | +| `-u`, `--user` | `string` | | Run the command as this user | +| `-w`, `--workdir` | `string` | | Path to workdir directory for this command | diff --git a/docs/reference/compose_images.md b/docs/reference/compose_images.md index 02a8f57ec4..a29af42f34 100644 --- a/docs/reference/compose_images.md +++ b/docs/reference/compose_images.md @@ -5,11 +5,11 @@ List images used by the created containers ### Options -| Name | Type | Default | Description | -|:----------------|:---------|:--------|:--------------------------------------------| -| `--dry-run` | | | Execute command in dry run mode | -| `--format` | `string` | `table` | Format the output. Values: [table \| json]. | -| `-q`, `--quiet` | | | Only display IDs | +| Name | Type | Default | Description | +|:----------------|:---------|:--------|:-------------------------------------------| +| `--dry-run` | | | Execute command in dry run mode | +| `--format` | `string` | `table` | Format the output. Values: [table \| json] | +| `-q`, `--quiet` | | | Only display IDs | diff --git a/docs/reference/compose_kill.md b/docs/reference/compose_kill.md index 2e79d806e9..a10ce55bef 100644 --- a/docs/reference/compose_kill.md +++ b/docs/reference/compose_kill.md @@ -1,15 +1,15 @@ # docker compose kill -Force stop service containers. +Force stop service containers ### Options -| Name | Type | Default | Description | -|:-------------------|:---------|:----------|:----------------------------------------------------------------| -| `--dry-run` | | | Execute command in dry run mode | -| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. | -| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. | +| Name | Type | Default | Description | +|:-------------------|:---------|:----------|:---------------------------------------------------------------| +| `--dry-run` | | | Execute command in dry run mode | +| `--remove-orphans` | | | Remove containers for services not defined in the Compose file | +| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container | diff --git a/docs/reference/compose_logs.md b/docs/reference/compose_logs.md index b6c705bab2..15291f71d9 100644 --- a/docs/reference/compose_logs.md +++ b/docs/reference/compose_logs.md @@ -8,13 +8,13 @@ View output from containers | Name | Type | Default | Description | |:---------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------| | `--dry-run` | | | Execute command in dry run mode | -| `-f`, `--follow` | | | Follow log output. | +| `-f`, `--follow` | | | Follow log output | | `--index` | `int` | `0` | index of the container if service has multiple replicas | -| `--no-color` | | | Produce monochrome output. | -| `--no-log-prefix` | | | Don't print prefix in logs. | +| `--no-color` | | | Produce monochrome output | +| `--no-log-prefix` | | | Don't print prefix in logs | | `--since` | `string` | | Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) | -| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container. | -| `-t`, `--timestamps` | | | Show timestamps. | +| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container | +| `-t`, `--timestamps` | | | Show timestamps | | `--until` | `string` | | Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) | @@ -22,4 +22,4 @@ View output from containers ## Description -Displays log output from services. \ No newline at end of file +Displays log output from services diff --git a/docs/reference/compose_ls.md b/docs/reference/compose_ls.md index 50a13d96f0..a1148a1675 100644 --- a/docs/reference/compose_ls.md +++ b/docs/reference/compose_ls.md @@ -5,17 +5,17 @@ List running compose projects ### Options -| Name | Type | Default | Description | -|:----------------|:---------|:--------|:--------------------------------------------| -| `-a`, `--all` | | | Show all stopped Compose projects | -| `--dry-run` | | | Execute command in dry run mode | -| `--filter` | `filter` | | Filter output based on conditions provided. | -| `--format` | `string` | `table` | Format the output. Values: [table \| json]. | -| `-q`, `--quiet` | | | Only display IDs. | +| Name | Type | Default | Description | +|:----------------|:---------|:--------|:-------------------------------------------| +| `-a`, `--all` | | | Show all stopped Compose projects | +| `--dry-run` | | | Execute command in dry run mode | +| `--filter` | `filter` | | Filter output based on conditions provided | +| `--format` | `string` | `table` | Format the output. Values: [table \| json] | +| `-q`, `--quiet` | | | Only display IDs | ## Description -Lists running Compose projects. \ No newline at end of file +Lists running Compose projects diff --git a/docs/reference/compose_port.md b/docs/reference/compose_port.md index ffd3d8eb53..5e70b35329 100644 --- a/docs/reference/compose_port.md +++ b/docs/reference/compose_port.md @@ -1,14 +1,14 @@ # docker compose port -Print the public port for a port binding. +Print the public port for a port binding ### Options | Name | Type | Default | Description | |:-------------|:---------|:--------|:--------------------------------------------------------| | `--dry-run` | | | Execute command in dry run mode | -| `--index` | `int` | `0` | index of the container if service has multiple replicas | +| `--index` | `int` | `0` | Index of the container if service has multiple replicas | | `--protocol` | `string` | `tcp` | tcp or udp | @@ -16,4 +16,4 @@ Print the public port for a port binding. ## Description -Prints the public port for a port binding. \ No newline at end of file +Prints the public port for a port binding diff --git a/docs/reference/compose_ps.md b/docs/reference/compose_ps.md index 81ef255098..f0c1a25762 100644 --- a/docs/reference/compose_ps.md +++ b/docs/reference/compose_ps.md @@ -9,7 +9,7 @@ List containers |:----------------------|:--------------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-a`, `--all` | | | Show all stopped containers (including those created by the run command) | | `--dry-run` | | | Execute command in dry run mode | -| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). | +| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status) | | [`--format`](#format) | `string` | `table` | Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates | | `--no-trunc` | | | Don't truncate output | | `--orphans` | | | Include orphaned services (not declared by project) | diff --git a/docs/reference/compose_pull.md b/docs/reference/compose_pull.md index 03be4de05e..2c29052fd8 100644 --- a/docs/reference/compose_pull.md +++ b/docs/reference/compose_pull.md @@ -5,22 +5,21 @@ Pull service images ### Options -| Name | Type | Default | Description | -|:-------------------------|:---------|:--------|:--------------------------------------------------------| -| `--dry-run` | | | Execute command in dry run mode | -| `--ignore-buildable` | | | Ignore images that can be built. | -| `--ignore-pull-failures` | | | Pull what it can and ignores images with pull failures. | -| `--include-deps` | | | Also pull services declared as dependencies. | -| `--policy` | `string` | | Apply pull policy ("missing"\|"always"). | -| `-q`, `--quiet` | | | Pull without printing progress information. | +| Name | Type | Default | Description | +|:-------------------------|:---------|:--------|:-------------------------------------------------------| +| `--dry-run` | | | Execute command in dry run mode | +| `--ignore-buildable` | | | Ignore images that can be built | +| `--ignore-pull-failures` | | | Pull what it can and ignores images with pull failures | +| `--include-deps` | | | Also pull services declared as dependencies | +| `--policy` | `string` | | Apply pull policy ("missing"\|"always") | +| `-q`, `--quiet` | | | Pull without printing progress information | ## Description -Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on -those images. +Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images ## Examples diff --git a/docs/reference/compose_restart.md b/docs/reference/compose_restart.md index 1e7bdf3c8c..d77b461a85 100644 --- a/docs/reference/compose_restart.md +++ b/docs/reference/compose_restart.md @@ -8,7 +8,7 @@ Restart service containers | Name | Type | Default | Description | |:------------------|:------|:--------|:--------------------------------------| | `--dry-run` | | | Execute command in dry run mode | -| `--no-deps` | | | Don't restart dependent services. | +| `--no-deps` | | | Don't restart dependent services | | `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds | diff --git a/docs/reference/compose_run.md b/docs/reference/compose_run.md index 033ac5e78d..185b4ad073 100644 --- a/docs/reference/compose_run.md +++ b/docs/reference/compose_run.md @@ -1,33 +1,33 @@ # docker compose run -Run a one-off command on a service. +Run a one-off command on a service ### Options -| Name | Type | Default | Description | -|:------------------------|:--------------|:--------|:----------------------------------------------------------------------------------| -| `--build` | | | Build image before starting container. | -| `--cap-add` | `list` | | Add Linux capabilities | -| `--cap-drop` | `list` | | Drop Linux capabilities | -| `-d`, `--detach` | | | Run container in background and print container ID | -| `--dry-run` | | | Execute command in dry run mode | -| `--entrypoint` | `string` | | Override the entrypoint of the image | -| `-e`, `--env` | `stringArray` | | Set environment variables | -| `-i`, `--interactive` | | | Keep STDIN open even if not attached. | -| `-l`, `--label` | `stringArray` | | Add or override a label | -| `--name` | `string` | | Assign a name to the container | -| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected). | -| `--no-deps` | | | Don't start linked services. | -| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. | -| `--quiet-pull` | | | Pull without printing progress information. | -| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. | -| `--rm` | | | Automatically remove the container when it exits | -| `-P`, `--service-ports` | | | Run command with all service's ports enabled and mapped to the host. | -| `--use-aliases` | | | Use the service's network useAliases in the network(s) the container connects to. | -| `-u`, `--user` | `string` | | Run as specified username or uid | -| `-v`, `--volume` | `stringArray` | | Bind mount a volume. | -| `-w`, `--workdir` | `string` | | Working directory inside the container | +| Name | Type | Default | Description | +|:------------------------|:--------------|:--------|:---------------------------------------------------------------------------------| +| `--build` | | | Build image before starting container | +| `--cap-add` | `list` | | Add Linux capabilities | +| `--cap-drop` | `list` | | Drop Linux capabilities | +| `-d`, `--detach` | | | Run container in background and print container ID | +| `--dry-run` | | | Execute command in dry run mode | +| `--entrypoint` | `string` | | Override the entrypoint of the image | +| `-e`, `--env` | `stringArray` | | Set environment variables | +| `-i`, `--interactive` | | | Keep STDIN open even if not attached | +| `-l`, `--label` | `stringArray` | | Add or override a label | +| `--name` | `string` | | Assign a name to the container | +| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected) | +| `--no-deps` | | | Don't start linked services | +| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host | +| `--quiet-pull` | | | Pull without printing progress information | +| `--remove-orphans` | | | Remove containers for services not defined in the Compose file | +| `--rm` | | | Automatically remove the container when it exits | +| `-P`, `--service-ports` | | | Run command with all service's ports enabled and mapped to the host | +| `--use-aliases` | | | Use the service's network useAliases in the network(s) the container connects to | +| `-u`, `--user` | `string` | | Run as specified username or uid | +| `-v`, `--volume` | `stringArray` | | Bind mount a volume | +| `-w`, `--workdir` | `string` | | Working directory inside the container | diff --git a/docs/reference/compose_scale.md b/docs/reference/compose_scale.md index 5cf5830e28..e30508328a 100644 --- a/docs/reference/compose_scale.md +++ b/docs/reference/compose_scale.md @@ -8,7 +8,7 @@ Scale services | Name | Type | Default | Description | |:------------|:-----|:--------|:--------------------------------| | `--dry-run` | | | Execute command in dry run mode | -| `--no-deps` | | | Don't start linked services. | +| `--no-deps` | | | Don't start linked services | diff --git a/docs/reference/compose_start.md b/docs/reference/compose_start.md index b525347f7e..4ea26e9b59 100644 --- a/docs/reference/compose_start.md +++ b/docs/reference/compose_start.md @@ -14,4 +14,4 @@ Start services ## Description -Starts existing containers for a service. +Starts existing containers for a service diff --git a/docs/reference/compose_top.md b/docs/reference/compose_top.md index 9aff0ce71f..a23373832a 100644 --- a/docs/reference/compose_top.md +++ b/docs/reference/compose_top.md @@ -14,7 +14,7 @@ Display the running processes ## Description -Displays the running processes. +Displays the running processes ## Examples diff --git a/docs/reference/compose_unpause.md b/docs/reference/compose_unpause.md index 0df10a9924..9d810e4710 100644 --- a/docs/reference/compose_unpause.md +++ b/docs/reference/compose_unpause.md @@ -14,4 +14,4 @@ Unpause services ## Description -Unpauses paused containers of a service. +Unpauses paused containers of a service diff --git a/docs/reference/compose_up.md b/docs/reference/compose_up.md index 49fc17225a..38d678d00f 100644 --- a/docs/reference/compose_up.md +++ b/docs/reference/compose_up.md @@ -5,33 +5,33 @@ Create and start containers ### Options -| Name | Type | Default | Description | -|:-----------------------------|:--------------|:---------|:---------------------------------------------------------------------------------------------------------| -| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d | -| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. | -| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. | -| `--attach-dependencies` | | | Automatically attach to log output of dependent services. | -| `--build` | | | Build images before starting containers. | -| `-d`, `--detach` | | | Detached mode: Run containers in the background | -| `--dry-run` | | | Execute command in dry run mode | -| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit | -| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. | -| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services. | -| `--no-build` | | | Don't build an image, even if it's policy. | -| `--no-color` | | | Produce monochrome output. | -| `--no-deps` | | | Don't start linked services. | -| `--no-log-prefix` | | | Don't print prefix in logs. | -| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. | -| `--no-start` | | | Don't start the services after creating them. | -| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") | -| `--quiet-pull` | | | Pull without printing progress information. | -| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. | -| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers. | -| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. | -| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running. | -| `--timestamps` | | | Show timestamps. | -| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. | -| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy. | +| Name | Type | Default | Description | +|:-----------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------------| +| `--abort-on-container-exit` | | | Stops all containers if any container was stopped. Incompatible with -d | +| `--always-recreate-deps` | | | Recreate dependent containers. Incompatible with --no-recreate. | +| `--attach` | `stringArray` | | Restrict attaching to the specified services. Incompatible with --attach-dependencies. | +| `--attach-dependencies` | | | Automatically attach to log output of dependent services | +| `--build` | | | Build images before starting containers | +| `-d`, `--detach` | | | Detached mode: Run containers in the background | +| `--dry-run` | | | Execute command in dry run mode | +| `--exit-code-from` | `string` | | Return the exit code of the selected service container. Implies --abort-on-container-exit | +| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed | +| `--no-attach` | `stringArray` | | Do not attach (stream logs) to the specified services | +| `--no-build` | | | Don't build an image, even if it's policy | +| `--no-color` | | | Produce monochrome output | +| `--no-deps` | | | Don't start linked services | +| `--no-log-prefix` | | | Don't print prefix in logs | +| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. | +| `--no-start` | | | Don't start the services after creating them | +| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") | +| `--quiet-pull` | | | Pull without printing progress information | +| `--remove-orphans` | | | Remove containers for services not defined in the Compose file | +| `-V`, `--renew-anon-volumes` | | | Recreate anonymous volumes instead of retrieving data from the previous containers | +| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. | +| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running | +| `--timestamps` | | | Show timestamps | +| `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. | +| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy | diff --git a/docs/reference/compose_version.md b/docs/reference/compose_version.md index 66081d65ec..9284d8e935 100644 --- a/docs/reference/compose_version.md +++ b/docs/reference/compose_version.md @@ -9,7 +9,7 @@ Show the Docker Compose version information |:-----------------|:---------|:--------|:---------------------------------------------------------------| | `--dry-run` | | | Execute command in dry run mode | | `-f`, `--format` | `string` | | Format the output. Values: [pretty \| json]. (Default: pretty) | -| `--short` | | | Shows only Compose's version number. | +| `--short` | | | Shows only Compose's version number | diff --git a/docs/reference/docker_compose.yaml b/docs/reference/docker_compose.yaml index 83f555b6c8..acdd391261 100644 --- a/docs/reference/docker_compose.yaml +++ b/docs/reference/docker_compose.yaml @@ -242,7 +242,7 @@ options: - option: env-file value_type: stringArray default_value: '[]' - description: Specify an alternate environment file. + description: Specify an alternate environment file deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_alpha_publish.yaml b/docs/reference/docker_compose_alpha_publish.yaml index f2237f2ae4..38868104a5 100644 --- a/docs/reference/docker_compose_alpha_publish.yaml +++ b/docs/reference/docker_compose_alpha_publish.yaml @@ -18,7 +18,7 @@ options: - option: resolve-image-digests value_type: bool default_value: "false" - description: Pin image tags to digests. + description: Pin image tags to digests deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_attach.yaml b/docs/reference/docker_compose_attach.yaml index 14c584f411..8fd6957ca1 100644 --- a/docs/reference/docker_compose_attach.yaml +++ b/docs/reference/docker_compose_attach.yaml @@ -1,8 +1,8 @@ command: docker compose attach short: | - Attach local standard input, output, and error streams to a service's running container. + Attach local standard input, output, and error streams to a service's running container long: | - Attach local standard input, output, and error streams to a service's running container. + Attach local standard input, output, and error streams to a service's running container usage: docker compose attach [OPTIONS] SERVICE pname: docker compose plink: docker_compose.yaml diff --git a/docs/reference/docker_compose_build.yaml b/docs/reference/docker_compose_build.yaml index d6085d0d15..34175696fb 100644 --- a/docs/reference/docker_compose_build.yaml +++ b/docs/reference/docker_compose_build.yaml @@ -17,7 +17,7 @@ options: - option: build-arg value_type: stringArray default_value: '[]' - description: Set build-time variables for services. + description: Set build-time variables for services deprecated: false hidden: false experimental: false @@ -26,7 +26,7 @@ options: swarm: false - option: builder value_type: string - description: Set builder to use. + description: Set builder to use deprecated: false hidden: false experimental: false @@ -109,7 +109,7 @@ options: - option: pull value_type: bool default_value: "false" - description: Always attempt to pull a newer version of the image. + description: Always attempt to pull a newer version of the image deprecated: false hidden: false experimental: false @@ -119,7 +119,7 @@ options: - option: push value_type: bool default_value: "false" - description: Push service images. + description: Push service images deprecated: false hidden: false experimental: false @@ -150,7 +150,7 @@ options: - option: with-dependencies value_type: bool default_value: "false" - description: Also build dependencies (transitively). + description: Also build dependencies (transitively) deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_config.yaml b/docs/reference/docker_compose_config.yaml index d180c8d143..ea7669b0d9 100644 --- a/docs/reference/docker_compose_config.yaml +++ b/docs/reference/docker_compose_config.yaml @@ -21,7 +21,7 @@ options: swarm: false - option: hash value_type: string - description: Print the service config hash, one per line. + description: Print the service config hash, one per line deprecated: false hidden: false experimental: false @@ -31,7 +31,7 @@ options: - option: images value_type: bool default_value: "false" - description: Print the image names, one per line. + description: Print the image names, one per line deprecated: false hidden: false experimental: false @@ -52,7 +52,7 @@ options: - option: no-interpolate value_type: bool default_value: "false" - description: Don't interpolate environment variables. + description: Don't interpolate environment variables deprecated: false hidden: false experimental: false @@ -62,7 +62,7 @@ options: - option: no-normalize value_type: bool default_value: "false" - description: Don't normalize compose model. + description: Don't normalize compose model deprecated: false hidden: false experimental: false @@ -72,7 +72,7 @@ options: - option: no-path-resolution value_type: bool default_value: "false" - description: Don't resolve file paths. + description: Don't resolve file paths deprecated: false hidden: false experimental: false @@ -92,7 +92,7 @@ options: - option: profiles value_type: bool default_value: "false" - description: Print the profile names, one per line. + description: Print the profile names, one per line deprecated: false hidden: false experimental: false @@ -103,7 +103,7 @@ options: shorthand: q value_type: bool default_value: "false" - description: Only validate the configuration, don't print anything. + description: Only validate the configuration, don't print anything deprecated: false hidden: false experimental: false @@ -113,7 +113,7 @@ options: - option: resolve-image-digests value_type: bool default_value: "false" - description: Pin image tags to digests. + description: Pin image tags to digests deprecated: false hidden: false experimental: false @@ -123,7 +123,7 @@ options: - option: services value_type: bool default_value: "false" - description: Print the service names, one per line. + description: Print the service names, one per line deprecated: false hidden: false experimental: false @@ -133,7 +133,7 @@ options: - option: volumes value_type: bool default_value: "false" - description: Print the volume names, one per line. + description: Print the volume names, one per line deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_cp.yaml b/docs/reference/docker_compose_cp.yaml index 4551c13c64..8ff3cf37e0 100644 --- a/docs/reference/docker_compose_cp.yaml +++ b/docs/reference/docker_compose_cp.yaml @@ -10,7 +10,7 @@ options: - option: all value_type: bool default_value: "false" - description: copy to all the containers of the service. + description: Copy to all the containers of the service deprecated: true hidden: true experimental: false @@ -42,7 +42,7 @@ options: - option: index value_type: int default_value: "0" - description: index of the container if service has multiple replicas + description: Index of the container if service has multiple replicas deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_create.yaml b/docs/reference/docker_compose_create.yaml index 05a4a51437..34425aebb8 100644 --- a/docs/reference/docker_compose_create.yaml +++ b/docs/reference/docker_compose_create.yaml @@ -1,6 +1,6 @@ command: docker compose create -short: Creates containers for a service. -long: Creates containers for a service. +short: Creates containers for a service +long: Creates containers for a service usage: docker compose create [OPTIONS] [SERVICE...] pname: docker compose plink: docker_compose.yaml @@ -8,7 +8,7 @@ options: - option: build value_type: bool default_value: "false" - description: Build images before starting containers. + description: Build images before starting containers deprecated: false hidden: false experimental: false @@ -19,7 +19,7 @@ options: value_type: bool default_value: "false" description: | - Recreate containers even if their configuration and image haven't changed. + Recreate containers even if their configuration and image haven't changed deprecated: false hidden: false experimental: false @@ -29,7 +29,7 @@ options: - option: no-build value_type: bool default_value: "false" - description: Don't build an image, even if it's policy. + description: Don't build an image, even if it's policy deprecated: false hidden: false experimental: false @@ -60,7 +60,7 @@ options: - option: remove-orphans value_type: bool default_value: "false" - description: Remove containers for services not defined in the Compose file. + description: Remove containers for services not defined in the Compose file deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_down.yaml b/docs/reference/docker_compose_down.yaml index 7964dd49c3..77bf526289 100644 --- a/docs/reference/docker_compose_down.yaml +++ b/docs/reference/docker_compose_down.yaml @@ -21,7 +21,7 @@ options: - option: remove-orphans value_type: bool default_value: "false" - description: Remove containers for services not defined in the Compose file. + description: Remove containers for services not defined in the Compose file deprecated: false hidden: false experimental: false @@ -54,7 +54,7 @@ options: value_type: bool default_value: "false" description: | - Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers. + Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_events.yaml b/docs/reference/docker_compose_events.yaml index cd51372f72..3486e22135 100644 --- a/docs/reference/docker_compose_events.yaml +++ b/docs/reference/docker_compose_events.yaml @@ -1,5 +1,5 @@ command: docker compose events -short: Receive real time events from containers. +short: Receive real time events from containers long: |- Stream container events for every container in the project. diff --git a/docs/reference/docker_compose_exec.yaml b/docs/reference/docker_compose_exec.yaml index ce6faeda6d..b2a1cf2068 100644 --- a/docs/reference/docker_compose_exec.yaml +++ b/docs/reference/docker_compose_exec.yaml @@ -1,5 +1,5 @@ command: docker compose exec -short: Execute a command in a running container. +short: Execute a command in a running container long: |- This is the equivalent of `docker exec` targeting a Compose service. @@ -13,7 +13,7 @@ options: shorthand: d value_type: bool default_value: "false" - description: 'Detached mode: Run command in the background.' + description: 'Detached mode: Run command in the background' deprecated: false hidden: false experimental: false @@ -34,7 +34,7 @@ options: - option: index value_type: int default_value: "0" - description: index of the container if service has multiple replicas + description: Index of the container if service has multiple replicas deprecated: false hidden: false experimental: false @@ -45,7 +45,7 @@ options: shorthand: i value_type: bool default_value: "true" - description: Keep STDIN open even if not attached. + description: Keep STDIN open even if not attached deprecated: false hidden: true experimental: false @@ -67,7 +67,7 @@ options: - option: privileged value_type: bool default_value: "false" - description: Give extended privileges to the process. + description: Give extended privileges to the process deprecated: false hidden: false experimental: false @@ -78,7 +78,7 @@ options: shorthand: t value_type: bool default_value: "true" - description: Allocate a pseudo-TTY. + description: Allocate a pseudo-TTY deprecated: false hidden: true experimental: false @@ -88,7 +88,7 @@ options: - option: user shorthand: u value_type: string - description: Run the command as this user. + description: Run the command as this user deprecated: false hidden: false experimental: false @@ -98,7 +98,7 @@ options: - option: workdir shorthand: w value_type: string - description: Path to workdir directory for this command. + description: Path to workdir directory for this command deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_images.yaml b/docs/reference/docker_compose_images.yaml index 4719590af5..33187df42d 100644 --- a/docs/reference/docker_compose_images.yaml +++ b/docs/reference/docker_compose_images.yaml @@ -8,7 +8,7 @@ options: - option: format value_type: string default_value: table - description: 'Format the output. Values: [table | json].' + description: 'Format the output. Values: [table | json]' deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_kill.yaml b/docs/reference/docker_compose_kill.yaml index 2134227641..b6d5334827 100644 --- a/docs/reference/docker_compose_kill.yaml +++ b/docs/reference/docker_compose_kill.yaml @@ -1,5 +1,5 @@ command: docker compose kill -short: Force stop service containers. +short: Force stop service containers long: |- Forces running containers to stop by sending a `SIGKILL` signal. Optionally the signal can be passed, for example: @@ -13,7 +13,7 @@ options: - option: remove-orphans value_type: bool default_value: "false" - description: Remove containers for services not defined in the Compose file. + description: Remove containers for services not defined in the Compose file deprecated: false hidden: false experimental: false @@ -24,7 +24,7 @@ options: shorthand: s value_type: string default_value: SIGKILL - description: SIGNAL to send to the container. + description: SIGNAL to send to the container deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_logs.yaml b/docs/reference/docker_compose_logs.yaml index fe6bbdb424..92d94dd108 100644 --- a/docs/reference/docker_compose_logs.yaml +++ b/docs/reference/docker_compose_logs.yaml @@ -1,6 +1,6 @@ command: docker compose logs short: View output from containers -long: Displays log output from services. +long: Displays log output from services usage: docker compose logs [OPTIONS] [SERVICE...] pname: docker compose plink: docker_compose.yaml @@ -9,7 +9,7 @@ options: shorthand: f value_type: bool default_value: "false" - description: Follow log output. + description: Follow log output deprecated: false hidden: false experimental: false @@ -29,7 +29,7 @@ options: - option: no-color value_type: bool default_value: "false" - description: Produce monochrome output. + description: Produce monochrome output deprecated: false hidden: false experimental: false @@ -39,7 +39,7 @@ options: - option: no-log-prefix value_type: bool default_value: "false" - description: Don't print prefix in logs. + description: Don't print prefix in logs deprecated: false hidden: false experimental: false @@ -61,7 +61,7 @@ options: value_type: string default_value: all description: | - Number of lines to show from the end of the logs for each container. + Number of lines to show from the end of the logs for each container deprecated: false hidden: false experimental: false @@ -72,7 +72,7 @@ options: shorthand: t value_type: bool default_value: "false" - description: Show timestamps. + description: Show timestamps deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_ls.yaml b/docs/reference/docker_compose_ls.yaml index c4b6d5f7c1..a2efac2a88 100644 --- a/docs/reference/docker_compose_ls.yaml +++ b/docs/reference/docker_compose_ls.yaml @@ -1,6 +1,6 @@ command: docker compose ls short: List running compose projects -long: Lists running Compose projects. +long: Lists running Compose projects usage: docker compose ls [OPTIONS] pname: docker compose plink: docker_compose.yaml @@ -18,7 +18,7 @@ options: swarm: false - option: filter value_type: filter - description: Filter output based on conditions provided. + description: Filter output based on conditions provided deprecated: false hidden: false experimental: false @@ -28,7 +28,7 @@ options: - option: format value_type: string default_value: table - description: 'Format the output. Values: [table | json].' + description: 'Format the output. Values: [table | json]' deprecated: false hidden: false experimental: false @@ -39,7 +39,7 @@ options: shorthand: q value_type: bool default_value: "false" - description: Only display IDs. + description: Only display IDs deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_port.yaml b/docs/reference/docker_compose_port.yaml index 7c8dee90ce..8a07f31ea5 100644 --- a/docs/reference/docker_compose_port.yaml +++ b/docs/reference/docker_compose_port.yaml @@ -1,6 +1,6 @@ command: docker compose port -short: Print the public port for a port binding. -long: Prints the public port for a port binding. +short: Print the public port for a port binding +long: Prints the public port for a port binding usage: docker compose port [OPTIONS] SERVICE PRIVATE_PORT pname: docker compose plink: docker_compose.yaml @@ -8,7 +8,7 @@ options: - option: index value_type: int default_value: "0" - description: index of the container if service has multiple replicas + description: Index of the container if service has multiple replicas deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_ps.yaml b/docs/reference/docker_compose_ps.yaml index 78842a9990..d037027569 100644 --- a/docs/reference/docker_compose_ps.yaml +++ b/docs/reference/docker_compose_ps.yaml @@ -35,7 +35,7 @@ options: swarm: false - option: filter value_type: string - description: 'Filter services by a property (supported filters: status).' + description: 'Filter services by a property (supported filters: status)' details_url: '#filter' deprecated: false hidden: false diff --git a/docs/reference/docker_compose_pull.yaml b/docs/reference/docker_compose_pull.yaml index 46a4711624..5b1316df13 100644 --- a/docs/reference/docker_compose_pull.yaml +++ b/docs/reference/docker_compose_pull.yaml @@ -1,8 +1,7 @@ command: docker compose pull short: Pull service images -long: |- - Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on - those images. +long: | + Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images usage: docker compose pull [OPTIONS] [SERVICE...] pname: docker compose plink: docker_compose.yaml @@ -10,7 +9,7 @@ options: - option: ignore-buildable value_type: bool default_value: "false" - description: Ignore images that can be built. + description: Ignore images that can be built deprecated: false hidden: false experimental: false @@ -20,7 +19,7 @@ options: - option: ignore-pull-failures value_type: bool default_value: "false" - description: Pull what it can and ignores images with pull failures. + description: Pull what it can and ignores images with pull failures deprecated: false hidden: false experimental: false @@ -30,7 +29,7 @@ options: - option: include-deps value_type: bool default_value: "false" - description: Also pull services declared as dependencies. + description: Also pull services declared as dependencies deprecated: false hidden: false experimental: false @@ -40,7 +39,7 @@ options: - option: no-parallel value_type: bool default_value: "true" - description: DEPRECATED disable parallel pulling. + description: DEPRECATED disable parallel pulling deprecated: false hidden: true experimental: false @@ -50,7 +49,7 @@ options: - option: parallel value_type: bool default_value: "true" - description: DEPRECATED pull multiple images in parallel. + description: DEPRECATED pull multiple images in parallel deprecated: false hidden: true experimental: false @@ -59,7 +58,7 @@ options: swarm: false - option: policy value_type: string - description: Apply pull policy ("missing"|"always"). + description: Apply pull policy ("missing"|"always") deprecated: false hidden: false experimental: false @@ -70,7 +69,7 @@ options: shorthand: q value_type: bool default_value: "false" - description: Pull without printing progress information. + description: Pull without printing progress information deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_restart.yaml b/docs/reference/docker_compose_restart.yaml index 3126eb005a..3b2a4bddd1 100644 --- a/docs/reference/docker_compose_restart.yaml +++ b/docs/reference/docker_compose_restart.yaml @@ -18,7 +18,7 @@ options: - option: no-deps value_type: bool default_value: "false" - description: Don't restart dependent services. + description: Don't restart dependent services deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_run.yaml b/docs/reference/docker_compose_run.yaml index 12abafc3b6..0584bdd070 100644 --- a/docs/reference/docker_compose_run.yaml +++ b/docs/reference/docker_compose_run.yaml @@ -1,5 +1,5 @@ command: docker compose run -short: Run a one-off command on a service. +short: Run a one-off command on a service long: |- Runs a one-time command against a service. @@ -61,7 +61,7 @@ options: - option: build value_type: bool default_value: "false" - description: Build image before starting container. + description: Build image before starting container deprecated: false hidden: false experimental: false @@ -121,7 +121,7 @@ options: shorthand: i value_type: bool default_value: "true" - description: Keep STDIN open even if not attached. + description: Keep STDIN open even if not attached deprecated: false hidden: false experimental: false @@ -152,7 +152,7 @@ options: shorthand: T value_type: bool default_value: "true" - description: 'Disable pseudo-TTY allocation (default: auto-detected).' + description: 'Disable pseudo-TTY allocation (default: auto-detected)' deprecated: false hidden: false experimental: false @@ -162,7 +162,7 @@ options: - option: no-deps value_type: bool default_value: "false" - description: Don't start linked services. + description: Don't start linked services deprecated: false hidden: false experimental: false @@ -173,7 +173,7 @@ options: shorthand: p value_type: stringArray default_value: '[]' - description: Publish a container's port(s) to the host. + description: Publish a container's port(s) to the host deprecated: false hidden: false experimental: false @@ -183,7 +183,7 @@ options: - option: quiet-pull value_type: bool default_value: "false" - description: Pull without printing progress information. + description: Pull without printing progress information deprecated: false hidden: false experimental: false @@ -193,7 +193,7 @@ options: - option: remove-orphans value_type: bool default_value: "false" - description: Remove containers for services not defined in the Compose file. + description: Remove containers for services not defined in the Compose file deprecated: false hidden: false experimental: false @@ -215,7 +215,7 @@ options: value_type: bool default_value: "false" description: | - Run command with all service's ports enabled and mapped to the host. + Run command with all service's ports enabled and mapped to the host deprecated: false hidden: false experimental: false @@ -226,7 +226,7 @@ options: shorthand: t value_type: bool default_value: "true" - description: Allocate a pseudo-TTY. + description: Allocate a pseudo-TTY deprecated: false hidden: true experimental: false @@ -237,7 +237,7 @@ options: value_type: bool default_value: "false" description: | - Use the service's network useAliases in the network(s) the container connects to. + Use the service's network useAliases in the network(s) the container connects to deprecated: false hidden: false experimental: false @@ -258,7 +258,7 @@ options: shorthand: v value_type: stringArray default_value: '[]' - description: Bind mount a volume. + description: Bind mount a volume deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_scale.yaml b/docs/reference/docker_compose_scale.yaml index 9391441f26..f840a51b4e 100644 --- a/docs/reference/docker_compose_scale.yaml +++ b/docs/reference/docker_compose_scale.yaml @@ -8,7 +8,7 @@ options: - option: no-deps value_type: bool default_value: "false" - description: Don't start linked services. + description: Don't start linked services deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_start.yaml b/docs/reference/docker_compose_start.yaml index 9a7fa379be..902b688d3e 100644 --- a/docs/reference/docker_compose_start.yaml +++ b/docs/reference/docker_compose_start.yaml @@ -1,6 +1,6 @@ command: docker compose start short: Start services -long: Starts existing containers for a service. +long: Starts existing containers for a service usage: docker compose start [SERVICE...] pname: docker compose plink: docker_compose.yaml diff --git a/docs/reference/docker_compose_top.yaml b/docs/reference/docker_compose_top.yaml index 7d87f7ccab..17cdf7e381 100644 --- a/docs/reference/docker_compose_top.yaml +++ b/docs/reference/docker_compose_top.yaml @@ -1,6 +1,6 @@ command: docker compose top short: Display the running processes -long: Displays the running processes. +long: Displays the running processes usage: docker compose top [SERVICES...] pname: docker compose plink: docker_compose.yaml diff --git a/docs/reference/docker_compose_unpause.yaml b/docs/reference/docker_compose_unpause.yaml index 2679f53f69..e2047720b8 100644 --- a/docs/reference/docker_compose_unpause.yaml +++ b/docs/reference/docker_compose_unpause.yaml @@ -1,6 +1,6 @@ command: docker compose unpause short: Unpause services -long: Unpauses paused containers of a service. +long: Unpauses paused containers of a service usage: docker compose unpause [SERVICE...] pname: docker compose plink: docker_compose.yaml diff --git a/docs/reference/docker_compose_up.yaml b/docs/reference/docker_compose_up.yaml index 2ee4d199c6..0ed06a09b2 100644 --- a/docs/reference/docker_compose_up.yaml +++ b/docs/reference/docker_compose_up.yaml @@ -59,7 +59,7 @@ options: - option: attach-dependencies value_type: bool default_value: "false" - description: Automatically attach to log output of dependent services. + description: Automatically attach to log output of dependent services deprecated: false hidden: false experimental: false @@ -69,7 +69,7 @@ options: - option: build value_type: bool default_value: "false" - description: Build images before starting containers. + description: Build images before starting containers deprecated: false hidden: false experimental: false @@ -101,7 +101,7 @@ options: value_type: bool default_value: "false" description: | - Recreate containers even if their configuration and image haven't changed. + Recreate containers even if their configuration and image haven't changed deprecated: false hidden: false experimental: false @@ -111,7 +111,7 @@ options: - option: no-attach value_type: stringArray default_value: '[]' - description: Do not attach (stream logs) to the specified services. + description: Do not attach (stream logs) to the specified services deprecated: false hidden: false experimental: false @@ -121,7 +121,7 @@ options: - option: no-build value_type: bool default_value: "false" - description: Don't build an image, even if it's policy. + description: Don't build an image, even if it's policy deprecated: false hidden: false experimental: false @@ -131,7 +131,7 @@ options: - option: no-color value_type: bool default_value: "false" - description: Produce monochrome output. + description: Produce monochrome output deprecated: false hidden: false experimental: false @@ -141,7 +141,7 @@ options: - option: no-deps value_type: bool default_value: "false" - description: Don't start linked services. + description: Don't start linked services deprecated: false hidden: false experimental: false @@ -151,7 +151,7 @@ options: - option: no-log-prefix value_type: bool default_value: "false" - description: Don't print prefix in logs. + description: Don't print prefix in logs deprecated: false hidden: false experimental: false @@ -172,7 +172,7 @@ options: - option: no-start value_type: bool default_value: "false" - description: Don't start the services after creating them. + description: Don't start the services after creating them deprecated: false hidden: false experimental: false @@ -192,7 +192,7 @@ options: - option: quiet-pull value_type: bool default_value: "false" - description: Pull without printing progress information. + description: Pull without printing progress information deprecated: false hidden: false experimental: false @@ -202,7 +202,7 @@ options: - option: remove-orphans value_type: bool default_value: "false" - description: Remove containers for services not defined in the Compose file. + description: Remove containers for services not defined in the Compose file deprecated: false hidden: false experimental: false @@ -214,7 +214,7 @@ options: value_type: bool default_value: "false" description: | - Recreate anonymous volumes instead of retrieving data from the previous containers. + Recreate anonymous volumes instead of retrieving data from the previous containers deprecated: false hidden: false experimental: false @@ -237,7 +237,7 @@ options: value_type: int default_value: "0" description: | - Use this timeout in seconds for container shutdown when attached or when containers are already running. + Use this timeout in seconds for container shutdown when attached or when containers are already running deprecated: false hidden: false experimental: false @@ -247,7 +247,7 @@ options: - option: timestamps value_type: bool default_value: "false" - description: Show timestamps. + description: Show timestamps deprecated: false hidden: false experimental: false @@ -267,7 +267,7 @@ options: - option: wait-timeout value_type: int default_value: "0" - description: Maximum duration to wait for the project to be running|healthy. + description: Maximum duration to wait for the project to be running|healthy deprecated: false hidden: false experimental: false diff --git a/docs/reference/docker_compose_version.yaml b/docs/reference/docker_compose_version.yaml index b06c4b1508..789e94818e 100644 --- a/docs/reference/docker_compose_version.yaml +++ b/docs/reference/docker_compose_version.yaml @@ -18,7 +18,7 @@ options: - option: short value_type: bool default_value: "false" - description: Shows only Compose's version number. + description: Shows only Compose's version number deprecated: false hidden: false experimental: false diff --git a/pkg/compose/build.go b/pkg/compose/build.go index bf865a47d0..c06f1cb4c2 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -177,7 +177,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti } if options.Memory != 0 { - fmt.Fprintln(s.stderr(), "WARNING: --memory is not supported by BuildKit and will be ignored.") + fmt.Fprintln(s.stderr(), "WARNING: --memory is not supported by BuildKit and will be ignored") } buildOptions, err := s.toBuildOptions(project, service, options) From a41fc584931e446edd0a55c116a4573a53ed1f76 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Tue, 13 Feb 2024 12:56:53 -0500 Subject: [PATCH 19/47] chore(watch): remove old `docker cp` implementation This has not been the default for quite a while and required setting an environment variable to revert back. The tar implementation is more performant and addresses several edge cases with the original `docker cp` version, so it's time to fully retire it. The scaffolding for multiple sync implementations remains to support future experimentation here. Signed-off-by: Milas Bowman --- internal/sync/docker_cp.go | 104 ------------------------ internal/sync/writer.go | 91 --------------------- internal/sync/writer_test.go | 152 ----------------------------------- pkg/compose/watch.go | 21 +++-- pkg/e2e/watch_test.go | 33 +++----- 5 files changed, 22 insertions(+), 379 deletions(-) delete mode 100644 internal/sync/docker_cp.go delete mode 100644 internal/sync/writer.go delete mode 100644 internal/sync/writer_test.go diff --git a/internal/sync/docker_cp.go b/internal/sync/docker_cp.go deleted file mode 100644 index 47077b404e..0000000000 --- a/internal/sync/docker_cp.go +++ /dev/null @@ -1,104 +0,0 @@ -/* - Copyright 2023 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package sync - -import ( - "context" - "errors" - "fmt" - "io" - "io/fs" - "os" - - "github.com/compose-spec/compose-go/v2/types" - "github.com/docker/compose/v2/pkg/api" - "github.com/sirupsen/logrus" -) - -type ComposeClient interface { - Exec(ctx context.Context, projectName string, options api.RunOptions) (int, error) - - Copy(ctx context.Context, projectName string, options api.CopyOptions) error -} - -type DockerCopy struct { - client ComposeClient - - projectName string - - infoWriter io.Writer -} - -var _ Syncer = &DockerCopy{} - -func NewDockerCopy(projectName string, client ComposeClient, infoWriter io.Writer) *DockerCopy { - return &DockerCopy{ - projectName: projectName, - client: client, - infoWriter: infoWriter, - } -} - -func (d *DockerCopy) Sync(ctx context.Context, service types.ServiceConfig, paths []PathMapping) error { - var errs []error - for i := range paths { - if err := d.sync(ctx, service, paths[i]); err != nil { - errs = append(errs, err) - } - } - return errors.Join(errs...) -} - -func (d *DockerCopy) sync(ctx context.Context, service types.ServiceConfig, pathMapping PathMapping) error { - scale := service.GetScale() - - if fi, statErr := os.Stat(pathMapping.HostPath); statErr == nil { - if fi.IsDir() { - for i := 1; i <= scale; i++ { - _, err := d.client.Exec(ctx, d.projectName, api.RunOptions{ - Service: service.Name, - Command: []string{"mkdir", "-p", pathMapping.ContainerPath}, - Index: i, - }) - if err != nil { - logrus.Warnf("failed to create %q from %s: %v", pathMapping.ContainerPath, service.Name, err) - } - } - fmt.Fprintf(d.infoWriter, "%s created\n", pathMapping.ContainerPath) - } else { - err := d.client.Copy(ctx, d.projectName, api.CopyOptions{ - Source: pathMapping.HostPath, - Destination: fmt.Sprintf("%s:%s", service.Name, pathMapping.ContainerPath), - }) - if err != nil { - return err - } - fmt.Fprintf(d.infoWriter, "%s updated\n", pathMapping.ContainerPath) - } - } else if errors.Is(statErr, fs.ErrNotExist) { - for i := 1; i <= scale; i++ { - _, err := d.client.Exec(ctx, d.projectName, api.RunOptions{ - Service: service.Name, - Command: []string{"rm", "-rf", pathMapping.ContainerPath}, - Index: i, - }) - if err != nil { - logrus.Warnf("failed to delete %q from %s: %v", pathMapping.ContainerPath, service.Name, err) - } - } - fmt.Fprintf(d.infoWriter, "%s deleted from service\n", pathMapping.ContainerPath) - } - return nil -} diff --git a/internal/sync/writer.go b/internal/sync/writer.go deleted file mode 100644 index f5c182d1bf..0000000000 --- a/internal/sync/writer.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright 2023 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package sync - -import ( - "errors" - "io" -) - -// lossyMultiWriter attempts to tee all writes to the provided io.PipeWriter -// instances. -// -// If a writer fails during a Write call, the write-side of the pipe is then -// closed with the error and no subsequent attempts are made to write to the -// pipe. -// -// If all writers fail during a write, an error is returned. -// -// On Close, any remaining writers are closed. -type lossyMultiWriter struct { - writers []*io.PipeWriter -} - -// newLossyMultiWriter creates a new writer that *attempts* to tee all data written to it to the provided io.PipeWriter -// instances. Rather than failing a write operation if any writer fails, writes only fail if there are no more valid -// writers. Otherwise, errors for specific writers are propagated via CloseWithError. -func newLossyMultiWriter(writers ...*io.PipeWriter) *lossyMultiWriter { - // reverse the writers because during the write we iterate - // backwards, so this way we'll end up writing in the same - // order as the writers were passed to us - writers = append([]*io.PipeWriter(nil), writers...) - for i, j := 0, len(writers)-1; i < j; i, j = i+1, j-1 { - writers[i], writers[j] = writers[j], writers[i] - } - - return &lossyMultiWriter{ - writers: writers, - } -} - -// Write writes to each writer that is still active (i.e. has not failed/encountered an error on write). -// -// If a writer encounters an error during the write, the write side of the pipe is closed with the error -// and no subsequent attempts will be made to write to that writer. -// -// An error is only returned from this function if ALL writers have failed. -func (l *lossyMultiWriter) Write(p []byte) (int, error) { - // NOTE: this function iterates backwards so that it can - // safely remove elements during the loop - for i := len(l.writers) - 1; i >= 0; i-- { - written, err := l.writers[i].Write(p) - if err == nil && written != len(p) { - err = io.ErrShortWrite - } - if err != nil { - // pipe writer close cannot fail - _ = l.writers[i].CloseWithError(err) - l.writers = append(l.writers[:i], l.writers[i+1:]...) - } - } - - if len(l.writers) == 0 { - return 0, errors.New("no writers remaining") - } - - return len(p), nil -} - -// Close closes any still open (non-failed) writers. -// -// Failed writers have already been closed with an error. -func (l *lossyMultiWriter) Close() { - for i := range l.writers { - // pipe writer close cannot fail - _ = l.writers[i].Close() - } -} diff --git a/internal/sync/writer_test.go b/internal/sync/writer_test.go deleted file mode 100644 index b6de694c72..0000000000 --- a/internal/sync/writer_test.go +++ /dev/null @@ -1,152 +0,0 @@ -/* - Copyright 2023 Docker Compose CLI authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package sync - -import ( - "context" - "errors" - "io" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestLossyMultiWriter(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - const count = 5 - readers := make([]*bufReader, count) - writers := make([]*io.PipeWriter, count) - for i := 0; i < count; i++ { - r, w := io.Pipe() - readers[i] = newBufReader(ctx, r) - writers[i] = w - } - - w := newLossyMultiWriter(writers...) - t.Cleanup(w.Close) - n, err := w.Write([]byte("hello world")) - require.Equal(t, 11, n) - require.NoError(t, err) - for i := range readers { - readers[i].waitForWrite(t) - require.Equal(t, "hello world", string(readers[i].contents())) - readers[i].reset() - } - - // even if a writer fails (in this case simulated by closing the receiving end of the pipe), - // write operations should continue to return nil error but the writer should be closed - // with an error - const failIndex = 3 - require.NoError(t, readers[failIndex].r.CloseWithError(errors.New("oh no"))) - n, err = w.Write([]byte("hello")) - require.Equal(t, 5, n) - require.NoError(t, err) - for i := range readers { - readers[i].waitForWrite(t) - if i == failIndex { - err := readers[i].error() - require.EqualError(t, err, "io: read/write on closed pipe") - require.Empty(t, readers[i].contents()) - } else { - require.Equal(t, "hello", string(readers[i].contents())) - } - } - - // perform another write, verify there's still no errors - n, err = w.Write([]byte(" world")) - require.Equal(t, 6, n) - require.NoError(t, err) -} - -type bufReader struct { - ctx context.Context - r *io.PipeReader - mu sync.Mutex - err error - data []byte - writeSync chan struct{} -} - -func newBufReader(ctx context.Context, r *io.PipeReader) *bufReader { - b := &bufReader{ - ctx: ctx, - r: r, - writeSync: make(chan struct{}), - } - go b.consume() - return b -} - -func (b *bufReader) waitForWrite(t testing.TB) { - t.Helper() - select { - case <-b.writeSync: - return - case <-time.After(50 * time.Millisecond): - t.Fatal("timed out waiting for write") - } -} - -func (b *bufReader) consume() { - defer close(b.writeSync) - for { - buf := make([]byte, 512) - n, err := b.r.Read(buf) - if n != 0 { - b.mu.Lock() - b.data = append(b.data, buf[:n]...) - b.mu.Unlock() - } - if errors.Is(err, io.EOF) { - return - } - if err != nil { - b.mu.Lock() - b.err = err - b.mu.Unlock() - return - } - // prevent goroutine leak, tie lifetime to the test - select { - case b.writeSync <- struct{}{}: - case <-b.ctx.Done(): - return - } - } -} - -func (b *bufReader) contents() []byte { - b.mu.Lock() - defer b.mu.Unlock() - return b.data -} - -func (b *bufReader) reset() { - b.mu.Lock() - defer b.mu.Unlock() - b.data = nil -} - -func (b *bufReader) error() error { - b.mu.Lock() - defer b.mu.Unlock() - return b.err -} diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go index 3673c43557..2e783026e1 100644 --- a/pkg/compose/watch.go +++ b/pkg/compose/watch.go @@ -46,21 +46,23 @@ type fileEvent struct { Action types.WatchAction } -// getSyncImplementation returns the the tar-based syncer unless it has been explicitly -// disabled with `COMPOSE_EXPERIMENTAL_WATCH_TAR=0`. Note that the absence of the env -// var means enabled. -func (s *composeService) getSyncImplementation(project *types.Project) sync.Syncer { +// getSyncImplementation returns an appropriate sync implementation for the +// project. +// +// Currently, an implementation that batches files and transfers them using +// the Moby `Untar` API. +func (s *composeService) getSyncImplementation(project *types.Project) (sync.Syncer, error) { var useTar bool if useTarEnv, ok := os.LookupEnv("COMPOSE_EXPERIMENTAL_WATCH_TAR"); ok { useTar, _ = strconv.ParseBool(useTarEnv) } else { useTar = true } - if useTar { - return sync.NewTar(project.Name, tarDockerClient{s: s}) + if !useTar { + return nil, errors.New("no available sync implementation") } - return sync.NewDockerCopy(project.Name, s, s.stdinfo()) + return sync.NewTar(project.Name, tarDockerClient{s: s}), nil } func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error { //nolint: gocyclo @@ -68,7 +70,10 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv if project, err = project.WithSelectedServices(services); err != nil { return err } - syncer := s.getSyncImplementation(project) + syncer, err := s.getSyncImplementation(project) + if err != nil { + return err + } eg, ctx := errgroup.WithContext(ctx) watching := false for i := range project.Services { diff --git a/pkg/e2e/watch_test.go b/pkg/e2e/watch_test.go index 0740e98c22..677b080a0a 100644 --- a/pkg/e2e/watch_test.go +++ b/pkg/e2e/watch_test.go @@ -21,7 +21,6 @@ import ( "fmt" "os" "path/filepath" - "strconv" "strings" "sync/atomic" "testing" @@ -38,23 +37,12 @@ func TestWatch(t *testing.T) { t.Skip("Skipping watch tests until we can figure out why they are flaky/failing") services := []string{"alpine", "busybox", "debian"} - t.Run("docker cp", func(t *testing.T) { - for _, svcName := range services { - t.Run(svcName, func(t *testing.T) { - t.Helper() - doTest(t, svcName, false) - }) - } - }) - - t.Run("tar", func(t *testing.T) { - for _, svcName := range services { - t.Run(svcName, func(t *testing.T) { - t.Helper() - doTest(t, svcName, true) - }) - } - }) + for _, svcName := range services { + t.Run(svcName, func(t *testing.T) { + t.Helper() + doTest(t, svcName) + }) + } } func TestRebuildOnDotEnvWithExternalNetwork(t *testing.T) { @@ -150,8 +138,9 @@ func TestRebuildOnDotEnvWithExternalNetwork(t *testing.T) { } -// NOTE: these tests all share a single Compose file but are safe to run concurrently -func doTest(t *testing.T, svcName string, tarSync bool) { +// NOTE: these tests all share a single Compose file but are safe to run +// concurrently (though that's not recommended). +func doTest(t *testing.T, svcName string) { tmpdir := t.TempDir() dataDir := filepath.Join(tmpdir, "data") configDir := filepath.Join(tmpdir, "config") @@ -171,13 +160,9 @@ func doTest(t *testing.T, svcName string, tarSync bool) { CopyFile(t, filepath.Join("fixtures", "watch", "compose.yaml"), composeFilePath) projName := "e2e-watch-" + svcName - if tarSync { - projName += "-tar" - } env := []string{ "COMPOSE_FILE=" + composeFilePath, "COMPOSE_PROJECT_NAME=" + projName, - "COMPOSE_EXPERIMENTAL_WATCH_TAR=" + strconv.FormatBool(tarSync), } cli := NewCLI(t, WithEnv(env...)) From ba30ec986c005051e5088fad8e2f986319532a93 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Fri, 9 Feb 2024 17:35:25 +0100 Subject: [PATCH 20/47] docs: update cli reference link Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> --- docs/reference/compose_events.md | 2 +- docs/reference/docker_compose_events.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/compose_events.md b/docs/reference/compose_events.md index e1db499484..0a97a46bc7 100644 --- a/docs/reference/compose_events.md +++ b/docs/reference/compose_events.md @@ -33,4 +33,4 @@ With the `--json` flag, a json object is printed one per line with the format: } ``` -The events that can be received using this can be seen [here](https://docs.docker.com/engine/reference/commandline/system_events/#object-types). +The events that can be received using this can be seen [here](https://docs.docker.com/reference/cli/docker/system/events/#object-types). diff --git a/docs/reference/docker_compose_events.yaml b/docs/reference/docker_compose_events.yaml index 3486e22135..fe6d4216ce 100644 --- a/docs/reference/docker_compose_events.yaml +++ b/docs/reference/docker_compose_events.yaml @@ -19,7 +19,7 @@ long: |- } ``` - The events that can be received using this can be seen [here](/engine/reference/commandline/system_events/#object-types). + The events that can be received using this can be seen [here](/reference/cli/docker/system/events/#object-types). usage: docker compose events [OPTIONS] [SERVICE...] pname: docker compose plink: docker_compose.yaml From 070d515f5bba9e8cdc8a30e4f3aef582d5af8fa2 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 15 Feb 2024 08:28:36 +0100 Subject: [PATCH 21/47] use listeners to collect include metrics Signed-off-by: Nicolas De Loof --- cmd/compose/compose.go | 36 ++++++++++++++++++++++++++++------ go.mod | 2 +- go.sum | 4 ++-- internal/tracing/attributes.go | 24 +++++++++-------------- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index c1799cb95d..082a70b694 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -29,6 +29,7 @@ import ( "github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/dotenv" + "github.com/compose-spec/compose-go/v2/loader" "github.com/compose-spec/compose-go/v2/types" composegoutils "github.com/compose-spec/compose-go/v2/utils" "github.com/docker/buildx/util/logutil" @@ -147,7 +148,7 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF return err } - ctx = context.WithValue(ctx, tracing.Metrics{}, metrics) + ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics) return fn(ctx, project, args) }) @@ -202,8 +203,10 @@ func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cl func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { var metrics tracing.Metrics - if !o.Offline { - po = append(po, o.remoteLoaders(dockerCli)...) + + remotes := o.remoteLoaders(dockerCli) + for _, r := range remotes { + po = append(po, cli.WithResourceLoader(r)) } po = append(po, cli.WithContext(ctx)) @@ -214,10 +217,28 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s } options.WithListeners(func(event string, metadata map[string]any) { - if event == "extends" { + switch event { + case "extends": metrics.CountExtends++ + case "include": + paths := metadata["path"].(types.StringList) + for _, path := range paths { + var isRemote bool + for _, r := range remotes { + if r.Accept(path) { + isRemote = true + break + } + } + if isRemote { + metrics.CountIncludesRemote++ + } else { + metrics.CountIncludesLocal++ + } + } } }) + if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) { api.Separator = "_" } @@ -257,10 +278,13 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s return project, metrics, err } -func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []cli.ProjectOptionsFn { +func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceLoader { + if o.Offline { + return nil + } git := remote.NewGitRemoteLoader(o.Offline) oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline) - return []cli.ProjectOptionsFn{cli.WithResourceLoader(git), cli.WithResourceLoader(oci)} + return []loader.ResourceLoader{git, oci} } func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) { diff --git a/go.mod b/go.mod index c946369ebd..dd28fa4876 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Microsoft/go-winio v0.6.1 github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.0.0-rc.6 + github.com/compose-spec/compose-go/v2 v2.0.0-rc.7 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.12 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 9ed3efa6fb..19dd1afd4c 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.6 h1:sv9W3U0IEYqgGqTbSDpU2c8cttWQmlbJ0D6jdt//Dv8= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.6/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.7 h1:koFIK+JwplWu1m/DscSO6MJw7hodaEHOaKQZPUSL4OY= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.7/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= diff --git a/internal/tracing/attributes.go b/internal/tracing/attributes.go index 23a9b13495..3d27c4796a 100644 --- a/internal/tracing/attributes.go +++ b/internal/tracing/attributes.go @@ -24,8 +24,6 @@ import ( "strings" "time" - "github.com/docker/compose/v2/pkg/utils" - "github.com/compose-spec/compose-go/v2/types" moby "github.com/docker/docker/api/types" "go.opentelemetry.io/otel/attribute" @@ -35,8 +33,13 @@ import ( // SpanOptions is a small helper type to make it easy to share the options helpers between // downstream functions that accept slices of trace.SpanStartOption and trace.EventOption. type SpanOptions []trace.SpanStartEventOption + +type MetricsKey struct{} + type Metrics struct { - CountExtends int + CountExtends int + CountIncludesLocal int + CountIncludesRemote int } func (s SpanOptions) SpanStartOptions() []trace.SpanStartOption { @@ -75,7 +78,6 @@ func ProjectOptions(ctx context.Context, proj *types.Project) SpanOptions { attribute.StringSlice("project.secrets", proj.SecretNames()), attribute.StringSlice("project.configs", proj.ConfigNames()), attribute.StringSlice("project.extensions", keys(proj.Extensions)), - attribute.StringSlice("project.includes", flattenIncludeReferences(proj.IncludeReferences)), attribute.StringSlice("project.services.active", proj.ServiceNames()), attribute.StringSlice("project.services.disabled", proj.DisabledServiceNames()), attribute.StringSlice("project.services.build", proj.ServicesWithBuild()), @@ -84,8 +86,10 @@ func ProjectOptions(ctx context.Context, proj *types.Project) SpanOptions { attribute.StringSlice("project.services.capabilities.gpu", gpu), attribute.StringSlice("project.services.capabilities.tpu", tpu), } - if metrics, ok := ctx.Value(Metrics{}).(Metrics); ok { + if metrics, ok := ctx.Value(MetricsKey{}).(Metrics); ok { attrs = append(attrs, attribute.Int("project.services.extends", metrics.CountExtends)) + attrs = append(attrs, attribute.Int("project.includes.local", metrics.CountIncludesLocal)) + attrs = append(attrs, attribute.Int("project.includes.remote", metrics.CountIncludesRemote)) } if projHash, ok := projectHash(proj); ok { @@ -168,16 +172,6 @@ func unixTimeAttr(key string, value int64) attribute.KeyValue { return timeAttr(key, time.Unix(value, 0).UTC()) } -func flattenIncludeReferences(includeRefs map[string][]types.IncludeConfig) []string { - ret := utils.NewSet[string]() - for _, included := range includeRefs { - for i := range included { - ret.AddAll(included[i].Path...) - } - } - return ret.Elements() -} - // projectHash returns a checksum from the JSON encoding of the project. func projectHash(p *types.Project) (string, bool) { if p == nil { From 4257567a977657dd1c03c263fb4180976dab9d7c Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 15 Feb 2024 11:23:20 +0100 Subject: [PATCH 22/47] discard stdout for laaarge log test Signed-off-by: Nicolas De Loof --- pkg/e2e/logs_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/e2e/logs_test.go b/pkg/e2e/logs_test.go index 6c641065eb..ff4cbab92d 100644 --- a/pkg/e2e/logs_test.go +++ b/pkg/e2e/logs_test.go @@ -116,7 +116,9 @@ func TestLocalComposeLargeLogs(t *testing.T) { } assert.NilError(t, f.Close()) - res := c.RunDockerComposeCmd(t, "-f", "./fixtures/logs-test/cat.yaml", "--project-name", projectName, "up", "--abort-on-container-exit") + cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/logs-test/cat.yaml", "--project-name", projectName, "up", "--abort-on-container-exit") + cmd.Stdout = io.Discard + res := icmd.RunCmd(cmd) res.Assert(t, icmd.Expected{Out: "test-1 exited with code 0"}) } From 8f9261a5d1449043206554c41f73d313ec0cea32 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 7 Feb 2024 15:07:19 +0100 Subject: [PATCH 23/47] sort containers to optimize scale down Signed-off-by: Nicolas De Loof --- pkg/compose/convergence.go | 18 ++++++++ pkg/e2e/fixtures/scale/compose.yaml | 2 + pkg/e2e/scale_test.go | 72 +++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index e4e3ad1882..6fb6f3e3c9 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -127,6 +127,24 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project, } sort.Slice(containers, func(i, j int) bool { + // select obsolete containers first, so they get removed as we scale down + if obsolete, _ := mustRecreate(service, containers[i], recreate); obsolete { + // i is obsolete, so must be first in the list + return true + } + if obsolete, _ := mustRecreate(service, containers[j], recreate); obsolete { + // j is obsolete, so must be first in the list + return false + } + + // For up-to-date containers, sort by container number to preserve low-values in container numbers + ni, erri := strconv.Atoi(containers[i].Labels[api.ContainerNumberLabel]) + nj, errj := strconv.Atoi(containers[j].Labels[api.ContainerNumberLabel]) + if erri == nil && errj == nil { + return ni < nj + } + + // If we don't get a container number (?) just sort by creation date return containers[i].Created < containers[j].Created }) for i, container := range containers { diff --git a/pkg/e2e/fixtures/scale/compose.yaml b/pkg/e2e/fixtures/scale/compose.yaml index 9ff67af699..619630876b 100644 --- a/pkg/e2e/fixtures/scale/compose.yaml +++ b/pkg/e2e/fixtures/scale/compose.yaml @@ -5,6 +5,8 @@ services: - db db: image: nginx:alpine + environment: + - MAYBE front: image: nginx:alpine deploy: diff --git a/pkg/e2e/scale_test.go b/pkg/e2e/scale_test.go index 1cd80ad1d3..21595dd477 100644 --- a/pkg/e2e/scale_test.go +++ b/pkg/e2e/scale_test.go @@ -95,6 +95,78 @@ func TestScaleWithDepsCases(t *testing.T) { checkServiceContainer(t, res.Combined(), "scale-deps-tests-db", NO_STATE_TO_CHECK, 1) } +func TestScaleUpAndDownPreserveContainerNumber(t *testing.T) { + const projectName = "scale-up-down-test" + + c := NewCLI(t, WithEnv( + "COMPOSE_PROJECT_NAME="+projectName)) + + reset := func() { + c.RunDockerComposeCmd(t, "down", "--rmi", "all") + } + t.Cleanup(reset) + res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db") + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db") + res.Assert(t, icmd.Success) + assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2") + + t.Log("scale down removes replica #2") + res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=1", "db") + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db") + res.Assert(t, icmd.Success) + assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1") + + t.Log("scale up restores replica #2") + res = c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db") + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db") + res.Assert(t, icmd.Success) + assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2") +} + +func TestScaleDownRemovesObsolete(t *testing.T) { + const projectName = "scale-down-obsolete-test" + c := NewCLI(t, WithEnv( + "COMPOSE_PROJECT_NAME="+projectName)) + + reset := func() { + c.RunDockerComposeCmd(t, "down", "--rmi", "all") + } + t.Cleanup(reset) + res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "db") + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db") + res.Assert(t, icmd.Success) + assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1") + + cmd := c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=2", "db") + res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) { + cmd.Env = append(cmd.Env, "MAYBE=value") + }) + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db") + res.Assert(t, icmd.Success) + assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1\n"+projectName+"-db-2") + + t.Log("scale down removes obsolete replica #1") + cmd = c.NewDockerComposeCmd(t, "--project-directory", "fixtures/scale", "up", "-d", "--scale", "db=1", "db") + res = icmd.RunCmd(cmd, func(cmd *icmd.Cmd) { + cmd.Env = append(cmd.Env, "MAYBE=value") + }) + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmd(t, "ps", "--format", "{{.Name}}", "db") + res.Assert(t, icmd.Success) + assert.Equal(t, strings.TrimSpace(res.Stdout()), projectName+"-db-1") +} + func checkServiceContainer(t *testing.T, stdout, containerName, containerState string, count int) { found := 0 lines := strings.Split(stdout, "\n") From 56280530f193ae7f88f41e4aee67b50da8076648 Mon Sep 17 00:00:00 2001 From: Laura Brehm Date: Fri, 16 Feb 2024 12:47:24 +0000 Subject: [PATCH 24/47] ci: bump engine version to `25.0.3` changes: https://github.com/moby/moby/releases/tag/v25.0.3 (Hopefully, this fixes some networking flakyness) Signed-off-by: Laura Brehm --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 770b665ed6..a765c5cd21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,7 +140,7 @@ jobs: - standalone engine: - 24.0.9 - - 25.0.2 + - 25.0.3 steps: - name: Checkout From 75343a578502e717f48f27cf3c36c96b900a8251 Mon Sep 17 00:00:00 2001 From: Gautham Hullikunte Date: Sat, 17 Feb 2024 07:14:56 +0530 Subject: [PATCH 25/47] Issue-11374: Modified compose up command to respect COMPOSE_REMOVE_ORPHANS environment variable Signed-off-by: Gautham Hullikunte <46633282+batcity@users.noreply.github.com> Signed-off-by: Gautham Hullikunte --- cmd/compose/up.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/compose/up.go b/cmd/compose/up.go index a85acf2731..9fed9951a4 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "os" "strings" "time" @@ -104,7 +105,8 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c flags.BoolVar(&create.Build, "build", false, "Build images before starting containers") flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy") flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`) - flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file") + removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans)) + flags.BoolVar(&create.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file") flags.StringArrayVar(&create.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.") flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output") flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs") From f74ee6d5d90cb2a46bd77e55f4ba77763d3a6b14 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 16 Feb 2024 18:18:09 +0100 Subject: [PATCH 26/47] when ran with ANSI disabled, force progress=plain Signed-off-by: Nicolas De Loof --- cmd/compose/compose.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 082a70b694..55fe71688a 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -395,6 +395,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // switch opts.Progress { case ui.ModeAuto: ui.Mode = ui.ModeAuto + if ansi == "never" { + ui.Mode = ui.ModePlain + } case ui.ModeTTY: if ansi == "never" { return fmt.Errorf("can't use --progress tty while ANSI support is disabled") From 5d119026b081ef0097591d51f54fbaa594ba7705 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:51:13 +0100 Subject: [PATCH 27/47] pass QuietOption when starting dependencies from run command Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- cmd/compose/run.go | 11 ++++++----- pkg/e2e/compose_run_test.go | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/compose/run.go b/cmd/compose/run.go index 1803268523..b4a5da365f 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -231,7 +231,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op } buildForDeps = &bo } - return startDependencies(ctx, backend, *project, buildForDeps, options.Service, options.ignoreOrphans) + return startDependencies(ctx, backend, *project, buildForDeps, options) }, dockerCli.Err()) if err != nil { return err @@ -298,11 +298,11 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op return err } -func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, requestedServiceName string, ignoreOrphans bool) error { +func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, options runOptions) error { dependencies := types.Services{} var requestedService types.ServiceConfig for name, service := range project.Services { - if name != requestedServiceName { + if name != options.Service { dependencies[name] = service } else { requestedService = service @@ -310,10 +310,11 @@ func startDependencies(ctx context.Context, backend api.Service, project types.P } project.Services = dependencies - project.DisabledServices[requestedServiceName] = requestedService + project.DisabledServices[options.Service] = requestedService err := backend.Create(ctx, &project, api.CreateOptions{ Build: buildOpts, - IgnoreOrphans: ignoreOrphans, + IgnoreOrphans: options.ignoreOrphans, + QuietPull: options.quietPull, }) if err != nil { return err diff --git a/pkg/e2e/compose_run_test.go b/pkg/e2e/compose_run_test.go index b074e6a80e..e552e4653d 100644 --- a/pkg/e2e/compose_run_test.go +++ b/pkg/e2e/compose_run_test.go @@ -160,4 +160,13 @@ func TestLocalComposeRun(t *testing.T) { c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/deps-not-required.yaml", "down", "--remove-orphans") }) + + t.Run("--quiet-pull", func(t *testing.T) { + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down", "--rmi", "all") + res.Assert(t, icmd.Success) + + res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--quiet-pull", "back") + assert.Assert(t, !strings.Contains(res.Combined(), "Pull complete"), res.Combined()) + assert.Assert(t, strings.Contains(res.Combined(), "Pulled"), res.Combined()) + }) } From dc6d7dd9c3564c0648f36e2fa319008979d44218 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:52:06 +0100 Subject: [PATCH 28/47] add support of QuietOption to create command Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- cmd/compose/create.go | 3 ++- docs/reference/compose_create.md | 1 + docs/reference/docker_compose_create.yaml | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cmd/compose/create.go b/cmd/compose/create.go index fbbeed7008..bf21ee7afc 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -75,6 +75,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers") flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy") flags.StringVar(&opts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never"|"build")`) + flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information") flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed") flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.") flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file") @@ -105,7 +106,7 @@ func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOp RecreateDependencies: createOpts.dependenciesRecreateStrategy(), Inherit: !createOpts.noInherit, Timeout: createOpts.GetTimeout(), - QuietPull: false, + QuietPull: createOpts.quietPull, }) } diff --git a/docs/reference/compose_create.md b/docs/reference/compose_create.md index 386d6359ac..06293625a1 100644 --- a/docs/reference/compose_create.md +++ b/docs/reference/compose_create.md @@ -13,6 +13,7 @@ Creates containers for a service | `--no-build` | | | Don't build an image, even if it's policy | | `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. | | `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never"\|"build") | +| `--quiet-pull` | | | Pull without printing progress information | | `--remove-orphans` | | | Remove containers for services not defined in the Compose file | | `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. | diff --git a/docs/reference/docker_compose_create.yaml b/docs/reference/docker_compose_create.yaml index 34425aebb8..a07e1c88cc 100644 --- a/docs/reference/docker_compose_create.yaml +++ b/docs/reference/docker_compose_create.yaml @@ -57,6 +57,16 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: quiet-pull + value_type: bool + default_value: "false" + description: Pull without printing progress information + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: remove-orphans value_type: bool default_value: "false" From 3989aa1df5d3a111688d0c144b067c019f0ee75a Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:08:53 +0100 Subject: [PATCH 29/47] use an dedicated compose file --quiet-pull e2e test Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/e2e/compose_run_test.go | 4 ++-- pkg/e2e/fixtures/run-test/quiet-pull.yaml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 pkg/e2e/fixtures/run-test/quiet-pull.yaml diff --git a/pkg/e2e/compose_run_test.go b/pkg/e2e/compose_run_test.go index e552e4653d..ceec152a86 100644 --- a/pkg/e2e/compose_run_test.go +++ b/pkg/e2e/compose_run_test.go @@ -162,10 +162,10 @@ func TestLocalComposeRun(t *testing.T) { }) t.Run("--quiet-pull", func(t *testing.T) { - res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "down", "--rmi", "all") + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "down", "--rmi", "all") res.Assert(t, icmd.Success) - res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--quiet-pull", "back") + res = c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/quiet-pull.yaml", "run", "--quiet-pull", "backend") assert.Assert(t, !strings.Contains(res.Combined(), "Pull complete"), res.Combined()) assert.Assert(t, strings.Contains(res.Combined(), "Pulled"), res.Combined()) }) diff --git a/pkg/e2e/fixtures/run-test/quiet-pull.yaml b/pkg/e2e/fixtures/run-test/quiet-pull.yaml new file mode 100644 index 0000000000..922676363f --- /dev/null +++ b/pkg/e2e/fixtures/run-test/quiet-pull.yaml @@ -0,0 +1,3 @@ +services: + backend: + image: hello-world \ No newline at end of file From 3a99e20bb1ba5ddd397e82814c0979cc76f81def Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:40:57 +0100 Subject: [PATCH 30/47] bump compose-go to version v2.0.0-rc.8 Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- cmd/compose/compose.go | 4 +--- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 55fe71688a..fc29014c4e 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -209,8 +209,6 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s po = append(po, cli.WithResourceLoader(r)) } - po = append(po, cli.WithContext(ctx)) - options, err := o.toProjectOptions(po...) if err != nil { return nil, metrics, compose.WrapComposeError(err) @@ -243,7 +241,7 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s api.Separator = "_" } - project, err := cli.ProjectFromOptions(options) + project, err := cli.ProjectFromOptions(ctx, options) if err != nil { return nil, metrics, compose.WrapComposeError(err) } diff --git a/go.mod b/go.mod index dd28fa4876..d0bdbd5b5a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Microsoft/go-winio v0.6.1 github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.0.0-rc.7 + github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.12 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 19dd1afd4c..e8c2d368b3 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.7 h1:koFIK+JwplWu1m/DscSO6MJw7hodaEHOaKQZPUSL4OY= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.7/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 h1:b7l+GqFF+2W4M4kLQUDRTGhqmTiRwT3bYd9X7xrxp5Q= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.8/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= From 476dbee6c7935758288c4290c32edd2b330c9b3c Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:17:16 +0100 Subject: [PATCH 31/47] Add a fallback check of Watch pid on Windows False positives were detected when checking the previous watch process state Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- go.mod | 1 + go.sum | 2 ++ internal/locker/pidfile.go | 7 ----- internal/locker/pidfile_unix.go | 29 ++++++++++++++++++ internal/locker/pidfile_windows.go | 47 ++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 internal/locker/pidfile_unix.go create mode 100644 internal/locker/pidfile_windows.go diff --git a/go.mod b/go.mod index d0bdbd5b5a..05fa0fbfaf 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/jonboulle/clockwork v0.4.0 github.com/mattn/go-shellwords v1.0.12 + github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.13.0-beta1.0.20231219135447-957cb50df991 github.com/moby/patternmatcher v0.6.0 diff --git a/go.sum b/go.sum index e8c2d368b3..22d96bb9b2 100644 --- a/go.sum +++ b/go.sum @@ -331,6 +331,8 @@ github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= diff --git a/internal/locker/pidfile.go b/internal/locker/pidfile.go index ea688fb66f..08dcea1f3a 100644 --- a/internal/locker/pidfile.go +++ b/internal/locker/pidfile.go @@ -18,10 +18,7 @@ package locker import ( "fmt" - "os" "path/filepath" - - "github.com/docker/docker/pkg/pidfile" ) type Pidfile struct { @@ -36,7 +33,3 @@ func NewPidfile(projectName string) (*Pidfile, error) { path := filepath.Join(run, fmt.Sprintf("%s.pid", projectName)) return &Pidfile{path: path}, nil } - -func (f *Pidfile) Lock() error { - return pidfile.Write(f.path, os.Getpid()) -} diff --git a/internal/locker/pidfile_unix.go b/internal/locker/pidfile_unix.go new file mode 100644 index 0000000000..484b65d825 --- /dev/null +++ b/internal/locker/pidfile_unix.go @@ -0,0 +1,29 @@ +//go:build !windows + +/* + Copyright 2023 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package locker + +import ( + "os" + + "github.com/docker/docker/pkg/pidfile" +) + +func (f *Pidfile) Lock() error { + return pidfile.Write(f.path, os.Getpid()) +} diff --git a/internal/locker/pidfile_windows.go b/internal/locker/pidfile_windows.go new file mode 100644 index 0000000000..9f8d4c3ee4 --- /dev/null +++ b/internal/locker/pidfile_windows.go @@ -0,0 +1,47 @@ +//go:build windows + +/* + Copyright 2023 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package locker + +import ( + "github.com/docker/docker/pkg/pidfile" + "github.com/mitchellh/go-ps" + "os" +) + +func (f *Pidfile) Lock() error { + newPID := os.Getpid() + err := pidfile.Write(f.path, newPID) + if err != nil { + // Get PID registered in the file + pid, errPid := pidfile.Read(f.path) + if errPid != nil { + return err + } + // Some users faced issues on Windows where the process written in the pidfile was identified as still existing + // So we used a 2nd process library to verify if this not a false positive feedback + // Check if the process exists + process, errPid := ps.FindProcess(pid) + if process == nil && errPid == nil { + // If the process does not exist, remove the pidfile and try to lock again + _ = os.Remove(f.path) + return pidfile.Write(f.path, newPID) + } + } + return err +} From 531f0f64f3d4dce99130e32a721940c06a113ff8 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 22 Feb 2024 18:27:05 +0100 Subject: [PATCH 32/47] get log to manage `attach` Signed-off-by: Nicolas De Loof --- cmd/compose/logs.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go index a6ce0a8d40..7661b02aed 100644 --- a/cmd/compose/logs.go +++ b/cmd/compose/logs.go @@ -75,6 +75,16 @@ func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, op if err != nil { return err } + + // exclude services configured to ignore output (attach: false), until explicitly selected + if project != nil && len(services) == 0 { + for n, service := range project.Services { + if service.Attach == nil || *service.Attach { + services = append(services, n) + } + } + } + consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !opts.noColor, !opts.noPrefix, false) return backend.Logs(ctx, name, consumer, api.LogOptions{ Project: project, From a2803e9b22b5c0dfaf0d04770074b53b93c613b3 Mon Sep 17 00:00:00 2001 From: Joana Hrotko Date: Tue, 27 Feb 2024 10:18:13 +0000 Subject: [PATCH 33/47] remove docker cli step in ci.yml Signed-off-by: Joana Hrotko --- .github/workflows/ci.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a765c5cd21..16eb4dfd2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,11 +162,6 @@ jobs: go-version-file: 'go.mod' check-latest: true cache: true - - - name: Setup docker CLI - run: | - curl https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz | tar xz - sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version - name: Build uses: docker/bake-action@v2 From 895f451f8ac5b9380f64cb48c9e6ef5ba323d92f Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 28 Feb 2024 12:39:24 +0100 Subject: [PATCH 34/47] restore support for `config --no-interpolate` Signed-off-by: Nicolas De Loof --- cmd/compose/compose.go | 22 ++++++++++- cmd/compose/config.go | 88 ++++++++++++++++++++++++++++++++--------- go.mod | 4 +- go.sum | 4 +- pkg/api/api.go | 2 - pkg/compose/compose.go | 34 +--------------- pkg/compose/publish.go | 13 +----- pkg/compose/pull.go | 18 +++++++++ pkg/e2e/compose_test.go | 24 ++++++----- 9 files changed, 127 insertions(+), 82 deletions(-) diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index fc29014c4e..8e9d525997 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -201,6 +201,24 @@ func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cl return project.Name, nil } +func (o *ProjectOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) { + remotes := o.remoteLoaders(dockerCli) + for _, r := range remotes { + po = append(po, cli.WithResourceLoader(r)) + } + + options, err := o.toProjectOptions(po...) + if err != nil { + return nil, err + } + + if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) { + api.Separator = "_" + } + + return options.LoadModel(ctx) +} + func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { var metrics tracing.Metrics @@ -241,7 +259,7 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s api.Separator = "_" } - project, err := cli.ProjectFromOptions(ctx, options) + project, err := options.LoadProject(ctx) if err != nil { return nil, metrics, compose.WrapComposeError(err) } @@ -468,7 +486,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // psCommand(&opts, dockerCli, backend), listCommand(dockerCli, backend), logsCommand(&opts, dockerCli, backend), - configCommand(&opts, dockerCli, backend), + configCommand(&opts, dockerCli), killCommand(&opts, dockerCli, backend), runCommand(&opts, dockerCli, backend), removeCommand(&opts, dockerCli, backend), diff --git a/cmd/compose/config.go b/cmd/compose/config.go index 481ad776d2..f9176ec7b0 100644 --- a/cmd/compose/config.go +++ b/cmd/compose/config.go @@ -19,6 +19,7 @@ package compose import ( "bytes" "context" + "encoding/json" "fmt" "os" "sort" @@ -28,8 +29,8 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" + "gopkg.in/yaml.v3" - "github.com/docker/compose/v2/internal/tracing" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/compose" ) @@ -51,18 +52,29 @@ type configOptions struct { noConsistency bool } -func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { - po = append(po, +func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) { + po = append(po, o.ToProjectOptions()...) + project, _, err := o.ProjectOptions.ToProject(ctx, dockerCli, services, po...) + return project, err +} + +func (o *configOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) { + po = append(po, o.ToProjectOptions()...) + return o.ProjectOptions.ToModel(ctx, dockerCli, services, po...) +} + +func (o *configOptions) ToProjectOptions() []cli.ProjectOptionsFn { + return []cli.ProjectOptionsFn{ cli.WithInterpolation(!o.noInterpolate), cli.WithResolvedPaths(!o.noResolvePath), cli.WithNormalization(!o.noNormalize), cli.WithConsistency(!o.noConsistency), cli.WithDefaultProfiles(o.Profiles...), - cli.WithDiscardEnvFile) - return o.ProjectOptions.ToProject(ctx, dockerCli, services, po...) + cli.WithDiscardEnvFile, + } } -func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command { opts := configOptions{ ProjectOptions: p, } @@ -100,7 +112,7 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return runConfigImages(ctx, dockerCli, opts, args) } - return runConfig(ctx, dockerCli, backend, opts, args) + return runConfig(ctx, dockerCli, opts, args) }), ValidArgsFunction: completeServiceNames(dockerCli, p), } @@ -123,18 +135,56 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return cmd } -func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, opts configOptions, services []string) error { +func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error { var content []byte - project, _, err := opts.ToProject(ctx, dockerCli, services) + model, err := opts.ToModel(ctx, dockerCli, services) if err != nil { return err } - content, err = backend.Config(ctx, project, api.ConfigOptions{ - Format: opts.Format, - Output: opts.Output, - ResolveImageDigests: opts.resolveImageDigests, - }) + if opts.resolveImageDigests { + // create a pseudo-project so we can rely on WithImagesResolved to resolve images + p := &types.Project{ + Services: types.Services{}, + } + services := model["services"].(map[string]any) + for name, s := range services { + service := s.(map[string]any) + if image, ok := service["image"]; ok { + p.Services[name] = types.ServiceConfig{ + Image: image.(string), + } + } + } + + p, err = p.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client())) + if err != nil { + return err + } + + for name, s := range services { + service := s.(map[string]any) + config := p.Services[name] + if config.Image != "" { + service["image"] = config.Image + } + services[name] = service + } + model["services"] = services + } + + switch opts.Format { + case "json": + content, err = json.MarshalIndent(model, "", " ") + case "yaml": + buf := bytes.NewBuffer([]byte{}) + encoder := yaml.NewEncoder(buf) + encoder.SetIndent(2) + err = encoder.Encode(model) + content = buf.Bytes() + default: + return fmt.Errorf("unsupported format %q", opts.Format) + } if err != nil { return err } @@ -155,7 +205,7 @@ func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, } func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error { - project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) + project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -167,7 +217,7 @@ func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) } func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error { - project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) + project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -182,7 +232,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err if opts.hash != "*" { services = append(services, strings.Split(opts.hash, ",")...) } - project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) + project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -218,7 +268,7 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error { set := map[string]struct{}{} - project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) + project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) if err != nil { return err } @@ -239,7 +289,7 @@ func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions, } func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error { - project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) + project, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) if err != nil { return err } diff --git a/go.mod b/go.mod index 05fa0fbfaf..65d2ed7a2d 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Microsoft/go-winio v0.6.1 github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 + github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.12 github.com/davecgh/go-spew v1.1.1 @@ -49,6 +49,7 @@ require ( golang.org/x/sync v0.6.0 golang.org/x/sys v0.16.0 google.golang.org/grpc v1.59.0 + gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.5.1 ) @@ -170,7 +171,6 @@ require ( google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.26.7 // indirect k8s.io/apimachinery v0.26.7 // indirect k8s.io/apiserver v0.26.7 // indirect diff --git a/go.sum b/go.sum index 22d96bb9b2..adabccef18 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.8 h1:b7l+GqFF+2W4M4kLQUDRTGhqmTiRwT3bYd9X7xrxp5Q= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.8/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60 h1:NlkpaLBPFr05mNJWVMH7PP4L30gFG6k4z1QpypLUSh8= +github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= diff --git a/pkg/api/api.go b/pkg/api/api.go index e62b2ffba3..957818cd83 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -53,8 +53,6 @@ type Service interface { Ps(ctx context.Context, projectName string, options PsOptions) ([]ContainerSummary, error) // List executes the equivalent to a `docker stack ls` List(ctx context.Context, options ListOptions) ([]Stack, error) - // Config executes the equivalent to a `compose config` - Config(ctx context.Context, project *types.Project, options ConfigOptions) ([]byte, error) // Kill executes the equivalent to a `compose kill` Kill(ctx context.Context, projectName string, options KillOptions) error // RunOneOffContainer creates a service oneoff container and starts its dependencies diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index f1db511230..a025b9d908 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -25,12 +25,10 @@ import ( "strings" "sync" - "github.com/jonboulle/clockwork" - "github.com/docker/docker/api/types/volume" + "github.com/jonboulle/clockwork" "github.com/compose-spec/compose-go/v2/types" - "github.com/distribution/reference" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/flags" @@ -40,7 +38,6 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" - "github.com/opencontainers/go-digest" ) var stdioToStdout bool @@ -147,35 +144,6 @@ func getContainerNameWithoutProject(c moby.Container) string { return name[len(project)+1:] } -func (s *composeService) Config(ctx context.Context, project *types.Project, options api.ConfigOptions) ([]byte, error) { - if options.ResolveImageDigests { - var err error - project, err = project.WithImagesResolved(func(named reference.Named) (digest.Digest, error) { - auth, err := encodedAuth(named, s.configFile()) - if err != nil { - return "", err - } - inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth) - if err != nil { - return "", err - } - return inspect.Descriptor.Digest, nil - }) - if err != nil { - return nil, err - } - } - - switch options.Format { - case "json": - return project.MarshalJSON() - case "yaml": - return project.MarshalYAML() - default: - return nil, fmt.Errorf("unsupported format %q", options.Format) - } -} - // projectFromName builds a types.Project based on actual resources with compose labels set func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) { project := &types.Project{ diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index 406da5ec2b..a01d7be584 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -26,7 +26,6 @@ import ( "github.com/docker/compose/v2/internal/ocipush" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" - "github.com/opencontainers/go-digest" ) func (s *composeService) Publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error { @@ -111,17 +110,7 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje if err != nil { return nil, err } - project, err = project.WithImagesResolved(func(named reference.Named) (digest.Digest, error) { - auth, err := encodedAuth(named, s.configFile()) - if err != nil { - return "", err - } - inspect, err := s.apiClient().DistributionInspect(ctx, named.String(), auth) - if err != nil { - return "", err - } - return inspect.Descriptor.Digest, nil - }) + project, err = project.WithImagesResolved(ImageDigestResolver(ctx, s.configFile(), s.apiClient())) if err != nil { return nil, err } diff --git a/pkg/compose/pull.go b/pkg/compose/pull.go index 977397bd09..222876d657 100644 --- a/pkg/compose/pull.go +++ b/pkg/compose/pull.go @@ -28,10 +28,13 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/distribution/reference" "github.com/docker/buildx/driver" + "github.com/docker/cli/cli/config/configfile" moby "github.com/docker/docker/api/types" + "github.com/docker/docker/client" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/registry" "github.com/hashicorp/go-multierror" + "github.com/opencontainers/go-digest" "golang.org/x/sync/errgroup" "github.com/docker/compose/v2/pkg/api" @@ -242,6 +245,21 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser return inspected.ID, nil } +// ImageDigestResolver creates a func able to resolve image digest from a docker ref, +func ImageDigestResolver(ctx context.Context, file *configfile.ConfigFile, apiClient client.APIClient) func(named reference.Named) (digest.Digest, error) { + return func(named reference.Named) (digest.Digest, error) { + auth, err := encodedAuth(named, file) + if err != nil { + return "", err + } + inspect, err := apiClient.DistributionInspect(ctx, named.String(), auth) + if err != nil { + return "", err + } + return inspect.Descriptor.Digest, nil + } +} + func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) { repoInfo, err := registry.ParseRepositoryInfo(ref) if err != nil { diff --git a/pkg/e2e/compose_test.go b/pkg/e2e/compose_test.go index b37ec0f485..28d8b0eb74 100644 --- a/pkg/e2e/compose_test.go +++ b/pkg/e2e/compose_test.go @@ -235,7 +235,7 @@ func TestCompatibility(t *testing.T) { }) } -func TestConvert(t *testing.T) { +func TestConfig(t *testing.T) { const projectName = "compose-e2e-convert" c := NewParallelCLI(t) @@ -244,20 +244,22 @@ func TestConvert(t *testing.T) { t.Run("up", func(t *testing.T) { res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert") - res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services: + res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s +networks: + default: + name: compose-e2e-convert_default +services: nginx: build: context: %s dockerfile: Dockerfile networks: default: null -networks: - default: - name: compose-e2e-convert_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0}) +`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0}) }) } -func TestConvertInterpolate(t *testing.T) { +func TestConfigInterpolate(t *testing.T) { const projectName = "compose-e2e-convert-interpolate" c := NewParallelCLI(t) @@ -266,16 +268,18 @@ func TestConvertInterpolate(t *testing.T) { t.Run("convert", func(t *testing.T) { res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose-interpolate.yaml", "-p", projectName, "convert", "--no-interpolate") - res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`services: + res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s +networks: + default: + name: compose-e2e-convert-interpolate_default +services: nginx: build: context: %s dockerfile: ${MYVAR} networks: default: null -networks: - default: - name: compose-e2e-convert-interpolate_default`, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0}) +`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0}) }) } From 96de87abf42c840f1b6996969fb1f2fd9d8ea69a Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 28 Feb 2024 17:11:50 +0100 Subject: [PATCH 35/47] move code into small functions for better readability Signed-off-by: Nicolas De Loof --- cmd/compose/config.go | 89 +++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/cmd/compose/config.go b/cmd/compose/config.go index f9176ec7b0..8631956cee 100644 --- a/cmd/compose/config.go +++ b/cmd/compose/config.go @@ -136,55 +136,19 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command { } func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error { - var content []byte model, err := opts.ToModel(ctx, dockerCli, services) if err != nil { return err } if opts.resolveImageDigests { - // create a pseudo-project so we can rely on WithImagesResolved to resolve images - p := &types.Project{ - Services: types.Services{}, - } - services := model["services"].(map[string]any) - for name, s := range services { - service := s.(map[string]any) - if image, ok := service["image"]; ok { - p.Services[name] = types.ServiceConfig{ - Image: image.(string), - } - } - } - - p, err = p.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client())) + err = resolveImageDigests(ctx, dockerCli, model) if err != nil { return err } - - for name, s := range services { - service := s.(map[string]any) - config := p.Services[name] - if config.Image != "" { - service["image"] = config.Image - } - services[name] = service - } - model["services"] = services } - switch opts.Format { - case "json": - content, err = json.MarshalIndent(model, "", " ") - case "yaml": - buf := bytes.NewBuffer([]byte{}) - encoder := yaml.NewEncoder(buf) - encoder.SetIndent(2) - err = encoder.Encode(model) - content = buf.Bytes() - default: - return fmt.Errorf("unsupported format %q", opts.Format) - } + content, err := formatModel(model, opts.Format) if err != nil { return err } @@ -204,6 +168,55 @@ func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, s return err } +func resolveImageDigests(ctx context.Context, dockerCli command.Cli, model map[string]any) (err error) { + // create a pseudo-project so we can rely on WithImagesResolved to resolve images + p := &types.Project{ + Services: types.Services{}, + } + services := model["services"].(map[string]any) + for name, s := range services { + service := s.(map[string]any) + if image, ok := service["image"]; ok { + p.Services[name] = types.ServiceConfig{ + Image: image.(string), + } + } + } + + p, err = p.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client())) + if err != nil { + return err + } + + // Collect image resolved with digest and update model accordingly + for name, s := range services { + service := s.(map[string]any) + config := p.Services[name] + if config.Image != "" { + service["image"] = config.Image + } + services[name] = service + } + model["services"] = services + return nil +} + +func formatModel(model map[string]any, format string) (content []byte, err error) { + switch format { + case "json": + content, err = json.MarshalIndent(model, "", " ") + case "yaml": + buf := bytes.NewBuffer([]byte{}) + encoder := yaml.NewEncoder(buf) + encoder.SetIndent(2) + err = encoder.Encode(model) + content = buf.Bytes() + default: + return nil, fmt.Errorf("unsupported format %q", format) + } + return +} + func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error { project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution) if err != nil { From d05c176f054fa74f4020024b8ecca6fb91d4ba18 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 19 Feb 2024 15:38:45 +0100 Subject: [PATCH 36/47] introduce --watch Signed-off-by: Nicolas De Loof --- cmd/compose/up.go | 6 +- cmd/compose/watch.go | 13 +++-- cmd/formatter/logs.go | 10 +++- docs/reference/compose_up.md | 1 + docs/reference/docker_compose_up.yaml | 12 ++++ pkg/api/api.go | 7 ++- pkg/compose/build.go | 3 + pkg/compose/up.go | 9 +++ pkg/compose/watch.go | 82 ++++++++++++++------------- pkg/compose/watch_test.go | 25 +++++++- 10 files changed, 118 insertions(+), 50 deletions(-) diff --git a/cmd/compose/up.go b/cmd/compose/up.go index 9fed9951a4..084e010fad 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -24,11 +24,10 @@ import ( "strings" "time" - xprogress "github.com/moby/buildkit/util/progress/progressui" - "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/cmd/formatter" + xprogress "github.com/moby/buildkit/util/progress/progressui" "github.com/spf13/cobra" "github.com/docker/compose/v2/pkg/api" @@ -55,6 +54,7 @@ type upOptions struct { timestamp bool wait bool waitTimeout int + watch bool } func (opts upOptions) apply(project *types.Project, services []string) (*types.Project, error) { @@ -126,6 +126,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services") flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.") flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy") + flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.") return upCmd } @@ -257,6 +258,7 @@ func runUp( CascadeStop: upOptions.cascadeStop, Wait: upOptions.wait, WaitTimeout: timeout, + Watch: upOptions.watch, Services: services, }, }) diff --git a/cmd/compose/watch.go b/cmd/compose/watch.go index e4f9427263..880711a205 100644 --- a/cmd/compose/watch.go +++ b/cmd/compose/watch.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/compose/v2/cmd/formatter" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/internal/locker" @@ -31,8 +32,7 @@ import ( type watchOptions struct { *ProjectOptions - quiet bool - noUp bool + noUp bool } func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { @@ -57,7 +57,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) ValidArgsFunction: completeServiceNames(dockerCli, p), } - cmd.Flags().BoolVar(&watchOpts.quiet, "quiet", false, "hide build output") + cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output") cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching") return cmd } @@ -101,7 +101,7 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w Recreate: api.RecreateDiverged, RecreateDependencies: api.RecreateNever, Inherit: true, - QuietPull: watchOpts.quiet, + QuietPull: buildOpts.quiet, }, Start: api.StartOptions{ Project: project, @@ -114,7 +114,10 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w return err } } + + consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false) return backend.Watch(ctx, project, services, api.WatchOptions{ - Build: build, + Build: &build, + LogTo: consumer, }) } diff --git a/cmd/formatter/logs.go b/cmd/formatter/logs.go index c6b8baa320..465aa229a9 100644 --- a/cmd/formatter/logs.go +++ b/cmd/formatter/logs.go @@ -62,7 +62,11 @@ func (l *logConsumer) Register(name string) { func (l *logConsumer) register(name string) *presenter { cf := monochrome if l.color { - cf = nextColor() + if name == api.WatchLogger { + cf = makeColorFunc("92") + } else { + cf = nextColor() + } } p := &presenter{ colors: cf, @@ -138,5 +142,9 @@ type presenter struct { } func (p *presenter) setPrefix(width int) { + if p.name == api.WatchLogger { + p.prefix = p.colors(strings.Repeat(" ", width) + " ⦿ ") + return + } p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name)) } diff --git a/docs/reference/compose_up.md b/docs/reference/compose_up.md index 38d678d00f..a34766c484 100644 --- a/docs/reference/compose_up.md +++ b/docs/reference/compose_up.md @@ -32,6 +32,7 @@ Create and start containers | `--timestamps` | | | Show timestamps | | `--wait` | | | Wait for services to be running\|healthy. Implies detached mode. | | `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy | +| `-w`, `--watch` | | | Watch source code and rebuild/refresh containers when files are updated. | diff --git a/docs/reference/docker_compose_up.yaml b/docs/reference/docker_compose_up.yaml index 0ed06a09b2..ec269c8b85 100644 --- a/docs/reference/docker_compose_up.yaml +++ b/docs/reference/docker_compose_up.yaml @@ -274,6 +274,18 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: watch + shorthand: w + value_type: bool + default_value: "false" + description: | + Watch source code and rebuild/refresh containers when files are updated. + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false inherited_options: - option: dry-run value_type: bool diff --git a/pkg/api/api.go b/pkg/api/api.go index 957818cd83..641840a3e5 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -115,9 +115,13 @@ type VizOptions struct { Indentation string } +// WatchLogger is a reserved name to log watch events +const WatchLogger = "#watch" + // WatchOptions group options of the Watch API type WatchOptions struct { - Build BuildOptions + Build *BuildOptions + LogTo LogConsumer } // BuildOptions group options of the Build API @@ -217,6 +221,7 @@ type StartOptions struct { WaitTimeout time.Duration // Services passed in the command line to be started Services []string + Watch bool } // RestartOptions group options of the Restart API diff --git a/pkg/compose/build.go b/pkg/compose/build.go index c06f1cb4c2..d9e38e6cc9 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -134,6 +134,9 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti outPrinter = options.OutPrinter } + if options.Quiet { + options.Progress = progress.ModeQuiet + } w, err = xprogress.NewPrinter(progressCtx, progressPrinter(outPrinter), progressui.DisplayMode(options.Progress), xprogress.WithDesc( fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver), diff --git a/pkg/compose/up.go b/pkg/compose/up.go index c1dafb5947..b91a4ef73d 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -125,6 +125,15 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options return err }) + if options.Start.Watch { + eg.Go(func() error { + return s.Watch(ctx, project, options.Start.Services, api.WatchOptions{ + Build: options.Create.Build, + LogTo: options.Start.Attach, + }) + }) + } + // We don't use parent (cancelable) context as we manage sigterm to stop the stack err = s.start(context.Background(), project.Name, options.Start, printer.HandleEvent) if err != nil && !isTerminated { // Ignore error if the process is terminated diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go index 2e783026e1..dca4d7c153 100644 --- a/pkg/compose/watch.go +++ b/pkg/compose/watch.go @@ -76,6 +76,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv } eg, ctx := errgroup.WithContext(ctx) watching := false + options.LogTo.Register(api.WatchLogger) for i := range project.Services { service := project.Services[i] config, err := loadDevelopmentConfig(service, project) @@ -91,9 +92,15 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv continue } - if len(config.Watch) > 0 && service.Build == nil { - // service configured with watchers but no build section - return fmt.Errorf("can't watch service %q without a build context", service.Name) + for _, trigger := range config.Watch { + if trigger.Action == types.WatchActionRebuild { + if service.Build == nil { + return fmt.Errorf("can't watch service %q with action %s without a build context", service.Name, types.WatchActionRebuild) + } + if options.Build == nil { + return fmt.Errorf("--no-build is incompatible with watch action %s in service %s", types.WatchActionRebuild, service.Name) + } + } } if len(services) > 0 && service.Build == nil { @@ -142,9 +149,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv return err } - fmt.Fprintf( - s.stdinfo(), - "Watch configuration for service %q:%s\n", + logrus.Debugf("Watch configuration for service %q:%s\n", service.Name, strings.Join(append([]string{""}, pathLogs...), "\n - "), ) @@ -163,6 +168,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv if !watching { return fmt.Errorf("none of the selected services is configured for watch, consider setting an 'develop' section") } + options.LogTo.Log(api.WatchLogger, "watch enabled") return eg.Wait() } @@ -190,7 +196,7 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, name case batch := <-batchEvents: start := time.Now() logrus.Debugf("batch start: service[%s] count[%d]", name, len(batch)) - if err := s.handleWatchBatch(ctx, project, name, options.Build, batch, syncer); err != nil { + if err := s.handleWatchBatch(ctx, project, name, options, batch, syncer); err != nil { logrus.Warnf("Error handling changed files for service %s: %v", name, err) } logrus.Debugf("batch complete: service[%s] duration[%s] count[%d]", @@ -431,32 +437,38 @@ func (t tarDockerClient) Untar(ctx context.Context, id string, archive io.ReadCl }) } -func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Project, serviceName string, build api.BuildOptions, batch []fileEvent, syncer sync.Syncer) error { +func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Project, serviceName string, options api.WatchOptions, batch []fileEvent, syncer sync.Syncer) error { pathMappings := make([]sync.PathMapping, len(batch)) restartService := false for i := range batch { if batch[i].Action == types.WatchActionRebuild { - fmt.Fprintf( - s.stdinfo(), - "Rebuilding service %q after changes were detected:%s\n", - serviceName, - strings.Join(append([]string{""}, batch[i].HostPath), "\n - "), - ) + options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service %q after changes were detected...", serviceName)) // restrict the build to ONLY this service, not any of its dependencies - build.Services = []string{serviceName} - err := s.Up(ctx, project, api.UpOptions{ - Create: api.CreateOptions{ - Build: &build, - Services: []string{serviceName}, - Inherit: true, - }, - Start: api.StartOptions{ - Services: []string{serviceName}, - Project: project, - }, + options.Build.Services = []string{serviceName} + options.Build.Quiet = true + _, err := s.build(ctx, project, *options.Build, nil) + if err != nil { + options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Build failed. Error: %v", err)) + return err + } + options.LogTo.Log(api.WatchLogger, fmt.Sprintf("service %q successfully built", serviceName)) + + err = s.create(ctx, project, api.CreateOptions{ + Services: []string{serviceName}, + Inherit: true, + Recreate: api.RecreateForce, }) if err != nil { - fmt.Fprintf(s.stderr(), "Application failed to start after update. Error: %v\n", err) + options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Failed to recreate service after update. Error: %v", err)) + return err + } + + err = s.start(ctx, project.Name, api.StartOptions{ + Project: project, + Services: []string{serviceName}, + }, nil) + if err != nil { + options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Application failed to start after update. Error: %v", err)) } return nil } @@ -466,7 +478,7 @@ func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Pr pathMappings[i] = batch[i].PathMapping } - writeWatchSyncMessage(s.stdinfo(), serviceName, pathMappings) + writeWatchSyncMessage(options.LogTo, serviceName, pathMappings) service, err := project.GetService(serviceName) if err != nil { @@ -486,29 +498,19 @@ func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Pr } // writeWatchSyncMessage prints out a message about the sync for the changed paths. -func writeWatchSyncMessage(w io.Writer, serviceName string, pathMappings []sync.PathMapping) { +func writeWatchSyncMessage(log api.LogConsumer, serviceName string, pathMappings []sync.PathMapping) { const maxPathsToShow = 10 if len(pathMappings) <= maxPathsToShow || logrus.IsLevelEnabled(logrus.DebugLevel) { hostPathsToSync := make([]string, len(pathMappings)) for i := range pathMappings { hostPathsToSync[i] = pathMappings[i].HostPath } - fmt.Fprintf( - w, - "Syncing %q after changes were detected:%s\n", - serviceName, - strings.Join(append([]string{""}, hostPathsToSync...), "\n - "), - ) + log.Log(api.WatchLogger, fmt.Sprintf("Syncing %q after changes were detected", serviceName)) } else { hostPathsToSync := make([]string, len(pathMappings)) for i := range pathMappings { hostPathsToSync[i] = pathMappings[i].HostPath } - fmt.Fprintf( - w, - "Syncing service %q after %d changes were detected\n", - serviceName, - len(pathMappings), - ) + log.Log(api.WatchLogger, fmt.Sprintf("Syncing service %q after %d changes were detected", serviceName, len(pathMappings))) } } diff --git a/pkg/compose/watch_test.go b/pkg/compose/watch_test.go index fc39ee7d34..39fbf2bcb2 100644 --- a/pkg/compose/watch_test.go +++ b/pkg/compose/watch_test.go @@ -16,6 +16,7 @@ package compose import ( "context" + "fmt" "os" "testing" "time" @@ -91,10 +92,29 @@ func (t testWatcher) Errors() chan error { return t.errors } +type stdLogger struct{} + +func (s stdLogger) Log(containerName, message string) { + fmt.Printf("%s: %s\n", containerName, message) +} + +func (s stdLogger) Err(containerName, message string) { + fmt.Fprintf(os.Stderr, "%s: %s\n", containerName, message) +} + +func (s stdLogger) Status(container, msg string) { + fmt.Printf("%s: %s\n", container, msg) +} + +func (s stdLogger) Register(container string) { + +} + func TestWatch_Sync(t *testing.T) { mockCtrl := gomock.NewController(t) cli := mocks.NewMockCli(mockCtrl) cli.EXPECT().Err().Return(os.Stderr).AnyTimes() + cli.EXPECT().BuildKitEnabled().Return(true, nil) apiClient := mocks.NewMockAPIClient(mockCtrl) apiClient.EXPECT().ContainerList(gomock.Any(), gomock.Any()).Return([]moby.Container{ testContainer("test", "123", false), @@ -124,7 +144,10 @@ func TestWatch_Sync(t *testing.T) { dockerCli: cli, clock: clock, } - err := service.watch(ctx, &proj, "test", api.WatchOptions{}, watcher, syncer, []types.Trigger{ + err := service.watch(ctx, &proj, "test", api.WatchOptions{ + Build: &api.BuildOptions{}, + LogTo: stdLogger{}, + }, watcher, syncer, []types.Trigger{ { Path: "/sync", Action: "sync", From fcef7a93a803425d6dc95bddd7ee01357bd40d44 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 29 Feb 2024 09:12:56 +0100 Subject: [PATCH 37/47] avoid duplicated "xx exited with code 0" message Signed-off-by: Nicolas De Loof --- pkg/compose/printer.go | 15 +++++++++------ pkg/compose/up.go | 4 +++- pkg/compose/watch.go | 1 - 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/compose/printer.go b/pkg/compose/printer.go index e54c8c8bf6..45b0f7914d 100644 --- a/pkg/compose/printer.go +++ b/pkg/compose/printer.go @@ -86,7 +86,8 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error ) defer p.Stop() - containers := map[string]struct{}{} + // containers we are tracking. Use true when container is running, false after we receive a stop|die signal + containers := map[string]bool{} for { select { case <-p.stopCh: @@ -100,18 +101,20 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error if _, ok := containers[id]; ok { continue } - containers[id] = struct{}{} + containers[id] = true p.consumer.Register(container) case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated: - if !event.Restarting { - delete(containers, id) - } - if !aborting { + if !aborting && containers[id] { p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode)) if event.Type == api.ContainerEventRecreated { p.consumer.Status(container, "has been recreated") } } + containers[id] = false + if !event.Restarting { + delete(containers, id) + } + if cascadeStop { if !aborting { aborting = true diff --git a/pkg/compose/up.go b/pkg/compose/up.go index b91a4ef73d..ab22de48b7 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -127,8 +127,10 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options if options.Start.Watch { eg.Go(func() error { + buildOpts := *options.Create.Build + buildOpts.Quiet = true return s.Watch(ctx, project, options.Start.Services, api.WatchOptions{ - Build: options.Create.Build, + Build: &buildOpts, LogTo: options.Start.Attach, }) }) diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go index dca4d7c153..2a651baac1 100644 --- a/pkg/compose/watch.go +++ b/pkg/compose/watch.go @@ -445,7 +445,6 @@ func (s *composeService) handleWatchBatch(ctx context.Context, project *types.Pr options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Rebuilding service %q after changes were detected...", serviceName)) // restrict the build to ONLY this service, not any of its dependencies options.Build.Services = []string{serviceName} - options.Build.Quiet = true _, err := s.build(ctx, project, *options.Build, nil) if err != nil { options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Build failed. Error: %v", err)) From ba5792e56b4f48ec914da61e298696eb4fc9705b Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 21 Feb 2024 14:30:14 +0100 Subject: [PATCH 38/47] make code simpler Signed-off-by: Nicolas De Loof --- pkg/compose/containers.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/compose/containers.go b/pkg/compose/containers.go index ce17228926..cd75eeff56 100644 --- a/pkg/compose/containers.go +++ b/pkg/compose/containers.go @@ -127,13 +127,7 @@ func isNotService(services ...string) containerPredicate { // isOrphaned is a predicate to select containers without a matching service definition in compose project func isOrphaned(project *types.Project) containerPredicate { - var services []string - for _, s := range project.Services { - services = append(services, s.Name) - } - for _, s := range project.DisabledServices { - services = append(services, s.Name) - } + services := append(project.ServiceNames(), project.DisabledServiceNames()...) return func(c moby.Container) bool { service := c.Labels[api.ServiceLabel] return !utils.StringContains(services, service) From cd93bac1c8c4c95198e09ca0dfa926c19065277e Mon Sep 17 00:00:00 2001 From: Joana Hrotko Date: Tue, 27 Feb 2024 10:18:13 +0000 Subject: [PATCH 39/47] Add test summary for test jobs in ci Signed-off-by: Joana Hrotko --- .github/workflows/ci.yml | 14 ++++++++++++-- Dockerfile | 5 ++++- Makefile | 4 ++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16eb4dfd2a..c3637bd466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,7 +129,12 @@ jobs: name: coverage-data-unit path: bin/coverage/unit/ if-no-files-found: error - + - + name: Unit Test Summary + uses: test-summary/action@v2 + with: + paths: bin/coverage/unit/report.xml + if: always() e2e: runs-on: ubuntu-latest strategy: @@ -202,7 +207,12 @@ jobs: rm -f /usr/local/bin/docker-compose cp bin/build/docker-compose /usr/local/bin make e2e-compose-standalone - + - + name: e2e Test Summary + uses: test-summary/action@v2 + with: + paths: /tmp/report/report.xml + if: always() coverage: runs-on: ubuntu-22.04 needs: diff --git a/Dockerfile b/Dockerfile index 51e4a52ffe..529856386e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -106,11 +106,14 @@ RUN --mount=type=bind,target=. \ --mount=type=cache,target=/go/pkg/mod \ rm -rf /tmp/coverage && \ mkdir -p /tmp/coverage && \ - go test -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/tmp/coverage" && \ + rm -rf /tmp/report && \ + mkdir -p /tmp/report && \ + go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/tmp/coverage" && \ go tool covdata percent -i=/tmp/coverage FROM scratch AS test-coverage COPY --from=test --link /tmp/coverage / +COPY --from=test --link /tmp/report / FROM base AS license-set ARG LICENSE_FILES diff --git a/Makefile b/Makefile index 2bce970904..df30dc8600 100644 --- a/Makefile +++ b/Makefile @@ -75,11 +75,11 @@ install: binary .PHONY: e2e-compose e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test - go test -v $(TEST_FLAGS) -count=1 ./pkg/e2e + go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e .PHONY: e2e-compose-standalone e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test - go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e + go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e .PHONY: build-and-e2e-compose build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test From 5a1c495caa83a863b664f8de91bb0b67e6231493 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Tue, 5 Mar 2024 15:39:18 -0500 Subject: [PATCH 40/47] ci(deps): bump moby/moby & docker/cli to v25.0.4 (#11566) --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 65d2ed7a2d..f8435dda6f 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/distribution/reference v0.5.0 github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315 - github.com/docker/cli v25.0.3+incompatible + github.com/docker/cli v25.0.4-0.20240305161310-2bf4225ad269+incompatible github.com/docker/cli-docs-tool v0.6.0 - github.com/docker/docker v25.0.1+incompatible + github.com/docker/docker v25.0.4-0.20240301160236-51e876cd964c+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 github.com/fsnotify/fsevents v0.1.1 diff --git a/go.sum b/go.sum index adabccef18..4c22388053 100644 --- a/go.sum +++ b/go.sum @@ -124,15 +124,15 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315 h1:UZxx9xBADdf/9UmSdEUi+pdJoPKpgcf9QUAY5gEIYmY= github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315/go.mod h1:X8ZHhuW6ncwtoJ36TlU+gyaROTcBkTE01VHYmTStQCE= -github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= -github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v25.0.4-0.20240305161310-2bf4225ad269+incompatible h1:xhVCHXq+P5LhT31+RuDuk0xXEbEnd50Fr37J1bGuyWg= +github.com/docker/cli v25.0.4-0.20240305161310-2bf4225ad269+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli-docs-tool v0.6.0 h1:Z9x10SaZgFaB6jHgz3OWooynhSa40CsWkpe5hEnG/qA= github.com/docker/cli-docs-tool v0.6.0/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v25.0.1+incompatible h1:k5TYd5rIVQRSqcTwCID+cyVA0yRg86+Pcrz1ls0/frA= -github.com/docker/docker v25.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v25.0.4-0.20240301160236-51e876cd964c+incompatible h1:sCE9u4l5Kr3Z0pvUEAC6XKe/wnH6Q4O19I/0Mcqlxz8= +github.com/docker/docker v25.0.4-0.20240301160236-51e876cd964c+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= From 0e527d4f2e15192eecb9d0464df215fd770e236f Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Tue, 5 Mar 2024 16:23:52 -0500 Subject: [PATCH 41/47] chore(deps): upgrade go to 1.21.8 (#11578) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 529856386e..a6f74d07d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -ARG GO_VERSION=1.21.6 +ARG GO_VERSION=1.21.8 ARG XX_VERSION=1.2.1 ARG GOLANGCI_LINT_VERSION=v1.55.2 ARG ADDLICENSE_VERSION=v1.0.0 From 7b7f6a090eb073497f0a6cb96ebbffa016bad790 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Tue, 12 Mar 2024 09:47:41 -0400 Subject: [PATCH 42/47] feat(desktop): add Docker Desktop detection and client skeleton (#11593) --- cmd/compose/compose.go | 33 +++++-- cmd/main.go | 13 +-- go.mod | 2 +- internal/desktop/client.go | 93 ++++++++++++++++++++ internal/desktop/integration.go | 25 ++++++ internal/memnet/conn.go | 50 +++++++++++ internal/{tracing => memnet}/conn_unix.go | 19 ++-- internal/{tracing => memnet}/conn_windows.go | 16 ++-- internal/tracing/docker_context.go | 5 +- pkg/compose/compose.go | 21 ++++- pkg/compose/desktop.go | 77 ++++++++++++++++ 11 files changed, 318 insertions(+), 36 deletions(-) create mode 100644 internal/desktop/client.go create mode 100644 internal/desktop/integration.go create mode 100644 internal/memnet/conn.go rename internal/{tracing => memnet}/conn_unix.go (64%) rename internal/{tracing => memnet}/conn_windows.go (73%) create mode 100644 pkg/compose/desktop.go diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 8e9d525997..8af123e517 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -37,6 +37,7 @@ import ( "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/cmd/formatter" + "github.com/docker/compose/v2/internal/desktop" "github.com/docker/compose/v2/internal/tracing" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/compose" @@ -365,11 +366,17 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // } }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + // (1) process env vars err := setEnvWithDotEnv(&opts) if err != nil { return err } parent := cmd.Root() + + // (2) call parent pre-run + // TODO(milas): this seems incorrect, remove or document if parent != nil { parentPrerun := parent.PersistentPreRunE if parentPrerun != nil { @@ -379,6 +386,11 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // } } } + + // (3) set up display/output + if verbose { + logrus.SetLevel(logrus.TraceLevel) + } if noAnsi { if ansi != "auto" { return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`) @@ -386,14 +398,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // ansi = "never" fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n") } - if verbose { - logrus.SetLevel(logrus.TraceLevel) - } - if v, ok := os.LookupEnv("COMPOSE_ANSI"); ok && !cmd.Flags().Changed("ansi") { ansi = v } - formatter.SetANSIMode(dockerCli, ansi) if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" { @@ -430,6 +437,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // return fmt.Errorf("unsupported --progress value %q", opts.Progress) } + // (4) options validation / normalization if opts.WorkDir != "" { if opts.ProjectDir != "" { return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`) @@ -466,13 +474,26 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { // parallel = i } if parallel > 0 { + logrus.Debugf("Limiting max concurrency to %d jobs", parallel) backend.MaxConcurrency(parallel) } - ctx, err := backend.DryRunMode(cmd.Context(), dryRun) + + // (5) dry run detection + ctx, err = backend.DryRunMode(ctx, dryRun) if err != nil { return err } cmd.SetContext(ctx) + + // (6) Desktop integration + if db, ok := backend.(desktop.IntegrationService); ok { + if err := db.MaybeEnableDesktopIntegration(ctx); err != nil { + // not fatal, Compose will still work but behave as though + // it's not running as part of Docker Desktop + logrus.Debugf("failed to enable Docker Desktop integration: %v", err) + } + } + return nil }, } diff --git a/cmd/main.go b/cmd/main.go index d8c011e4d0..038d07cf56 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,6 +25,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/cmd/cmdtrace" "github.com/docker/docker/client" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/docker/compose/v2/cmd/compatibility" @@ -37,7 +38,7 @@ func pluginMain() { plugin.Run(func(dockerCli command.Cli) *cobra.Command { backend := compose.NewComposeService(dockerCli) cmd := commands.RootCommand(dockerCli, backend) - originalPreRun := cmd.PersistentPreRunE + originalPreRunE := cmd.PersistentPreRunE cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { // initialize the dockerCli instance if err := plugin.PersistentPreRunE(cmd, args); err != nil { @@ -46,12 +47,12 @@ func pluginMain() { // compose-specific initialization dockerCliPostInitialize(dockerCli) - // TODO(milas): add an env var to enable logging from the - // OTel components for debugging purposes - _ = cmdtrace.Setup(cmd, dockerCli, os.Args[1:]) + if err := cmdtrace.Setup(cmd, dockerCli, os.Args[1:]); err != nil { + logrus.Debugf("failed to enable tracing: %v", err) + } - if originalPreRun != nil { - return originalPreRun(cmd, args) + if originalPreRunE != nil { + return originalPreRunE(cmd, args) } return nil } diff --git a/go.mod b/go.mod index f8435dda6f..e84508c7c0 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/theupdateframework/notary v0.7.0 github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 go.opentelemetry.io/otel v1.19.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 @@ -147,7 +148,6 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.45.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.42.0 // indirect diff --git a/internal/desktop/client.go b/internal/desktop/client.go new file mode 100644 index 0000000000..e43a10a096 --- /dev/null +++ b/internal/desktop/client.go @@ -0,0 +1,93 @@ +/* + Copyright 2024 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package desktop + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "strings" + + "github.com/docker/compose/v2/internal/memnet" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +// Client for integration with Docker Desktop features. +type Client struct { + client *http.Client +} + +// NewClient creates a Desktop integration client for the provided in-memory +// socket address (AF_UNIX or named pipe). +func NewClient(apiEndpoint string) *Client { + var transport http.RoundTripper = &http.Transport{ + DisableCompression: true, + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return memnet.DialEndpoint(ctx, apiEndpoint) + }, + } + transport = otelhttp.NewTransport(transport) + + c := &Client{ + client: &http.Client{Transport: transport}, + } + return c +} + +// Close releases any open connections. +func (c *Client) Close() error { + c.client.CloseIdleConnections() + return nil +} + +type PingResponse struct { + ServerTime int64 `json:"serverTime"` +} + +// Ping is a minimal API used to ensure that the server is available. +func (c *Client) Ping(ctx context.Context) (*PingResponse, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, backendURL("/ping"), http.NoBody) + if err != nil { + return nil, err + } + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer func() { + _ = resp.Body.Close() + }() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var ret PingResponse + if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil { + return nil, err + } + return &ret, nil +} + +// backendURL generates a URL for the given API path. +// +// NOTE: Custom transport handles communication. The host is to create a valid +// URL for the Go http.Client that is also descriptive in error/logs. +func backendURL(path string) string { + return "http://docker-desktop/" + strings.TrimPrefix(path, "/") +} diff --git a/internal/desktop/integration.go b/internal/desktop/integration.go new file mode 100644 index 0000000000..62dd4b9315 --- /dev/null +++ b/internal/desktop/integration.go @@ -0,0 +1,25 @@ +/* + Copyright 2024 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package desktop + +import ( + "context" +) + +type IntegrationService interface { + MaybeEnableDesktopIntegration(ctx context.Context) error +} diff --git a/internal/memnet/conn.go b/internal/memnet/conn.go new file mode 100644 index 0000000000..224bec7883 --- /dev/null +++ b/internal/memnet/conn.go @@ -0,0 +1,50 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package memnet + +import ( + "context" + "fmt" + "net" + "strings" +) + +func DialEndpoint(ctx context.Context, endpoint string) (net.Conn, error) { + if addr, ok := strings.CutPrefix(endpoint, "unix://"); ok { + return Dial(ctx, "unix", addr) + } + if addr, ok := strings.CutPrefix(endpoint, "npipe://"); ok { + return Dial(ctx, "npipe", addr) + } + return nil, fmt.Errorf("unsupported protocol for address: %s", endpoint) +} + +func Dial(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + switch network { + case "unix": + if err := validateSocketPath(addr); err != nil { + return nil, err + } + return d.DialContext(ctx, "unix", addr) + case "npipe": + // N.B. this will return an error on non-Windows + return dialNamedPipe(ctx, addr) + default: + return nil, fmt.Errorf("unsupported network: %s", network) + } +} diff --git a/internal/tracing/conn_unix.go b/internal/memnet/conn_unix.go similarity index 64% rename from internal/tracing/conn_unix.go rename to internal/memnet/conn_unix.go index 78294f4beb..e151984848 100644 --- a/internal/tracing/conn_unix.go +++ b/internal/memnet/conn_unix.go @@ -16,29 +16,24 @@ limitations under the License. */ -package tracing +package memnet import ( "context" "fmt" "net" - "strings" "syscall" ) const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) -func DialInMemory(ctx context.Context, addr string) (net.Conn, error) { - if !strings.HasPrefix(addr, "unix://") { - return nil, fmt.Errorf("not a Unix socket address: %s", addr) - } - addr = strings.TrimPrefix(addr, "unix://") +func dialNamedPipe(_ context.Context, _ string) (net.Conn, error) { + return nil, fmt.Errorf("named pipes are only available on Windows") +} +func validateSocketPath(addr string) error { if len(addr) > maxUnixSocketPathSize { - //goland:noinspection GoErrorStringFormat - return nil, fmt.Errorf("Unix socket address is too long: %s", addr) + return fmt.Errorf("socket address is too long: %s", addr) } - - var d net.Dialer - return d.DialContext(ctx, "unix", addr) + return nil } diff --git a/internal/tracing/conn_windows.go b/internal/memnet/conn_windows.go similarity index 73% rename from internal/tracing/conn_windows.go rename to internal/memnet/conn_windows.go index 30deaa464d..b7f7d9ea8f 100644 --- a/internal/tracing/conn_windows.go +++ b/internal/memnet/conn_windows.go @@ -14,22 +14,20 @@ limitations under the License. */ -package tracing +package memnet import ( "context" - "fmt" "net" - "strings" "github.com/Microsoft/go-winio" ) -func DialInMemory(ctx context.Context, addr string) (net.Conn, error) { - if !strings.HasPrefix(addr, "npipe://") { - return nil, fmt.Errorf("not a named pipe address: %s", addr) - } - addr = strings.TrimPrefix(addr, "npipe://") - +func dialNamedPipe(ctx context.Context, addr string) (net.Conn, error) { return winio.DialPipeContext(ctx, addr) } + +func validateSocketPath(addr string) error { + // AF_UNIX sockets do not have strict path limits on Windows + return nil +} diff --git a/internal/tracing/docker_context.go b/internal/tracing/docker_context.go index f5f5ece3f7..229e77477d 100644 --- a/internal/tracing/docker_context.go +++ b/internal/tracing/docker_context.go @@ -24,6 +24,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/context/store" + "github.com/docker/compose/v2/internal/memnet" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "google.golang.org/grpc" @@ -67,7 +68,9 @@ func traceClientFromDockerContext(dockerCli command.Cli, otelEnv envMap) (otlptr conn, err := grpc.DialContext( dialCtx, cfg.Endpoint, - grpc.WithContextDialer(DialInMemory), + grpc.WithContextDialer(memnet.DialEndpoint), + // this dial is restricted to using a local Unix socket / named pipe, + // so there is no need for TLS grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index a025b9d908..fa631bfadb 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -18,6 +18,7 @@ package compose import ( "context" + "errors" "fmt" "io" "os" @@ -25,6 +26,7 @@ import ( "strings" "sync" + "github.com/docker/compose/v2/internal/desktop" "github.com/docker/docker/api/types/volume" "github.com/jonboulle/clockwork" @@ -60,12 +62,29 @@ func NewComposeService(dockerCli command.Cli) api.Service { } type composeService struct { - dockerCli command.Cli + dockerCli command.Cli + desktopCli *desktop.Client + clock clockwork.Clock maxConcurrency int dryRun bool } +// Close releases any connections/resources held by the underlying clients. +// +// In practice, this service has the same lifetime as the process, so everything +// will get cleaned up at about the same time regardless even if not invoked. +func (s *composeService) Close() error { + var errs []error + if s.dockerCli != nil { + errs = append(errs, s.dockerCli.Client().Close()) + } + if s.desktopCli != nil { + errs = append(errs, s.desktopCli.Close()) + } + return errors.Join(errs...) +} + func (s *composeService) apiClient() client.APIClient { return s.dockerCli.Client() } diff --git a/pkg/compose/desktop.go b/pkg/compose/desktop.go new file mode 100644 index 0000000000..9af977fde2 --- /dev/null +++ b/pkg/compose/desktop.go @@ -0,0 +1,77 @@ +/* + Copyright 2024 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/docker/compose/v2/internal/desktop" + "github.com/sirupsen/logrus" +) + +// engineLabelDesktopAddress is used to detect that Compose is running with a +// Docker Desktop context. When this label is present, the value is an endpoint +// address for an in-memory socket (AF_UNIX or named pipe). +const engineLabelDesktopAddress = "com.docker.desktop.address" + +var _ desktop.IntegrationService = &composeService{} + +// MaybeEnableDesktopIntegration initializes the desktop.Client instance if +// the server info from the Docker Engine is a Docker Desktop instance. +// +// EXPERIMENTAL: Requires `COMPOSE_EXPERIMENTAL_DESKTOP=1` env var set. +func (s *composeService) MaybeEnableDesktopIntegration(ctx context.Context) error { + if desktopEnabled, _ := strconv.ParseBool(os.Getenv("COMPOSE_EXPERIMENTAL_DESKTOP")); !desktopEnabled { + return nil + } + + if s.dryRun { + return nil + } + + // safeguard to make sure this doesn't get stuck indefinitely + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + info, err := s.dockerCli.Client().Info(ctx) + if err != nil { + return fmt.Errorf("querying server info: %w", err) + } + for _, l := range info.Labels { + k, v, ok := strings.Cut(l, "=") + if !ok || k != engineLabelDesktopAddress { + continue + } + + desktopCli := desktop.NewClient(v) + _, err := desktopCli.Ping(ctx) + if err != nil { + return fmt.Errorf("pinging Desktop API: %w", err) + } + logrus.Debugf("Enabling Docker Desktop integration (experimental): %s", v) + s.desktopCli = desktopCli + return nil + } + + logrus.Trace("Docker Desktop not detected, no integration enabled") + return nil +} From 5a8c9c082086ad8a88b574b8d6cee55455835d96 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Tue, 12 Mar 2024 11:19:16 +0100 Subject: [PATCH 43/47] only use ToModel when --no-interpolate is set Signed-off-by: Nicolas De Loof --- cmd/compose/config.go | 37 +++++++++++++++++++++++++------------ pkg/e2e/compose_test.go | 6 +++--- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/cmd/compose/config.go b/cmd/compose/config.go index 8631956cee..b06e620b11 100644 --- a/cmd/compose/config.go +++ b/cmd/compose/config.go @@ -136,21 +136,34 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command { } func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error { - model, err := opts.ToModel(ctx, dockerCli, services) - if err != nil { - return err - } - - if opts.resolveImageDigests { - err = resolveImageDigests(ctx, dockerCli, model) + var content []byte + if opts.noInterpolate { + // we can't use ToProject, so the model we render here is only partially resolved + model, err := opts.ToModel(ctx, dockerCli, services) if err != nil { return err } - } - content, err := formatModel(model, opts.Format) - if err != nil { - return err + if opts.resolveImageDigests { + err = resolveImageDigests(ctx, dockerCli, model) + if err != nil { + return err + } + } + + content, err = formatModel(model, opts.Format) + if err != nil { + return err + } + } else { + project, err := opts.ToProject(ctx, dockerCli, services) + if err != nil { + return err + } + content, err = project.MarshalYAML() + if err != nil { + return err + } } if !opts.noInterpolate { @@ -164,7 +177,7 @@ func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, s if opts.Output != "" && len(content) > 0 { return os.WriteFile(opts.Output, content, 0o666) } - _, err = fmt.Fprint(dockerCli.Out(), string(content)) + _, err := fmt.Fprint(dockerCli.Out(), string(content)) return err } diff --git a/pkg/e2e/compose_test.go b/pkg/e2e/compose_test.go index 28d8b0eb74..75d546f730 100644 --- a/pkg/e2e/compose_test.go +++ b/pkg/e2e/compose_test.go @@ -245,9 +245,6 @@ func TestConfig(t *testing.T) { t.Run("up", func(t *testing.T) { res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "convert") res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s -networks: - default: - name: compose-e2e-convert_default services: nginx: build: @@ -255,6 +252,9 @@ services: dockerfile: Dockerfile networks: default: null +networks: + default: + name: compose-e2e-convert_default `, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0}) }) } From bf3028715265e34659fc5405019a83e91ec9607c Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 15 Mar 2024 10:25:15 +0100 Subject: [PATCH 44/47] fix TestBuildPlatformsWithCorrectBuildxConfig Signed-off-by: Nicolas De Loof --- pkg/e2e/build_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/e2e/build_test.go b/pkg/e2e/build_test.go index 349ab9f4cb..65044519b5 100644 --- a/pkg/e2e/build_test.go +++ b/pkg/e2e/build_test.go @@ -306,7 +306,7 @@ func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) { "-f", "fixtures/build-test/platforms/compose-unsupported-platform.yml", "build") res.Assert(t, icmd.Expected{ ExitCode: 17, - Err: "failed to solve: alpine: no match for platform in", + Err: "no match for platform in", }) }) From ea75fd7038136ea0a6d2597a77f63a5ca6b27fc4 Mon Sep 17 00:00:00 2001 From: Andrew Onyshchuk Date: Wed, 28 Feb 2024 12:36:13 -0800 Subject: [PATCH 45/47] services shell completion bugfix Signed-off-by: Andrew Onyshchuk --- cmd/compose/completion.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/compose/completion.go b/cmd/compose/completion.go index 71da360c14..d9c56b15da 100644 --- a/cmd/compose/completion.go +++ b/cmd/compose/completion.go @@ -41,13 +41,14 @@ func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } + var values []string serviceNames := append(project.ServiceNames(), project.DisabledServiceNames()...) for _, s := range serviceNames { if toComplete == "" || strings.HasPrefix(s, toComplete) { - serviceNames = append(serviceNames, s) + values = append(values, s) } } - return serviceNames, cobra.ShellCompDirectiveNoFileComp + return values, cobra.ShellCompDirectiveNoFileComp } } From 11ac649718e57d4459f87fd457397a71e0fcbe37 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 15 Mar 2024 11:28:18 +0100 Subject: [PATCH 46/47] Bump compose-go v2.0.0 Signed-off-by: Nicolas De Loof --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e84508c7c0..890fa14167 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Microsoft/go-winio v0.6.1 github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60 + github.com/compose-spec/compose-go/v2 v2.0.0 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.12 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 4c22388053..c23181627e 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60 h1:NlkpaLBPFr05mNJWVMH7PP4L30gFG6k4z1QpypLUSh8= -github.com/compose-spec/compose-go/v2 v2.0.0-rc.8.0.20240228111658-a0507e98fe60/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.0.0 h1:RLI8GmNxRLg759CzZITh/kGYZTYhEak121FaVYdXTC8= +github.com/compose-spec/compose-go/v2 v2.0.0/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= From bff6d3633d04caac05b737812747a5b90d7a80f4 Mon Sep 17 00:00:00 2001 From: Rodrigo Arguello Date: Fri, 15 Mar 2024 13:49:06 +0100 Subject: [PATCH 47/47] add an option to use a custom progress printer for the build progress output Signed-off-by: Rodrigo Arguello --- pkg/compose/build.go | 2 +- pkg/compose/create.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/compose/build.go b/pkg/compose/build.go index d9e38e6cc9..a6ce19a12d 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -20,7 +20,6 @@ import ( "context" "errors" "fmt" - "github.com/containerd/console" "io" "os" "path/filepath" @@ -28,6 +27,7 @@ import ( "github.com/moby/buildkit/util/progress/progressui" "github.com/compose-spec/compose-go/v2/types" + "github.com/containerd/console" "github.com/containerd/containerd/platforms" "github.com/docker/buildx/build" "github.com/docker/buildx/builder" diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 65e948ecd2..c034b14662 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -279,7 +279,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context, DNSOptions: service.DNSOpts, ExtraHosts: service.ExtraHosts.AsList(":"), SecurityOpt: securityOpts, - StorageOpt: nil, + StorageOpt: service.StorageOpt, UsernsMode: container.UsernsMode(service.UserNSMode), UTSMode: container.UTSMode(service.Uts), Privileged: service.Privileged,