Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package docker-compose for openSUSE:Factory checked in at 2026-04-18 21:35:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/docker-compose (Old) and /work/SRC/openSUSE:Factory/.docker-compose.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "docker-compose" Sat Apr 18 21:35:32 2026 rev:90 rq:1347861 version:5.1.3 Changes: -------- --- /work/SRC/openSUSE:Factory/docker-compose/docker-compose.changes 2026-04-09 16:25:34.744574264 +0200 +++ /work/SRC/openSUSE:Factory/.docker-compose.new.11940/docker-compose.changes 2026-04-18 21:35:47.780148605 +0200 @@ -1,0 +2,20 @@ +Fri Apr 17 20:21:19 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 5.1.3: + * Fixes + - fix: provider output handling and watch rebuild re-invocation + by @glours in #13732 + * Internal + - Add Docker Desktop Logs view hints and navigation shortcut by + @glours in #13721 + - Build and push Docker Desktop module image on release by + @glours in #13726 + - Fix typo in SECURITY.md by @glours in #13730 + - Make hook hint deep links clickable using OSC 8 terminal + hyperlinks by @glours in #13734 + - Remove 'provenance' attribute' by @glours in #13738 + * Dependencies + - build(deps): bump github.com/containerd/containerd/v2 from + 2.2.2 to 2.2.3 by @dependabot[bot] in #13737 + +------------------------------------------------------------------- Old: ---- docker-compose-5.1.2.obscpio New: ---- docker-compose-5.1.3.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ docker-compose.spec ++++++ --- /var/tmp/diff_new_pack.ZAnkgz/_old 2026-04-18 21:35:48.580181210 +0200 +++ /var/tmp/diff_new_pack.ZAnkgz/_new 2026-04-18 21:35:48.580181210 +0200 @@ -17,7 +17,7 @@ Name: docker-compose -Version: 5.1.2 +Version: 5.1.3 Release: 0 Summary: Define and run multi-container applications with Docker License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.ZAnkgz/_old 2026-04-18 21:35:48.616182678 +0200 +++ /var/tmp/diff_new_pack.ZAnkgz/_new 2026-04-18 21:35:48.624183003 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/docker/compose</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v5.1.2</param> + <param name="revision">v5.1.3</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.ZAnkgz/_old 2026-04-18 21:35:48.652184145 +0200 +++ /var/tmp/diff_new_pack.ZAnkgz/_new 2026-04-18 21:35:48.664184634 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/docker/compose</param> - <param name="changesrevision">ae92bef4e1d02bfb75afeafc0ce9334a133f952f</param></service></servicedata> + <param name="changesrevision">977a4310f9f6d89d4b176fee01a5b7c109c1816a</param></service></servicedata> (No newline at EOF) ++++++ docker-compose-5.1.2.obscpio -> docker-compose-5.1.3.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/Dockerfile new/docker-compose-5.1.3/Dockerfile --- old/docker-compose-5.1.2/Dockerfile 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/Dockerfile 2026-04-15 15:46:06.000000000 +0200 @@ -195,3 +195,16 @@ FROM scratch AS release COPY --from=releaser /out/ / + +FROM --platform=$BUILDPLATFORM alpine AS module-releaser +WORKDIR /work +ARG TARGETOS +RUN --mount=from=binary \ + mkdir -p /cli-plugins/compose/$TARGETOS && \ + cp docker-compose* "/cli-plugins/compose/$TARGETOS/docker-compose$(ls docker-compose* | sed -e 's/^docker-compose//')" + +FROM scratch AS module +ARG TARGETOS +COPY --from=module-releaser /cli-plugins/compose/$TARGETOS /cli-plugins/compose/$TARGETOS +COPY ./desktop-module/module-metadata.json / +COPY LICENSE / diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/cmd/compose/compose.go new/docker-compose-5.1.3/cmd/compose/compose.go --- old/docker-compose-5.1.2/cmd/compose/compose.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/cmd/compose/compose.go 2026-04-15 15:46:06.000000000 +0200 @@ -409,7 +409,7 @@ // RunningAsStandalone detects when running as a standalone program func RunningAsStandalone() bool { - return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName && os.Args[1] != PluginName + return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName && os.Args[1] != metadata.HookSubcommandName && os.Args[1] != PluginName } type BackendOptions struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/cmd/compose/hooks.go new/docker-compose-5.1.3/cmd/compose/hooks.go --- old/docker-compose-5.1.2/cmd/compose/hooks.go 1970-01-01 01:00:00.000000000 +0100 +++ new/docker-compose-5.1.3/cmd/compose/hooks.go 2026-04-15 15:46:06.000000000 +0200 @@ -0,0 +1,136 @@ +/* + 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 compose + +import ( + "encoding/json" + "io" + "os" + + "github.com/docker/cli/cli-plugins/hooks" + "github.com/docker/cli/cli-plugins/metadata" + "github.com/spf13/cobra" + + "github.com/docker/compose/v5/cmd/formatter" +) + +const deepLink = "docker-desktop://dashboard/logs" + +func composeLogsHint() string { + return "Filter, search, and stream logs from all your Compose services\nin one place with Docker Desktop's Logs view. " + hintLink(deepLink) +} + +func dockerLogsHint() string { + return "View and search logs for all containers in one place\nwith Docker Desktop's Logs view. " + hintLink(deepLink) +} + +// hintLink returns a clickable OSC 8 terminal hyperlink when ANSI is allowed, +// or the plain URL when ANSI output is suppressed via NO_COLOR or COMPOSE_ANSI. +func hintLink(url string) string { + if shouldDisableAnsi() { + return url + } + return formatter.OSC8Link(url, url) +} + +// shouldDisableAnsi checks whether ANSI escape sequences should be explicitly +// suppressed via environment variables. The hook runs as a separate subprocess +// where the normal PersistentPreRunE (which calls formatter.SetANSIMode) is +// skipped, so we check NO_COLOR and COMPOSE_ANSI directly. +// +// TTY detection is intentionally omitted: the hook produces a JSON response +// whose template is rendered by the parent Docker CLI process via +// PrintNextSteps (which itself emits bold ANSI unconditionally). The hook +// subprocess cannot reliably detect whether the parent's output is a terminal. +func shouldDisableAnsi() bool { + if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" { + return true + } + if v, ok := os.LookupEnv("COMPOSE_ANSI"); ok && v == formatter.Never { + return true + } + return false +} + +// hookHint defines a hint that can be returned by the hooks handler. +// When checkFlags is nil, the hint is always returned for the matching command. +// When checkFlags is set, the hint is only returned if the check passes. +type hookHint struct { + template func() string + checkFlags func(flags map[string]string) bool +} + +// hooksHints maps hook root commands to their hint definitions. +var hooksHints = map[string]hookHint{ + // standalone "docker logs" (not a compose subcommand) + "logs": {template: dockerLogsHint}, + "compose logs": {template: composeLogsHint}, + "compose up": { + template: composeLogsHint, + checkFlags: func(flags map[string]string) bool { + // Only show the hint when running in detached mode + _, hasDetach := flags["detach"] + _, hasD := flags["d"] + return hasDetach || hasD + }, + }, +} + +// HooksCommand returns the hidden subcommand that the Docker CLI invokes +// after command execution when the compose plugin has hooks configured. +// Docker Desktop is responsible for registering which commands trigger hooks +// and for gating on feature flags/settings — the hook handler simply +// responds with the appropriate hint message. +func HooksCommand() *cobra.Command { + return &cobra.Command{ + Use: metadata.HookSubcommandName, + Hidden: true, + // Override PersistentPreRunE to prevent the parent's PersistentPreRunE + // (plugin initialization) from running for hook invocations. + PersistentPreRunE: func(*cobra.Command, []string) error { return nil }, + RunE: func(cmd *cobra.Command, args []string) error { + return handleHook(args, cmd.OutOrStdout()) + }, + } +} + +func handleHook(args []string, w io.Writer) error { + if len(args) == 0 { + return nil + } + + var hookData hooks.Request + if err := json.Unmarshal([]byte(args[0]), &hookData); err != nil { + return nil + } + + hint, ok := hooksHints[hookData.RootCmd] + if !ok { + return nil + } + + if hint.checkFlags != nil && !hint.checkFlags(hookData.Flags) { + return nil + } + + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + return enc.Encode(hooks.Response{ + Type: hooks.NextSteps, + Template: hint.template(), + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/cmd/compose/hooks_test.go new/docker-compose-5.1.3/cmd/compose/hooks_test.go --- old/docker-compose-5.1.2/cmd/compose/hooks_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/docker-compose-5.1.3/cmd/compose/hooks_test.go 2026-04-15 15:46:06.000000000 +0200 @@ -0,0 +1,186 @@ +/* + 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 compose + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + "github.com/docker/cli/cli-plugins/hooks" + "gotest.tools/v3/assert" + + "github.com/docker/compose/v5/cmd/formatter" +) + +func TestHandleHook_NoArgs(t *testing.T) { + var buf bytes.Buffer + err := handleHook(nil, &buf) + assert.NilError(t, err) + assert.Equal(t, buf.String(), "") +} + +func TestHandleHook_InvalidJSON(t *testing.T) { + var buf bytes.Buffer + err := handleHook([]string{"not json"}, &buf) + assert.NilError(t, err) + assert.Equal(t, buf.String(), "") +} + +func TestHandleHook_UnknownCommand(t *testing.T) { + data := marshalHookData(t, hooks.Request{ + RootCmd: "compose push", + }) + var buf bytes.Buffer + err := handleHook([]string{data}, &buf) + assert.NilError(t, err) + assert.Equal(t, buf.String(), "") +} + +func TestHandleHook_LogsCommand(t *testing.T) { + tests := []struct { + rootCmd string + wantHint func() string + }{ + {rootCmd: "compose logs", wantHint: composeLogsHint}, + {rootCmd: "logs", wantHint: dockerLogsHint}, + } + for _, tt := range tests { + t.Run(tt.rootCmd, func(t *testing.T) { + data := marshalHookData(t, hooks.Request{ + RootCmd: tt.rootCmd, + }) + var buf bytes.Buffer + err := handleHook([]string{data}, &buf) + assert.NilError(t, err) + + msg := unmarshalResponse(t, buf.Bytes()) + assert.Equal(t, msg.Type, hooks.NextSteps) + assert.Equal(t, msg.Template, tt.wantHint()) + }) + } +} + +func TestHandleHook_ComposeUpDetached(t *testing.T) { + tests := []struct { + name string + flags map[string]string + wantHint bool + }{ + { + name: "with --detach flag", + flags: map[string]string{"detach": ""}, + wantHint: true, + }, + { + name: "with -d flag", + flags: map[string]string{"d": ""}, + wantHint: true, + }, + { + name: "without detach flag", + flags: map[string]string{"build": ""}, + wantHint: false, + }, + { + name: "no flags", + flags: map[string]string{}, + wantHint: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := marshalHookData(t, hooks.Request{ + RootCmd: "compose up", + Flags: tt.flags, + }) + var buf bytes.Buffer + err := handleHook([]string{data}, &buf) + assert.NilError(t, err) + + if tt.wantHint { + msg := unmarshalResponse(t, buf.Bytes()) + assert.Equal(t, msg.Template, composeLogsHint()) + } else { + assert.Equal(t, buf.String(), "") + } + }) + } +} + +func TestHandleHook_HintContainsOSC8Link(t *testing.T) { + // Ensure ANSI is not suppressed by the test runner environment + t.Setenv("NO_COLOR", "") + t.Setenv("COMPOSE_ANSI", "") + data := marshalHookData(t, hooks.Request{ + RootCmd: "compose logs", + }) + var buf bytes.Buffer + err := handleHook([]string{data}, &buf) + assert.NilError(t, err) + + msg := unmarshalResponse(t, buf.Bytes()) + // Verify the template contains the OSC 8 hyperlink sequence + wantLink := formatter.OSC8Link(deepLink, deepLink) + assert.Assert(t, len(wantLink) > len(deepLink), "OSC8Link should wrap the URL with escape sequences") + assert.Assert(t, strings.Contains(msg.Template, wantLink), "hint should contain OSC 8 hyperlink") +} + +func TestHandleHook_NoColorDisablesOsc8(t *testing.T) { + t.Setenv("NO_COLOR", "1") + data := marshalHookData(t, hooks.Request{ + RootCmd: "compose logs", + }) + var buf bytes.Buffer + err := handleHook([]string{data}, &buf) + assert.NilError(t, err) + + msg := unmarshalResponse(t, buf.Bytes()) + // With NO_COLOR set, the hint should contain the plain URL without escape sequences + assert.Assert(t, strings.Contains(msg.Template, deepLink), "hint should contain the deep link URL") + assert.Assert(t, !strings.Contains(msg.Template, "\033"), "hint should not contain ANSI escape sequences") +} + +func TestHandleHook_ComposeAnsiNeverDisablesOsc8(t *testing.T) { + t.Setenv("COMPOSE_ANSI", "never") + data := marshalHookData(t, hooks.Request{ + RootCmd: "compose logs", + }) + var buf bytes.Buffer + err := handleHook([]string{data}, &buf) + assert.NilError(t, err) + + msg := unmarshalResponse(t, buf.Bytes()) + assert.Assert(t, strings.Contains(msg.Template, deepLink), "hint should contain the deep link URL") + assert.Assert(t, !strings.Contains(msg.Template, "\033"), "hint should not contain ANSI escape sequences") +} + +func marshalHookData(t *testing.T, data hooks.Request) string { + t.Helper() + b, err := json.Marshal(data) + assert.NilError(t, err) + return string(b) +} + +func unmarshalResponse(t *testing.T, data []byte) hooks.Response { + t.Helper() + var msg hooks.Response + err := json.Unmarshal(data, &msg) + assert.NilError(t, err) + return msg +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/cmd/formatter/ansi.go new/docker-compose-5.1.3/cmd/formatter/ansi.go --- old/docker-compose-5.1.2/cmd/formatter/ansi.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/cmd/formatter/ansi.go 2026-04-15 15:46:06.000000000 +0200 @@ -87,13 +87,20 @@ } func newLine() { - // Like \n fmt.Print("\012") } +// lenAnsi returns the visible length of s after stripping ANSI escape codes. func lenAnsi(s string) int { - // len has into consideration ansi codes, if we want - // the len of the actual len(string) we need to strip - // all ansi codes return len(stripansi.Strip(s)) } + +// OSC8Link wraps text in an OSC 8 terminal hyperlink escape sequence with +// underline styling, making it clickable in supported terminal emulators. +// When ANSI output is disabled, returns the plain text without escape sequences. +func OSC8Link(url, text string) string { + if disableAnsi { + return text + } + return "\033]8;;" + url + "\033\\\033[4m" + text + "\033[24m\033]8;;\033\\" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/cmd/formatter/ansi_test.go new/docker-compose-5.1.3/cmd/formatter/ansi_test.go --- old/docker-compose-5.1.2/cmd/formatter/ansi_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/docker-compose-5.1.3/cmd/formatter/ansi_test.go 2026-04-15 15:46:06.000000000 +0200 @@ -0,0 +1,50 @@ +/* + 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 formatter + +import ( + "testing" + + "gotest.tools/v3/assert" +) + +func TestOSC8Link(t *testing.T) { + disableAnsi = false + t.Cleanup(func() { disableAnsi = false }) + + got := OSC8Link("http://example.com", "click here") + want := "\x1b]8;;http://example.com\x1b\\\x1b[4mclick here\x1b[24m\x1b]8;;\x1b\\" + assert.Equal(t, got, want) +} + +func TestOSC8Link_AnsiDisabled(t *testing.T) { + disableAnsi = true + t.Cleanup(func() { disableAnsi = false }) + + got := OSC8Link("http://example.com", "click here") + assert.Equal(t, got, "click here") +} + +func TestOSC8Link_URLAsDisplayText(t *testing.T) { + disableAnsi = false + t.Cleanup(func() { disableAnsi = false }) + + url := "docker-desktop://dashboard/logs" + got := OSC8Link(url, url) + want := "\x1b]8;;docker-desktop://dashboard/logs\x1b\\\x1b[4mdocker-desktop://dashboard/logs\x1b[24m\x1b]8;;\x1b\\" + assert.Equal(t, got, want) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/cmd/formatter/shortcut.go new/docker-compose-5.1.3/cmd/formatter/shortcut.go --- old/docker-compose-5.1.2/cmd/formatter/shortcut.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/cmd/formatter/shortcut.go 2026-04-15 15:46:06.000000000 +0200 @@ -94,13 +94,15 @@ Watch *KeyboardWatch Detach func() IsDockerDesktopActive bool + IsLogsViewEnabled bool logLevel KEYBOARD_LOG_LEVEL signalChannel chan<- os.Signal } -func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal) *LogKeyboard { +func NewKeyboardManager(isDockerDesktopActive, isLogsViewEnabled bool, sc chan<- os.Signal) *LogKeyboard { return &LogKeyboard{ IsDockerDesktopActive: isDockerDesktopActive, + IsLogsViewEnabled: isLogsViewEnabled, logLevel: INFO, signalChannel: sc, } @@ -173,6 +175,10 @@ items = append(items, shortcutKeyColor("o")+navColor(" View Config")) } + if lk.IsLogsViewEnabled { + items = append(items, shortcutKeyColor("l")+navColor(" View Logs")) + } + isEnabled := " Enable" if lk.Watch != nil && lk.Watch.Watching { isEnabled = " Disable" @@ -232,6 +238,24 @@ }() } +func (lk *LogKeyboard) openDDLogsView(ctx context.Context) { + if !lk.IsLogsViewEnabled { + return + } + go func() { + _ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/logsview", tracing.SpanOptions{}, + func(ctx context.Context) error { + link := "docker-desktop://dashboard/logs" + err := open.Run(link) + if err != nil { + err = fmt.Errorf("could not open Docker Desktop Logs view: %w", err) + lk.keyboardError("View Logs", err) + } + return err + })() + }() +} + func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Project) { go func() { _ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/watch", tracing.SpanOptions{}, @@ -311,6 +335,8 @@ lk.ToggleWatch(ctx, options) case 'o': lk.openDDComposeUI(ctx, project) + case 'l': + lk.openDDLogsView(ctx) } switch key := event.Key; key { case keyboard.KeyCtrlC: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/cmd/main.go new/docker-compose-5.1.3/cmd/main.go --- old/docker-compose-5.1.2/cmd/main.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/cmd/main.go 2026-04-15 15:46:06.000000000 +0200 @@ -44,6 +44,7 @@ } cmd := commands.RootCommand(cli, backendOptions) + cmd.AddCommand(commands.HooksCommand()) originalPreRunE := cmd.PersistentPreRunE cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { // initialize the cli instance diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/desktop-module/module-metadata.json new/docker-compose-5.1.3/desktop-module/module-metadata.json --- old/docker-compose-5.1.2/desktop-module/module-metadata.json 1970-01-01 01:00:00.000000000 +0100 +++ new/docker-compose-5.1.3/desktop-module/module-metadata.json 2026-04-15 15:46:06.000000000 +0200 @@ -0,0 +1,9 @@ +{ + "version": 1, + "content": [ + { + "type": "cli-plugin", + "name": "compose" + } + ] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/docker-bake.hcl new/docker-compose-5.1.3/docker-bake.hcl --- old/docker-compose-5.1.2/docker-bake.hcl 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/docker-bake.hcl 2026-04-15 15:46:06.000000000 +0200 @@ -146,3 +146,17 @@ inherits = ["meta-helper", "binary-cross"] output = ["type=image"] } + +target "image-module-cross" { + inherits = ["meta-helper", "binary-cross"] + target = "module" + output = ["type=image"] + platforms = [ + "darwin/amd64", + "darwin/arm64", + "linux/amd64", + "linux/arm64", + "windows/amd64", + "windows/arm64", + ] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/go.mod new/docker-compose-5.1.3/go.mod --- old/docker-compose-5.1.2/go.mod 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/go.mod 2026-04-15 15:46:06.000000000 +0200 @@ -10,7 +10,7 @@ github.com/buger/goterm v1.0.4 github.com/compose-spec/compose-go/v2 v2.10.2 github.com/containerd/console v1.0.5 - github.com/containerd/containerd/v2 v2.2.2 + github.com/containerd/containerd/v2 v2.2.3 github.com/containerd/errdefs v1.0.0 github.com/containerd/platforms v1.0.0-rc.4 github.com/creack/pty v1.1.24 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/go.sum new/docker-compose-5.1.3/go.sum --- old/docker-compose-5.1.2/go.sum 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/go.sum 2026-04-15 15:46:06.000000000 +0200 @@ -10,8 +10,8 @@ github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ= -github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c= +github.com/Microsoft/hcsshim v0.14.1 h1:CMuB3fqQVfPdhyXhUqYdUmPUIOhJkmghCx3dJet8Cqs= +github.com/Microsoft/hcsshim v0.14.1/go.mod h1:VnzvPLyWUhxiPVsJ31P6XadxCcTogTguBFDy/1GR/OM= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= @@ -44,8 +44,8 @@ github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= -github.com/containerd/containerd/v2 v2.2.2 h1:mjVQdtfryzT7lOqs5EYUFZm8ioPVjOpkSoG1GJPxEMY= -github.com/containerd/containerd/v2 v2.2.2/go.mod h1:5Jhevmv6/2J+Iu/A2xXAdUIdI5Ah/hfyO7okJ4AFIdY= +github.com/containerd/containerd/v2 v2.2.3 h1:mOBRLaHGvmgy0bRo1Sg6OD8ugMKZIvCoWWMeMMygliA= +github.com/containerd/containerd/v2 v2.2.3/go.mod h1:ns24cwt+p36mRnuKE3hLRxVBpuSP+a/Y25AMki1t/RY= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/internal/desktop/client.go new/docker-compose-5.1.3/internal/desktop/client.go --- old/docker-compose-5.1.2/internal/desktop/client.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/internal/desktop/client.go 2026-04-15 15:46:06.000000000 +0200 @@ -23,6 +23,7 @@ "io" "net" "net/http" + "path/filepath" "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" @@ -31,6 +32,14 @@ "github.com/docker/compose/v5/internal/memnet" ) +// EngineLabel is used to detect that Compose is running with a Docker +// Desktop context. When present, the value is an endpoint address for an +// in-memory socket (AF_UNIX or named pipe). +const EngineLabel = "com.docker.desktop.address" + +// FeatureLogsTab is the feature flag name for the Docker Desktop Logs view. +const FeatureLogsTab = "LogsTab" + // identify this client in the logs var userAgent = "compose/" + internal.Version @@ -128,6 +137,104 @@ return ret, nil } +// SettingValue represents a Docker Desktop setting with a locked flag and a value. +type SettingValue struct { + Locked bool `json:"locked"` + Value bool `json:"value"` +} + +// DesktopSettings represents the "desktop" section of Docker Desktop settings. +type DesktopSettings struct { + EnableLogsTab SettingValue `json:"enableLogsTab"` +} + +// SettingsResponse represents the Docker Desktop settings response. +type SettingsResponse struct { + Desktop DesktopSettings `json:"desktop"` +} + +// Settings fetches the Docker Desktop application settings. +func (c *Client) Settings(ctx context.Context) (*SettingsResponse, error) { + req, err := c.newRequest(ctx, http.MethodGet, "/app/settings", 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 SettingsResponse + if err := json.NewDecoder(resp.Body).Decode(&ret); err != nil { + return nil, err + } + return &ret, nil +} + +// IsFeatureEnabled checks both the feature flag (GET /features) and the user +// setting (GET /app/settings) for a given feature. Returns true only when the +// feature is both rolled out and enabled by the user. Features without a +// corresponding setting entry are considered enabled if the flag is set. +func (c *Client) IsFeatureEnabled(ctx context.Context, feature string) (bool, error) { + flags, err := c.FeatureFlags(ctx) + if err != nil { + return false, err + } + if !flags[feature].Enabled { + return false, nil + } + + check, hasCheck := featureSettingChecks[feature] + if !hasCheck { + // No setting to verify — feature flag alone is sufficient + return true, nil + } + + // The /app/settings endpoint is served by the backend socket, not the + // docker-cli socket. Derive the backend socket path from the current + // endpoint. + backendEndpoint := backendSocketEndpoint(c.apiEndpoint) + backendCli := NewClient(backendEndpoint) + defer backendCli.Close() //nolint:errcheck + + settings, err := backendCli.Settings(ctx) + if err != nil { + return false, err + } + return check(settings), nil +} + +// backendSocketEndpoint derives the Docker Desktop backend socket endpoint +// from any socket endpoint in the same directory. +// +// On macOS/Linux: unix:///path/to/Data/docker-cli.sock → unix:///path/to/Data/backend.sock +// On Windows: npipe://./pipe/dockerDesktopLinuxEngine → npipe://./pipe/dockerBackendApiServer +func backendSocketEndpoint(endpoint string) string { + if sockPath, ok := strings.CutPrefix(endpoint, "unix://"); ok { + return "unix://" + filepath.Join(filepath.Dir(sockPath), "backend.sock") + } + if _, ok := strings.CutPrefix(endpoint, "npipe://"); ok { + return "npipe://./pipe/dockerBackendApiServer" + } + return endpoint +} + +// featureSettingChecks maps feature flag names to their corresponding +// Docker Desktop setting check functions. +var featureSettingChecks = map[string]func(*SettingsResponse) bool{ + FeatureLogsTab: func(s *SettingsResponse) bool { + return s.Desktop.EnableLogsTab.Value + }, +} + func (c *Client) newRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { req, err := http.NewRequestWithContext(ctx, method, backendURL(path), body) if err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/internal/desktop/client_test.go new/docker-compose-5.1.3/internal/desktop/client_test.go --- old/docker-compose-5.1.2/internal/desktop/client_test.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/internal/desktop/client_test.go 2026-04-15 15:46:06.000000000 +0200 @@ -24,6 +24,41 @@ "gotest.tools/v3/assert" ) +func TestBackendSocketEndpoint(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "macOS unix socket", + input: "unix:///Users/me/Library/Containers/com.docker.docker/Data/docker-cli.sock", + expected: "unix:///Users/me/Library/Containers/com.docker.docker/Data/backend.sock", + }, + { + name: "Linux unix socket", + input: "unix:///run/desktop/docker-cli.sock", + expected: "unix:///run/desktop/backend.sock", + }, + { + name: "Windows named pipe", + input: "npipe://./pipe/dockerDesktopLinuxEngine", + expected: "npipe://./pipe/dockerBackendApiServer", + }, + { + name: "unknown scheme passthrough", + input: "tcp://localhost:2375", + expected: "tcp://localhost:2375", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := backendSocketEndpoint(tt.input) + assert.Equal(t, result, tt.expected) + }) + } +} + func TestClientPing(t *testing.T) { if testing.Short() { t.Skip("Skipped in short mode - test connects to Docker Desktop") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/internal/tracing/keyboard_metrics.go new/docker-compose-5.1.3/internal/tracing/keyboard_metrics.go --- old/docker-compose-5.1.2/internal/tracing/keyboard_metrics.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/internal/tracing/keyboard_metrics.go 2026-04-15 15:46:06.000000000 +0200 @@ -22,12 +22,15 @@ "go.opentelemetry.io/otel/attribute" ) -func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive bool) { +func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isLogsViewEnabled bool) { commandAvailable := []string{} if isDockerDesktopActive { commandAvailable = append(commandAvailable, "gui") commandAvailable = append(commandAvailable, "gui/composeview") } + if isLogsViewEnabled { + commandAvailable = append(commandAvailable, "gui/logsview") + } AddAttributeToSpan(ctx, attribute.Bool("navmenu.enabled", enabled), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/pkg/api/api.go new/docker-compose-5.1.3/pkg/api/api.go --- old/docker-compose-5.1.2/pkg/api/api.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/pkg/api/api.go 2026-04-15 15:46:06.000000000 +0200 @@ -278,6 +278,8 @@ Timeout *time.Duration // QuietPull makes the pulling process quiet QuietPull bool + // SkipProviders skips provider services during convergence (e.g. watch rebuild) + SkipProviders bool } // StartOptions group options of the Start API diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/pkg/compose/convergence.go new/docker-compose-5.1.3/pkg/compose/convergence.go --- old/docker-compose-5.1.2/pkg/compose/convergence.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/pkg/compose/convergence.go 2026-04-15 15:46:06.000000000 +0200 @@ -100,6 +100,12 @@ return err } + // Skip provider services when the caller opted out (e.g. watch rebuild), + // since providers were already set up during initial "up". + if service.Provider != nil && options.SkipProviders { + return nil + } + return tracing.SpanWrapFunc("service/apply", tracing.ServiceOptions(service), func(ctx context.Context) error { strategy := options.RecreateDependencies if slices.Contains(options.Services, name) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/pkg/compose/desktop.go new/docker-compose-5.1.3/pkg/compose/desktop.go --- old/docker-compose-5.1.2/pkg/compose/desktop.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/pkg/compose/desktop.go 2026-04-15 15:46:06.000000000 +0200 @@ -21,23 +21,48 @@ "strings" "github.com/moby/moby/client" -) -// 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" + "github.com/docker/compose/v5/internal/desktop" +) -func (s *composeService) isDesktopIntegrationActive(ctx context.Context) (bool, error) { +// desktopEndpoint returns the Docker Desktop API socket address discovered +// from the Docker engine info labels. It returns "" when the active engine +// is not a Docker Desktop instance. +func (s *composeService) desktopEndpoint(ctx context.Context) (string, error) { res, err := s.apiClient().Info(ctx, client.InfoOptions{}) if err != nil { - return false, err + return "", err } for _, l := range res.Info.Labels { - k, _, ok := strings.Cut(l, "=") - if ok && k == engineLabelDesktopAddress { - return true, nil + k, v, ok := strings.Cut(l, "=") + if ok && k == desktop.EngineLabel { + return v, nil } } - return false, nil + return "", nil +} + +// isDesktopIntegrationActive returns true when Docker Desktop is the active engine. +func (s *composeService) isDesktopIntegrationActive(ctx context.Context) (bool, error) { + endpoint, err := s.desktopEndpoint(ctx) + return endpoint != "", err +} + +// isDesktopFeatureActive checks whether a Docker Desktop feature is both +// available (feature flag) and enabled by the user (settings). Returns false +// silently when Desktop is not running or unreachable. +func (s *composeService) isDesktopFeatureActive(ctx context.Context, feature string) bool { + endpoint, err := s.desktopEndpoint(ctx) + if err != nil || endpoint == "" { + return false + } + + ddClient := desktop.NewClient(endpoint) + defer ddClient.Close() //nolint:errcheck + + enabled, err := ddClient.IsFeatureEnabled(ctx, feature) + if err != nil { + return false + } + return enabled } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/pkg/compose/plugins.go new/docker-compose-5.1.3/pkg/compose/plugins.go --- old/docker-compose-5.1.2/pkg/compose/plugins.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/pkg/compose/plugins.go 2026-04-15 15:46:06.000000000 +0200 @@ -125,10 +125,10 @@ } switch msg.Type { case ErrorType: - s.events.On(newEvent(service.Name, api.Error, msg.Message)) + s.events.On(newEvent(service.Name, api.Error, firstLine(msg.Message))) return nil, errors.New(msg.Message) case InfoType: - s.events.On(newEvent(service.Name, api.Working, msg.Message)) + s.events.On(newEvent(service.Name, api.Working, firstLine(msg.Message))) case SetEnvType: key, val, found := strings.Cut(msg.Message, "=") if !found { @@ -281,3 +281,12 @@ } return nil } + +// firstLine returns the first line of s, stripping any trailing newlines. +func firstLine(s string) string { + s = strings.TrimRight(s, "\n") + if i := strings.IndexByte(s, '\n'); i >= 0 { + return s[:i] + } + return s +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/pkg/compose/up.go new/docker-compose-5.1.3/pkg/compose/up.go --- old/docker-compose-5.1.2/pkg/compose/up.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/pkg/compose/up.go 2026-04-15 15:46:06.000000000 +0200 @@ -36,6 +36,7 @@ "golang.org/x/sync/errgroup" "github.com/docker/compose/v5/cmd/formatter" + "github.com/docker/compose/v5/internal/desktop" "github.com/docker/compose/v5/internal/tracing" "github.com/docker/compose/v5/pkg/api" ) @@ -88,8 +89,9 @@ if err != nil { return err } - tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive) - navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan) + isLogsViewEnabled := s.isDesktopFeatureActive(ctx, desktop.FeatureLogsTab) + tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, isLogsViewEnabled) + navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, isLogsViewEnabled, signalChan) logConsumer = navigationMenu.Decorate(logConsumer) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-compose-5.1.2/pkg/compose/watch.go new/docker-compose-5.1.3/pkg/compose/watch.go --- old/docker-compose-5.1.2/pkg/compose/watch.go 2026-04-08 08:52:50.000000000 +0200 +++ new/docker-compose-5.1.3/pkg/compose/watch.go 2026-04-15 15:46:06.000000000 +0200 @@ -662,9 +662,10 @@ options.LogTo.Log(api.WatchLogger, fmt.Sprintf("service(s) %q successfully built", services)) err = s.create(ctx, project, api.CreateOptions{ - Services: services, - Inherit: true, - Recreate: api.RecreateForce, + Services: services, + Inherit: true, + Recreate: api.RecreateForce, + SkipProviders: true, }) if err != nil { options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Failed to recreate services after update. Error: %v", err)) ++++++ docker-compose.obsinfo ++++++ --- /var/tmp/diff_new_pack.ZAnkgz/_old 2026-04-18 21:35:49.260208925 +0200 +++ /var/tmp/diff_new_pack.ZAnkgz/_new 2026-04-18 21:35:49.268209251 +0200 @@ -1,5 +1,5 @@ name: docker-compose -version: 5.1.2 -mtime: 1775631170 -commit: ae92bef4e1d02bfb75afeafc0ce9334a133f952f +version: 5.1.3 +mtime: 1776260766 +commit: 977a4310f9f6d89d4b176fee01a5b7c109c1816a ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/docker-compose/vendor.tar.gz /work/SRC/openSUSE:Factory/.docker-compose.new.11940/vendor.tar.gz differ: char 13, line 1
