feat(discovery): tag-agnostic image name normalisation
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
package discovery
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// NormaliseImage strips the tag and digest from an image reference,
|
||||||
|
// returning the bare repository name.
|
||||||
|
//
|
||||||
|
// Tricky case: "localhost:5000/foo:v1" — the first colon is a port,
|
||||||
|
// not a tag. We disambiguate by splitting on "/" first and only
|
||||||
|
// treating colons in the last segment as tag separators.
|
||||||
|
func NormaliseImage(ref string) string {
|
||||||
|
if at := strings.Index(ref, "@"); at >= 0 {
|
||||||
|
ref = ref[:at]
|
||||||
|
}
|
||||||
|
slash := strings.LastIndex(ref, "/")
|
||||||
|
if slash < 0 {
|
||||||
|
if colon := strings.Index(ref, ":"); colon >= 0 {
|
||||||
|
return ref[:colon]
|
||||||
|
}
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
prefix, last := ref[:slash], ref[slash+1:]
|
||||||
|
if colon := strings.Index(last, ":"); colon >= 0 {
|
||||||
|
last = last[:colon]
|
||||||
|
}
|
||||||
|
return prefix + "/" + last
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImagesMatch reports whether two image references resolve to the same
|
||||||
|
// repository, ignoring tag and digest. Case-sensitive per spec section 5.2.
|
||||||
|
func ImagesMatch(a, b string) bool {
|
||||||
|
return NormaliseImage(a) == NormaliseImage(b)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package discovery_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/shcizo/package-updater/internal/discovery"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormaliseImage(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
in string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"registry.example.com/myapp", "registry.example.com/myapp"},
|
||||||
|
{"registry.example.com/myapp:v1.2.3", "registry.example.com/myapp"},
|
||||||
|
{"registry.example.com/myapp:latest", "registry.example.com/myapp"},
|
||||||
|
{"registry.example.com/myapp@sha256:abc123", "registry.example.com/myapp"},
|
||||||
|
{"registry.example.com/myapp:v1.2.3@sha256:abc123", "registry.example.com/myapp"},
|
||||||
|
{"nginx", "nginx"},
|
||||||
|
{"nginx:1.25-alpine", "nginx"},
|
||||||
|
{"library/nginx:latest", "library/nginx"},
|
||||||
|
{"gcr.io/proj/svc:tag", "gcr.io/proj/svc"},
|
||||||
|
{"localhost:5000/myimg:v1", "localhost:5000/myimg"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.in, func(t *testing.T) {
|
||||||
|
require.Equal(t, c.want, discovery.NormaliseImage(c.in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImagesMatch(t *testing.T) {
|
||||||
|
require.True(t, discovery.ImagesMatch(
|
||||||
|
"registry.example.com/myapp",
|
||||||
|
"registry.example.com/myapp:v1.2.3",
|
||||||
|
))
|
||||||
|
require.True(t, discovery.ImagesMatch(
|
||||||
|
"registry.example.com/myapp:v1.0.0",
|
||||||
|
"registry.example.com/myapp:v9.9.9",
|
||||||
|
))
|
||||||
|
require.False(t, discovery.ImagesMatch(
|
||||||
|
"registry.example.com/myapp",
|
||||||
|
"registry.example.com/otherapp",
|
||||||
|
))
|
||||||
|
// Case-sensitive per spec section 5.2
|
||||||
|
require.False(t, discovery.ImagesMatch(
|
||||||
|
"registry.example.com/MyApp",
|
||||||
|
"registry.example.com/myapp",
|
||||||
|
))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user