Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
132 changes: 73 additions & 59 deletions go.mod

Large diffs are not rendered by default.

306 changes: 169 additions & 137 deletions go.sum

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (req *Request) MustConstruct() (method string, ustr string) {
return method, ustr
}

func (req *Request) construct() (method string, url string) {
func (req *Request) construct() (method string, urlStr string) {
switch req.Kind {
case ReqPing:
return "GET", "/v2/"
Expand Down Expand Up @@ -77,7 +77,11 @@ func (req *Request) construct() (method string, url string) {
case ReqTagsList:
return "GET", "/v2/" + req.Repo + "/tags/list" + req.listParams()
case ReqReferrersList:
return "GET", "/v2/" + req.Repo + "/referrers/" + req.Digest
p := "/v2/" + req.Repo + "/referrers/" + req.Digest
if req.ArtifactType != "" {
p += "?" + url.Values{"artifactType": {req.ArtifactType}}.Encode()
}
return "GET", p
case ReqCatalogList:
return "GET", "/v2/_catalog" + req.listParams()
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ package ocirequest

import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
Expand All @@ -27,27 +27,20 @@ import (
"cuelabs.dev/go/oci/ociregistry/ociref"
)

// ParseError represents an error that can happen when parsing.
// The Err field holds one of the possible error values below.
type ParseError struct {
Err error
}
var (
errBadlyFormedDigest = ociregistry.NewError("badly formed digest", ociregistry.ErrDigestInvalid.Code(), nil)
errMethodNotAllowed = httpErrorf(http.StatusMethodNotAllowed, "method not allowed")
errNotFound = httpErrorf(http.StatusNotFound, "page not found")
)

func (e *ParseError) Error() string {
return e.Err.Error()
func badRequestf(f string, a ...any) error {
return httpErrorf(http.StatusBadRequest, f, a...)
}

func (e *ParseError) Unwrap() error {
return e.Err
func httpErrorf(statusCode int, f string, a ...any) error {
return ociregistry.NewHTTPError(fmt.Errorf(f, a...), statusCode, nil, nil)
}

var (
ErrNotFound = errors.New("page not found")
ErrBadlyFormedDigest = errors.New("badly formed digest")
ErrMethodNotAllowed = errors.New("method not allowed")
ErrBadRequest = errors.New("bad request")
)

type Request struct {
Kind Kind

Expand Down Expand Up @@ -98,17 +91,22 @@ type Request struct {
// Valid for:
// ReqTagsList
// ReqCatalog
// ReqReferrers
ListN int

// listLast holds the item to start just after
// ListLast holds the item to start just after
// when listing.
//
// Valid for:
// ReqTagsList
// ReqCatalog
// ReqReferrers
ListLast string

// ArtifactType holds the artifact type to filter by when
// listing.
//
// Valid for:
// ReqReferrersList
ArtifactType string
}

type Kind int
Expand Down Expand Up @@ -185,22 +183,14 @@ const (
// Parse parses the given HTTP method and URL as an OCI registry request.
// It understands the endpoints described in the [distribution spec].
//
// If it returns an error, it will be of type *ParseError.
// If it returns an error, it will be of type [ociregistry.Error] or [ociregistry.HTTPError].
//
// [distribution spec]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#endpoints
func Parse(method string, u *url.URL) (*Request, error) {
req, err := parse(method, u)
if err != nil {
return nil, &ParseError{err}
}
return req, nil
}

func parse(method string, u *url.URL) (*Request, error) {
path := u.Path
urlq, err := url.ParseQuery(u.RawQuery)
if err != nil {
return nil, err
return nil, badRequestf("invalid query parameters: %v", err)
}

var rreq Request
Expand All @@ -214,7 +204,7 @@ func parse(method string, u *url.URL) (*Request, error) {
}
if path == "_catalog" {
if method != "GET" {
return nil, ErrMethodNotAllowed
return nil, errMethodNotAllowed
}
rreq.Kind = ReqCatalogList
setListQueryParams(&rreq, urlq)
Expand All @@ -230,7 +220,7 @@ func parse(method string, u *url.URL) (*Request, error) {
return nil, ociregistry.ErrNameInvalid
}
if method != "POST" {
return nil, ErrMethodNotAllowed
return nil, errMethodNotAllowed
}
if d := urlq.Get("mount"); d != "" {
// end-11
Expand All @@ -257,7 +247,7 @@ func parse(method string, u *url.URL) (*Request, error) {
// end-4b
rreq.Digest = d
if !ociref.IsValidDigest(d) {
return nil, ErrBadlyFormedDigest
return nil, errBadlyFormedDigest
}
rreq.Kind = ReqBlobUploadBlob
return &rreq, nil
Expand All @@ -268,17 +258,17 @@ func parse(method string, u *url.URL) (*Request, error) {
}
path, last, ok := cutLast(path, "/")
if !ok {
return nil, ErrNotFound
return nil, errNotFound
}
path, lastButOne, ok := cutLast(path, "/")
if !ok {
return nil, ErrNotFound
return nil, errNotFound
}
switch lastButOne {
case "blobs":
rreq.Repo = path
if !ociref.IsValidDigest(last) {
return nil, ErrBadlyFormedDigest
return nil, errBadlyFormedDigest
}
if !ociref.IsValidRepository(rreq.Repo) {
return nil, ociregistry.ErrNameInvalid
Expand All @@ -292,30 +282,30 @@ func parse(method string, u *url.URL) (*Request, error) {
case "DELETE":
rreq.Kind = ReqBlobDelete
default:
return nil, ErrMethodNotAllowed
return nil, errMethodNotAllowed
}
return &rreq, nil
case "uploads":
// Note: this section is all specific to ociserver and
// isn't part of the OCI registry spec.
repo, ok := strings.CutSuffix(path, "/blobs")
if !ok {
return nil, ErrNotFound
return nil, errNotFound
}
rreq.Repo = repo
if !ociref.IsValidRepository(rreq.Repo) {
return nil, ociregistry.ErrNameInvalid
}
uploadID64 := last
if uploadID64 == "" {
return nil, ErrNotFound
return nil, errNotFound
}
uploadID, err := base64.RawURLEncoding.DecodeString(uploadID64)
if err != nil {
return nil, fmt.Errorf("invalid upload ID %q (cannot decode)", uploadID64)
return nil, badRequestf("invalid upload ID %q (cannot decode)", uploadID64)
}
if !utf8.Valid(uploadID) {
return nil, fmt.Errorf("upload ID %q decoded to invalid utf8", uploadID64)
return nil, badRequestf("upload ID %q decoded to invalid utf8", uploadID64)
}
rreq.UploadID = string(uploadID)

Expand All @@ -328,10 +318,10 @@ func parse(method string, u *url.URL) (*Request, error) {
rreq.Kind = ReqBlobCompleteUpload
rreq.Digest = urlq.Get("digest")
if !ociref.IsValidDigest(rreq.Digest) {
return nil, ErrBadlyFormedDigest
return nil, errBadlyFormedDigest
}
default:
return nil, ErrMethodNotAllowed
return nil, errMethodNotAllowed
}
return &rreq, nil
case "manifests":
Expand All @@ -345,7 +335,7 @@ func parse(method string, u *url.URL) (*Request, error) {
case ociref.IsValidTag(last):
rreq.Tag = last
default:
return nil, ErrNotFound
return nil, errNotFound
}
switch method {
case "GET":
Expand All @@ -357,19 +347,19 @@ func parse(method string, u *url.URL) (*Request, error) {
case "DELETE":
rreq.Kind = ReqManifestDelete
default:
return nil, ErrMethodNotAllowed
return nil, errMethodNotAllowed
}
return &rreq, nil

case "tags":
if last != "list" {
return nil, ErrNotFound
return nil, errNotFound
}
if err := setListQueryParams(&rreq, urlq); err != nil {
return nil, err
}
if method != "GET" {
return nil, ErrMethodNotAllowed
return nil, errMethodNotAllowed
}
rreq.Repo = path
if !ociref.IsValidRepository(rreq.Repo) {
Expand All @@ -379,31 +369,32 @@ func parse(method string, u *url.URL) (*Request, error) {
return &rreq, nil
case "referrers":
if !ociref.IsValidDigest(last) {
return nil, ErrBadlyFormedDigest
return nil, errBadlyFormedDigest
}
if method != "GET" {
return nil, ErrMethodNotAllowed
return nil, errMethodNotAllowed
}
rreq.Repo = path
if !ociref.IsValidRepository(rreq.Repo) {
return nil, ociregistry.ErrNameInvalid
}
// TODO is there any kind of pagination for referrers?
// We'll set ListN to be future-proof.
// Unlike other list-oriented endpoints, there appears to be no defined way for the client
// to indicate the desired number of results, but set ListN anyway to be future-proof.
rreq.ListN = -1
rreq.Digest = last
rreq.ArtifactType = urlq.Get("artifactType")
rreq.Kind = ReqReferrersList
return &rreq, nil
}
return nil, ErrNotFound
return nil, errNotFound
}

func setListQueryParams(rreq *Request, urlq url.Values) error {
rreq.ListN = -1
if nstr := urlq.Get("n"); nstr != "" {
n, err := strconv.Atoi(nstr)
if err != nil {
return fmt.Errorf("n is not a valid integer: %w", ErrBadRequest)
return badRequestf("query parameter n is not a valid integer")
}
rreq.ListN = n
}
Expand Down
15 changes: 6 additions & 9 deletions vendor/cuelabs.dev/go/oci/ociregistry/iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,23 @@

package ociregistry

// TODO(go1.23) when we can depend on Go 1.23, this should be:
// TODO(go1.24) when we can depend on Go 1.24, this should be:
// type Seq[T any] = iter.Seq2[T, error]

// Seq defines the type of an iterator sequence returned from
// the iterator functions. In general, a non-nil
// error means that the item is the last in the sequence.
type Seq[T any] func(yield func(T, error) bool)

func All[T any](it Seq[T]) (_ []T, _err error) {
func All[T any](it Seq[T]) ([]T, error) {
xs := []T{}
// TODO(go1.23) for x, err := range it
it(func(x T, err error) bool {
for x, err := range it {
if err != nil {
_err = err
return false
return nil, err
}
xs = append(xs, x)
return true
})
return xs, _err
}
return xs, nil
}

func SliceSeq[T any](xs []T) Seq[T] {
Expand Down
2 changes: 1 addition & 1 deletion vendor/cuelabs.dev/go/oci/ociregistry/ociauth/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func init() {
// token = 1*<any CHAR except CTLs or separators>
// qdtext = <any TEXT except <">>

for c := 0; c < 256; c++ {
for c := range 256 {
var t octetType
isCtl := c <= 31 || c == 127
isChar := 0 <= c && c <= 127
Expand Down
7 changes: 3 additions & 4 deletions vendor/cuelabs.dev/go/oci/ociregistry/ociclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"log"
"net/http"
"net/url"
"slices"
"strconv"
"strings"
"sync/atomic"
Expand Down Expand Up @@ -324,10 +325,8 @@ func (c *client) do(req *http.Request, okStatuses ...int) (*http.Response, error
if len(okStatuses) == 0 && resp.StatusCode == http.StatusOK {
return resp, nil
}
for _, status := range okStatuses {
if resp.StatusCode == status {
return resp, nil
}
if slices.Contains(okStatuses, resp.StatusCode) {
return resp, nil
}
defer resp.Body.Close()
if !isOKStatus(resp.StatusCode) {
Expand Down
Loading
Loading