diff --git a/commands/compose.go b/commands/compose.go index dd9cb972..ef415ebd 100644 --- a/commands/compose.go +++ b/commands/compose.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/docker/model-cli/pkg/types" "github.com/spf13/pflag" "slices" "strings" @@ -48,7 +49,7 @@ func newUpCommand() *cobra.Command { if err != nil { _ = sendErrorf("Failed to initialize standalone model runner: %v", err) return fmt.Errorf("Failed to initialize standalone model runner: %w", err) - } else if ((kind == desktop.ModelRunnerEngineKindMoby || kind == desktop.ModelRunnerEngineKindCloud) && + } else if ((kind == types.ModelRunnerEngineKindMoby || kind == types.ModelRunnerEngineKindCloud) && standalone == nil) || (standalone != nil && (standalone.gatewayIP == "" || standalone.gatewayPort == 0)) { return errors.New("unable to determine standalone runner endpoint") @@ -79,13 +80,13 @@ func newUpCommand() *cobra.Command { } switch kind { - case desktop.ModelRunnerEngineKindDesktop: + case types.ModelRunnerEngineKindDesktop: _ = setenv("URL", "http://model-runner.docker.internal/engines/v1/") - case desktop.ModelRunnerEngineKindMobyManual: + case types.ModelRunnerEngineKindMobyManual: _ = setenv("URL", modelRunner.URL("/engines/v1/")) - case desktop.ModelRunnerEngineKindCloud: + case types.ModelRunnerEngineKindCloud: fallthrough - case desktop.ModelRunnerEngineKindMoby: + case types.ModelRunnerEngineKindMoby: _ = setenv("URL", fmt.Sprintf("http://%s:%d/engines/v1", standalone.gatewayIP, standalone.gatewayPort)) default: return fmt.Errorf("unhandled engine kind: %v", kind) diff --git a/commands/install-runner.go b/commands/install-runner.go index 173bdeb4..e073008e 100644 --- a/commands/install-runner.go +++ b/commands/install-runner.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/docker/model-cli/pkg/types" "os" "time" @@ -77,8 +78,8 @@ func inspectStandaloneRunner(container container.Summary) *standaloneRunner { func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.StatusPrinter) (*standaloneRunner, error) { // If we're not in a supported model runner context, then don't do anything. engineKind := modelRunner.EngineKind() - standaloneSupported := engineKind == desktop.ModelRunnerEngineKindMoby || - engineKind == desktop.ModelRunnerEngineKindCloud + standaloneSupported := engineKind == types.ModelRunnerEngineKindMoby || + engineKind == types.ModelRunnerEngineKindCloud if !standaloneSupported { return nil, nil } @@ -127,11 +128,11 @@ func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.Sta // Create the model runner container. port := uint16(standalone.DefaultControllerPortMoby) environment := "moby" - if engineKind == desktop.ModelRunnerEngineKindCloud { + if engineKind == types.ModelRunnerEngineKindCloud { port = standalone.DefaultControllerPortCloud environment = "cloud" } - if err := standalone.CreateControllerContainer(ctx, dockerClient, port, environment, false, gpu, modelStorageVolume, printer); err != nil { + if err := standalone.CreateControllerContainer(ctx, dockerClient, port, environment, false, gpu, modelStorageVolume, printer, engineKind); err != nil { return nil, fmt.Errorf("unable to initialize standalone model runner container: %w", err) } @@ -166,14 +167,14 @@ func newInstallRunner() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { // Ensure that we're running in a supported model runner context. engineKind := modelRunner.EngineKind() - if engineKind == desktop.ModelRunnerEngineKindDesktop { + if engineKind == types.ModelRunnerEngineKindDesktop { // TODO: We may eventually want to auto-forward this to // docker desktop enable model-runner, but we should first make // sure the CLI flags match. cmd.Println("Standalone installation not supported with Docker Desktop") cmd.Println("Use `docker desktop enable model-runner` instead") return nil - } else if engineKind == desktop.ModelRunnerEngineKindMobyManual { + } else if engineKind == types.ModelRunnerEngineKindMobyManual { cmd.Println("Standalone installation not supported with MODEL_RUNNER_HOST set") return nil } @@ -185,14 +186,14 @@ func newInstallRunner() *cobra.Command { // when context detection happens. So assume that a default value // indicates that we want the Cloud default port. This is less // problematic in Cloud since the UX there is mostly invisible. - if engineKind == desktop.ModelRunnerEngineKindCloud && + if engineKind == types.ModelRunnerEngineKindCloud && port == standalone.DefaultControllerPortMoby { port = standalone.DefaultControllerPortCloud } // Set the appropriate environment. environment := "moby" - if engineKind == desktop.ModelRunnerEngineKindCloud { + if engineKind == types.ModelRunnerEngineKindCloud { environment = "cloud" } @@ -238,7 +239,7 @@ func newInstallRunner() *cobra.Command { return fmt.Errorf("unable to initialize standalone model storage: %w", err) } // Create the model runner container. - if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, environment, doNotTrack, gpu, modelStorageVolume, cmd); err != nil { + if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, environment, doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil { return fmt.Errorf("unable to initialize standalone model runner container: %w", err) } diff --git a/commands/logs.go b/commands/logs.go index 319167a9..7513a9ff 100644 --- a/commands/logs.go +++ b/commands/logs.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "github.com/docker/model-cli/pkg/types" "io" "os" "os/signal" @@ -37,8 +38,8 @@ func newLogsCmd() *cobra.Command { // If we're running in standalone mode, then print the container // logs. engineKind := modelRunner.EngineKind() - useStandaloneLogs := engineKind == desktop.ModelRunnerEngineKindMoby || - engineKind == desktop.ModelRunnerEngineKindCloud + useStandaloneLogs := engineKind == types.ModelRunnerEngineKindMoby || + engineKind == types.ModelRunnerEngineKindCloud if useStandaloneLogs { dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext()) if err != nil { diff --git a/commands/status.go b/commands/status.go index 2b90120c..de448477 100644 --- a/commands/status.go +++ b/commands/status.go @@ -3,6 +3,7 @@ package commands import ( "encoding/json" "fmt" + "github.com/docker/model-cli/pkg/types" "os" "github.com/docker/cli/cli-plugins/hooks" @@ -74,13 +75,13 @@ func jsonStatus(standalone *standaloneRunner, status desktop.Status, backendStat var endpoint string kind := modelRunner.EngineKind() switch kind { - case desktop.ModelRunnerEngineKindDesktop: + case types.ModelRunnerEngineKindDesktop: endpoint = "http://model-runner.docker.internal/engines/v1/" - case desktop.ModelRunnerEngineKindMobyManual: + case types.ModelRunnerEngineKindMobyManual: endpoint = modelRunner.URL("/engines/v1/") - case desktop.ModelRunnerEngineKindCloud: + case types.ModelRunnerEngineKindCloud: fallthrough - case desktop.ModelRunnerEngineKindMoby: + case types.ModelRunnerEngineKindMoby: endpoint = fmt.Sprintf("http://%s:%d/engines/v1", standalone.gatewayIP, standalone.gatewayPort) default: return fmt.Errorf("unhandled engine kind: %v", kind) diff --git a/commands/uninstall-runner.go b/commands/uninstall-runner.go index 27fd9b1d..a11f11c6 100644 --- a/commands/uninstall-runner.go +++ b/commands/uninstall-runner.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "github.com/docker/model-cli/pkg/types" "github.com/docker/model-cli/commands/completion" "github.com/docker/model-cli/desktop" @@ -16,14 +17,14 @@ func newUninstallRunner() *cobra.Command { Short: "Uninstall Docker Model Runner", RunE: func(cmd *cobra.Command, args []string) error { // Ensure that we're running in a supported model runner context. - if kind := modelRunner.EngineKind(); kind == desktop.ModelRunnerEngineKindDesktop { + if kind := modelRunner.EngineKind(); kind == types.ModelRunnerEngineKindDesktop { // TODO: We may eventually want to auto-forward this to // docker desktop disable model-runner, but we should first // make install-runner forward in the same way. cmd.Println("Standalone uninstallation not supported with Docker Desktop") cmd.Println("Use `docker desktop disable model-runner` instead") return nil - } else if kind == desktop.ModelRunnerEngineKindMobyManual { + } else if kind == types.ModelRunnerEngineKindMobyManual { cmd.Println("Standalone uninstallation not supported with MODEL_RUNNER_HOST set") return nil } diff --git a/desktop/context.go b/desktop/context.go index 736c18b1..e302185d 100644 --- a/desktop/context.go +++ b/desktop/context.go @@ -3,6 +3,7 @@ package desktop import ( "context" "fmt" + "github.com/docker/model-cli/pkg/types" "net/http" "net/url" "os" @@ -79,47 +80,11 @@ func DockerClientForContext(cli *command.DockerCli, name string) (*clientpkg.Cli ) } -// ModelRunnerEngineKind encodes the kind of Docker engine associated with the -// model runner context. -type ModelRunnerEngineKind uint8 - -const ( - // ModelRunnerEngineKindMoby represents a non-Desktop/Cloud engine on which - // the Model CLI command is responsible for managing a Model Runner. - ModelRunnerEngineKindMoby ModelRunnerEngineKind = iota - // ModelRunnerEngineKindMobyManual represents a non-Desktop/Cloud engine - // that's explicitly targeted by a MODEL_RUNNER_HOST environment variable on - // which the user is responsible for managing a Model Runner. - ModelRunnerEngineKindMobyManual - // ModelRunnerEngineKindDesktop represents a Docker Desktop engine. It only - // refers to a Docker Desktop Linux engine, i.e. not a Windows container - // engine in the case of Docker Desktop for Windows. - ModelRunnerEngineKindDesktop - // ModelRunnerEngineKindCloud represents a Docker Cloud engine. - ModelRunnerEngineKindCloud -) - -// String returns a human-readable engine kind description. -func (k ModelRunnerEngineKind) String() string { - switch k { - case ModelRunnerEngineKindMoby: - return "Docker Engine" - case ModelRunnerEngineKindMobyManual: - return "Docker Engine (Manual Install)" - case ModelRunnerEngineKindDesktop: - return "Docker Desktop" - case ModelRunnerEngineKindCloud: - return "Docker Cloud" - default: - return "Unknown" - } -} - // ModelRunnerContext encodes the operational context of a Model CLI command and // provides facilities for inspecting and interacting with the Model Runner. type ModelRunnerContext struct { // kind stores the associated engine kind. - kind ModelRunnerEngineKind + kind types.ModelRunnerEngineKind // urlPrefix is the prefix URL for all requests. urlPrefix *url.URL // client is the model runner client. @@ -134,7 +99,7 @@ func NewContextForMock(client DockerHttpClient) *ModelRunnerContext { panic("error occurred while parsing known-good URL") } return &ModelRunnerContext{ - kind: ModelRunnerEngineKindDesktop, + kind: types.ModelRunnerEngineKindDesktop, urlPrefix: urlPrefix, client: client, } @@ -150,25 +115,25 @@ func DetectContext(ctx context.Context, cli *command.DockerCli) (*ModelRunnerCon treatDesktopAsMoby := os.Getenv("_MODEL_RUNNER_TREAT_DESKTOP_AS_MOBY") == "1" // Detect the associated engine type. - kind := ModelRunnerEngineKindMoby + kind := types.ModelRunnerEngineKindMoby if modelRunnerHost != "" { - kind = ModelRunnerEngineKindMobyManual + kind = types.ModelRunnerEngineKindMobyManual } else if isDesktopContext(ctx, cli) { - kind = ModelRunnerEngineKindDesktop + kind = types.ModelRunnerEngineKindDesktop if treatDesktopAsMoby { - kind = ModelRunnerEngineKindMoby + kind = types.ModelRunnerEngineKindMoby } } else if isCloudContext(cli) { - kind = ModelRunnerEngineKindCloud + kind = types.ModelRunnerEngineKindCloud } // Compute the URL prefix based on the associated engine kind. var rawURLPrefix string - if kind == ModelRunnerEngineKindMoby { + if kind == types.ModelRunnerEngineKindMoby { rawURLPrefix = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortMoby) - } else if kind == ModelRunnerEngineKindCloud { + } else if kind == types.ModelRunnerEngineKindCloud { rawURLPrefix = "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortCloud) - } else if kind == ModelRunnerEngineKindMobyManual { + } else if kind == types.ModelRunnerEngineKindMobyManual { rawURLPrefix = modelRunnerHost } else { // ModelRunnerEngineKindDesktop rawURLPrefix = "http://localhost" + inference.ExperimentalEndpointsPrefix @@ -180,7 +145,7 @@ func DetectContext(ctx context.Context, cli *command.DockerCli) (*ModelRunnerCon // Construct the HTTP client. var client DockerHttpClient - if kind == ModelRunnerEngineKindDesktop { + if kind == types.ModelRunnerEngineKindDesktop { dockerClient, err := DockerClientForContext(cli, cli.CurrentContext()) if err != nil { return nil, fmt.Errorf("unable to create model runner client: %w", err) @@ -199,7 +164,7 @@ func DetectContext(ctx context.Context, cli *command.DockerCli) (*ModelRunnerCon } // EngineKind returns the Docker engine kind associated with the model runner. -func (c *ModelRunnerContext) EngineKind() ModelRunnerEngineKind { +func (c *ModelRunnerContext) EngineKind() types.ModelRunnerEngineKind { return c.kind } diff --git a/pkg/standalone/containers.go b/pkg/standalone/containers.go index 9c8ccfc3..520ca817 100644 --- a/pkg/standalone/containers.go +++ b/pkg/standalone/containers.go @@ -19,6 +19,7 @@ import ( "github.com/docker/docker/client" "github.com/docker/go-connections/nat" gpupkg "github.com/docker/model-cli/pkg/gpu" + "github.com/docker/model-cli/pkg/types" ) // controllerContainerName is the name to use for the controller container. @@ -31,7 +32,13 @@ var concurrentInstallMatcher = regexp.MustCompile(`is already in use by containe // copyDockerConfigToContainer copies the Docker config file from the host to the container // and sets up proper ownership and permissions for the modelrunner user. -func copyDockerConfigToContainer(ctx context.Context, dockerClient *client.Client, containerID string) error { +// It does nothing for Desktop and Cloud engine kinds. +func copyDockerConfigToContainer(ctx context.Context, dockerClient *client.Client, containerID string, engineKind types.ModelRunnerEngineKind) error { + // Do nothing for Desktop and Cloud engine kinds + if engineKind == types.ModelRunnerEngineKindDesktop || engineKind == types.ModelRunnerEngineKindCloud { + return nil + } + dockerConfigPath := os.ExpandEnv("$HOME/.docker/config.json") if s, err := os.Stat(dockerConfigPath); err != nil || s.Mode()&os.ModeType != 0 { return nil @@ -215,7 +222,7 @@ func waitForContainerToStart(ctx context.Context, dockerClient client.ContainerA } // CreateControllerContainer creates and starts a controller container. -func CreateControllerContainer(ctx context.Context, dockerClient *client.Client, port uint16, environment string, doNotTrack bool, gpu gpupkg.GPUSupport, modelStorageVolume string, printer StatusPrinter) error { +func CreateControllerContainer(ctx context.Context, dockerClient *client.Client, port uint16, environment string, doNotTrack bool, gpu gpupkg.GPUSupport, modelStorageVolume string, printer StatusPrinter, engineKind types.ModelRunnerEngineKind) error { // Determine the target image. var imageName string switch gpu { @@ -294,7 +301,7 @@ func CreateControllerContainer(ctx context.Context, dockerClient *client.Client, } // Copy Docker config file if it exists - if err := copyDockerConfigToContainer(ctx, dockerClient, resp.ID); err != nil { + if err := copyDockerConfigToContainer(ctx, dockerClient, resp.ID, engineKind); err != nil { // Log warning but continue - don't fail container creation printer.Printf("Warning: failed to copy Docker config: %v\n", err) } diff --git a/pkg/types/engine.go b/pkg/types/engine.go new file mode 100644 index 00000000..b16e5299 --- /dev/null +++ b/pkg/types/engine.go @@ -0,0 +1,37 @@ +package types + +// ModelRunnerEngineKind encodes the kind of Docker engine associated with the +// model runner context. +type ModelRunnerEngineKind uint8 + +const ( + // ModelRunnerEngineKindMoby represents a non-Desktop/Cloud engine on which + // the Model CLI command is responsible for managing a Model Runner. + ModelRunnerEngineKindMoby ModelRunnerEngineKind = iota + // ModelRunnerEngineKindMobyManual represents a non-Desktop/Cloud engine + // that's explicitly targeted by a MODEL_RUNNER_HOST environment variable on + // which the user is responsible for managing a Model Runner. + ModelRunnerEngineKindMobyManual + // ModelRunnerEngineKindDesktop represents a Docker Desktop engine. It only + // refers to a Docker Desktop Linux engine, i.e. not a Windows container + // engine in the case of Docker Desktop for Windows. + ModelRunnerEngineKindDesktop + // ModelRunnerEngineKindCloud represents a Docker Cloud engine. + ModelRunnerEngineKindCloud +) + +// String returns a human-readable engine kind description. +func (k ModelRunnerEngineKind) String() string { + switch k { + case ModelRunnerEngineKindMoby: + return "Docker Engine" + case ModelRunnerEngineKindMobyManual: + return "Docker Engine (Manual Install)" + case ModelRunnerEngineKindDesktop: + return "Docker Desktop" + case ModelRunnerEngineKindCloud: + return "Docker Cloud" + default: + return "Unknown" + } +}