Skip to content

Commit

Permalink
Update TagList and helm check to use oras client.
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Nelson <minelson@vmware.com>
  • Loading branch information
absoludity committed Sep 5, 2023
1 parent dd9a3b1 commit 31a09cd
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 23 deletions.
72 changes: 57 additions & 15 deletions cmd/asset-syncer/server/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import (
"helm.sh/helm/v3/pkg/chart"
helmregistry "helm.sh/helm/v3/pkg/registry"
log "k8s.io/klog/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
"sigs.k8s.io/yaml"
)

Expand Down Expand Up @@ -349,35 +352,76 @@ type OciAPIClient struct {
GrpcClient ocicatalog.OCICatalogServiceClient
}

func (o *OciAPIClient) getOrasRepoClient(appName string, userAgent string) (*remote.Repository, error) {
url := *o.RegistryNamespaceUrl
repoName := path.Join(url.Path, appName)
repoRef := path.Join(url.Host, repoName)
orasRepoClient, err := remote.NewRepository(repoRef)
if err != nil {
return nil, fmt.Errorf("unable to create ORAS client for %q: %w", repoRef, err)
}
if url.Scheme == "http" {
orasRepoClient.PlainHTTP = true
}

// Set the http client using our own which adds headers for auth.
header := auth.DefaultClient.Header.Clone()
if userAgent != "" {
header.Set("User-Agent", userAgent)
}
orasRepoClient.Client = &auth.Client{
Client: o.HttpClient,
Cache: auth.DefaultCache,
Header: header,
}
return orasRepoClient, nil
}

// TagList retrieves the list of tags for an asset
func (o *OciAPIClient) TagList(appName string, userAgent string) (*TagList, error) {
url := *o.RegistryNamespaceUrl
url.Path = path.Join("v2", url.Path, appName, "tags", "list")
headers := map[string]string{}
data, err := doReq(url.String(), o.HttpClient, headers, userAgent)
orasRepoClient, err := o.getOrasRepoClient(appName, userAgent)
if err != nil {
return nil, err
}

var appTags TagList
err = json.Unmarshal(data, &appTags)
tags := []string{}

err = orasRepoClient.Tags(context.TODO(), "", func(ts []string) error {
tags = append(tags, ts...)
return nil
})
if err != nil {
return nil, err
}
return &appTags, nil

return &TagList{
Name: orasRepoClient.Reference.Repository,
Tags: tags,
}, nil
}

