mirror of
https://github.com/containers/skopeo.git
synced 2025-09-21 09:57:19 +00:00
Update vendor containers/(common,image)
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
2
vendor/github.com/containers/common/pkg/auth/auth.go
generated
vendored
2
vendor/github.com/containers/common/pkg/auth/auth.go
generated
vendored
@@ -158,7 +158,7 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
|
||||
}
|
||||
if unauthorized, ok := err.(docker.ErrUnauthorizedForCredentials); ok {
|
||||
logrus.Debugf("error logging into %q: %v", key, unauthorized)
|
||||
return fmt.Errorf("error logging into %q: invalid username/password", key)
|
||||
return fmt.Errorf("logging into %q: invalid username/password", key)
|
||||
}
|
||||
return fmt.Errorf("authenticating creds for %q: %w", key, err)
|
||||
}
|
||||
|
20
vendor/github.com/containers/common/pkg/report/formatter.go
generated
vendored
20
vendor/github.com/containers/common/pkg/report/formatter.go
generated
vendored
@@ -59,16 +59,22 @@ type Formatter struct {
|
||||
func (f *Formatter) Parse(origin Origin, text string) (*Formatter, error) {
|
||||
f.Origin = origin
|
||||
|
||||
// docker tries to be smart and replaces \n with the actual newline character.
|
||||
// For compat we do the same but this will break formats such as '{{printf "\n"}}'
|
||||
// To be backwards compatible with the previous behavior we try to replace and
|
||||
// parse the template. If it fails use the original text and parse again.
|
||||
var normText string
|
||||
switch {
|
||||
case strings.HasPrefix(text, "table "):
|
||||
f.RenderTable = true
|
||||
text = "{{range .}}" + NormalizeFormat(text) + "{{end -}}"
|
||||
normText = "{{range .}}" + NormalizeFormat(text) + "{{end -}}"
|
||||
text = "{{range .}}" + text + "{{end -}}"
|
||||
case OriginUser == origin:
|
||||
text = EnforceRange(NormalizeFormat(text))
|
||||
normText = EnforceRange(NormalizeFormat(text))
|
||||
text = EnforceRange(text)
|
||||
default:
|
||||
text = NormalizeFormat(text)
|
||||
normText = NormalizeFormat(text)
|
||||
}
|
||||
f.text = text
|
||||
|
||||
if f.RenderTable || origin == OriginPodman {
|
||||
tw := tabwriter.NewWriter(f.writer, 12, 2, 2, ' ', tabwriter.StripEscape)
|
||||
@@ -77,10 +83,14 @@ func (f *Formatter) Parse(origin Origin, text string) (*Formatter, error) {
|
||||
f.RenderHeaders = true
|
||||
}
|
||||
|
||||
tmpl, err := f.template.Funcs(template.FuncMap(DefaultFuncs)).Parse(text)
|
||||
tmpl, err := f.template.Funcs(template.FuncMap(DefaultFuncs)).Parse(normText)
|
||||
if err != nil {
|
||||
tmpl, err = f.template.Funcs(template.FuncMap(DefaultFuncs)).Parse(text)
|
||||
f.template = tmpl
|
||||
f.text = text
|
||||
return f, err
|
||||
}
|
||||
f.text = normText
|
||||
f.template = tmpl
|
||||
return f, nil
|
||||
}
|
||||
|
39
vendor/github.com/containers/image/v5/docker/archive/dest.go
generated
vendored
39
vendor/github.com/containers/image/v5/docker/archive/dest.go
generated
vendored
@@ -3,7 +3,6 @@ package archive
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/v5/docker/internal/tarfile"
|
||||
"github.com/containers/image/v5/internal/private"
|
||||
@@ -13,8 +12,8 @@ import (
|
||||
type archiveImageDestination struct {
|
||||
*tarfile.Destination // Implements most of types.ImageDestination
|
||||
ref archiveReference
|
||||
archive *tarfile.Writer // Should only be closed if writer != nil
|
||||
writer io.Closer // May be nil if the archive is shared
|
||||
writer *Writer // Should be closed if closeWriter
|
||||
closeWriter bool
|
||||
}
|
||||
|
||||
func newImageDestination(sys *types.SystemContext, ref archiveReference) (private.ImageDestination, error) {
|
||||
@@ -22,29 +21,28 @@ func newImageDestination(sys *types.SystemContext, ref archiveReference) (privat
|
||||
return nil, fmt.Errorf("Destination reference must not contain a manifest index @%d", ref.sourceIndex)
|
||||
}
|
||||
|
||||
var archive *tarfile.Writer
|
||||
var writer io.Closer
|
||||
if ref.archiveWriter != nil {
|
||||
archive = ref.archiveWriter
|
||||
writer = nil
|
||||
var writer *Writer
|
||||
var closeWriter bool
|
||||
if ref.writer != nil {
|
||||
writer = ref.writer
|
||||
closeWriter = false
|
||||
} else {
|
||||
fh, err := openArchiveForWriting(ref.path)
|
||||
w, err := NewWriter(sys, ref.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive = tarfile.NewWriter(fh)
|
||||
writer = fh
|
||||
writer = w
|
||||
closeWriter = true
|
||||
}
|
||||
tarDest := tarfile.NewDestination(sys, archive, ref.Transport().Name(), ref.ref)
|
||||
tarDest := tarfile.NewDestination(sys, writer.archive, ref.Transport().Name(), ref.ref)
|
||||
if sys != nil && sys.DockerArchiveAdditionalTags != nil {
|
||||
tarDest.AddRepoTags(sys.DockerArchiveAdditionalTags)
|
||||
}
|
||||
return &archiveImageDestination{
|
||||
Destination: tarDest,
|
||||
ref: ref,
|
||||
archive: archive,
|
||||
writer: writer,
|
||||
closeWriter: closeWriter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -56,7 +54,7 @@ func (d *archiveImageDestination) Reference() types.ImageReference {
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any.
|
||||
func (d *archiveImageDestination) Close() error {
|
||||
if d.writer != nil {
|
||||
if d.closeWriter {
|
||||
return d.writer.Close()
|
||||
}
|
||||
return nil
|
||||
@@ -70,8 +68,15 @@ func (d *archiveImageDestination) Close() error {
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *archiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error {
|
||||
if d.writer != nil {
|
||||
return d.archive.Close()
|
||||
d.writer.imageCommitted()
|
||||
if d.closeWriter {
|
||||
// We could do this only in .Close(), but failures in .Close() are much more likely to be
|
||||
// ignored by callers that use defer. So, in single-image destinations, try to complete
|
||||
// the archive here.
|
||||
// But if Commit() is never called, let .Close() clean up.
|
||||
err := d.writer.Close()
|
||||
d.closeWriter = false
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
6
vendor/github.com/containers/image/v5/docker/archive/transport.go
generated
vendored
6
vendor/github.com/containers/image/v5/docker/archive/transport.go
generated
vendored
@@ -53,7 +53,7 @@ type archiveReference struct {
|
||||
// file, not necessarily path precisely).
|
||||
archiveReader *tarfile.Reader
|
||||
// If not nil, must have been created for path
|
||||
archiveWriter *tarfile.Writer
|
||||
writer *Writer
|
||||
}
|
||||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference.
|
||||
@@ -108,7 +108,7 @@ func NewIndexReference(path string, sourceIndex int) (types.ImageReference, erro
|
||||
// newReference returns a docker archive reference for a path, an optional reference or sourceIndex,
|
||||
// and optionally a tarfile.Reader and/or a tarfile.Writer matching path.
|
||||
func newReference(path string, ref reference.NamedTagged, sourceIndex int,
|
||||
archiveReader *tarfile.Reader, archiveWriter *tarfile.Writer) (types.ImageReference, error) {
|
||||
archiveReader *tarfile.Reader, writer *Writer) (types.ImageReference, error) {
|
||||
if strings.Contains(path, ":") {
|
||||
return nil, fmt.Errorf("Invalid docker-archive: reference: colon in path %q is not supported", path)
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func newReference(path string, ref reference.NamedTagged, sourceIndex int,
|
||||
ref: ref,
|
||||
sourceIndex: sourceIndex,
|
||||
archiveReader: archiveReader,
|
||||
archiveWriter: archiveWriter,
|
||||
writer: writer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
98
vendor/github.com/containers/image/v5/docker/archive/writer.go
generated
vendored
98
vendor/github.com/containers/image/v5/docker/archive/writer.go
generated
vendored
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/containers/image/v5/docker/internal/tarfile"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
@@ -13,47 +14,19 @@ import (
|
||||
|
||||
// Writer manages a single in-progress Docker archive and allows adding images to it.
|
||||
type Writer struct {
|
||||
path string // The original, user-specified path; not the maintained temporary file, if any
|
||||
archive *tarfile.Writer
|
||||
writer io.Closer
|
||||
path string // The original, user-specified path; not the maintained temporary file, if any
|
||||
regularFile bool // path refers to a regular file (e.g. not a pipe)
|
||||
archive *tarfile.Writer
|
||||
writer io.Closer
|
||||
|
||||
// The following state can only be acccessed with the mutex held.
|
||||
mutex sync.Mutex
|
||||
hadCommit bool // At least one successful commit has happened
|
||||
}
|
||||
|
||||
// NewWriter returns a Writer for path.
|
||||
// The caller should call .Close() on the returned object.
|
||||
func NewWriter(sys *types.SystemContext, path string) (*Writer, error) {
|
||||
fh, err := openArchiveForWriting(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
archive := tarfile.NewWriter(fh)
|
||||
|
||||
return &Writer{
|
||||
path: path,
|
||||
archive: archive,
|
||||
writer: fh,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close writes all outstanding data about images to the archive, and
|
||||
// releases state associated with the Writer, if any.
|
||||
// No more images can be added after this is called.
|
||||
func (w *Writer) Close() error {
|
||||
err := w.archive.Close()
|
||||
if err2 := w.writer.Close(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewReference returns an ImageReference that allows adding an image to Writer,
|
||||
// with an optional reference.
|
||||
func (w *Writer) NewReference(destinationRef reference.NamedTagged) (types.ImageReference, error) {
|
||||
return newReference(w.path, destinationRef, -1, nil, w.archive)
|
||||
}
|
||||
|
||||
// openArchiveForWriting opens path for writing a tar archive,
|
||||
// making a few sanity checks.
|
||||
func openArchiveForWriting(path string) (*os.File, error) {
|
||||
// path can be either a pipe or a regular file
|
||||
// in the case of a pipe, we require that we can open it for write
|
||||
// in the case of a regular file, we don't want to overwrite any pre-existing file
|
||||
@@ -69,15 +42,62 @@ func openArchiveForWriting(path string) (*os.File, error) {
|
||||
fh.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
fhStat, err := fh.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("statting file %q: %w", path, err)
|
||||
}
|
||||
|
||||
if fhStat.Mode().IsRegular() && fhStat.Size() != 0 {
|
||||
regularFile := fhStat.Mode().IsRegular()
|
||||
if regularFile && fhStat.Size() != 0 {
|
||||
return nil, errors.New("docker-archive doesn't support modifying existing images")
|
||||
}
|
||||
|
||||
archive := tarfile.NewWriter(fh)
|
||||
|
||||
succeeded = true
|
||||
return fh, nil
|
||||
return &Writer{
|
||||
path: path,
|
||||
regularFile: regularFile,
|
||||
archive: archive,
|
||||
writer: fh,
|
||||
hadCommit: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// imageCommitted notifies the Writer that at least one image was successfully commited to the stream.
|
||||
func (w *Writer) imageCommitted() {
|
||||
w.mutex.Lock()
|
||||
defer w.mutex.Unlock()
|
||||
w.hadCommit = true
|
||||
}
|
||||
|
||||
// Close writes all outstanding data about images to the archive, and
|
||||
// releases state associated with the Writer, if any.
|
||||
// No more images can be added after this is called.
|
||||
func (w *Writer) Close() error {
|
||||
err := w.archive.Close()
|
||||
if err2 := w.writer.Close(); err2 != nil && err == nil {
|
||||
err = err2
|
||||
}
|
||||
if err == nil && w.regularFile && !w.hadCommit {
|
||||
// Writing to the destination never had a success; delete the destination if we created it.
|
||||
// This is done primarily because we don’t implement adding another image to a pre-existing image, so if we
|
||||
// left a partial archive around (notably because reading from the _source_ has failed), we couldn’t retry without
|
||||
// the caller manually deleting the partial archive. So, delete it instead.
|
||||
//
|
||||
// Archives with at least one successfully created image are left around; they might still be valuable.
|
||||
//
|
||||
// Note a corner case: If there _originally_ was an empty file (which is not a valid archive anyway), this deletes it.
|
||||
// Ideally, if w.regularFile, we should write the full contents to a temporary file and use os.Rename here, only on success.
|
||||
if err2 := os.Remove(w.path); err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewReference returns an ImageReference that allows adding an image to Writer,
|
||||
// with an optional reference.
|
||||
func (w *Writer) NewReference(destinationRef reference.NamedTagged) (types.ImageReference, error) {
|
||||
return newReference(w.path, destinationRef, -1, nil, w)
|
||||
}
|
||||
|
147
vendor/github.com/containers/image/v5/docker/distribution_error.go
generated
vendored
Normal file
147
vendor/github.com/containers/image/v5/docker/distribution_error.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
// Code below is taken from https://github.com/distribution/distribution/blob/a4d9db5a884b70be0c96dd6a7a9dbef4f2798c51/registry/client/errors.go
|
||||
// Copyright 2022 github.com/distribution/distribution authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
dockerChallenge "github.com/docker/distribution/registry/client/auth/challenge"
|
||||
)
|
||||
|
||||
// errNoErrorsInBody is returned when an HTTP response body parses to an empty
|
||||
// errcode.Errors slice.
|
||||
var errNoErrorsInBody = errors.New("no error details found in HTTP response body")
|
||||
|
||||
// unexpectedHTTPStatusError is returned when an unexpected HTTP status is
|
||||
// returned when making a registry api call.
|
||||
type unexpectedHTTPStatusError struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
func (e *unexpectedHTTPStatusError) Error() string {
|
||||
return fmt.Sprintf("received unexpected HTTP status: %s", e.Status)
|
||||
}
|
||||
|
||||
// unexpectedHTTPResponseError is returned when an expected HTTP status code
|
||||
// is returned, but the content was unexpected and failed to be parsed.
|
||||
type unexpectedHTTPResponseError struct {
|
||||
ParseErr error
|
||||
StatusCode int
|
||||
Response []byte
|
||||
}
|
||||
|
||||
func (e *unexpectedHTTPResponseError) Error() string {
|
||||
return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response))
|
||||
}
|
||||
|
||||
func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
|
||||
var errors errcode.Errors
|
||||
body, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For backward compatibility, handle irregularly formatted
|
||||
// messages that contain a "details" field.
|
||||
var detailsErr struct {
|
||||
Details string `json:"details"`
|
||||
}
|
||||
err = json.Unmarshal(body, &detailsErr)
|
||||
if err == nil && detailsErr.Details != "" {
|
||||
switch statusCode {
|
||||
case http.StatusUnauthorized:
|
||||
return errcode.ErrorCodeUnauthorized.WithMessage(detailsErr.Details)
|
||||
case http.StatusTooManyRequests:
|
||||
return errcode.ErrorCodeTooManyRequests.WithMessage(detailsErr.Details)
|
||||
default:
|
||||
return errcode.ErrorCodeUnknown.WithMessage(detailsErr.Details)
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &errors); err != nil {
|
||||
return &unexpectedHTTPResponseError{
|
||||
ParseErr: err,
|
||||
StatusCode: statusCode,
|
||||
Response: body,
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) == 0 {
|
||||
// If there was no error specified in the body, return
|
||||
// UnexpectedHTTPResponseError.
|
||||
return &unexpectedHTTPResponseError{
|
||||
ParseErr: errNoErrorsInBody,
|
||||
StatusCode: statusCode,
|
||||
Response: body,
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func makeErrorList(err error) []error {
|
||||
if errL, ok := err.(errcode.Errors); ok {
|
||||
return []error(errL)
|
||||
}
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
func mergeErrors(err1, err2 error) error {
|
||||
return errcode.Errors(append(makeErrorList(err1), makeErrorList(err2)...))
|
||||
}
|
||||
|
||||
// handleErrorResponse returns error parsed from HTTP response for an
|
||||
// unsuccessful HTTP response code (in the range 400 - 499 inclusive). An
|
||||
// UnexpectedHTTPStatusError returned for response code outside of expected
|
||||
// range.
|
||||
func handleErrorResponse(resp *http.Response) error {
|
||||
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
|
||||
// Check for OAuth errors within the `WWW-Authenticate` header first
|
||||
// See https://tools.ietf.org/html/rfc6750#section-3
|
||||
for _, c := range dockerChallenge.ResponseChallenges(resp) {
|
||||
if c.Scheme == "bearer" {
|
||||
var err errcode.Error
|
||||
// codes defined at https://tools.ietf.org/html/rfc6750#section-3.1
|
||||
switch c.Parameters["error"] {
|
||||
case "invalid_token":
|
||||
err.Code = errcode.ErrorCodeUnauthorized
|
||||
case "insufficient_scope":
|
||||
err.Code = errcode.ErrorCodeDenied
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if description := c.Parameters["error_description"]; description != "" {
|
||||
err.Message = description
|
||||
} else {
|
||||
err.Message = err.Code.Message()
|
||||
}
|
||||
|
||||
return mergeErrors(err, parseHTTPErrorResponse(resp.StatusCode, resp.Body))
|
||||
}
|
||||
}
|
||||
err := parseHTTPErrorResponse(resp.StatusCode, resp.Body)
|
||||
if uErr, ok := err.(*unexpectedHTTPResponseError); ok && resp.StatusCode == 401 {
|
||||
return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return &unexpectedHTTPStatusError{Status: resp.Status}
|
||||
}
|
82
vendor/github.com/containers/image/v5/docker/docker_client.go
generated
vendored
82
vendor/github.com/containers/image/v5/docker/docker_client.go
generated
vendored
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
v2 "github.com/docker/distribution/registry/api/v2"
|
||||
clientLib "github.com/docker/distribution/registry/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -126,8 +125,9 @@ type dockerClient struct {
|
||||
}
|
||||
|
||||
type authScope struct {
|
||||
remoteName string
|
||||
actions string
|
||||
resourceType string
|
||||
remoteName string
|
||||
actions string
|
||||
}
|
||||
|
||||
// sendAuth determines whether we need authentication for v2 or v1 endpoint.
|
||||
@@ -236,6 +236,7 @@ func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, regis
|
||||
}
|
||||
client.signatureBase = sigBase
|
||||
client.useSigstoreAttachments = registryConfig.useSigstoreAttachments(ref)
|
||||
client.scope.resourceType = "repository"
|
||||
client.scope.actions = actions
|
||||
client.scope.remoteName = reference.Path(ref.ref)
|
||||
return client, nil
|
||||
@@ -474,6 +475,33 @@ func (c *dockerClient) makeRequest(ctx context.Context, method, path string, hea
|
||||
return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth, extraScope)
|
||||
}
|
||||
|
||||
// Checks if the auth headers in the response contain an indication of a failed
|
||||
// authorizdation because of an "insufficient_scope" error. If that's the case,
|
||||
// returns the required scope to be used for fetching a new token.
|
||||
func needsRetryWithUpdatedScope(err error, res *http.Response) (bool, *authScope) {
|
||||
if err == nil && res.StatusCode == http.StatusUnauthorized {
|
||||
challenges := parseAuthHeader(res.Header)
|
||||
for _, challenge := range challenges {
|
||||
if challenge.Scheme == "bearer" {
|
||||
if errmsg, ok := challenge.Parameters["error"]; ok && errmsg == "insufficient_scope" {
|
||||
if scope, ok := challenge.Parameters["scope"]; ok && scope != "" {
|
||||
if newScope, err := parseAuthScope(scope); err == nil {
|
||||
return true, newScope
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"scope": scope,
|
||||
"challenge": challenge,
|
||||
}).Error("Failed to parse the authentication scope from the given challenge")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// parseRetryAfter determines the delay required by the "Retry-After" header in res and returns it,
|
||||
// silently falling back to fallbackDelay if the header is missing or invalid.
|
||||
func parseRetryAfter(res *http.Response, fallbackDelay time.Duration) time.Duration {
|
||||
@@ -513,6 +541,29 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method stri
|
||||
for {
|
||||
res, err := c.makeRequestToResolvedURLOnce(ctx, method, url, headers, stream, streamLen, auth, extraScope)
|
||||
attempts++
|
||||
|
||||
// By default we use pre-defined scopes per operation. In
|
||||
// certain cases, this can fail when our authentication is
|
||||
// insufficient, then we might be getting an error back with a
|
||||
// Www-Authenticate Header indicating an insufficient scope.
|
||||
//
|
||||
// Check for that and update the client challenges to retry after
|
||||
// requesting a new token
|
||||
//
|
||||
// We only try this on the first attempt, to not overload an
|
||||
// already struggling server.
|
||||
// We also cannot retry with a body (stream != nil) as stream
|
||||
// was already read
|
||||
if attempts == 1 && stream == nil && auth != noAuth {
|
||||
if retry, newScope := needsRetryWithUpdatedScope(err, res); retry {
|
||||
logrus.Debug("Detected insufficient_scope error, will retry request with updated scope")
|
||||
// Note: This retry ignores extraScope. That’s, strictly speaking, incorrect, but we don’t currently
|
||||
// expect the insufficient_scope errors to happen for those callers. If that changes, we can add support
|
||||
// for more than one extra scope.
|
||||
res, err = c.makeRequestToResolvedURLOnce(ctx, method, url, headers, stream, streamLen, auth, newScope)
|
||||
extraScope = newScope
|
||||
}
|
||||
}
|
||||
if res == nil || res.StatusCode != http.StatusTooManyRequests || // Only retry on StatusTooManyRequests, success or other failure is returned to caller immediately
|
||||
stream != nil || // We can't retry with a body (which is not restartable in the general case)
|
||||
attempts == backoffNumIterations {
|
||||
@@ -592,8 +643,18 @@ func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope
|
||||
cacheKey := ""
|
||||
scopes := []authScope{c.scope}
|
||||
if extraScope != nil {
|
||||
// Using ':' as a separator here is unambiguous because getBearerToken below uses the same separator when formatting a remote request (and because repository names can't contain colons).
|
||||
cacheKey = fmt.Sprintf("%s:%s", extraScope.remoteName, extraScope.actions)
|
||||
// Using ':' as a separator here is unambiguous because getBearerToken below
|
||||
// uses the same separator when formatting a remote request (and because
|
||||
// repository names that we create can't contain colons, and extraScope values
|
||||
// coming from a server come from `parseAuthScope`, which also splits on colons).
|
||||
cacheKey = fmt.Sprintf("%s:%s:%s", extraScope.resourceType, extraScope.remoteName, extraScope.actions)
|
||||
if colonCount := strings.Count(cacheKey, ":"); colonCount != 2 {
|
||||
return fmt.Errorf(
|
||||
"Internal error: there must be exactly 2 colons in the cacheKey ('%s') but got %d",
|
||||
cacheKey,
|
||||
colonCount,
|
||||
)
|
||||
}
|
||||
scopes = append(scopes, *extraScope)
|
||||
}
|
||||
var token bearerToken
|
||||
@@ -648,9 +709,10 @@ func (c *dockerClient) getBearerTokenOAuth2(ctx context.Context, challenge chall
|
||||
if service, ok := challenge.Parameters["service"]; ok && service != "" {
|
||||
params.Add("service", service)
|
||||
}
|
||||
|
||||
for _, scope := range scopes {
|
||||
if scope.remoteName != "" && scope.actions != "" {
|
||||
params.Add("scope", fmt.Sprintf("repository:%s:%s", scope.remoteName, scope.actions))
|
||||
if scope.resourceType != "" && scope.remoteName != "" && scope.actions != "" {
|
||||
params.Add("scope", fmt.Sprintf("%s:%s:%s", scope.resourceType, scope.remoteName, scope.actions))
|
||||
}
|
||||
}
|
||||
params.Add("grant_type", "refresh_token")
|
||||
@@ -700,8 +762,8 @@ func (c *dockerClient) getBearerToken(ctx context.Context, challenge challenge,
|
||||
}
|
||||
|
||||
for _, scope := range scopes {
|
||||
if scope.remoteName != "" && scope.actions != "" {
|
||||
params.Add("scope", fmt.Sprintf("repository:%s:%s", scope.remoteName, scope.actions))
|
||||
if scope.resourceType != "" && scope.remoteName != "" && scope.actions != "" {
|
||||
params.Add("scope", fmt.Sprintf("%s:%s:%s", scope.resourceType, scope.remoteName, scope.actions))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,7 +1039,7 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("downloading signatures for %s in %s: %w", manifestDigest, ref.ref.Name(), clientLib.HandleErrorResponse(res))
|
||||
return nil, fmt.Errorf("downloading signatures for %s in %s: %w", manifestDigest, ref.ref.Name(), handleErrorResponse(res))
|
||||
}
|
||||
|
||||
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureListBodySize)
|
||||
|
5
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
5
vendor/github.com/containers/image/v5/docker/docker_image_dest.go
generated
vendored
@@ -358,8 +358,9 @@ func (d *dockerImageDestination) TryReusingBlobWithOptions(ctx context.Context,
|
||||
// Checking candidateRepo, and mounting from it, requires an
|
||||
// expanded token scope.
|
||||
extraScope := &authScope{
|
||||
remoteName: reference.Path(candidateRepo),
|
||||
actions: "pull",
|
||||
resourceType: "repository",
|
||||
remoteName: reference.Path(candidateRepo),
|
||||
actions: "pull",
|
||||
}
|
||||
// This existence check is not, strictly speaking, necessary: We only _really_ need it to get the blob size, and we could record that in the cache instead.
|
||||
// But a "failed" d.mountBlob currently leaves around an unterminated server-side upload, which we would try to cancel.
|
||||
|
8
vendor/github.com/containers/image/v5/docker/errors.go
generated
vendored
8
vendor/github.com/containers/image/v5/docker/errors.go
generated
vendored
@@ -4,8 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/distribution/registry/client"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -35,7 +33,7 @@ func httpResponseToError(res *http.Response, context string) error {
|
||||
case http.StatusTooManyRequests:
|
||||
return ErrTooManyRequests
|
||||
case http.StatusUnauthorized:
|
||||
err := client.HandleErrorResponse(res)
|
||||
err := handleErrorResponse(res)
|
||||
return ErrUnauthorizedForCredentials{Err: err}
|
||||
default:
|
||||
if context != "" {
|
||||
@@ -48,8 +46,8 @@ func httpResponseToError(res *http.Response, context string) error {
|
||||
// registryHTTPResponseToError creates a Go error from an HTTP error response of a docker/distribution
|
||||
// registry
|
||||
func registryHTTPResponseToError(res *http.Response) error {
|
||||
err := client.HandleErrorResponse(res)
|
||||
if e, ok := err.(*client.UnexpectedHTTPResponseError); ok {
|
||||
err := handleErrorResponse(res)
|
||||
if e, ok := err.(*unexpectedHTTPResponseError); ok {
|
||||
response := string(e.Response)
|
||||
if len(response) > 50 {
|
||||
response = response[:50] + "..."
|
||||
|
13
vendor/github.com/containers/image/v5/docker/wwwauthenticate.go
generated
vendored
13
vendor/github.com/containers/image/v5/docker/wwwauthenticate.go
generated
vendored
@@ -3,6 +3,7 @@ package docker
|
||||
// Based on github.com/docker/distribution/registry/client/auth/authchallenge.go, primarily stripping unnecessary dependencies.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@@ -70,6 +71,18 @@ func parseAuthHeader(header http.Header) []challenge {
|
||||
return challenges
|
||||
}
|
||||
|
||||
// parseAuthScope parses an authentication scope string of the form `$resource:$remote:$actions`
|
||||
func parseAuthScope(scopeStr string) (*authScope, error) {
|
||||
if parts := strings.Split(scopeStr, ":"); len(parts) == 3 {
|
||||
return &authScope{
|
||||
resourceType: parts[0],
|
||||
remoteName: parts[1],
|
||||
actions: parts[2],
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("error parsing auth scope: '%s'", scopeStr)
|
||||
}
|
||||
|
||||
// NOTE: This is not a fully compliant parser per RFC 7235:
|
||||
// Most notably it does not support more than one challenge within a single header
|
||||
// Some of the whitespace parsing also seems noncompliant.
|
||||
|
15
vendor/github.com/containers/image/v5/pkg/docker/config/config.go
generated
vendored
15
vendor/github.com/containers/image/v5/pkg/docker/config/config.go
generated
vendored
@@ -683,7 +683,7 @@ func findCredentialsInFile(key, registry, path string, legacyFormat bool) (types
|
||||
// keys we prefer exact matches as well.
|
||||
for _, key := range keys {
|
||||
if val, exists := auths.AuthConfigs[key]; exists {
|
||||
return decodeDockerAuth(val)
|
||||
return decodeDockerAuth(path, key, val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,7 +698,7 @@ func findCredentialsInFile(key, registry, path string, legacyFormat bool) (types
|
||||
registry = normalizeRegistry(registry)
|
||||
for k, v := range auths.AuthConfigs {
|
||||
if normalizeAuthFileKey(k, legacyFormat) == registry {
|
||||
return decodeDockerAuth(v)
|
||||
return decodeDockerAuth(path, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,9 +729,9 @@ func authKeysForKey(key string) (res []string) {
|
||||
return res
|
||||
}
|
||||
|
||||
// decodeDockerAuth decodes the username and password, which is
|
||||
// encoded in base64.
|
||||
func decodeDockerAuth(conf dockerAuthConfig) (types.DockerAuthConfig, error) {
|
||||
// decodeDockerAuth decodes the username and password from conf,
|
||||
// which is entry key in path.
|
||||
func decodeDockerAuth(path, key string, conf dockerAuthConfig) (types.DockerAuthConfig, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(conf.Auth)
|
||||
if err != nil {
|
||||
return types.DockerAuthConfig{}, err
|
||||
@@ -740,6 +740,11 @@ func decodeDockerAuth(conf dockerAuthConfig) (types.DockerAuthConfig, error) {
|
||||
parts := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
// if it's invalid just skip, as docker does
|
||||
if len(decoded) > 0 { // Docker writes "auths": { "$host": {} } entries if a credential helper is used, don’t warn about those
|
||||
logrus.Warnf(`Error parsing the "auth" field of a credential entry %q in %q, missing semicolon`, key, path) // Don’t include the text of decoded, because that might put secrets into a log.
|
||||
} else {
|
||||
logrus.Debugf("Found an empty credential entry %q in %q (an unhandled credential helper marker?), moving on", key, path)
|
||||
}
|
||||
return types.DockerAuthConfig{}, nil
|
||||
}
|
||||
|
||||
|
6
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
6
vendor/github.com/containers/image/v5/version/version.go
generated
vendored
@@ -6,12 +6,12 @@ const (
|
||||
// VersionMajor is for an API incompatible changes
|
||||
VersionMajor = 5
|
||||
// VersionMinor is for functionality in a backwards-compatible manner
|
||||
VersionMinor = 22
|
||||
VersionMinor = 23
|
||||
// VersionPatch is for backwards-compatible bug fixes
|
||||
VersionPatch = 1
|
||||
VersionPatch = 0
|
||||
|
||||
// VersionDev indicates development branch. Releases will be empty string.
|
||||
VersionDev = "-dev"
|
||||
VersionDev = ""
|
||||
)
|
||||
|
||||
// Version is the specification version that the package types support.
|
||||
|
Reference in New Issue
Block a user