Merge pull request #174 from stevvooe/registry-interface-improvements
Add error return to Repository method on Registry
This commit is contained in:
commit
61fb9ae16a
14 changed files with 236 additions and 174 deletions
109
errors.go
Normal file
109
errors.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package distribution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/distribution/manifest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrLayerExists returned when layer already exists
|
||||||
|
ErrLayerExists = fmt.Errorf("layer exists")
|
||||||
|
|
||||||
|
// ErrLayerTarSumVersionUnsupported when tarsum is unsupported version.
|
||||||
|
ErrLayerTarSumVersionUnsupported = fmt.Errorf("unsupported tarsum version")
|
||||||
|
|
||||||
|
// ErrLayerUploadUnknown returned when upload is not found.
|
||||||
|
ErrLayerUploadUnknown = fmt.Errorf("layer upload unknown")
|
||||||
|
|
||||||
|
// ErrLayerClosed returned when an operation is attempted on a closed
|
||||||
|
// Layer or LayerUpload.
|
||||||
|
ErrLayerClosed = fmt.Errorf("layer closed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrRepositoryUnknown is returned if the named repository is not known by
|
||||||
|
// the registry.
|
||||||
|
type ErrRepositoryUnknown struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrRepositoryUnknown) Error() string {
|
||||||
|
return fmt.Sprintf("unknown respository name=%s", err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRepositoryNameInvalid should be used to denote an invalid repository
|
||||||
|
// name. Reason may set, indicating the cause of invalidity.
|
||||||
|
type ErrRepositoryNameInvalid struct {
|
||||||
|
Name string
|
||||||
|
Reason error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrRepositoryNameInvalid) Error() string {
|
||||||
|
return fmt.Sprintf("repository name %q invalid: %v", err.Name, err.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrManifestUnknown is returned if the manifest is not known by the
|
||||||
|
// registry.
|
||||||
|
type ErrManifestUnknown struct {
|
||||||
|
Name string
|
||||||
|
Tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrManifestUnknown) Error() string {
|
||||||
|
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnknownManifestRevision is returned when a manifest cannot be found by
|
||||||
|
// revision within a repository.
|
||||||
|
type ErrUnknownManifestRevision struct {
|
||||||
|
Name string
|
||||||
|
Revision digest.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrUnknownManifestRevision) Error() string {
|
||||||
|
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrManifestUnverified is returned when the registry is unable to verify
|
||||||
|
// the manifest.
|
||||||
|
type ErrManifestUnverified struct{}
|
||||||
|
|
||||||
|
func (ErrManifestUnverified) Error() string {
|
||||||
|
return fmt.Sprintf("unverified manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrManifestVerification provides a type to collect errors encountered
|
||||||
|
// during manifest verification. Currently, it accepts errors of all types,
|
||||||
|
// but it may be narrowed to those involving manifest verification.
|
||||||
|
type ErrManifestVerification []error
|
||||||
|
|
||||||
|
func (errs ErrManifestVerification) Error() string {
|
||||||
|
var parts []string
|
||||||
|
for _, err := range errs {
|
||||||
|
parts = append(parts, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnknownLayer returned when layer cannot be found.
|
||||||
|
type ErrUnknownLayer struct {
|
||||||
|
FSLayer manifest.FSLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrUnknownLayer) Error() string {
|
||||||
|
return fmt.Sprintf("unknown layer %v", err.FSLayer.BlobSum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrLayerInvalidDigest returned when tarsum check fails.
|
||||||
|
type ErrLayerInvalidDigest struct {
|
||||||
|
Digest digest.Digest
|
||||||
|
Reason error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrLayerInvalidDigest) Error() string {
|
||||||
|
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
|
||||||
|
err.Digest, err.Reason)
|
||||||
|
}
|
84
layer.go
84
layer.go
|
@ -1,84 +0,0 @@
|
||||||
package distribution
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/distribution/manifest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Layer provides a readable and seekable layer object. Typically,
|
|
||||||
// implementations are *not* goroutine safe.
|
|
||||||
type Layer interface {
|
|
||||||
// http.ServeContent requires an efficient implementation of
|
|
||||||
// ReadSeeker.Seek(0, os.SEEK_END).
|
|
||||||
io.ReadSeeker
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// Digest returns the unique digest of the blob, which is the tarsum for
|
|
||||||
// layers.
|
|
||||||
Digest() digest.Digest
|
|
||||||
|
|
||||||
// CreatedAt returns the time this layer was created.
|
|
||||||
CreatedAt() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// LayerUpload provides a handle for working with in-progress uploads.
|
|
||||||
// Instances can be obtained from the LayerService.Upload and
|
|
||||||
// LayerService.Resume.
|
|
||||||
type LayerUpload interface {
|
|
||||||
io.WriteSeeker
|
|
||||||
io.ReaderFrom
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// UUID returns the identifier for this upload.
|
|
||||||
UUID() string
|
|
||||||
|
|
||||||
// StartedAt returns the time this layer upload was started.
|
|
||||||
StartedAt() time.Time
|
|
||||||
|
|
||||||
// Finish marks the upload as completed, returning a valid handle to the
|
|
||||||
// uploaded layer. The digest is validated against the contents of the
|
|
||||||
// uploaded layer.
|
|
||||||
Finish(digest digest.Digest) (Layer, error)
|
|
||||||
|
|
||||||
// Cancel the layer upload process.
|
|
||||||
Cancel() error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrLayerExists returned when layer already exists
|
|
||||||
ErrLayerExists = fmt.Errorf("layer exists")
|
|
||||||
|
|
||||||
// ErrLayerTarSumVersionUnsupported when tarsum is unsupported version.
|
|
||||||
ErrLayerTarSumVersionUnsupported = fmt.Errorf("unsupported tarsum version")
|
|
||||||
|
|
||||||
// ErrLayerUploadUnknown returned when upload is not found.
|
|
||||||
ErrLayerUploadUnknown = fmt.Errorf("layer upload unknown")
|
|
||||||
|
|
||||||
// ErrLayerClosed returned when an operation is attempted on a closed
|
|
||||||
// Layer or LayerUpload.
|
|
||||||
ErrLayerClosed = fmt.Errorf("layer closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnknownLayer returned when layer cannot be found.
|
|
||||||
type ErrUnknownLayer struct {
|
|
||||||
FSLayer manifest.FSLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownLayer) Error() string {
|
|
||||||
return fmt.Sprintf("unknown layer %v", err.FSLayer.BlobSum)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrLayerInvalidDigest returned when tarsum check fails.
|
|
||||||
type ErrLayerInvalidDigest struct {
|
|
||||||
Digest digest.Digest
|
|
||||||
Reason error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrLayerInvalidDigest) Error() string {
|
|
||||||
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
|
|
||||||
err.Digest, err.Reason)
|
|
||||||
}
|
|
|
@ -21,7 +21,11 @@ func TestListener(t *testing.T) {
|
||||||
ops: make(map[string]int),
|
ops: make(map[string]int),
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
repository := Listen(registry.Repository(ctx, "foo/bar"), tl)
|
repository, err := registry.Repository(ctx, "foo/bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
repository = Listen(repository, tl)
|
||||||
|
|
||||||
// Now take the registry through a number of operations
|
// Now take the registry through a number of operations
|
||||||
checkExerciseRepository(t, repository)
|
checkExerciseRepository(t, repository)
|
||||||
|
|
46
registry.go
46
registry.go
|
@ -1,19 +1,20 @@
|
||||||
package distribution
|
package distribution
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(stevvooe): These types need to be moved out of the storage package.
|
|
||||||
|
|
||||||
// Registry represents a collection of repositories, addressable by name.
|
// Registry represents a collection of repositories, addressable by name.
|
||||||
type Registry interface {
|
type Registry interface {
|
||||||
// Repository should return a reference to the named repository. The
|
// Repository should return a reference to the named repository. The
|
||||||
// registry may or may not have the repository but should always return a
|
// registry may or may not have the repository but should always return a
|
||||||
// reference.
|
// reference.
|
||||||
Repository(ctx context.Context, name string) Repository
|
Repository(ctx context.Context, name string) (Repository, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repository is a named collection of manifests and layers.
|
// Repository is a named collection of manifests and layers.
|
||||||
|
@ -82,3 +83,42 @@ type LayerService interface {
|
||||||
// before proceeding.
|
// before proceeding.
|
||||||
Resume(uuid string) (LayerUpload, error)
|
Resume(uuid string) (LayerUpload, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Layer provides a readable and seekable layer object. Typically,
|
||||||
|
// implementations are *not* goroutine safe.
|
||||||
|
type Layer interface {
|
||||||
|
// http.ServeContent requires an efficient implementation of
|
||||||
|
// ReadSeeker.Seek(0, os.SEEK_END).
|
||||||
|
io.ReadSeeker
|
||||||
|
io.Closer
|
||||||
|
|
||||||
|
// Digest returns the unique digest of the blob, which is the tarsum for
|
||||||
|
// layers.
|
||||||
|
Digest() digest.Digest
|
||||||
|
|
||||||
|
// CreatedAt returns the time this layer was created.
|
||||||
|
CreatedAt() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// LayerUpload provides a handle for working with in-progress uploads.
|
||||||
|
// Instances can be obtained from the LayerService.Upload and
|
||||||
|
// LayerService.Resume.
|
||||||
|
type LayerUpload interface {
|
||||||
|
io.WriteSeeker
|
||||||
|
io.ReaderFrom
|
||||||
|
io.Closer
|
||||||
|
|
||||||
|
// UUID returns the identifier for this upload.
|
||||||
|
UUID() string
|
||||||
|
|
||||||
|
// StartedAt returns the time this layer upload was started.
|
||||||
|
StartedAt() time.Time
|
||||||
|
|
||||||
|
// Finish marks the upload as completed, returning a valid handle to the
|
||||||
|
// uploaded layer. The digest is validated against the contents of the
|
||||||
|
// uploaded layer.
|
||||||
|
Finish(digest digest.Digest) (Layer, error)
|
||||||
|
|
||||||
|
// Cancel the layer upload process.
|
||||||
|
Cancel() error
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(stevvooe): Move these definitions back to an exported package. While
|
||||||
|
// they are used with v2 definitions, their relevance expands beyond.
|
||||||
|
// "distribution/names" is a candidate package.
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RepositoryNameComponentMinLength is the minimum number of characters in a
|
// RepositoryNameComponentMinLength is the minimum number of characters in a
|
||||||
// single repository name slash-delimited component
|
// single repository name slash-delimited component
|
||||||
|
@ -37,10 +41,6 @@ var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9
|
||||||
// RepositoryNameComponentRegexp which must completely match the content
|
// RepositoryNameComponentRegexp which must completely match the content
|
||||||
var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`)
|
var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`)
|
||||||
|
|
||||||
// TODO(stevvooe): RepositoryName needs to be limited to some fixed length.
|
|
||||||
// Looking path prefixes and s3 limitation of 1024, this should likely be
|
|
||||||
// around 512 bytes. 256 bytes might be more manageable.
|
|
||||||
|
|
||||||
// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to
|
// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to
|
||||||
// 5 path components, separated by a forward slash.
|
// 5 path components, separated by a forward slash.
|
||||||
var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String())
|
var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String())
|
||||||
|
|
|
@ -227,10 +227,30 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// decorate the authorized repository with an event bridge.
|
if app.nameRequired(r) {
|
||||||
|
repository, err := app.registry.Repository(context, getName(context))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctxu.GetLogger(context).Errorf("error resolving repository: %v", err)
|
||||||
|
|
||||||
|
switch err := err.(type) {
|
||||||
|
case distribution.ErrRepositoryUnknown:
|
||||||
|
context.Errors.Push(v2.ErrorCodeNameUnknown, err)
|
||||||
|
case distribution.ErrRepositoryNameInvalid:
|
||||||
|
context.Errors.Push(v2.ErrorCodeNameInvalid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
serveJSON(w, context.Errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign and decorate the authorized repository with an event bridge.
|
||||||
context.Repository = notifications.Listen(
|
context.Repository = notifications.Listen(
|
||||||
app.registry.Repository(context, getName(context)),
|
repository,
|
||||||
app.eventBridge(context, r))
|
app.eventBridge(context, r))
|
||||||
|
}
|
||||||
|
|
||||||
handler := dispatch(context, r)
|
handler := dispatch(context, r)
|
||||||
|
|
||||||
ssrw := &singleStatusResponseWriter{ResponseWriter: w}
|
ssrw := &singleStatusResponseWriter{ResponseWriter: w}
|
||||||
|
@ -318,9 +338,7 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only allow the name not to be set on the base route.
|
// Only allow the name not to be set on the base route.
|
||||||
route := mux.CurrentRoute(r)
|
if app.nameRequired(r) {
|
||||||
|
|
||||||
if route == nil || route.GetName() != v2.RouteNameBase {
|
|
||||||
// For this to be properly secured, repo must always be set for a
|
// For this to be properly secured, repo must always be set for a
|
||||||
// resource that may make a modification. The only condition under
|
// resource that may make a modification. The only condition under
|
||||||
// which name is not set and we still allow access is when the
|
// which name is not set and we still allow access is when the
|
||||||
|
@ -378,6 +396,12 @@ func (app *App) eventBridge(ctx *Context, r *http.Request) notifications.Listene
|
||||||
return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, request, app.events.sink)
|
return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, request, app.events.sink)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nameRequired returns true if the route requires a name.
|
||||||
|
func (app *App) nameRequired(r *http.Request) bool {
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
return route == nil || route.GetName() != v2.RouteNameBase
|
||||||
|
}
|
||||||
|
|
||||||
// apiBase implements a simple yes-man for doing overall checks against the
|
// apiBase implements a simple yes-man for doing overall checks against the
|
||||||
// api. This can support auth roundtrips to support docker login.
|
// api. This can support auth roundtrips to support docker login.
|
||||||
func apiBase(w http.ResponseWriter, r *http.Request) {
|
func apiBase(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/storage"
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,12 +69,12 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
|
||||||
// TODO(stevvooe): These error handling switches really need to be
|
// TODO(stevvooe): These error handling switches really need to be
|
||||||
// handled by an app global mapper.
|
// handled by an app global mapper.
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrManifestVerification:
|
case distribution.ErrManifestVerification:
|
||||||
for _, verificationError := range err {
|
for _, verificationError := range err {
|
||||||
switch verificationError := verificationError.(type) {
|
switch verificationError := verificationError.(type) {
|
||||||
case distribution.ErrUnknownLayer:
|
case distribution.ErrUnknownLayer:
|
||||||
imh.Errors.Push(v2.ErrorCodeBlobUnknown, verificationError.FSLayer)
|
imh.Errors.Push(v2.ErrorCodeBlobUnknown, verificationError.FSLayer)
|
||||||
case storage.ErrManifestUnverified:
|
case distribution.ErrManifestUnverified:
|
||||||
imh.Errors.Push(v2.ErrorCodeManifestUnverified)
|
imh.Errors.Push(v2.ErrorCodeManifestUnverified)
|
||||||
default:
|
default:
|
||||||
if verificationError == digest.ErrDigestInvalidFormat {
|
if verificationError == digest.ErrDigestInvalidFormat {
|
||||||
|
@ -104,7 +103,7 @@ func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *h
|
||||||
manifests := imh.Repository.Manifests()
|
manifests := imh.Repository.Manifests()
|
||||||
if err := manifests.Delete(imh.Tag); err != nil {
|
if err := manifests.Delete(imh.Tag); err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrUnknownManifest:
|
case distribution.ErrManifestUnknown:
|
||||||
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
|
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/storage"
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
|
||||||
tags, err := manifests.Tags()
|
tags, err := manifests.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrUnknownRepository:
|
case distribution.ErrRepositoryUnknown:
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
th.Errors.Push(v2.ErrorCodeNameUnknown, map[string]string{"name": th.Repository.Name()})
|
th.Errors.Push(v2.ErrorCodeNameUnknown, map[string]string{"name": th.Repository.Name()})
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -36,7 +36,11 @@ func TestSimpleLayerUpload(t *testing.T) {
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
ls := registry.Repository(ctx, imageName).Layers()
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
ls := repository.Layers()
|
||||||
|
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
rd := io.TeeReader(randomDataReader, h)
|
rd := io.TeeReader(randomDataReader, h)
|
||||||
|
@ -140,7 +144,11 @@ func TestSimpleLayerRead(t *testing.T) {
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
ls := registry.Repository(ctx, imageName).Layers()
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
ls := repository.Layers()
|
||||||
|
|
||||||
randomLayerReader, tarSumStr, err := testutil.CreateRandomTarFile()
|
randomLayerReader, tarSumStr, err := testutil.CreateRandomTarFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -245,7 +253,11 @@ func TestLayerUploadZeroLength(t *testing.T) {
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
ls := registry.Repository(ctx, imageName).Layers()
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
ls := repository.Layers()
|
||||||
|
|
||||||
upload, err := ls.Upload()
|
upload, err := ls.Upload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,69 +2,13 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
ctxu "github.com/docker/distribution/context"
|
ctxu "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownRepository is returned if the named repository is not known by
|
|
||||||
// the registry.
|
|
||||||
type ErrUnknownRepository struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownRepository) Error() string {
|
|
||||||
return fmt.Sprintf("unknown respository name=%s", err.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrUnknownManifest is returned if the manifest is not known by the
|
|
||||||
// registry.
|
|
||||||
type ErrUnknownManifest struct {
|
|
||||||
Name string
|
|
||||||
Tag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownManifest) Error() string {
|
|
||||||
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrUnknownManifestRevision is returned when a manifest cannot be found by
|
|
||||||
// revision within a repository.
|
|
||||||
type ErrUnknownManifestRevision struct {
|
|
||||||
Name string
|
|
||||||
Revision digest.Digest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownManifestRevision) Error() string {
|
|
||||||
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrManifestUnverified is returned when the registry is unable to verify
|
|
||||||
// the manifest.
|
|
||||||
type ErrManifestUnverified struct{}
|
|
||||||
|
|
||||||
func (ErrManifestUnverified) Error() string {
|
|
||||||
return fmt.Sprintf("unverified manifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrManifestVerification provides a type to collect errors encountered
|
|
||||||
// during manifest verification. Currently, it accepts errors of all types,
|
|
||||||
// but it may be narrowed to those involving manifest verification.
|
|
||||||
type ErrManifestVerification []error
|
|
||||||
|
|
||||||
func (errs ErrManifestVerification) Error() string {
|
|
||||||
var parts []string
|
|
||||||
for _, err := range errs {
|
|
||||||
parts = append(parts, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
type manifestStore struct {
|
type manifestStore struct {
|
||||||
repository *repository
|
repository *repository
|
||||||
|
|
||||||
|
@ -147,7 +91,7 @@ func (ms *manifestStore) Delete(tag string) error {
|
||||||
// registry only tries to store valid content, leaving trust policies of that
|
// registry only tries to store valid content, leaving trust policies of that
|
||||||
// content up to consumers.
|
// content up to consumers.
|
||||||
func (ms *manifestStore) verifyManifest(tag string, mnfst *manifest.SignedManifest) error {
|
func (ms *manifestStore) verifyManifest(tag string, mnfst *manifest.SignedManifest) error {
|
||||||
var errs ErrManifestVerification
|
var errs distribution.ErrManifestVerification
|
||||||
if mnfst.Name != ms.repository.Name() {
|
if mnfst.Name != ms.repository.Name() {
|
||||||
// TODO(stevvooe): This needs to be an exported error
|
// TODO(stevvooe): This needs to be an exported error
|
||||||
errs = append(errs, fmt.Errorf("repository name does not match manifest name"))
|
errs = append(errs, fmt.Errorf("repository name does not match manifest name"))
|
||||||
|
@ -161,10 +105,10 @@ func (ms *manifestStore) verifyManifest(tag string, mnfst *manifest.SignedManife
|
||||||
if _, err := manifest.Verify(mnfst); err != nil {
|
if _, err := manifest.Verify(mnfst); err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
|
case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
|
||||||
errs = append(errs, ErrManifestUnverified{})
|
errs = append(errs, distribution.ErrManifestUnverified{})
|
||||||
default:
|
default:
|
||||||
if err.Error() == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust
|
if err.Error() == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust
|
||||||
errs = append(errs, ErrManifestUnverified{})
|
errs = append(errs, distribution.ErrManifestUnverified{})
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
@ -20,7 +21,10 @@ func TestManifestStorage(t *testing.T) {
|
||||||
tag := "thetag"
|
tag := "thetag"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
repo := registry.Repository(ctx, name)
|
repo, err := registry.Repository(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
ms := repo.Manifests()
|
ms := repo.Manifests()
|
||||||
|
|
||||||
exists, err := ms.Exists(tag)
|
exists, err := ms.Exists(tag)
|
||||||
|
@ -34,7 +38,7 @@ func TestManifestStorage(t *testing.T) {
|
||||||
|
|
||||||
if _, err := ms.Get(tag); true {
|
if _, err := ms.Get(tag); true {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ErrUnknownManifest:
|
case distribution.ErrManifestUnknown:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
t.Fatalf("expected manifest unknown error: %#v", err)
|
t.Fatalf("expected manifest unknown error: %#v", err)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -36,12 +37,19 @@ func NewRegistryWithDriver(driver storagedriver.StorageDriver) distribution.Regi
|
||||||
// Repository returns an instance of the repository tied to the registry.
|
// Repository returns an instance of the repository tied to the registry.
|
||||||
// Instances should not be shared between goroutines but are cheap to
|
// Instances should not be shared between goroutines but are cheap to
|
||||||
// allocate. In general, they should be request scoped.
|
// allocate. In general, they should be request scoped.
|
||||||
func (reg *registry) Repository(ctx context.Context, name string) distribution.Repository {
|
func (reg *registry) Repository(ctx context.Context, name string) (distribution.Repository, error) {
|
||||||
|
if err := v2.ValidateRespositoryName(name); err != nil {
|
||||||
|
return nil, distribution.ErrRepositoryNameInvalid{
|
||||||
|
Name: name,
|
||||||
|
Reason: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &repository{
|
return &repository{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
registry: reg,
|
registry: reg,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// repository provides name-scoped access to various services.
|
// repository provides name-scoped access to various services.
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
|
@ -40,7 +41,7 @@ func (rs *revisionStore) get(revision digest.Digest) (*manifest.SignedManifest,
|
||||||
if exists, err := rs.exists(revision); err != nil {
|
if exists, err := rs.exists(revision); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !exists {
|
} else if !exists {
|
||||||
return nil, ErrUnknownManifestRevision{
|
return nil, distribution.ErrUnknownManifestRevision{
|
||||||
Name: rs.Name(),
|
Name: rs.Name(),
|
||||||
Revision: revision,
|
Revision: revision,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package storage
|
||||||
import (
|
import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +27,7 @@ func (ts *tagStore) tags() ([]string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storagedriver.PathNotFoundError:
|
case storagedriver.PathNotFoundError:
|
||||||
return nil, ErrUnknownRepository{Name: ts.name}
|
return nil, distribution.ErrRepositoryUnknown{Name: ts.name}
|
||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -104,7 +105,7 @@ func (ts *tagStore) resolve(tag string) (digest.Digest, error) {
|
||||||
if exists, err := exists(ts.driver, currentPath); err != nil {
|
if exists, err := exists(ts.driver, currentPath); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if !exists {
|
} else if !exists {
|
||||||
return "", ErrUnknownManifest{Name: ts.Name(), Tag: tag}
|
return "", distribution.ErrManifestUnknown{Name: ts.Name(), Tag: tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
revision, err := ts.blobStore.readlink(currentPath)
|
revision, err := ts.blobStore.readlink(currentPath)
|
||||||
|
|
Loading…
Reference in a new issue