Skip to content

Commit dece7d2

Browse files
committed
Add reinstall-runner command implementation
So one can reinstall in one command Signed-off-by: Eric Curtin <eric.curtin@docker.com>
1 parent 38a800e commit dece7d2

5 files changed

Lines changed: 207 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
model-distribution-tool
33
model-runner
44
model-runner.sock
5+
docker-model
56
# Default MODELS_PATH in Makefile
67
models-store/
78
# Default MODELS_PATH in mdltool
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"github.com/docker/model-runner/cmd/cli/pkg/types"
6+
7+
"github.com/docker/model-runner/cmd/cli/commands/completion"
8+
"github.com/docker/model-runner/cmd/cli/desktop"
9+
gpupkg "github.com/docker/model-runner/cmd/cli/pkg/gpu"
10+
"github.com/docker/model-runner/cmd/cli/pkg/standalone"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func newReinstallRunner() *cobra.Command {
15+
var port uint16
16+
var host string
17+
var gpuMode string
18+
var doNotTrack bool
19+
c := &cobra.Command{
20+
Use: "reinstall-runner",
21+
Short: "Reinstall Docker Model Runner (Docker Engine only)",
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
// Ensure that we're running in a supported model runner context.
24+
engineKind := modelRunner.EngineKind()
25+
if engineKind == types.ModelRunnerEngineKindDesktop {
26+
cmd.Println("Standalone reinstallation not supported with Docker Desktop")
27+
cmd.Println("Use `docker desktop disable model-runner` and `docker desktop enable model-runner` instead")
28+
return nil
29+
} else if engineKind == types.ModelRunnerEngineKindMobyManual {
30+
cmd.Println("Standalone reinstallation not supported with MODEL_RUNNER_HOST set")
31+
return nil
32+
}
33+
34+
if port == 0 {
35+
// Use "0" as a sentinel default flag value so it's not displayed automatically.
36+
// The default values are written in the usage string.
37+
// Hence, the user currently won't be able to set the port to 0 in order to get a random available port.
38+
port = standalone.DefaultControllerPortMoby
39+
}
40+
// HACK: If we're in a Cloud context, then we need to use a
41+
// different default port because it conflicts with Docker Desktop's
42+
// default model runner host-side port. Unfortunately we can't make
43+
// the port flag default dynamic (at least not easily) because of
44+
// when context detection happens. So assume that a default value
45+
// indicates that we want the Cloud default port. This is less
46+
// problematic in Cloud since the UX there is mostly invisible.
47+
if engineKind == types.ModelRunnerEngineKindCloud &&
48+
port == standalone.DefaultControllerPortMoby {
49+
port = standalone.DefaultControllerPortCloud
50+
}
51+
52+
// Set the appropriate environment.
53+
environment := "moby"
54+
if engineKind == types.ModelRunnerEngineKindCloud {
55+
environment = "cloud"
56+
}
57+
58+
// Create a Docker client for the active context.
59+
dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext())
60+
if err != nil {
61+
return fmt.Errorf("failed to create Docker client: %w", err)
62+
}
63+
64+
// Remove any model runner containers (but keep models and images).
65+
if err := standalone.PruneControllerContainers(cmd.Context(), dockerClient, false, cmd); err != nil {
66+
return fmt.Errorf("unable to remove model runner container(s): %w", err)
67+
}
68+
69+
// Determine GPU support.
70+
var gpu gpupkg.GPUSupport
71+
if gpuMode == "auto" {
72+
gpu, err = gpupkg.ProbeGPUSupport(cmd.Context(), dockerClient)
73+
if err != nil {
74+
return fmt.Errorf("unable to probe GPU support: %w", err)
75+
}
76+
} else if gpuMode == "cuda" {
77+
gpu = gpupkg.GPUSupportCUDA
78+
} else if gpuMode != "none" {
79+
return fmt.Errorf("unknown GPU specification: %q", gpuMode)
80+
}
81+
82+
// Ensure that we have an up-to-date copy of the image.
83+
if err := standalone.EnsureControllerImage(cmd.Context(), dockerClient, gpu, cmd); err != nil {
84+
return fmt.Errorf("unable to pull latest standalone model runner image: %w", err)
85+
}
86+
87+
// Ensure that we have a model storage volume.
88+
modelStorageVolume, err := standalone.EnsureModelStorageVolume(cmd.Context(), dockerClient, cmd)
89+
if err != nil {
90+
return fmt.Errorf("unable to initialize standalone model storage: %w", err)
91+
}
92+
93+
// Create the model runner container.
94+
if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, host, environment, doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil {
95+
return fmt.Errorf("unable to initialize standalone model runner container: %w", err)
96+
}
97+
98+
// Poll until we get a response from the model runner.
99+
return waitForStandaloneRunnerAfterInstall(cmd.Context())
100+
},
101+
ValidArgsFunction: completion.NoComplete,
102+
}
103+
c.Flags().Uint16Var(&port, "port", 0,
104+
"Docker container port for Docker Model Runner (default: 12434 for Docker CE, 12435 for Cloud mode)")
105+
c.Flags().StringVar(&host, "host", "127.0.0.1", "Host address to bind Docker Model Runner")
106+
c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)")
107+
c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner")
108+
return c
109+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package commands
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestReinstallRunnerHostFlag(t *testing.T) {
8+
testCases := []struct {
9+
name string
10+
value string
11+
}{
12+
{"localhost", "127.0.0.1"},
13+
{"all_interfaces", "0.0.0.0"},
14+
{"specific_IP", "192.168.1.100"},
15+
}
16+
17+
for _, tc := range testCases {
18+
t.Run(tc.name, func(t *testing.T) {
19+
// Reset the command for each test
20+
cmd := newReinstallRunner()
21+
err := cmd.Flags().Set("host", tc.value)
22+
if err != nil {
23+
t.Errorf("Failed to set host flag to '%s': %v", tc.value, err)
24+
}
25+
26+
// Verify the value was set
27+
hostValue, err := cmd.Flags().GetString("host")
28+
if err != nil {
29+
t.Errorf("Failed to get host flag value: %v", err)
30+
}
31+
if hostValue != tc.value {
32+
t.Errorf("Expected host value to be '%s', got '%s'", tc.value, hostValue)
33+
}
34+
})
35+
}
36+
}
37+
38+
func TestReinstallRunnerCommandFlags(t *testing.T) {
39+
cmd := newReinstallRunner()
40+
41+
// Verify all expected flags exist
42+
expectedFlags := []string{"port", "host", "gpu", "do-not-track"}
43+
for _, flagName := range expectedFlags {
44+
if cmd.Flags().Lookup(flagName) == nil {
45+
t.Errorf("Expected flag '--%s' not found", flagName)
46+
}
47+
}
48+
}
49+
50+
func TestReinstallRunnerCommandType(t *testing.T) {
51+
cmd := newReinstallRunner()
52+
53+
// Verify command properties
54+
if cmd.Use != "reinstall-runner" {
55+
t.Errorf("Expected command Use to be 'reinstall-runner', got '%s'", cmd.Use)
56+
}
57+
58+
if cmd.Short != "Reinstall Docker Model Runner (Docker Engine only)" {
59+
t.Errorf("Unexpected command Short description: %s", cmd.Short)
60+
}
61+
62+
// Verify RunE is set
63+
if cmd.RunE == nil {
64+
t.Error("Expected RunE to be set")
65+
}
66+
}
67+
68+
func TestReinstallRunnerValidArgsFunction(t *testing.T) {
69+
cmd := newReinstallRunner()
70+
71+
// The reinstall-runner command should not accept any arguments
72+
// So ValidArgsFunction should be set to handle no arguments
73+
if cmd.ValidArgsFunction == nil {
74+
t.Error("Expected ValidArgsFunction to be set")
75+
}
76+
}

