Skip to content

Commit 50cf866

Browse files
Replace go-containerregistry with containerd/moby (docker#536)
* Replace go-containerregistry with containerd/moby This commit removes the vendored go-containerregistry package and replaces it with containerd and moby packages for OCI registry operations. Signed-off-by: Eric Curtin <[email protected]> * fix for removing ggcr (docker#545) * Add Docker Hub integration tests and hostname remapping for API requests * Refactor Docker Model Runner setup to use a configuration struct for environment variables and log messages * Update go.mod dependencies to include latest versions of cloud.google.com/go/compute/metadata and golang.org/x/oauth2 * Add support for HuggingFace registry in manifest fetching * Refactor manifest endpoint logic to apply it only on HF repos * Remove Docker Hub hostname remapping and update manifest fetching logic for HuggingFace registry * Refactor manifest endpoint check for HuggingFace to use dedicated media type function --------- Signed-off-by: Eric Curtin <[email protected]> Co-authored-by: Ignasi <[email protected]>
1 parent fe10622 commit 50cf866

File tree

554 files changed

+3614
-67581
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

554 files changed

+3614
-67581
lines changed

Dockerfile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ WORKDIR /app
1919
# Copy go mod/sum first for better caching
2020
COPY --link go.mod go.sum ./
2121

22-
# Copy pkg/go-containerregistry for the replace directive in go.mod
23-
COPY --link pkg/go-containerregistry ./pkg/go-containerregistry
24-
2522
# Download dependencies (with cache mounts)
2623
RUN --mount=type=cache,target=/go/pkg/mod \
2724
--mount=type=cache,target=/root/.cache/go-build \

cmd/cli/commands/configure_test.go

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ func TestConfigureCmdHfOverridesFlag(t *testing.T) {
1212
hfOverridesFlag := cmd.Flags().Lookup("hf_overrides")
1313
if hfOverridesFlag == nil {
1414
t.Fatal("--hf_overrides flag not found")
15+
return // unreachable but satisfies staticcheck SA5011
1516
}
1617

18+
// Get values to avoid potential nil dereference flagged by linter
19+
defValue := hfOverridesFlag.DefValue
20+
1721
// Verify the default value is empty
18-
if hfOverridesFlag.DefValue != "" {
19-
t.Errorf("Expected default hf_overrides value to be empty, got '%s'", hfOverridesFlag.DefValue)
22+
if defValue != "" {
23+
t.Errorf("Expected default hf_overrides value to be empty, got '%s'", defValue)
2024
}
2125

2226
// Verify the flag type
@@ -33,11 +37,15 @@ func TestConfigureCmdContextSizeFlag(t *testing.T) {
3337
contextSizeFlag := cmd.Flags().Lookup("context-size")
3438
if contextSizeFlag == nil {
3539
t.Fatal("--context-size flag not found")
40+
return // unreachable but satisfies staticcheck SA5011
3641
}
3742

43+
// Get values to avoid potential nil dereference flagged by linter
44+
defValue := contextSizeFlag.DefValue
45+
3846
// Verify the default value is empty (nil pointer)
39-
if contextSizeFlag.DefValue != "" {
40-
t.Errorf("Expected default context-size value to be '' (nil), got '%s'", contextSizeFlag.DefValue)
47+
if defValue != "" {
48+
t.Errorf("Expected default context-size value to be '' (nil), got '%s'", defValue)
4149
}
4250

4351
// Test setting the flag value
@@ -83,11 +91,15 @@ func TestConfigureCmdModeFlag(t *testing.T) {
8391
modeFlag := cmd.Flags().Lookup("mode")
8492
if modeFlag == nil {
8593
t.Fatal("--mode flag not found")
94+
return // unreachable but satisfies staticcheck SA5011
8695
}
8796

97+
// Get values to avoid potential nil dereference flagged by linter
98+
defValue := modeFlag.DefValue
99+
88100
// Verify the default value is empty
89-
if modeFlag.DefValue != "" {
90-
t.Errorf("Expected default mode value to be empty, got '%s'", modeFlag.DefValue)
101+
if defValue != "" {
102+
t.Errorf("Expected default mode value to be empty, got '%s'", defValue)
91103
}
92104

93105
// Verify the flag type
@@ -104,11 +116,15 @@ func TestConfigureCmdThinkFlag(t *testing.T) {
104116
thinkFlag := cmd.Flags().Lookup("think")
105117
if thinkFlag == nil {
106118
t.Fatal("--think flag not found")
119+
return // unreachable but satisfies staticcheck SA5011
107120
}
108121

122+
// Get values to avoid potential nil dereference flagged by linter
123+
defValue := thinkFlag.DefValue
124+
109125
// Verify the default value is empty
110-
if thinkFlag.DefValue != "" {
111-
t.Errorf("Expected default think value to be empty (nil), got '%s'", thinkFlag.DefValue)
126+
if defValue != "" {
127+
t.Errorf("Expected default think value to be empty (nil), got '%s'", defValue)
112128
}
113129

114130
// Verify the flag type
@@ -136,11 +152,15 @@ func TestConfigureCmdGPUMemoryUtilizationFlag(t *testing.T) {
136152
gpuMemFlag := cmd.Flags().Lookup("gpu-memory-utilization")
137153
if gpuMemFlag == nil {
138154
t.Fatal("--gpu-memory-utilization flag not found")
155+
return // unreachable but satisfies staticcheck SA5011
139156
}
140157

158+
// Get values to avoid potential nil dereference flagged by linter
159+
defValue := gpuMemFlag.DefValue
160+
141161
// Verify the default value is empty (nil pointer)
142-
if gpuMemFlag.DefValue != "" {
143-
t.Errorf("Expected default gpu-memory-utilization value to be '' (nil), got '%s'", gpuMemFlag.DefValue)
162+
if defValue != "" {
163+
t.Errorf("Expected default gpu-memory-utilization value to be '' (nil), got '%s'", defValue)
144164
}
145165

146166
// Verify the flag type

cmd/cli/commands/install-runner_test.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ func TestInstallRunnerHostFlag(t *testing.T) {
1515
hostFlag := cmd.Flags().Lookup("host")
1616
if hostFlag == nil {
1717
t.Fatal("--host flag not found")
18+
return // unreachable but satisfies staticcheck SA5011
1819
}
1920

21+
// Get values to avoid potential nil dereference flagged by linter
22+
defValue := hostFlag.DefValue
23+
2024
// Verify the default value
21-
if hostFlag.DefValue != "127.0.0.1" {
22-
t.Errorf("Expected default host value to be '127.0.0.1', got '%s'", hostFlag.DefValue)
25+
if defValue != "127.0.0.1" {
26+
t.Errorf("Expected default host value to be '127.0.0.1', got '%s'", defValue)
2327
}
2428

2529
// Verify the flag type
@@ -77,11 +81,15 @@ func TestInstallRunnerBackendFlag(t *testing.T) {
7781
backendFlag := cmd.Flags().Lookup("backend")
7882
if backendFlag == nil {
7983
t.Fatal("--backend flag not found")
84+
return // unreachable but satisfies staticcheck SA5011
8085
}
8186

87+
// Get values to avoid potential nil dereference flagged by linter
88+
defValue := backendFlag.DefValue
89+
8290
// Verify the default value
83-
if backendFlag.DefValue != "" {
84-
t.Errorf("Expected default backend value to be empty, got '%s'", backendFlag.DefValue)
91+
if defValue != "" {
92+
t.Errorf("Expected default backend value to be empty, got '%s'", defValue)
8593
}
8694

8795
// Verify the flag type

cmd/cli/commands/integration_test.go

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/docker/model-runner/cmd/cli/desktop"
1818
"github.com/docker/model-runner/cmd/cli/pkg/types"
1919
"github.com/docker/model-runner/pkg/distribution/builder"
20+
"github.com/docker/model-runner/pkg/distribution/oci/reference"
2021
"github.com/docker/model-runner/pkg/distribution/registry"
2122
"github.com/stretchr/testify/require"
2223
"github.com/testcontainers/testcontainers-go"
@@ -110,6 +111,11 @@ func generateReferenceTestCases(info modelInfo) []referenceTestCase {
110111
func setupTestEnv(t *testing.T) *testEnv {
111112
ctx := context.Background()
112113

114+
// Set environment variables for the test process to match the DMR container.
115+
// This ensures CLI functions use the same default registry when parsing references.
116+
t.Setenv("DEFAULT_REGISTRY", "registry.local:5000")
117+
t.Setenv("INSECURE_REGISTRY", "true")
118+
113119
// Create a custom network for container communication
114120
net, err := network.New(ctx)
115121
require.NoError(t, err)
@@ -149,16 +155,26 @@ func ociRegistry(t *testing.T, ctx context.Context, net *testcontainers.DockerNe
149155
return registryURL
150156
}
151157

152-
func dockerModelRunner(t *testing.T, ctx context.Context, net *testcontainers.DockerNetwork) string {
158+
// dmrConfig holds configuration options for Docker Model Runner container.
159+
type dmrConfig struct {
160+
envVars map[string]string // Optional environment variables to set
161+
logMsg string // Custom log message (defaults to "Starting DMR container...")
162+
}
163+
164+
// startDockerModelRunner starts a DMR container with the given configuration.
165+
// If config.envVars is nil or empty, no extra environment variables are set.
166+
func startDockerModelRunner(t *testing.T, ctx context.Context, net *testcontainers.DockerNetwork, config dmrConfig) string {
153167
containerCustomizerOpts := []testcontainers.ContainerCustomizer{
154168
testcontainers.WithExposedPorts("12434/tcp"),
155169
testcontainers.WithWaitStrategy(wait.ForHTTP("/engines/status").WithPort("12434/tcp").WithStartupTimeout(10 * time.Second)),
156-
testcontainers.WithEnv(map[string]string{
157-
"DEFAULT_REGISTRY": "registry.local:5000",
158-
"INSECURE_REGISTRY": "true",
159-
}),
160170
network.WithNetwork([]string{"dmr"}, net),
161171
}
172+
173+
// Add environment variables if provided
174+
if len(config.envVars) > 0 {
175+
containerCustomizerOpts = append(containerCustomizerOpts, testcontainers.WithEnv(config.envVars))
176+
}
177+
162178
if os.Getenv("BUILD_DMR") == "1" {
163179
t.Log("Building DMR container...")
164180
out, err := exec.CommandContext(ctx, "make", "-C", "../../..", "docker-build").CombinedOutput()
@@ -169,7 +185,13 @@ func dockerModelRunner(t *testing.T, ctx context.Context, net *testcontainers.Do
169185
// Always pull the image if it's not build locally.
170186
containerCustomizerOpts = append(containerCustomizerOpts, testcontainers.WithAlwaysPull())
171187
}
172-
t.Log("Starting DMR container...")
188+
189+
logMsg := config.logMsg
190+
if logMsg == "" {
191+
logMsg = "Starting DMR container..."
192+
}
193+
t.Log(logMsg)
194+
173195
ctr, err := testcontainers.Run(
174196
ctx, "docker/model-runner:latest",
175197
containerCustomizerOpts...,
@@ -185,6 +207,17 @@ func dockerModelRunner(t *testing.T, ctx context.Context, net *testcontainers.Do
185207
return dmrURL
186208
}
187209

210+
// dockerModelRunner starts a DMR container configured for local registry tests.
211+
// Sets DEFAULT_REGISTRY and INSECURE_REGISTRY environment variables.
212+
func dockerModelRunner(t *testing.T, ctx context.Context, net *testcontainers.DockerNetwork) string {
213+
return startDockerModelRunner(t, ctx, net, dmrConfig{
214+
envVars: map[string]string{
215+
"DEFAULT_REGISTRY": "registry.local:5000",
216+
"INSECURE_REGISTRY": "true",
217+
},
218+
})
219+
}
220+
188221
// removeModel removes a model from the local store
189222
func removeModel(client *desktop.Client, modelID string, force bool) error {
190223
_, err := client.Remove([]string{modelID}, force)
@@ -1037,7 +1070,7 @@ func TestIntegration_PackageModel(t *testing.T) {
10371070
model, err := env.client.Inspect(targetTag, false)
10381071
require.NoError(t, err, "Failed to inspect packaged model by tag: %s", targetTag)
10391072
require.NotEmpty(t, model.ID, "Model ID should not be empty")
1040-
require.Contains(t, model.Tags, targetTag, "Model should have the expected tag")
1073+
require.Contains(t, model.Tags, normalizeRef(t, targetTag), "Model should have the expected tag")
10411074

10421075
t.Logf("✓ Successfully packaged and tagged model: %s (ID: %s)", targetTag, model.ID[7:19])
10431076

@@ -1070,7 +1103,7 @@ func TestIntegration_PackageModel(t *testing.T) {
10701103
// Verify the model was loaded and tagged
10711104
model, err := env.client.Inspect(targetTag, false)
10721105
require.NoError(t, err, "Failed to inspect packaged model")
1073-
require.Contains(t, model.Tags, targetTag, "Model should have the expected tag")
1106+
require.Contains(t, model.Tags, normalizeRef(t, targetTag), "Model should have the expected tag")
10741107

10751108
t.Logf("✓ Successfully packaged model with context size: %s", targetTag)
10761109

@@ -1100,7 +1133,7 @@ func TestIntegration_PackageModel(t *testing.T) {
11001133
// Verify the model was loaded and tagged
11011134
model, err := env.client.Inspect(targetTag, false)
11021135
require.NoError(t, err, "Failed to inspect packaged model")
1103-
require.Contains(t, model.Tags, targetTag, "Model should have the expected tag")
1136+
require.Contains(t, model.Tags, normalizeRef(t, targetTag), "Model should have the expected tag")
11041137

11051138
t.Logf("✓ Successfully packaged model with custom org: %s", targetTag)
11061139

@@ -1118,3 +1151,93 @@ func TestIntegration_PackageModel(t *testing.T) {
11181151
func int32ptr(n int32) *int32 {
11191152
return &n
11201153
}
1154+
1155+
// setupDockerHubTestEnv creates a test environment for Docker Hub tests.
1156+
// Unlike setupTestEnv, this does NOT set DEFAULT_REGISTRY, so it uses
1157+
// the real Docker Hub (index.docker.io) as the default registry.
1158+
// This is used to test that pulling from Docker Hub works correctly.
1159+
func setupDockerHubTestEnv(t *testing.T) *testEnv {
1160+
ctx := context.Background()
1161+
1162+
// Create a custom network for container communication
1163+
net, err := network.New(ctx)
1164+
require.NoError(t, err)
1165+
testcontainers.CleanupNetwork(t, net)
1166+
1167+
// dockerModelRunnerForDockerHub starts a DMR container configured for Docker Hub tests.
1168+
// it uses the real Docker Hub as the default registry.
1169+
dmrURL := startDockerModelRunner(t, ctx, net, dmrConfig{
1170+
logMsg: "Starting DMR container for Docker Hub tests (no DEFAULT_REGISTRY)...",
1171+
})
1172+
1173+
modelRunnerCtx, err := desktop.NewContextForTest(dmrURL, nil, types.ModelRunnerEngineKindMoby)
1174+
require.NoError(t, err, "Failed to create model runner context")
1175+
1176+
client := desktop.New(modelRunnerCtx)
1177+
if !client.Status().Running {
1178+
t.Fatal("DMR is not running")
1179+
}
1180+
1181+
return &testEnv{
1182+
ctx: ctx,
1183+
client: client,
1184+
net: net,
1185+
}
1186+
}
1187+
1188+
// TestIntegration_PullFromDockerHub is a smoke test that pulls a real model
1189+
// from Docker Hub to verify that the OCI registry code works correctly
1190+
// with the real Docker Hub registry (index.docker.io -> registry-1.docker.io).
1191+
//
1192+
// This test catches regressions where the code doesn't properly handle
1193+
// Docker Hub's hostname remapping requirements.
1194+
func TestIntegration_PullFromDockerHub(t *testing.T) {
1195+
env := setupDockerHubTestEnv(t)
1196+
1197+
// Ensure no models exist initially
1198+
models, err := listModels(false, env.client, true, false, "")
1199+
require.NoError(t, err)
1200+
if len(models) != 0 {
1201+
t.Fatal("Expected no initial models, but found some")
1202+
}
1203+
1204+
// Pull a small model from Docker Hub
1205+
// ai/smollm2:135M-Q4_0 is a small model that's quick to download
1206+
modelRef := "ai/smollm2:135M-Q4_0"
1207+
t.Logf("Pulling model from Docker Hub: %s", modelRef)
1208+
1209+
err = pullModel(newPullCmd(), env.client, modelRef)
1210+
require.NoError(t, err, "Failed to pull model from Docker Hub: %s", modelRef)
1211+
1212+
// Verify the model was pulled
1213+
t.Log("Verifying model was pulled successfully")
1214+
models, err = listModels(false, env.client, true, false, "")
1215+
require.NoError(t, err)
1216+
require.NotEmpty(t, strings.TrimSpace(models), "Model should exist after pull from Docker Hub")
1217+
1218+
// Verify we can inspect the model
1219+
model, err := env.client.Inspect(modelRef, false)
1220+
require.NoError(t, err, "Failed to inspect model pulled from Docker Hub")
1221+
require.NotEmpty(t, model.ID, "Model ID should not be empty")
1222+
1223+
t.Logf("✓ Successfully pulled model from Docker Hub: %s (ID: %s)", modelRef, model.ID[7:19])
1224+
1225+
// Cleanup: remove the model
1226+
t.Logf("Cleaning up: removing model %s", model.ID[7:19])
1227+
err = removeModel(env.client, model.ID, true)
1228+
require.NoError(t, err, "Failed to remove model")
1229+
1230+
// Verify model was removed
1231+
models, err = listModels(false, env.client, true, false, "")
1232+
require.NoError(t, err)
1233+
require.Empty(t, strings.TrimSpace(models), "Model should be removed after cleanup")
1234+
}
1235+
1236+
// normalizeRef normalizes a reference to its fully qualified form.
1237+
// This is used in tests to compare against the stored tags which are always normalized.
1238+
func normalizeRef(t *testing.T, ref string) string {
1239+
t.Helper()
1240+
parsed, err := reference.ParseReference(ref, registry.GetDefaultRegistryOptions()...)
1241+
require.NoError(t, err, "Failed to parse reference: %s", ref)
1242+
return parsed.String()
1243+
}

cmd/cli/commands/package.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import (
1414
"github.com/docker/model-runner/cmd/cli/desktop"
1515
"github.com/docker/model-runner/pkg/distribution/builder"
1616
"github.com/docker/model-runner/pkg/distribution/distribution"
17+
"github.com/docker/model-runner/pkg/distribution/oci/reference"
1718
"github.com/docker/model-runner/pkg/distribution/packaging"
1819
"github.com/docker/model-runner/pkg/distribution/registry"
1920
"github.com/docker/model-runner/pkg/distribution/tarball"
2021
"github.com/docker/model-runner/pkg/distribution/types"
21-
"github.com/docker/model-runner/pkg/go-containerregistry/pkg/name"
2222
"github.com/spf13/cobra"
2323
)
2424

@@ -434,7 +434,7 @@ func packageModel(ctx context.Context, cmd *cobra.Command, client *desktop.Clien
434434
// modelRunnerTarget loads model to Docker Model Runner via models/load endpoint
435435
type modelRunnerTarget struct {
436436
client *desktop.Client
437-
tag name.Tag
437+
tag *reference.Tag
438438
}
439439

440440
func newModelRunnerTarget(client *desktop.Client, tag string) (*modelRunnerTarget, error) {
@@ -443,7 +443,7 @@ func newModelRunnerTarget(client *desktop.Client, tag string) (*modelRunnerTarge
443443
}
444444
if tag != "" {
445445
var err error
446-
target.tag, err = name.NewTag(tag)
446+
target.tag, err = reference.NewTag(tag, registry.GetDefaultRegistryOptions()...)
447447
if err != nil {
448448
return nil, fmt.Errorf("invalid tag: %w", err)
449449
}
@@ -477,7 +477,7 @@ func (t *modelRunnerTarget) Write(ctx context.Context, mdl types.ModelArtifact,
477477
if err != nil {
478478
return fmt.Errorf("get model ID: %w", err)
479479
}
480-
if t.tag.String() != "" {
480+
if t.tag != nil {
481481
if err := t.client.Tag(id, parseRepo(t.tag), t.tag.TagStr()); err != nil {
482482
return fmt.Errorf("tag model: %w", err)
483483
}

0 commit comments

Comments
 (0)