forked from TrueCloudLab/distribution
4decfaa82e
This change implements the first pass at image manifest storage on top of the storagedriver. Very similar to LayerService, its much simpler due to less complexity of pushing and pulling images. Various components are still missing, such as detailed error reporting on missing layers during verification, but the base functionality is present.
289 lines
8.1 KiB
Go
289 lines
8.1 KiB
Go
package registry
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/docker/docker-registry/digest"
|
|
"github.com/docker/docker-registry/storage"
|
|
)
|
|
|
|
// ErrorCode represents the error type. The errors are serialized via strings
|
|
// and the integer format may change and should *never* be exported.
|
|
type ErrorCode int
|
|
|
|
const (
|
|
// ErrorCodeUnknown is a catch-all for errors not defined below.
|
|
ErrorCodeUnknown ErrorCode = iota
|
|
|
|
// The following errors can happen during a layer upload.
|
|
|
|
// ErrorCodeInvalidDigest is returned when uploading a layer if the
|
|
// provided digest does not match the layer contents.
|
|
ErrorCodeInvalidDigest
|
|
|
|
// ErrorCodeInvalidLength is returned when uploading a layer if the provided
|
|
// length does not match the content length.
|
|
ErrorCodeInvalidLength
|
|
|
|
// ErrorCodeInvalidName is returned when the name in the manifest does not
|
|
// match the provided name.
|
|
ErrorCodeInvalidName
|
|
|
|
// ErrorCodeInvalidTag is returned when the tag in the manifest does not
|
|
// match the provided tag.
|
|
ErrorCodeInvalidTag
|
|
|
|
// ErrorCodeUnverifiedManifest is returned when the manifest fails signature
|
|
// validation.
|
|
ErrorCodeUnverifiedManifest
|
|
|
|
// ErrorCodeUnknownLayer is returned when the manifest references a
|
|
// nonexistent layer.
|
|
ErrorCodeUnknownLayer
|
|
|
|
// ErrorCodeUnknownLayerUpload is returned when an upload is accessed.
|
|
ErrorCodeUnknownLayerUpload
|
|
|
|
// ErrorCodeUntrustedSignature is returned when the manifest is signed by an
|
|
// untrusted source.
|
|
ErrorCodeUntrustedSignature
|
|
)
|
|
|
|
var errorCodeStrings = map[ErrorCode]string{
|
|
ErrorCodeUnknown: "UNKNOWN",
|
|
ErrorCodeInvalidDigest: "INVALID_DIGEST",
|
|
ErrorCodeInvalidLength: "INVALID_LENGTH",
|
|
ErrorCodeInvalidName: "INVALID_NAME",
|
|
ErrorCodeInvalidTag: "INVALID_TAG",
|
|
ErrorCodeUnverifiedManifest: "UNVERIFIED_MANIFEST",
|
|
ErrorCodeUnknownLayer: "UNKNOWN_LAYER",
|
|
ErrorCodeUnknownLayerUpload: "UNKNOWN_LAYER_UPLOAD",
|
|
ErrorCodeUntrustedSignature: "UNTRUSTED_SIGNATURE",
|
|
}
|
|
|
|
var errorCodesMessages = map[ErrorCode]string{
|
|
ErrorCodeUnknown: "unknown error",
|
|
ErrorCodeInvalidDigest: "provided digest did not match uploaded content",
|
|
ErrorCodeInvalidLength: "provided length did not match content length",
|
|
ErrorCodeInvalidName: "Manifest name did not match URI",
|
|
ErrorCodeInvalidTag: "Manifest tag did not match URI",
|
|
ErrorCodeUnverifiedManifest: "Manifest failed signature validation",
|
|
ErrorCodeUnknownLayer: "Referenced layer not available",
|
|
ErrorCodeUnknownLayerUpload: "cannot resume unknown layer upload",
|
|
ErrorCodeUntrustedSignature: "Manifest signed by untrusted source",
|
|
}
|
|
|
|
var stringToErrorCode map[string]ErrorCode
|
|
|
|
func init() {
|
|
stringToErrorCode = make(map[string]ErrorCode, len(errorCodeStrings))
|
|
|
|
// Build up reverse error code map
|
|
for k, v := range errorCodeStrings {
|
|
stringToErrorCode[v] = k
|
|
}
|
|
}
|
|
|
|
// ParseErrorCode attempts to parse the error code string, returning
|
|
// ErrorCodeUnknown if the error is not known.
|
|
func ParseErrorCode(s string) ErrorCode {
|
|
ec, ok := stringToErrorCode[s]
|
|
|
|
if !ok {
|
|
return ErrorCodeUnknown
|
|
}
|
|
|
|
return ec
|
|
}
|
|
|
|
// String returns the canonical identifier for this error code.
|
|
func (ec ErrorCode) String() string {
|
|
s, ok := errorCodeStrings[ec]
|
|
|
|
if !ok {
|
|
return errorCodeStrings[ErrorCodeUnknown]
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// Message returned the human-readable error message for this error code.
|
|
func (ec ErrorCode) Message() string {
|
|
m, ok := errorCodesMessages[ec]
|
|
|
|
if !ok {
|
|
return errorCodesMessages[ErrorCodeUnknown]
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// MarshalText encodes the receiver into UTF-8-encoded text and returns the
|
|
// result.
|
|
func (ec ErrorCode) MarshalText() (text []byte, err error) {
|
|
return []byte(ec.String()), nil
|
|
}
|
|
|
|
// UnmarshalText decodes the form generated by MarshalText.
|
|
func (ec *ErrorCode) UnmarshalText(text []byte) error {
|
|
*ec = stringToErrorCode[string(text)]
|
|
|
|
return nil
|
|
}
|
|
|
|
// Error provides a wrapper around ErrorCode with extra Details provided.
|
|
type Error struct {
|
|
Code ErrorCode `json:"code"`
|
|
Message string `json:"message,omitempty"`
|
|
Detail interface{} `json:"detail,omitempty"`
|
|
}
|
|
|
|
// Error returns a human readable representation of the error.
|
|
func (e Error) Error() string {
|
|
return fmt.Sprintf("%s: %s",
|
|
strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)),
|
|
e.Message)
|
|
}
|
|
|
|
// Errors provides the envelope for multiple errors and a few sugar methods
|
|
// for use within the application.
|
|
type Errors struct {
|
|
Errors []error `json:"errors,omitempty"`
|
|
}
|
|
|
|
// Push pushes an error on to the error stack, with the optional detail
|
|
// argument. It is a programming error (ie panic) to push more than one
|
|
// detail at a time.
|
|
func (errs *Errors) Push(code ErrorCode, details ...interface{}) {
|
|
if len(details) > 1 {
|
|
panic("please specify zero or one detail items for this error")
|
|
}
|
|
|
|
var detail interface{}
|
|
if len(details) > 0 {
|
|
detail = details[0]
|
|
}
|
|
|
|
if err, ok := detail.(error); ok {
|
|
detail = err.Error()
|
|
}
|
|
|
|
errs.PushErr(Error{
|
|
Code: code,
|
|
Message: code.Message(),
|
|
Detail: detail,
|
|
})
|
|
}
|
|
|
|
// PushErr pushes an error interface onto the error stack.
|
|
func (errs *Errors) PushErr(err error) {
|
|
errs.Errors = append(errs.Errors, err)
|
|
}
|
|
|
|
func (errs *Errors) Error() string {
|
|
switch errs.Len() {
|
|
case 0:
|
|
return "<nil>"
|
|
case 1:
|
|
return errs.Errors[0].Error()
|
|
default:
|
|
msg := "errors:\n"
|
|
for _, err := range errs.Errors {
|
|
msg += err.Error() + "\n"
|
|
}
|
|
return msg
|
|
}
|
|
}
|
|
|
|
// Clear clears the errors.
|
|
func (errs *Errors) Clear() {
|
|
errs.Errors = errs.Errors[:0]
|
|
}
|
|
|
|
// Len returns the current number of errors.
|
|
func (errs *Errors) Len() int {
|
|
return len(errs.Errors)
|
|
}
|
|
|
|
// DetailUnknownLayer provides detail for unknown layer errors, returned by
|
|
// image manifest push for layers that are not yet transferred. This intended
|
|
// to only be used on the backend to return detail for this specific error.
|
|
type DetailUnknownLayer struct {
|
|
|
|
// Unknown should contain the contents of a layer descriptor, which is a
|
|
// single FSLayer currently.
|
|
Unknown storage.FSLayer `json:"unknown"`
|
|
}
|
|
|
|
// RepositoryNotFoundError is returned when making an operation against a
|
|
// repository that does not exist in the registry.
|
|
type RepositoryNotFoundError struct {
|
|
Name string
|
|
}
|
|
|
|
func (e *RepositoryNotFoundError) Error() string {
|
|
return fmt.Sprintf("No repository found with Name: %s", e.Name)
|
|
}
|
|
|
|
// ImageManifestNotFoundError is returned when making an operation against a
|
|
// given image manifest that does not exist in the registry.
|
|
type ImageManifestNotFoundError struct {
|
|
Name string
|
|
Tag string
|
|
}
|
|
|
|
func (e *ImageManifestNotFoundError) Error() string {
|
|
return fmt.Sprintf("No manifest found with Name: %s, Tag: %s",
|
|
e.Name, e.Tag)
|
|
}
|
|
|
|
// BlobNotFoundError is returned when making an operation against a given image
|
|
// layer that does not exist in the registry.
|
|
type BlobNotFoundError struct {
|
|
Name string
|
|
Digest digest.Digest
|
|
}
|
|
|
|
func (e *BlobNotFoundError) Error() string {
|
|
return fmt.Sprintf("No blob found with Name: %s, Digest: %s",
|
|
e.Name, e.Digest)
|
|
}
|
|
|
|
// BlobUploadNotFoundError is returned when making a blob upload operation against an
|
|
// invalid blob upload location url.
|
|
// This may be the result of using a cancelled, completed, or stale upload
|
|
// location.
|
|
type BlobUploadNotFoundError struct {
|
|
Location string
|
|
}
|
|
|
|
func (e *BlobUploadNotFoundError) Error() string {
|
|
return fmt.Sprintf("No blob upload found at Location: %s", e.Location)
|
|
}
|
|
|
|
// BlobUploadInvalidRangeError is returned when attempting to upload an image
|
|
// blob chunk that is out of order.
|
|
// This provides the known BlobSize and LastValidRange which can be used to
|
|
// resume the upload.
|
|
type BlobUploadInvalidRangeError struct {
|
|
Location string
|
|
LastValidRange int
|
|
BlobSize int
|
|
}
|
|
|
|
func (e *BlobUploadInvalidRangeError) Error() string {
|
|
return fmt.Sprintf(
|
|
"Invalid range provided for upload at Location: %s. Last Valid Range: %d, Blob Size: %d",
|
|
e.Location, e.LastValidRange, e.BlobSize)
|
|
}
|
|
|
|
// 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)
|
|
}
|