cmd/cli/commands/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command {
106106
newTagCmd(),
107107
newInstallRunner(),
108108
newUninstallRunner(),
109+
newReinstallRunner(),
109110
newConfigureCmd(),
110111
newPSCmd(),
111112
newDFCmd(),
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# docker model reinstall-runner
2+
3+
<!---MARKER_GEN_START-->
4+
Reinstall Docker Model Runner (Docker Engine only)
5+
6+
### Options
7+
8+
| Name | Type | Default | Description |
9+
|:-----------------|:---------|:------------|:---------------------------------------------------------------------------------------------------|
10+
| `--do-not-track` | `bool` | | Do not track models usage in Docker Model Runner |
11+
| `--gpu` | `string` | `auto` | Specify GPU support (none\|auto\|cuda) |
12+
| `--host` | `string` | `127.0.0.1` | Host address to bind Docker Model Runner |
13+
| `--port` | `uint16` | `0` | Docker container port for Docker Model Runner (default: 12434 for Docker CE, 12435 for Cloud mode) |
14+
15+
16+
<!---MARKER_GEN_END-->
17+
18+
## Description
19+
20+
This command removes the existing Docker Model Runner container and reinstalls it with the specified configuration. Models and images are preserved during reinstallation.

0 commit comments

Comments
 (0)