@@ -11,6 +11,7 @@ import (
11
11
"github.com/spf13/cobra"
12
12
13
13
"github.com/StacklokLabs/toolhive/pkg/container"
14
+ "github.com/StacklokLabs/toolhive/pkg/container/runtime"
14
15
"github.com/StacklokLabs/toolhive/pkg/logger"
15
16
"github.com/StacklokLabs/toolhive/pkg/permissions"
16
17
"github.com/StacklokLabs/toolhive/pkg/registry"
@@ -144,14 +145,14 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
144
145
oidcClientID := GetStringFlagOrEmpty (cmd , "oidc-client-id" )
145
146
146
147
// Create container runtime
147
- runtime , err := container .NewFactory ().Create (ctx )
148
+ rt , err := container .NewFactory ().Create (ctx )
148
149
if err != nil {
149
150
return fmt .Errorf ("failed to create container runtime: %v" , err )
150
151
}
151
152
152
153
// Check if the serverOrImage contains a protocol scheme (uvx:// or npx://)
153
154
// 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 )
155
156
if err != nil {
156
157
return fmt .Errorf ("failed to process protocol scheme: %v" , err )
157
158
}
@@ -164,7 +165,7 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
164
165
165
166
// Initialize a new RunConfig with values from command-line flags
166
167
config := runner .NewRunConfigFromFlags (
167
- runtime ,
168
+ rt ,
168
169
cmdArgs ,
169
170
runName ,
170
171
debugMode ,
@@ -206,30 +207,9 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
206
207
logDebug (debugMode , "Server '%s' not found in registry, treating as Docker image" , serverOrImage )
207
208
config .Image = serverOrImage
208
209
}
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
- }
227
210
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 )
233
213
}
234
214
235
215
// Configure the RunConfig with transport, ports, permissions, etc.
@@ -241,6 +221,59 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
241
221
return RunMCPServer (ctx , cmd , config , runForeground )
242
222
}
243
223
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
+
244
277
// applyRegistrySettings applies settings from a registry server to the run config
245
278
func applyRegistrySettings (
246
279
cmd * cobra.Command ,
0 commit comments