func (o *OciAPIClient) IsHelmChart(appName, tag, userAgent string) (bool, error) {
repoURL := *o.RegistryNamespaceUrl
repoURL.Path = path.Join("v2", repoURL.Path, appName, "manifests", tag)
log.V(4).Infof("Getting tag %s", repoURL.String())
headers := map[string]string{
"Accept": "application/vnd.oci.image.manifest.v1+json",
orasRepoClient, err := o.getOrasRepoClient(appName, userAgent)
if err != nil {
return false, err
}
ctx := context.TODO()
descriptor, err := orasRepoClient.Resolve(ctx, tag)
if err != nil {
log.Errorf("got error: %+v", err)
return false, err
}
manifestData, err := doReq(repoURL.String(), o.HttpClient, headers, userAgent)
rc, err := orasRepoClient.Fetch(ctx, descriptor)
if err != nil {
return false, err
}
defer rc.Close()

manifestData, err := content.ReadAll(rc, descriptor)
if err != nil {
return false, err
}

var manifest OCIManifest
err = json.Unmarshal(manifestData, &manifest)
if err != nil {
Expand Down Expand Up @@ -485,8 +529,6 @@ func (o *OciAPIClient) getVACReposForManifest(manifest *OCIManifest, userAgent s
// Catalog returns the list of repositories in the (namespaced) registry
// when discoverable.
func (o *OciAPIClient) Catalog(ctx context.Context, userAgent string) ([]string, error) {
// TODO(minelson): all Kubeapps interactions with OCI registries should
// be updated to use the oras go lib.
manifest, err := o.catalogManifest(userAgent)
if err == nil {
return o.getVACReposForManifest(manifest, userAgent)
Expand Down
67 changes: 59 additions & 8 deletions cmd/asset-syncer/server/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
tartest "github.com/vmware-tanzu/kubeapps/pkg/tarutil/test"
"helm.sh/helm/v3/pkg/chart"
log "k8s.io/klog/v2"
"oras.land/oras-go/v2/registry/remote/errcode"
)

var validRepoIndexYAMLBytes, _ = os.ReadFile("testdata/valid-index.yaml")
Expand Down Expand Up @@ -708,14 +709,23 @@ func Test_ociAPICli(t *testing.T) {
HttpClient: server.Client(),
}
_, err = apiCli.TagList("apache", "my-user-agent")
assert.Equal(t, fmt.Errorf("GET request to [%s/v2/apache/tags/list] failed due to status [500]", server.URL), err)
if err == nil {
t.Fatalf("got: nil, want: error")
}
errResponse, ok := err.(*errcode.ErrorResponse)
if !ok {
t.Fatalf("got: %+v, want: *errcode.ErrorResponse", err)
}
if got, want := errResponse.StatusCode, http.StatusInternalServerError; got != want {
t.Errorf("got: %d, want: %d", got, want)
}
})

t.Run("TagList - successful request", func(t *testing.T) {
server := newFakeServer(t, map[string]*http.Response{
"/v2/apache/tags/list": &http.Response{
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(`{"name":"test/apache","tags":["7.5.1","8.1.1"]}`)),
Body: io.NopCloser(strings.NewReader(`{"name":"apache","tags":["7.5.1","8.1.1"]}`)),
},
})
defer server.Close()
Expand All @@ -729,7 +739,7 @@ func Test_ociAPICli(t *testing.T) {
}
result, err := apiCli.TagList("apache", "my-user-agent")
assert.NoError(t, err)
expectedTagList := &TagList{Name: "test/apache", Tags: []string{"7.5.1", "8.1.1"}}
expectedTagList := &TagList{Name: "apache", Tags: []string{"7.5.1", "8.1.1"}}
if !cmp.Equal(result, expectedTagList) {
t.Errorf("Unexpected result %v", cmp.Diff(result, expectedTagList))
}
Expand All @@ -752,18 +762,59 @@ func Test_ociAPICli(t *testing.T) {
HttpClient: server.Client(),
}
_, err = apiCli.IsHelmChart("apache-bad", "7.5.1", "my-user-agent")
assert.Equal(t, fmt.Errorf("GET request to [%s/v2/apache-bad/manifests/7.5.1] failed due to status [500]", server.URL), err)

if err == nil {
t.Fatalf("got: nil, want: error")
}
errResponse, ok := err.(*errcode.ErrorResponse)
if !ok {
t.Fatalf("got: %+v, want: *errcode.ErrorResponse", err)
}
if got, want := errResponse.StatusCode, http.StatusInternalServerError; got != want {
t.Errorf("got: %d, want: %d", got, want)
}
})

t.Run("IsHelmChart - successful request", func(t *testing.T) {
manifest751 := `{"schemaVersion":2,"config":{"mediaType":"other","digest":"sha256:123","size":665}}`
sha751, err := getSha256([]byte(manifest751))
if err != nil {
t.Fatalf("%+v", err)
}
sha751 = "sha256:" + sha751
header751 := http.Header{}
header751.Set("Docker-Content-Digest", sha751)
header751.Set("Content-Type", "foo")
header751.Set("Content-Length", fmt.Sprintf("%d", len(manifest751)))

manifest811 := `{"schemaVersion":2,"config":{"mediaType":"application/vnd.cncf.helm.config.v1+json","digest":"sha256:456","size":665}}`
sha811, err := getSha256([]byte(manifest811))
if err != nil {
t.Fatalf("%+v", err)
}
sha811 = "sha256:" + sha811
header811 := http.Header{}
header811.Set("Docker-Content-Digest", sha811)
header811.Set("Content-Type", "foo")
header811.Set("Content-Length", fmt.Sprintf("%d", len(manifest811)))
server := newFakeServer(t, map[string]*http.Response{
"/v2/test/apache/manifests/7.5.1": &http.Response{
"/v2/test/apache/manifests/7.5.1": {
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(`{"schemaVersion":2,"config":{"mediaType":"other","digest":"sha256:123","size":665}}`)),
Header: header751,
},
"/v2/test/apache/manifests/8.1.1": &http.Response{
"/v2/test/apache/blobs/" + sha751: {
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.cncf.helm.config.v1+json","digest":"sha256:123","size":665}}`)),
Body: io.NopCloser(strings.NewReader(manifest751)),
Header: header751,
},
"/v2/test/apache/manifests/8.1.1": {
StatusCode: 200,
Header: header811,
},
"/v2/test/apache/blobs/" + sha811: {
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(manifest811)),
Header: header811,
},
})
defer server.Close()
Expand Down

0 comments on commit 31a09cd

Please sign in to comment.