Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Unit test for image ref glob matching, with support for **
package glob_test
import (
"fmt"
"regexp"
"strings"
"testing"
"github.com/google/go-containerregistry/pkg/name"
)
func TestGlob(t *testing.T) {
t.Parallel()
for _, c := range cases {
t.Run(c.in+"|"+c.glob, func(t *testing.T) {
match, err := glob(c.in, c.glob)
if match != c.wantMatch {
t.Errorf("match: got %t, want %t", match, c.wantMatch)
}
if gotErr := err != nil; gotErr != c.wantErr {
t.Errorf("err: got %v, want %t", err, c.wantErr)
}
})
}
}
var validGlob = regexp.MustCompile(`^[a-zA-Z0-9-_:\/\*\.]+$`)
func glob(in, glob string) (bool, error) {
ref, err := name.ParseReference(in, name.WeakValidation)
if err != nil {
return false, err
}
// Reject that glob doesn't look like a regexp
if !validGlob.MatchString(glob) {
return false, fmt.Errorf("invalid glob %q", glob)
}
// Translate glob to regexp.
glob = strings.ReplaceAll(glob, ".", `\.`) // . in glob means \. in regexp
glob = strings.ReplaceAll(glob, "**", ".+") // ** in glob means .+ in regexp
glob = strings.ReplaceAll(glob, "*", "[^/]+") // * in glob means any non-/ in regexp
glob = fmt.Sprintf("^%s$", glob) // glob must match the whole string
// TODO: do we want : to count as a separator like / is?
return regexp.MatchString(glob, ref.Name())
}
var cases = []struct {
in, glob string
wantMatch, wantErr bool
}{
{"ubuntu", "index.docker.io/library/ubuntu:latest", true, false},
{"ubuntu", "index.docker.io/library/ubuntu:*", true, false},
{"ubuntu", "index.docker.io/library/*", true, false},
{"ubuntu", "index.docker.io/library/*:latest", true, false},
{"ubuntu", "index.docker.io/*/*", true, false},
{"ubuntu", "index.docker.io/**", true, false},
{"ubuntu", "index.docker.**", true, false},
{"ubuntu", "inde**", true, false},
{"ubuntu", "**", true, false},
{"myuser/myapp", "index.docker.io/myuser/myapp:latest", true, false},
{"myuser/myapp", "index.docker.io/myuser/myapp:*", true, false},
{"myuser/myapp", "index.docker.io/myuser/*", true, false},
{"myuser/myapp", "index.docker.io/myuser/*:latest", true, false},
{"myuser/myapp", "index.docker.io/*/*", true, false},
{"myuser/myapp", "index.docker.io/**", true, false},
{"myuser/myapp", "index.docker.**", true, false},
{"myuser/myapp", "inde**", true, false},
{"myuser/myapp", "**", true, false},
{"ghcr.io/foo/bar", "ghcr.io/*/*", true, false},
{"ghcr.io/foo/bar", "ghcr.io/**", true, false},
{"ghcr.io/foo", "ghcr.io/*/*", false, false},
{"ghcr.io/foo", "ghcr.io/**", true, false},
{"ghcr.io/foo", "ghc**", true, false},
{"ghcr.io/foo", "**", true, false},
{"prefix-ghcr.io/foo", "ghcr.io/foo", false, false}, // glob starts at beginning.
{"ghcr.io/foo-suffix", "ghcr.io/foo", false, false}, // glob ends at the end.
{"ghcrxio/foo", "ghcr.io/**", false, false}, // dots in glob are replaced with \., not treated as regexp .
{"invalid&name", "**", false, true}, // invalid refs are not matched.
{"invalid-glob", ".+", false, true}, // invalid globs are rejected.
{"invalid-glob", "[a-z]*", false, true}, // invalid globs are rejected.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment