Skip to content

Commit fcbff17

Browse files
authored
Merge pull request #694 from thaJeztah/migrate_moby
migrate to moby modules
2 parents e574090 + e4da837 commit fcbff17

File tree

11 files changed

+200
-173
lines changed

11 files changed

+200
-173
lines changed

cmd/cli/commands/install-runner.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"strings"
99
"time"
1010

11-
"github.com/docker/docker/api/types/container"
1211
"github.com/docker/model-runner/cmd/cli/commands/completion"
1312
"github.com/docker/model-runner/cmd/cli/desktop"
1413
gpupkg "github.com/docker/model-runner/cmd/cli/pkg/gpu"
@@ -18,6 +17,7 @@ import (
1817
"github.com/docker/model-runner/pkg/inference/backends/llamacpp"
1918
"github.com/docker/model-runner/pkg/inference/backends/vllm"
2019
"github.com/docker/model-runner/pkg/inference/backends/vllmmetal"
20+
"github.com/moby/moby/api/types/container"
2121
"github.com/spf13/cobra"
2222
)
2323

@@ -55,6 +55,8 @@ type standaloneRunner struct {
5555
// hostPort is the port that the runner is listening to on the host.
5656
hostPort uint16
5757
// gatewayIP is the gateway IP address that the runner is listening on.
58+
//
59+
// TODO(thaJeztah): consider changing this to a netip.Addr
5860
gatewayIP string
5961
// gatewayPort is the gateway port that the runner is listening on.
6062
gatewayPort uint16
@@ -65,13 +67,15 @@ type standaloneRunner struct {
6567
func inspectStandaloneRunner(container container.Summary) *standaloneRunner {
6668
result := &standaloneRunner{}
6769
for _, port := range container.Ports {
68-
if port.IP == "127.0.0.1" {
70+
if port.IP.IsLoopback() {
6971
result.hostPort = port.PublicPort
7072
} else {
7173
// We don't really have a good way of knowing what the gateway IP
7274
// address is, but in the standard standalone configuration we only
7375
// bind to two interfaces: 127.0.0.1 and the gateway interface.
74-
result.gatewayIP = port.IP
76+
if port.IP.IsValid() {
77+
result.gatewayIP = port.IP.String()
78+
}
7579
result.gatewayPort = port.PublicPort
7680
}
7781
}

cmd/cli/commands/logs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ import (
1313
"runtime"
1414
"time"
1515

16-
"github.com/docker/docker/api/types/container"
17-
"github.com/docker/docker/pkg/stdcopy"
1816
"github.com/docker/model-runner/cmd/cli/commands/completion"
1917
"github.com/docker/model-runner/cmd/cli/desktop"
2018
"github.com/docker/model-runner/cmd/cli/pkg/standalone"
2119
"github.com/docker/model-runner/cmd/cli/pkg/types"
20+
"github.com/moby/moby/api/pkg/stdcopy"
21+
"github.com/moby/moby/client"
2222
"github.com/nxadm/tail"
2323
"github.com/spf13/cobra"
2424
"golang.org/x/sync/errgroup"
@@ -51,7 +51,7 @@ func newLogsCmd() *cobra.Command {
5151
} else if ctrID == "" {
5252
return errors.New("unable to identify Model Runner container")
5353
}
54-
log, err := dockerClient.ContainerLogs(cmd.Context(), ctrID, container.LogsOptions{
54+
log, err := dockerClient.ContainerLogs(cmd.Context(), ctrID, client.ContainerLogsOptions{
5555
ShowStdout: true,
5656
ShowStderr: true,
5757
Follow: follow,

cmd/cli/commands/nim.go

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ import (
88
"fmt"
99
"io"
1010
"net/http"
11+
"net/netip"
1112
"os"
1213
"path/filepath"
1314
"strconv"
1415
"strings"
1516
"time"
1617

17-
"github.com/docker/docker/api/types/container"
18-
"github.com/docker/docker/api/types/image"
19-
"github.com/docker/docker/api/types/mount"
20-
"github.com/docker/docker/client"
21-
"github.com/docker/go-connections/nat"
2218
gpupkg "github.com/docker/model-runner/cmd/cli/pkg/gpu"
19+
"github.com/moby/moby/api/types/container"
20+
"github.com/moby/moby/api/types/mount"
21+
"github.com/moby/moby/api/types/network"
22+
"github.com/moby/moby/client"
2323
"github.com/spf13/cobra"
2424
)
2525

@@ -137,7 +137,7 @@ func pullNIMImage(ctx context.Context, dockerClient *client.Client, model string
137137
}
138138
}
139139

140-
pullOptions := image.PullOptions{}
140+
pullOptions := client.ImagePullOptions{}
141141

142142
// Set authentication if available
143143
if authStr != "" {
@@ -186,7 +186,9 @@ func pullNIMImage(ctx context.Context, dockerClient *client.Client, model string
186186
defer reader.Close()
187187

188188
// Stream pull progress
189-
io.Copy(cmd.OutOrStdout(), reader)
189+
//
190+
// TODO(thaJeztah): format output / progress?
191+
_, _ = io.Copy(cmd.OutOrStdout(), reader)
190192

191193
return nil
192194
}
@@ -195,15 +197,16 @@ func pullNIMImage(ctx context.Context, dockerClient *client.Client, model string
195197
func findNIMContainer(ctx context.Context, dockerClient *client.Client, model string) (string, error) {
196198
containerName := nimContainerName(model)
197199

198-
containers, err := dockerClient.ContainerList(ctx, container.ListOptions{
200+
res, err := dockerClient.ContainerList(ctx, client.ContainerListOptions{
199201
All: true,
200202
})
201203
if err != nil {
202204
return "", fmt.Errorf("failed to list containers: %w", err)
203205
}
204206

205-
for _, c := range containers {
207+
for _, c := range res.Items {
206208
for _, name := range c.Names {
209+
// TODO(thaJeztah): replace this with a filter, or use "inspect"
207210
if strings.TrimPrefix(name, "/") == containerName {
208211
return c.ID, nil
209212
}
@@ -246,17 +249,18 @@ func createNIMContainer(ctx context.Context, dockerClient *client.Client, model
246249
}
247250

248251
// Container configuration
249-
env := []string{}
252+
var env []string
250253
if ngcAPIKey != "" {
251254
env = append(env, "NGC_API_KEY="+ngcAPIKey)
252255
}
253256

254-
portStr := strconv.Itoa(nimDefaultPort)
257+
hostPort, _ := network.PortFrom(nimDefaultPort, network.TCP)
258+
255259
config := &container.Config{
256260
Image: model,
257261
Env: env,
258-
ExposedPorts: nat.PortSet{
259-
nat.Port(portStr + "/tcp"): struct{}{},
262+
ExposedPorts: network.PortSet{
263+
hostPort: struct{}{},
260264
},
261265
}
262266

@@ -269,11 +273,11 @@ func createNIMContainer(ctx context.Context, dockerClient *client.Client, model
269273
Target: "/opt/nim/.cache",
270274
},
271275
},
272-
PortBindings: nat.PortMap{
273-
nat.Port(portStr + "/tcp"): []nat.PortBinding{
276+
PortBindings: network.PortMap{
277+
hostPort: []network.PortBinding{
274278
{
275-
HostIP: "127.0.0.1",
276-
HostPort: portStr,
279+
HostIP: netip.MustParseAddr("127.0.0.1"),
280+
HostPort: strconv.Itoa(nimDefaultPort),
277281
},
278282
},
279283
},
@@ -291,13 +295,19 @@ func createNIMContainer(ctx context.Context, dockerClient *client.Client, model
291295
}
292296

293297
// Create the container
294-
resp, err := dockerClient.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName)
298+
resp, err := dockerClient.ContainerCreate(ctx, client.ContainerCreateOptions{
299+
Config: config,
300+
HostConfig: hostConfig,
301+
NetworkingConfig: nil,
302+
Platform: nil,
303+
Name: containerName,
304+
})
295305
if err != nil {
296306
return "", fmt.Errorf("failed to create NIM container: %w", err)
297307
}
298308

299309
// Start the container
300-
if err := dockerClient.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
310+
if _, err := dockerClient.ContainerStart(ctx, resp.ID, client.ContainerStartOptions{}); err != nil {
301311
return "", fmt.Errorf("failed to start NIM container: %w", err)
302312
}
303313

@@ -315,13 +325,13 @@ func createNIMContainer(ctx context.Context, dockerClient *client.Client, model
315325
func waitForNIMReady(ctx context.Context, cmd *cobra.Command) error {
316326
cmd.Println("Waiting for NIM to be ready (this may take several minutes)...")
317327

318-
client := &http.Client{
328+
httpClient := &http.Client{
319329
Timeout: 5 * time.Second,
320330
}
321331

322332
maxRetries := 120 // 10 minutes with 5 second intervals
323333
for i := 0; i < maxRetries; i++ {
324-
resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/v1/models", nimDefaultPort))
334+
resp, err := httpClient.Get(fmt.Sprintf("http://127.0.0.1:%d/v1/models", nimDefaultPort))
325335
if err == nil {
326336
resp.Body.Close()
327337
if resp.StatusCode == http.StatusOK {
@@ -356,14 +366,14 @@ func runNIMModel(ctx context.Context, dockerClient *client.Client, model string,
356366

357367
if containerID != "" {
358368
// Container exists, check if it's running
359-
inspect, err := dockerClient.ContainerInspect(ctx, containerID)
369+
inspect, err := dockerClient.ContainerInspect(ctx, containerID, client.ContainerInspectOptions{})
360370
if err != nil {
361371
return fmt.Errorf("failed to inspect NIM container: %w", err)
362372
}
363373

364-
if !inspect.State.Running {
374+
if !inspect.Container.State.Running {
365375
// Container exists but is not running, start it
366-
if err := dockerClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
376+
if _, err := dockerClient.ContainerStart(ctx, containerID, client.ContainerStartOptions{}); err != nil {
367377
return fmt.Errorf("failed to start existing NIM container: %w", err)
368378
}
369379
cmd.Printf("Started existing NIM container %s\n", nimContainerName(model))
@@ -397,7 +407,7 @@ func chatWithNIM(cmd *cobra.Command, model, prompt string) error {
397407
// The NIM container runs on localhost:8000 and provides an OpenAI-compatible API
398408

399409
// Create a simple HTTP client to talk to the NIM
400-
client := &http.Client{
410+
httpClient := &http.Client{
401411
Timeout: 300 * time.Second,
402412
}
403413

@@ -422,7 +432,7 @@ func chatWithNIM(cmd *cobra.Command, model, prompt string) error {
422432

423433
req.Header.Set("Content-Type", "application/json")
424434

425-
resp, err := client.Do(req)
435+
resp, err := httpClient.Do(req)
426436
if err != nil {
427437
return fmt.Errorf("failed to send request to NIM: %w", err)
428438
}

cmd/cli/desktop/context.go

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ import (
1515
"github.com/docker/cli/cli/command"
1616
"github.com/docker/cli/cli/connhelper"
1717
"github.com/docker/cli/cli/context/docker"
18-
"github.com/docker/docker/api/types/container"
19-
clientpkg "github.com/docker/docker/client"
2018
"github.com/docker/model-runner/cmd/cli/pkg/standalone"
2119
"github.com/docker/model-runner/cmd/cli/pkg/types"
2220
"github.com/docker/model-runner/pkg/inference"
2321
modeltls "github.com/docker/model-runner/pkg/tls"
22+
"github.com/moby/moby/api/types/container"
2423
"github.com/moby/moby/client"
2524
)
2625

@@ -75,7 +74,7 @@ func isCloudContext(cli *command.DockerCli) bool {
7574
}
7675

7776
// DockerClientForContext creates a Docker client for the specified context.
78-
func DockerClientForContext(cli *command.DockerCli, name string) (*clientpkg.Client, error) {
77+
func DockerClientForContext(cli *command.DockerCli, name string) (*client.Client, error) {
7978
c, err := cli.ContextStore().GetMetadata(name)
8079
if err != nil {
8180
return nil, fmt.Errorf("unable to load context metadata: %w", err)
@@ -85,10 +84,9 @@ func DockerClientForContext(cli *command.DockerCli, name string) (*clientpkg.Cli
8584
return nil, fmt.Errorf("unable to determine context endpoint: %w", err)
8685
}
8786

88-
opts := []clientpkg.Opt{
89-
clientpkg.FromEnv,
90-
clientpkg.WithAPIVersionNegotiation(),
91-
clientpkg.WithHost(endpoint.Host),
87+
opts := []client.Opt{
88+
client.FromEnv,
89+
client.WithHost(endpoint.Host),
9290
}
9391

9492
helper, err := connhelper.GetConnectionHelper(endpoint.Host)
@@ -97,12 +95,12 @@ func DockerClientForContext(cli *command.DockerCli, name string) (*clientpkg.Cli
9795
}
9896
if helper != nil {
9997
opts = append(opts,
100-
clientpkg.WithHost(helper.Host),
101-
clientpkg.WithDialContext(helper.Dialer),
98+
client.WithHost(helper.Host),
99+
client.WithDialContext(helper.Dialer),
102100
)
103101
}
104102

105-
return clientpkg.NewClientWithOpts(opts...)
103+
return client.New(opts...)
106104
}
107105

108106
// ModelRunnerContext encodes the operational context of a Model CLI command and
@@ -205,11 +203,14 @@ func wakeUpCloudIfIdle(ctx context.Context, cli *command.DockerCli) error {
205203
if err != nil {
206204
return fmt.Errorf("failed to create Docker client: %w", err)
207205
}
206+
defer dockerClient.Close()
208207

209208
// The call is expected to fail with a client error due to nil arguments, but it triggers
210209
// Docker Cloud to wake up from idle. Only return unexpected failures (network issues,
211210
// server errors) so they're logged as warnings.
212-
_, err = dockerClient.ContainerCreate(ctx, &container.Config{}, nil, nil, nil, "")
211+
_, err = dockerClient.ContainerCreate(ctx, client.ContainerCreateOptions{
212+
Config: &container.Config{},
213+
})
213214
if err != nil && !errdefs.IsInvalidArgument(err) {
214215
return fmt.Errorf("failed to wake up Docker Cloud: %w", err)
215216
}
@@ -337,11 +338,21 @@ func DetectContext(ctx context.Context, cli *command.DockerCli, printer standalo
337338
// Construct the HTTP client.
338339
var httpClient DockerHttpClient
339340
if kind == types.ModelRunnerEngineKindDesktop {
340-
dockerClient, err := DockerClientForContext(cli, cli.CurrentContext())
341-
if err != nil {
342-
return nil, fmt.Errorf("unable to create model runner client: %w", err)
341+
if useTLS {
342+
// For Desktop context, if TLS is enabled, we should either fully support it or fail fast
343+
// Since Desktop context uses Docker client, we need to handle TLS differently
344+
// For now, we'll fail fast to make the behavior clear
345+
return nil, fmt.Errorf("TLS is not supported for Desktop contexts")
343346
}
344-
httpClient = dockerClient.HTTPClient()
347+
348+
// FIXME(thaJeztah): can we get the user-agent in some other way? (or just use a default, specific to DMR)?
349+
// dockerClient, err := DockerClientForContext(cli, cli.CurrentContext())
350+
// if err != nil {
351+
// return nil, fmt.Errorf("unable to create model runner client: %w", err)
352+
// }
353+
// httpClient = dockerClient.HTTPClient()
354+
// dockerClient.Close()
355+
httpClient = http.DefaultClient
345356
} else {
346357
httpClient = http.DefaultClient
347358
}
@@ -353,13 +364,6 @@ func DetectContext(ctx context.Context, cli *command.DockerCli, printer standalo
353364
// Construct TLS client if TLS is enabled
354365
var tlsClient DockerHttpClient
355366
if useTLS {
356-
if kind == types.ModelRunnerEngineKindDesktop {
357-
// For Desktop context, if TLS is enabled, we should either fully support it or fail fast
358-
// Since Desktop context uses Docker client, we need to handle TLS differently
359-
// For now, we'll fail fast to make the behavior clear
360-
return nil, fmt.Errorf("TLS is not supported for Desktop contexts")
361-
}
362-
363367
tlsConfig, err := modeltls.LoadClientTLSConfig(tlsCACert, tlsSkipVerify)
364368
if err != nil {
365369
return nil, fmt.Errorf("unable to load TLS configuration: %w", err)

0 commit comments

Comments
 (0)