Skip to content

Commit

Permalink
Reuse the reader returned by FetchReference
Browse files Browse the repository at this point in the history
FetchReference already returns a reader to the manifest content.
However, content.Successors operates on a Fetcher. In order to funnel
the already open reader into content.Successors, create a FetcherFunc
that returns it.

This saves one additional GET request.

Before:
    GET /v2/repository/artifact/manifests/latest
    GET /v2/repository/artifact/manifests/sha256:341098d11767212bb4d148f3fe8e82fc2939c3be247b233291a62e39a594ed09
    GET /v2/repository/artifact/blobs/sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

After:
    GET /v2/repository/artifact/manifests/latest
    GET /v2/repository/artifact/blobs/sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

Signed-off-by: Tom Wieczorek <[email protected]>
  • Loading branch information
twz123 committed Oct 10, 2024
1 parent 84c523c commit c19f5fb
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 7 deletions.
36 changes: 29 additions & 7 deletions internal/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ package oci
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"os"
"sync/atomic"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content"
Expand Down Expand Up @@ -91,13 +93,7 @@ func Download(ctx context.Context, url string, target io.Writer, options ...Down
}

tag := imgref.Reference
desc, data, err := repo.Manifests().FetchReference(ctx, tag)
if err != nil {
return fmt.Errorf("failed to fetch manifest: %w", err)
}
defer data.Close()

successors, err := content.Successors(ctx, repo, desc)
successors, err := fetchSuccessors(ctx, repo.Manifests(), tag)
if err != nil {
return fmt.Errorf("failed to fetch successors: %w", err)
}
Expand All @@ -121,6 +117,32 @@ func Download(ctx context.Context, url string, target io.Writer, options ...Down
return nil
}

// Fetches the manifest for the given reference and returns all of its successors.
func fetchSuccessors(ctx context.Context, repo registry.ReferenceFetcher, reference string) ([]ocispec.Descriptor, error) {
var dataConsumed atomic.Bool
desc, data, err := repo.FetchReference(ctx, reference)
if err != nil {
return nil, fmt.Errorf("failed to fetch manifest: %w", err)
}
defer func() {
if dataConsumed.Swap(true) {
return
}
if closeErr := data.Close(); closeErr != nil {
err = errors.Join(err, closeErr)
}
}()

fetcher := content.FetcherFunc(func(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) {
if target.Digest == desc.Digest && !dataConsumed.Swap(true) {
return data, nil
}
return nil, errors.ErrUnsupported
})

return content.Successors(ctx, fetcher, desc)
}

// findArtifactDescriptor filters, out of the provided list of descriptors, the
// one that matches the given options. If no artifact name is provided, it
// returns the first descriptor.
Expand Down
5 changes: 5 additions & 0 deletions internal/oci/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ func startOCIMockServer(t *testing.T, tname string, test testFile) string {

server := starter(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Log(r.Proto, r.Method, r.RequestURI)
if !assert.Equal(t, r.Method, http.MethodGet) {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

// this is a request to authenticate.
if strings.Contains(r.URL.Path, "/token") {
Expand Down

0 comments on commit c19f5fb

Please sign in to comment.