Skip to content

Commit 21c1aa8

Browse files
authored
Always pull the latest reference if using latest tag (#147)
Signed-off-by: Juan Antonio Osorio <ozz@stacklok.com>
1 parent ec5826a commit 21c1aa8

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

cmd/thv/app/run.go

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

10+
nameref "github.com/google/go-containerregistry/pkg/name"
1011
"github.com/spf13/cobra"
1112

1213
"github.com/StacklokLabs/toolhive/pkg/container"
@@ -146,14 +147,26 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
146147
logDebug(debugMode, "Server '%s' not found in registry, treating as Docker image", serverOrImage)
147148
config.Image = serverOrImage
148149
}
150+
// Check if the image has the "latest" tag
151+
isLatestTag := hasLatestTag(config.Image)
149152

150-
// Check if the image exists locally, and pull it if not
153+
// Check if the image exists locally
151154
imageExists, err := runtime.ImageExists(ctx, config.Image)
152155
if err != nil {
153156
return fmt.Errorf("failed to check if image exists: %v", err)
154157
}
155-
if !imageExists {
156-
logger.Log.Info(fmt.Sprintf("Image %s not found locally, pulling...", config.Image))
158+
159+
// Always pull if the tag is "latest" or if the image doesn't exist locally
160+
if isLatestTag || !imageExists {
161+
var pullMsg string
162+
if !imageExists {
163+
pullMsg = "Image %s not found locally, pulling..."
164+
}
165+
if isLatestTag && imageExists {
166+
pullMsg = "Image %s has 'latest' tag, pulling to ensure we have the most recent version..."
167+
}
168+
169+
logger.Log.Info(fmt.Sprintf(pullMsg, config.Image))
157170
if err := runtime.PullImage(ctx, config.Image); err != nil {
158171
return fmt.Errorf("failed to pull image: %v", err)
159172
}
@@ -267,6 +280,27 @@ func isEnvVarProvided(name string, envVars []string, secrets []string) bool {
267280
return findEnvironmentVariableFromSecrets(secrets, name)
268281
}
269282

283+
// hasLatestTag checks if the given image reference has the "latest" tag or no tag (which defaults to "latest")
284+
func hasLatestTag(imageRef string) bool {
285+
ref, err := nameref.ParseReference(imageRef)
286+
if err != nil {
287+
// If we can't parse the reference, assume it's not "latest"
288+
logger.Log.Warn(fmt.Sprintf("Warning: Failed to parse image reference: %v", err))
289+
return false
290+
}
291+
292+
// Check if the reference is a tag
293+
if taggedRef, ok := ref.(nameref.Tag); ok {
294+
// Check if the tag is "latest"
295+
return taggedRef.TagStr() == "latest"
296+
}
297+
298+
// If the reference is not a tag (e.g., it's a digest), it's not "latest"
299+
// If no tag was specified, it defaults to "latest"
300+
_, isDigest := ref.(nameref.Digest)
301+
return !isDigest
302+
}
303+
270304
// createPermissionProfileFile creates a temporary file with the permission profile
271305
func createPermissionProfileFile(serverName string, permProfile *permissions.Profile, debugMode bool) (string, error) {
272306
tempFile, err := os.CreateTemp("", fmt.Sprintf("toolhive-%s-permissions-*.json", serverName))

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ require (
3434
github.com/golang/protobuf v1.5.4 // indirect
3535
github.com/google/gnostic-models v0.6.8 // indirect
3636
github.com/google/go-cmp v0.7.0 // indirect
37+
github.com/google/go-containerregistry v0.20.3 // indirect
3738
github.com/google/gofuzz v1.2.0 // indirect
3839
github.com/gorilla/websocket v1.5.0 // indirect
3940
github.com/josharian/intern v1.0.0 // indirect
@@ -51,7 +52,7 @@ require (
5152
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
5253
golang.org/x/exp/event v0.0.0-20220217172124-1812c5b45e43 // indirect
5354
golang.org/x/net v0.35.0 // indirect
54-
golang.org/x/oauth2 v0.23.0 // indirect
55+
golang.org/x/oauth2 v0.25.0 // indirect
5556
golang.org/x/text v0.22.0 // indirect
5657
golang.org/x/time v0.7.0 // indirect
5758
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
@@ -68,7 +69,7 @@ require (
6869
)
6970

7071
require (
71-
github.com/Microsoft/go-winio v0.4.14 // indirect
72+
github.com/Microsoft/go-winio v0.6.2 // indirect
7273
github.com/adrg/xdg v0.5.3
7374
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
7475
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK
44
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
55
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
66
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
7+
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
78
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
89
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
910
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -71,6 +72,8 @@ github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYu
7172
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
7273
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
7374
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
75+
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
76+
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
7477
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
7578
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
7679
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -222,6 +225,8 @@ golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
222225
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
223226
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
224227
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
228+
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
229+
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
225230
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
226231
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
227232
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

0 commit comments

Comments
 (0)