From 84b73705493ed65f692699347c44815c40a46c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Thu, 2 Apr 2026 12:54:06 +0200 Subject: [PATCH 1/3] feat: implement engine kind detection for default context in context ls --- cmd/cli/commands/context.go | 56 +++++++++++++++++++++++++++++++++---- cmd/cli/desktop/context.go | 15 ++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/cmd/cli/commands/context.go b/cmd/cli/commands/context.go index 4a1d1489c..86f6cea1f 100644 --- a/cmd/cli/commands/context.go +++ b/cmd/cli/commands/context.go @@ -2,19 +2,28 @@ package commands import ( "bytes" + "context" "fmt" "net/url" "os" "path/filepath" "sort" + "strconv" "time" "github.com/docker/cli/cli/command" "github.com/docker/model-runner/cmd/cli/commands/formatter" + "github.com/docker/model-runner/cmd/cli/desktop" "github.com/docker/model-runner/cmd/cli/pkg/modelctx" + "github.com/docker/model-runner/cmd/cli/pkg/standalone" + "github.com/docker/model-runner/cmd/cli/pkg/types" "github.com/spf13/cobra" ) +// defaultContextDetectTimeout is the maximum time allowed for detecting the +// engine kind when building the synthetic "default" context row. +const defaultContextDetectTimeout = 2 * time.Second + // newContextCmd returns the "docker model context" parent command. Its // subcommands manage named Model Runner contexts stored on disk, so they do // not require a running backend and override PersistentPreRunE accordingly. @@ -66,6 +75,37 @@ func dockerConfigDir() (string, error) { return filepath.Join(home, ".docker"), nil } +// resolveDefaultContext attempts to detect the Docker engine kind for the +// synthetic "default" context row. When the CLI is available it probes the +// Docker daemon (with a short timeout) and returns a descriptive host and +// description derived from the detected engine kind. If detection fails or +// the CLI is unavailable it falls back to generic strings. +func resolveDefaultContext(ctx context.Context) (host, description string) { + const fallbackHost = "(auto-detect)" + const fallbackDescription = "Auto-detected Docker context" + + if dockerCLI == nil { + return fallbackHost, fallbackDescription + } + + detectCtx, cancel := context.WithTimeout(ctx, defaultContextDetectTimeout) + defer cancel() + + kind := desktop.DetectEngineKind(detectCtx, dockerCLI) + description = "Model Runner on " + kind.String() + + switch kind { + case types.ModelRunnerEngineKindDesktop: + host = kind.String() + case types.ModelRunnerEngineKindCloud: + host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortCloud) + case types.ModelRunnerEngineKindMoby, types.ModelRunnerEngineKindMobyManual: + host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortMoby) + } + + return host, description +} + // newContextCreateCmd returns the "context create" command. func newContextCreateCmd() *cobra.Command { var ( @@ -223,12 +263,16 @@ func newContextLsCmd() *cobra.Command { ) } + // Resolve the host and description for the synthetic "default" + // row by detecting the engine kind (Desktop, Moby, Cloud). + defaultHost, defaultDescription := resolveDefaultContext(cmd.Context()) + // Build rows: synthetic "default" first, then named contexts sorted. rows := []contextListRow{ { name: modelctx.DefaultContextName, - host: "(auto-detect)", - description: "Auto-detected Docker context", + host: defaultHost, + description: defaultDescription, active: activeName == modelctx.DefaultContextName, }, } @@ -328,12 +372,14 @@ func newContextInspectCmd() *cobra.Command { results := make([]namedContextInspect, 0, len(args)) for _, name := range args { if name == modelctx.DefaultContextName { - // Return a synthetic entry for "default". + // Return a synthetic entry for "default", + // resolved from the detected engine kind. + defaultHost, defaultDescription := resolveDefaultContext(cmd.Context()) results = append(results, namedContextInspect{ Name: modelctx.DefaultContextName, ContextConfig: modelctx.ContextConfig{ - Host: "(auto-detect)", - Description: "Auto-detected Docker context", + Host: defaultHost, + Description: defaultDescription, }, }) continue diff --git a/cmd/cli/desktop/context.go b/cmd/cli/desktop/context.go index 7e6546b2c..8f3659dc1 100644 --- a/cmd/cli/desktop/context.go +++ b/cmd/cli/desktop/context.go @@ -243,6 +243,21 @@ func namedContextStore(cli *command.DockerCli) (*modelctx.Store, error) { return modelctx.New(configDir) } +// DetectEngineKind determines the Docker engine kind associated with the +// current CLI context without performing any side effects (such as waking +// up Docker Cloud). It is intended for informational commands like +// "context ls" that need to display the resolved engine kind without +// triggering backend initialisation. +func DetectEngineKind(ctx context.Context, cli *command.DockerCli) types.ModelRunnerEngineKind { + if isDesktopContext(ctx, cli) { + return types.ModelRunnerEngineKindDesktop + } + if isCloudContext(cli) { + return types.ModelRunnerEngineKindCloud + } + return types.ModelRunnerEngineKindMoby +} + // DetectContext determines the current Docker Model Runner context. func DetectContext(ctx context.Context, cli *command.DockerCli, printer standalone.StatusPrinter) (*ModelRunnerContext, error) { // Check for an explicit endpoint setting. From c6ef7c1bca51d854c8e9cbc5197fa37504f98287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Thu, 2 Apr 2026 13:12:52 +0200 Subject: [PATCH 2/3] fix: address review feedback for engine kind detection - Handle WSL2 edge case in DetectEngineKind where a Moby controller container may be running alongside Docker Desktop - Respect MODEL_RUNNER_TLS env var in resolveDefaultContext to show correct scheme (https) and TLS ports (12444/12445) when TLS is enabled - Resolve default context info lazily once in inspect command to avoid repeated network probes when 'default' appears multiple times in args --- cmd/cli/commands/context.go | 28 +++++++++++++++++++++++----- cmd/cli/desktop/context.go | 11 +++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/cmd/cli/commands/context.go b/cmd/cli/commands/context.go index 86f6cea1f..fa45d1169 100644 --- a/cmd/cli/commands/context.go +++ b/cmd/cli/commands/context.go @@ -94,13 +94,25 @@ func resolveDefaultContext(ctx context.Context) (host, description string) { kind := desktop.DetectEngineKind(detectCtx, dockerCLI) description = "Model Runner on " + kind.String() + // Check whether TLS is enabled so the displayed scheme and port match + // what DetectContext would actually use at runtime. + useTLS := os.Getenv("MODEL_RUNNER_TLS") == "true" + switch kind { case types.ModelRunnerEngineKindDesktop: host = kind.String() case types.ModelRunnerEngineKindCloud: - host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortCloud) + if useTLS { + host = "https://localhost:" + strconv.Itoa(standalone.DefaultTLSPortCloud) + } else { + host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortCloud) + } case types.ModelRunnerEngineKindMoby, types.ModelRunnerEngineKindMobyManual: - host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortMoby) + if useTLS { + host = "https://localhost:" + strconv.Itoa(standalone.DefaultTLSPortMoby) + } else { + host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortMoby) + } } return host, description @@ -369,12 +381,18 @@ func newContextInspectCmd() *cobra.Command { return fmt.Errorf("unable to open context store: %w", err) } + // Resolve the default context info once (lazily) so that + // repeated "default" args do not trigger multiple probes. + var defaultHost, defaultDescription string + defaultResolved := false + results := make([]namedContextInspect, 0, len(args)) for _, name := range args { if name == modelctx.DefaultContextName { - // Return a synthetic entry for "default", - // resolved from the detected engine kind. - defaultHost, defaultDescription := resolveDefaultContext(cmd.Context()) + if !defaultResolved { + defaultHost, defaultDescription = resolveDefaultContext(cmd.Context()) + defaultResolved = true + } results = append(results, namedContextInspect{ Name: modelctx.DefaultContextName, ContextConfig: modelctx.ContextConfig{ diff --git a/cmd/cli/desktop/context.go b/cmd/cli/desktop/context.go index 8f3659dc1..17a059ffe 100644 --- a/cmd/cli/desktop/context.go +++ b/cmd/cli/desktop/context.go @@ -250,6 +250,17 @@ func namedContextStore(cli *command.DockerCli) (*modelctx.Store, error) { // triggering backend initialisation. func DetectEngineKind(ctx context.Context, cli *command.DockerCli) types.ModelRunnerEngineKind { if isDesktopContext(ctx, cli) { + // On WSL2, a Moby-based controller container may be running + // alongside Docker Desktop. Mirror the logic in DetectContext + // so that "context ls" reports the same engine kind. + if IsDesktopWSLContext(ctx, cli) { + if dockerClient, err := DockerClientForContext(cli, cli.CurrentContext()); err == nil { + defer dockerClient.Close() + if containerID, _, _, findErr := standalone.FindControllerContainer(ctx, dockerClient); findErr == nil && containerID != "" { + return types.ModelRunnerEngineKindMoby + } + } + } return types.ModelRunnerEngineKindDesktop } if isCloudContext(cli) { From d3e11a3c465a35d9fdc3b9c9318402984ef241cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Thu, 2 Apr 2026 16:45:48 +0200 Subject: [PATCH 3/3] fix: use envconfig for TLS checks in engine kind detection --- cmd/cli/commands/context.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cmd/cli/commands/context.go b/cmd/cli/commands/context.go index fa45d1169..9a1843cc9 100644 --- a/cmd/cli/commands/context.go +++ b/cmd/cli/commands/context.go @@ -17,6 +17,7 @@ import ( "github.com/docker/model-runner/cmd/cli/pkg/modelctx" "github.com/docker/model-runner/cmd/cli/pkg/standalone" "github.com/docker/model-runner/cmd/cli/pkg/types" + "github.com/docker/model-runner/pkg/envconfig" "github.com/spf13/cobra" ) @@ -94,21 +95,17 @@ func resolveDefaultContext(ctx context.Context) (host, description string) { kind := desktop.DetectEngineKind(detectCtx, dockerCLI) description = "Model Runner on " + kind.String() - // Check whether TLS is enabled so the displayed scheme and port match - // what DetectContext would actually use at runtime. - useTLS := os.Getenv("MODEL_RUNNER_TLS") == "true" - switch kind { case types.ModelRunnerEngineKindDesktop: host = kind.String() case types.ModelRunnerEngineKindCloud: - if useTLS { + if envconfig.TLSEnabled() { host = "https://localhost:" + strconv.Itoa(standalone.DefaultTLSPortCloud) } else { host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortCloud) } case types.ModelRunnerEngineKindMoby, types.ModelRunnerEngineKindMobyManual: - if useTLS { + if envconfig.TLSEnabled() { host = "https://localhost:" + strconv.Itoa(standalone.DefaultTLSPortMoby) } else { host = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortMoby)