Skip to content

Commit 0057a9c

Browse files
authored
fix: sorts out conflicting code between local and remote image pulls (#271)
1 parent b8fa015 commit 0057a9c

File tree

1 file changed

+59
-26
lines changed

1 file changed

+59
-26
lines changed

cmd/thv/app/run.go

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/spf13/cobra"
1212

1313
"github.com/StacklokLabs/toolhive/pkg/container"
14+
"github.com/StacklokLabs/toolhive/pkg/container/runtime"
1415
"github.com/StacklokLabs/toolhive/pkg/logger"
1516
"github.com/StacklokLabs/toolhive/pkg/permissions"
1617
"github.com/StacklokLabs/toolhive/pkg/registry"
@@ -144,14 +145,14 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
144145
oidcClientID := GetStringFlagOrEmpty(cmd, "oidc-client-id")
145146

146147
// Create container runtime
147-
runtime, err := container.NewFactory().Create(ctx)
148+
rt, err := container.NewFactory().Create(ctx)
148149
if err != nil {
149150
return fmt.Errorf("failed to create container runtime: %v", err)
150151
}
151152

152153
// Check if the serverOrImage contains a protocol scheme (uvx:// or npx://)
153154
// and build a Docker image for it if needed
154-
processedImage, err := handleProtocolScheme(ctx, runtime, serverOrImage, debugMode)
155+
processedImage, err := handleProtocolScheme(ctx, rt, serverOrImage, debugMode)
155156
if err != nil {
156157
return fmt.Errorf("failed to process protocol scheme: %v", err)
157158
}
@@ -164,7 +165,7 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
164165

165166
// Initialize a new RunConfig with values from command-line flags
166167
config := runner.NewRunConfigFromFlags(
167-
runtime,
168+
rt,
168169
cmdArgs,
169170
runName,
170171
debugMode,
@@ -206,30 +207,9 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
206207
logDebug(debugMode, "Server '%s' not found in registry, treating as Docker image", serverOrImage)
207208
config.Image = serverOrImage
208209
}
209-
// Check if the image has the "latest" tag
210-
isLatestTag := hasLatestTag(config.Image)
211-
212-
// Check if the image exists locally
213-
imageExists, err := runtime.ImageExists(ctx, config.Image)
214-
if err != nil {
215-
return fmt.Errorf("failed to check if image exists: %v", err)
216-
}
217-
218-
// Always pull if the tag is "latest" or if the image doesn't exist locally
219-
if isLatestTag || !imageExists {
220-
var pullMsg string
221-
if !imageExists {
222-
pullMsg = "Image %s not found locally, pulling..."
223-
}
224-
if isLatestTag && imageExists {
225-
pullMsg = "Image %s has 'latest' tag, pulling to ensure we have the most recent version..."
226-
}
227210

228-
logger.Log.Infof(pullMsg, config.Image)
229-
if err := runtime.PullImage(ctx, config.Image); err != nil {
230-
return fmt.Errorf("failed to pull image: %v", err)
231-
}
232-
logger.Log.Infof("Successfully pulled image: %s", config.Image)
211+
if err := pullImage(ctx, config.Image, rt); err != nil {
212+
return fmt.Errorf("failed to retrieve or pull image: %v", err)
233213
}
234214

235215
// Configure the RunConfig with transport, ports, permissions, etc.
@@ -241,6 +221,59 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
241221
return RunMCPServer(ctx, cmd, config, runForeground)
242222
}
243223

224+
// pullImage pulls an image from a remote registry if it has the "latest" tag
225+
// or if it doesn't exist locally. If the image is a local image, it will not be pulled.
226+
// If the image has the latest tag, it will be pulled to ensure we have the most recent version.
227+
// however, if there is a failure in pulling the "latest" tag, it will check if the image exists locally
228+
// as it is possible that the image was locally built.
229+
func pullImage(ctx context.Context, image string, rt runtime.Runtime) error {
230+
// Check if the image has the "latest" tag
231+
isLatestTag := hasLatestTag(image)
232+
233+
if isLatestTag {
234+
// For "latest" tag, try to pull first
235+
logger.Log.Infof("Image %s has 'latest' tag, pulling to ensure we have the most recent version...", image)
236+
err := rt.PullImage(ctx, image)
237+
if err != nil {
238+
// Pull failed, check if it exists locally
239+
logger.Log.Infof("Pull failed, checking if image exists locally: %s", image)
240+
imageExists, checkErr := rt.ImageExists(ctx, image)
241+
if checkErr != nil {
242+
return fmt.Errorf("failed to check if image exists: %v", checkErr)
243+
}
244+
245+
if imageExists {
246+
logger.Log.Debugf("Using existing local image: %s", image)
247+
} else {
248+
return fmt.Errorf("failed to pull image from remote registry and image doesn't exist locally. %v", err)
249+
}
250+
} else {
251+
logger.Log.Infof("Successfully pulled image: %s", image)
252+
}
253+
} else {
254+
// For non-latest tags, check locally first
255+
logger.Log.Debugf("Checking if image exists locally: %s", image)
256+
imageExists, err := rt.ImageExists(ctx, image)
257+
logger.Log.Debugf("ImageExists locally: %t", imageExists)
258+
if err != nil {
259+
return fmt.Errorf("failed to check if image exists locally: %v", err)
260+
}
261+
262+
if imageExists {
263+
logger.Log.Debugf("Using existing local image: %s", image)
264+
} else {
265+
// Image doesn't exist locally, try to pull
266+
logger.Log.Infof("Image %s not found locally, pulling...", image)
267+
if err := rt.PullImage(ctx, image); err != nil {
268+
return fmt.Errorf("failed to pull image: %v", err)
269+
}
270+
logger.Log.Infof("Successfully pulled image: %s", image)
271+
}
272+
}
273+
274+
return nil
275+
}
276+
244277
// applyRegistrySettings applies settings from a registry server to the run config
245278
func applyRegistrySettings(
246279
cmd *cobra.Command,

0 commit comments

Comments
 (0)