From d4165f16f2e79a41e611bf881427aec223ab3554 Mon Sep 17 00:00:00 2001 From: Ilya Dmitrichenko Date: Mon, 11 Sep 2023 15:26:59 +0100 Subject: [PATCH] Add tests for searching aliases --- manifest/image/alias.go | 24 ++-- manifest/image/alias_test.go | 258 +++++++++++++++++++++++++++++++++++ manifest/image/image_test.go | 3 +- tape/app/package.go | 3 +- 4 files changed, 274 insertions(+), 14 deletions(-) diff --git a/manifest/image/alias.go b/manifest/image/alias.go index 643a3d6..3eb8bf4 100644 --- a/manifest/image/alias.go +++ b/manifest/image/alias.go @@ -37,21 +37,22 @@ func NewAliasCache[T imageList](imageNames T) AliasCache { type AliasCache []*imageName func (l AliasCache) Match(name string) (string, []string, bool) { + candidates := []string{} + for i := range l { - if l[i].shortest() == name { - return l[i].join(), nil, true - } - if l[i].join() == name { - return l[i].join(), nil, true + switch name { + case l[i].join(), l[i].longest(): + candidates = append(candidates, l[i].longest()) } } - candidates := []string{} - parts := strings.Split(name, separator) - for i := range l { - for j := range l[i].parts { - if slices.Compare(l[i].parts[j:], parts) == 0 { - candidates = append(candidates, l[i].join()) + if len(candidates) == 0 { + parts := strings.Split(name, separator) + for i := range l { + for j := range l[i].parts { + if slices.Compare(l[i].parts[j:], parts) == 0 { + candidates = append(candidates, l[i].longest()) + } } } } @@ -127,6 +128,7 @@ type imageName struct { } func (i *imageName) shortest() string { return i.parts[i.last] } +func (i *imageName) longest() string { return strings.Join(i.parts, separator) } func (i *imageName) join() string { return strings.Join(i.parts[i.current:], separator) } func (i *imageName) extendable() bool { return i.current > 0 } diff --git a/manifest/image/alias_test.go b/manifest/image/alias_test.go index ab35323..d4cc154 100644 --- a/manifest/image/alias_test.go +++ b/manifest/image/alias_test.go @@ -87,3 +87,261 @@ func TestMakeAliasesForNames(t *testing.T) { } } } + +func TestSearchAliases(t *testing.T) { + cases := [][]struct { + image, alias string + }{ + { + {image: "example.com/a1/bar/foo", alias: "bar/foo"}, // 0 + {image: "example.com/a1/bar/foo1", alias: "foo1"}, // 1 + {image: "example.io/b1/baz/foo", alias: "baz/foo"}, // 2 + {image: "example.io/b1/baz/foo", alias: "baz/foo"}, // 3 + {image: "example.com/f1/foo", alias: "f1/foo"}, // 4 + {image: "example.io/f2/foo", alias: "f2/foo"}, // 5 + {image: "foo", alias: "foo"}, // 6 + {image: "example.io/b2/barfoo", alias: "example.io/b2/barfoo"}, // 7 + {image: "example.sh/b2/barfoo", alias: "example.sh/b2/barfoo"}, // 8 + {image: "example.io/b1/barfoo", alias: "b1/barfoo"}, // 9 + }, + { + {image: "example.io/b1/barfoo", alias: "b1/barfoo"}, // 0 + {image: "example.io/b1/x/barfoo", alias: "x/barfoo"}, // 1 + {image: "example.io/b1/baz/foo", alias: "baz/foo"}, // 2 + {image: "example.io/b1/foo/baz", alias: "baz"}, // 3 + {image: "example.io/b1/foo", alias: "b1/foo"}, // 4 + }, + } + + searches := [][]struct { + term string + results []int + }{ + { + { + term: "*/foo", + results: nil, + }, + { + term: "**/foo", + results: nil, + }, + { + term: ".*", + results: nil, + }, + { + term: "bar/foo", + results: []int{0}, + }, + { + term: "foo", + results: []int{6}, + }, + { + term: "/foo", + results: nil, + }, + { + term: "//foo", + results: nil, + }, + { + term: "///foo", + results: nil, + }, + { + term: "//foo/", + results: nil, + }, + { + term: "/foo/", + results: nil, + }, + { + term: "foo//", + results: nil, + }, + { + term: "a1/bar/foo", + results: []int{0}, + }, + { + term: "example.com/a1/bar/foo", + results: []int{0}, + }, + { + term: "example.com/foobar", + results: nil, + }, + { + term: "example.com", + results: nil, + }, + { + term: "foobar", + results: nil, + }, + + { + term: "xample.com/a1/bar/foo", + results: nil, + }, + { + term: "example.com/a1/bar/fo", + results: nil, + }, + + { + term: "example.com/a1/bar/foo1", + results: []int{1}, + }, + { + term: "/a1/bar/foo1", + results: nil, + }, + { + term: "a1/bar/foo1", + results: []int{1}, + }, + { + term: "bar/foo1", + results: []int{1}, + }, + { + term: "foo1", + results: []int{1}, + }, + { + term: "example.io/b1/baz/foo", + results: []int{2, 3}, + }, + { + term: "b1/baz/foo", + results: []int{2, 3}, + }, + { + term: "example.com/f1/foo", + results: []int{4}, + }, + { + term: "example.io/f2/foo", + results: []int{5}, + }, + { + term: "example.io/f2/f", + results: nil, + }, + { + term: "example.io/f2", + results: nil, + }, + { + term: "f2/foo", + results: []int{5}, + }, + + { + term: "example.io/b2/barfoo", + results: []int{7}, + }, + { + term: "example.sh/b2/barfoo", + results: []int{8}, + }, + { + term: "b2/barfoo", + results: []int{7, 8}, + }, + { + term: "barfoo", + results: []int{7, 8, 9}, + }, + { + term: "example.io/b1/barfoo", + results: []int{9}, + }, + }, + { + { + term: "example.io/b1/barfoo", + results: []int{0}, + }, + { + term: "example.io/b1/x/barfoo", + results: []int{1}, + }, + { + term: "x/barfoo", + results: []int{1}, + }, + { + term: "barfoo", + results: []int{0, 1}, + }, + { + term: "example.io/b1/baz/foo", + results: []int{2}, + }, + { + term: "foo", + results: []int{2, 4}, + }, + { + term: "example.io/b1/foo/baz", + results: []int{3}, + }, + { + term: "foo/baz", + results: []int{3}, + }, + { + term: "example.io/b1/foo", + results: []int{4}, + }, + }, + } + + g := NewWithT(t) + + var cache AliasCache + + for c := range cases { + images := make([]string, len(cases[c])) + for i := range cases[c] { + images[i] = cases[c][i].image + } + + cache = NewAliasCache(images) + aliases := cache.MakeAliasesForNames() + for i := range cases[c] { + g.Expect(aliases[i]).To(Equal(cases[c][i].alias)) + } + + for s := range searches[c] { + search := searches[c][s] + match, matches, ok := cache.Match(search.term) + + // t.Log(search, match, matches, ok) + + if search.results == nil { + g.Expect(ok).To(BeFalse()) + continue + } + + if len(search.results) == 1 { + g.Expect(ok).To(BeTrue()) + g.Expect(match).To(Equal(cases[c][search.results[0]].image)) + continue + } + + g.Expect(ok).To(BeFalse()) + g.Expect(matches).To(HaveLen(len(search.results))) + expectedMatches := make([]string, len(search.results)) + for i, v := range search.results { + expectedMatches[i] = cases[c][v].image + } + g.Expect(matches).To(ConsistOf(expectedMatches)) + } + } +} diff --git a/manifest/image/image_test.go b/manifest/image/image_test.go index f0e7287..bf547ad 100644 --- a/manifest/image/image_test.go +++ b/manifest/image/image_test.go @@ -12,8 +12,7 @@ func TestDedup(t *testing.T) { g := NewWithT(t) cases := []struct { - image string - alias string + image, alias string }{ {image: "example.com/a1/bar/foo:f@sha256:foo1", alias: "bar/foo"}, {image: "example.io/b1/baz/foo:v1@sha256:foo1", alias: "baz/foo"}, diff --git a/tape/app/package.go b/tape/app/package.go index ec45a05..7d9c34f 100644 --- a/tape/app/package.go +++ b/tape/app/package.go @@ -24,7 +24,8 @@ import ( type TapePackageCommand struct { CommonOptions - OutputImage string `short:"O" long:"output-image" description:"Name of the output image" required:"true"` + WithImages map[string]string `short:"I" long:"with-images" required:"false" description:"Names of new images to use instead of what specified in the manifests"` + OutputImage string `short:"O" long:"output-image" required:"true" description:"Name of the taped image to push"` // TODO: implement Push bool `short:"P" long:"push" description:"Push the resulting image to the registry"`