Skip to content

Commit

Permalink
Add tests for the OCI client (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
ribbybibby authored Sep 6, 2024
1 parent 15b604e commit e1f8fd2
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 7 deletions.
28 changes: 21 additions & 7 deletions pkg/client/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,38 @@ import (
"github.com/jetstack/version-checker/pkg/api"
)

type Client struct{}
// Client is a client for a registry compatible with the OCI Distribution Spec
type Client struct {
puller *remote.Puller
}

// New returns a new client
func New() (*Client, error) {
return &Client{}, nil
puller, err := remote.NewPuller()
if err != nil {
return nil, fmt.Errorf("creating puller: %w", err)
}

return &Client{
puller: puller,
}, nil
}

// Name is the name of this client
func (c *Client) Name() string {
return "oci"
}

// Tags lists all the tags in the specified repository
func (c *Client) Tags(ctx context.Context, host, repo, image string) ([]api.ImageTag, error) {
src := fmt.Sprintf("%s/%s/%s", host, repo, image)
rpo, err := name.NewRepository(src)
reg, err := name.NewRegistry(host)
if err != nil {
return []api.ImageTag{}, err
return nil, fmt.Errorf("parsing registry host: %w", err)
}

bareTags, err := remote.List(rpo, remote.WithContext(ctx))
bareTags, err := c.puller.List(ctx, reg.Repo(repo, image))
if err != nil {
return []api.ImageTag{}, err
return nil, fmt.Errorf("listing tags: %w", err)
}

var tags []api.ImageTag
Expand All @@ -40,10 +52,12 @@ func (c *Client) Tags(ctx context.Context, host, repo, image string) ([]api.Imag
return tags, nil
}

// IsHost always returns true because it supports any host
func (c *Client) IsHost(_ string) bool {
return true
}

// RepoImageFromPath splits a repository path into 'repo' and 'image' segments
func (c *Client) RepoImageFromPath(path string) (string, string) {
split := strings.Split(path, "/")

Expand Down
204 changes: 204 additions & 0 deletions pkg/client/oci/oci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package oci

import (
"context"
"fmt"
"net/http/httptest"
"net/url"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/registry"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/jetstack/version-checker/pkg/api"
)

func TestClientTags(t *testing.T) {
ctx := context.Background()

type testCase struct {
repo string
img string
wantTags []api.ImageTag
wantErr bool
}
testCases := map[string]func(t *testing.T, host string) *testCase{
"should list expected tags": func(t *testing.T, host string) *testCase {
tc := &testCase{
repo: "foo",
img: "bar",
wantTags: []api.ImageTag{
{
Tag: "a",
},
{
Tag: "b",
},
{
Tag: "c",
},
},
}
repo, err := name.NewRepository(fmt.Sprintf("%s/%s/%s", host, tc.repo, tc.img))
if err != nil {
t.Fatalf("unexpected error parsing repo: %s", err)
}
for _, tag := range tc.wantTags {
if err := remote.Write(repo.Tag(tag.Tag), empty.Image); err != nil {
t.Fatalf("unexpected error writing image to tag: %s", err)
}
}
return tc
},
"should list expected tags within a root repository": func(t *testing.T, host string) *testCase {
tc := &testCase{
img: "foo",
wantTags: []api.ImageTag{
{
Tag: "a",
},
{
Tag: "b",
},
},
}
repo, err := name.NewRepository(fmt.Sprintf("%s/%s", host, tc.img))
if err != nil {
t.Fatalf("unexpected error parsing repo: %s", err)
}
for _, tag := range tc.wantTags {
if err := remote.Write(repo.Tag(tag.Tag), empty.Image); err != nil {
t.Fatalf("unexpected error writing image to tag: %s", err)
}
}
return tc
},
"should list expected tags within a sub-repository": func(t *testing.T, host string) *testCase {
tc := &testCase{
repo: "foo/bar",
img: "baz",
wantTags: []api.ImageTag{
{
Tag: "a",
},
},
}
repo, err := name.NewRepository(fmt.Sprintf("%s/%s/%s", host, tc.repo, tc.img))
if err != nil {
t.Fatalf("unexpected error parsing repo: %s", err)
}
for _, tag := range tc.wantTags {
if err := remote.Write(repo.Tag(tag.Tag), empty.Image); err != nil {
t.Fatalf("unexpected error writing image to tag: %s", err)
}
}
return tc
},
"should return an empty list and no error for a repository with no tags": func(t *testing.T, host string) *testCase {
tc := &testCase{
repo: "foo",
img: "bar",
}
repo, err := name.NewRepository(fmt.Sprintf("%s/%s/%s", host, tc.repo, tc.img))
if err != nil {
t.Fatalf("unexpected error parsing repo: %s", err)
}

// Write a tag but then delete it so the repository
// exists but it has no tags
if err := remote.Write(repo.Tag("latest"), empty.Image); err != nil {
t.Fatalf("unexpected error writing image to tag: %s", err)
}
if err := remote.Delete(repo.Tag("latest")); err != nil {
t.Fatalf("unexpected error writing image to tag: %s", err)
}
return tc
},
"should return an error when listing a repository that doesn't exist": func(t *testing.T, host string) *testCase {
return &testCase{
repo: "foo",
img: "bar",
wantErr: true,
}
},
}

for testName, fn := range testCases {
t.Run(testName, func(t *testing.T) {
host := setupRegistry(t)

c, err := New()
if err != nil {
t.Fatalf("unexpected error creating client: %s", err)
}

tc := fn(t, host)

gotTags, err := c.Tags(ctx, host, tc.repo, tc.img)
if tc.wantErr && err == nil {
t.Errorf("unexpected nil error listing tags")
}
if !tc.wantErr && err != nil {
t.Errorf("unexpected error listing tags: %s", err)
}
if diff := cmp.Diff(tc.wantTags, gotTags); diff != "" {
t.Errorf("unexpected tags:\n%s", diff)
}

})
}
}

func TestClientRepoImageFromPath(t *testing.T) {
tests := map[string]struct {
path string
expRepo, expImage string
}{
"empty path should be interpreted as an empty repo and image": {
path: "",
expRepo: "",
expImage: "",
},
"one segment should be interpreted as 'repo'": {
path: "jetstack-cre",
expRepo: "",
expImage: "jetstack-cre",
},
"two segments to path should return both": {
path: "jetstack-cre/version-checker",
expRepo: "jetstack-cre",
expImage: "version-checker",
},
"multiple segments to path should return first segments in repo, last segment in image": {
path: "k8s-artifacts-prod/ingress-nginx/nginx",
expRepo: "k8s-artifacts-prod/ingress-nginx",
expImage: "nginx",
},
}

c, err := New()
if err != nil {
t.Fatalf("unexpected error creating client: %s", err)
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
repo, image := c.RepoImageFromPath(test.path)
if repo != test.expRepo && image != test.expImage {
t.Errorf("%s: unexpected repo/image, exp=%s/%s got=%s/%s",
test.path, test.expRepo, test.expImage, repo, image)
}
})
}
}

func setupRegistry(t *testing.T) string {
r := httptest.NewServer(registry.New())
t.Cleanup(r.Close)
u, err := url.Parse(r.URL)
if err != nil {
t.Fatalf("unexpected error parsing registry url: %s", err)
}
return u.Host
}

0 comments on commit e1f8fd2

Please sign in to comment.