commit
						12326f7090
					
				
							
								
								
									
										55
									
								
								api_test.go
								
								
								
								
							
							
						
						
									
										55
									
								
								api_test.go
								
								
								
								
							|  | @ -195,6 +195,32 @@ func TestManifestAPI(t *testing.T) { | ||||||
| 		t.Fatalf("expected manifest unknown error: got %v", respErrs) | 		t.Fatalf("expected manifest unknown error: got %v", respErrs) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	tagsURL, err := builder.buildTagsURL(imageName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error building tags url: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err = http.Get(tagsURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error getting unknown tags: %v", err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	// Check that we get an unknown repository error when asking for tags
 | ||||||
|  | 	checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound) | ||||||
|  | 	dec = json.NewDecoder(resp.Body) | ||||||
|  | 	if err := dec.Decode(&respErrs); err != nil { | ||||||
|  | 		t.Fatalf("unexpected error decoding error response: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(respErrs.Errors) == 0 { | ||||||
|  | 		t.Fatalf("expected errors in response") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if respErrs.Errors[0].Code != ErrorCodeUnknownRepository { | ||||||
|  | 		t.Fatalf("expected respository unknown error: got %v", respErrs) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// --------------------------------
 | 	// --------------------------------
 | ||||||
| 	// Attempt to push unsigned manifest with missing layers
 | 	// Attempt to push unsigned manifest with missing layers
 | ||||||
| 	unsignedManifest := &storage.Manifest{ | 	unsignedManifest := &storage.Manifest{ | ||||||
|  | @ -300,6 +326,35 @@ func TestManifestAPI(t *testing.T) { | ||||||
| 	if !bytes.Equal(fetchedManifest.Raw, signedManifest.Raw) { | 	if !bytes.Equal(fetchedManifest.Raw, signedManifest.Raw) { | ||||||
| 		t.Fatalf("manifests do not match") | 		t.Fatalf("manifests do not match") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	// Ensure that the tag is listed.
 | ||||||
|  | 	resp, err = http.Get(tagsURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error getting unknown tags: %v", err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	// Check that we get an unknown repository error when asking for tags
 | ||||||
|  | 	checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK) | ||||||
|  | 	dec = json.NewDecoder(resp.Body) | ||||||
|  | 
 | ||||||
|  | 	var tagsResponse tagsAPIResponse | ||||||
|  | 
 | ||||||
|  | 	if err := dec.Decode(&tagsResponse); err != nil { | ||||||
|  | 		t.Fatalf("unexpected error decoding error response: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if tagsResponse.Name != imageName { | ||||||
|  | 		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(tagsResponse.Tags) != 1 { | ||||||
|  | 		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if tagsResponse.Tags[0] != tag { | ||||||
|  | 		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response { | func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response { | ||||||
|  |  | ||||||
|  | @ -34,6 +34,9 @@ const ( | ||||||
| 	// match the provided tag.
 | 	// match the provided tag.
 | ||||||
| 	ErrorCodeInvalidTag | 	ErrorCodeInvalidTag | ||||||
| 
 | 
 | ||||||
|  | 	// ErrorCodeUnknownRepository when the repository name is not known.
 | ||||||
|  | 	ErrorCodeUnknownRepository | ||||||
|  | 
 | ||||||
| 	// ErrorCodeUnknownManifest returned when image manifest name and tag is
 | 	// ErrorCodeUnknownManifest returned when image manifest name and tag is
 | ||||||
| 	// unknown, accompanied by a 404 status.
 | 	// unknown, accompanied by a 404 status.
 | ||||||
| 	ErrorCodeUnknownManifest | 	ErrorCodeUnknownManifest | ||||||
|  | @ -64,6 +67,7 @@ var errorCodeStrings = map[ErrorCode]string{ | ||||||
| 	ErrorCodeInvalidLength:      "INVALID_LENGTH", | 	ErrorCodeInvalidLength:      "INVALID_LENGTH", | ||||||
| 	ErrorCodeInvalidName:        "INVALID_NAME", | 	ErrorCodeInvalidName:        "INVALID_NAME", | ||||||
| 	ErrorCodeInvalidTag:         "INVALID_TAG", | 	ErrorCodeInvalidTag:         "INVALID_TAG", | ||||||
|  | 	ErrorCodeUnknownRepository:  "UNKNOWN_REPOSITORY", | ||||||
| 	ErrorCodeUnknownManifest:    "UNKNOWN_MANIFEST", | 	ErrorCodeUnknownManifest:    "UNKNOWN_MANIFEST", | ||||||
| 	ErrorCodeInvalidManifest:    "INVALID_MANIFEST", | 	ErrorCodeInvalidManifest:    "INVALID_MANIFEST", | ||||||
| 	ErrorCodeUnverifiedManifest: "UNVERIFIED_MANIFEST", | 	ErrorCodeUnverifiedManifest: "UNVERIFIED_MANIFEST", | ||||||
|  | @ -78,6 +82,7 @@ var errorCodesMessages = map[ErrorCode]string{ | ||||||
| 	ErrorCodeInvalidLength:      "provided length did not match content length", | 	ErrorCodeInvalidLength:      "provided length did not match content length", | ||||||
| 	ErrorCodeInvalidName:        "manifest name did not match URI", | 	ErrorCodeInvalidName:        "manifest name did not match URI", | ||||||
| 	ErrorCodeInvalidTag:         "manifest tag did not match URI", | 	ErrorCodeInvalidTag:         "manifest tag did not match URI", | ||||||
|  | 	ErrorCodeUnknownRepository:  "repository not known to registry", | ||||||
| 	ErrorCodeUnknownManifest:    "manifest not known", | 	ErrorCodeUnknownManifest:    "manifest not known", | ||||||
| 	ErrorCodeInvalidManifest:    "manifest is invalid", | 	ErrorCodeInvalidManifest:    "manifest is invalid", | ||||||
| 	ErrorCodeUnverifiedManifest: "manifest failed signature validation", | 	ErrorCodeUnverifiedManifest: "manifest failed signature validation", | ||||||
|  |  | ||||||
|  | @ -3,49 +3,12 @@ package storage | ||||||
| import ( | import ( | ||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" |  | ||||||
| 	"strings" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/Sirupsen/logrus" | 	"github.com/Sirupsen/logrus" | ||||||
| 
 |  | ||||||
| 	"github.com/docker/libtrust" |  | ||||||
| 
 |  | ||||||
| 	"github.com/docker/docker-registry/digest" | 	"github.com/docker/docker-registry/digest" | ||||||
|  | 	"github.com/docker/libtrust" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // 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) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 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, ",")) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Versioned provides a struct with just the manifest schemaVersion. Incoming
 | // Versioned provides a struct with just the manifest schemaVersion. Incoming
 | ||||||
| // content with unknown schema version can be decoded against this struct to
 | // content with unknown schema version can be decoded against this struct to
 | ||||||
| // check the version.
 | // check the version.
 | ||||||
|  |  | ||||||
|  | @ -99,6 +99,20 @@ func TestManifestStorage(t *testing.T) { | ||||||
| 	if !reflect.DeepEqual(fetchedManifest, sm) { | 	if !reflect.DeepEqual(fetchedManifest, sm) { | ||||||
| 		t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedManifest, sm) | 		t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedManifest, sm) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	// Grabs the tags and check that this tagged manifest is present
 | ||||||
|  | 	tags, err := ms.Tags(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error fetching tags: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(tags) != 1 { | ||||||
|  | 		t.Fatalf("unexpected tags returned: %v", tags) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if tags[0] != tag { | ||||||
|  | 		t.Fatalf("unexpected tag found in tags: %v != %v", tags, []string{tag}) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type layerKey struct { | type layerKey struct { | ||||||
|  |  | ||||||
|  | @ -3,11 +3,56 @@ package storage | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"path" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/docker-registry/storagedriver" | 	"github.com/docker/docker-registry/storagedriver" | ||||||
| 	"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) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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 { | ||||||
| 	driver       storagedriver.StorageDriver | 	driver       storagedriver.StorageDriver | ||||||
| 	pathMapper   *pathMapper | 	pathMapper   *pathMapper | ||||||
|  | @ -16,6 +61,34 @@ type manifestStore struct { | ||||||
| 
 | 
 | ||||||
| var _ ManifestService = &manifestStore{} | var _ ManifestService = &manifestStore{} | ||||||
| 
 | 
 | ||||||
|  | func (ms *manifestStore) Tags(name string) ([]string, error) { | ||||||
|  | 	p, err := ms.pathMapper.path(manifestTagsPath{ | ||||||
|  | 		name: name, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var tags []string | ||||||
|  | 	entries, err := ms.driver.List(p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		switch err := err.(type) { | ||||||
|  | 		case storagedriver.PathNotFoundError: | ||||||
|  | 			return nil, ErrUnknownRepository{Name: name} | ||||||
|  | 		default: | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, entry := range entries { | ||||||
|  | 		_, filename := path.Split(entry) | ||||||
|  | 
 | ||||||
|  | 		tags = append(tags, filename) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return tags, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (ms *manifestStore) Exists(name, tag string) (bool, error) { | func (ms *manifestStore) Exists(name, tag string) (bool, error) { | ||||||
| 	p, err := ms.path(name, tag) | 	p, err := ms.path(name, tag) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -64,6 +64,8 @@ func (pm *pathMapper) path(spec pathSpec) (string, error) { | ||||||
| 	repoPrefix := append(rootPrefix, "repositories") | 	repoPrefix := append(rootPrefix, "repositories") | ||||||
| 
 | 
 | ||||||
| 	switch v := spec.(type) { | 	switch v := spec.(type) { | ||||||
|  | 	case manifestTagsPath: | ||||||
|  | 		return path.Join(append(repoPrefix, v.name, "manifests")...), nil | ||||||
| 	case manifestPathSpec: | 	case manifestPathSpec: | ||||||
| 		// TODO(sday): May need to store manifest by architecture.
 | 		// TODO(sday): May need to store manifest by architecture.
 | ||||||
| 		return path.Join(append(repoPrefix, v.name, "manifests", v.tag)...), nil | 		return path.Join(append(repoPrefix, v.name, "manifests", v.tag)...), nil | ||||||
|  | @ -109,6 +111,14 @@ type pathSpec interface { | ||||||
| 	pathSpec() | 	pathSpec() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // manifestTagsPath describes the path elements required to point to the
 | ||||||
|  | // directory with all manifest tags under the repository.
 | ||||||
|  | type manifestTagsPath struct { | ||||||
|  | 	name string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (manifestTagsPath) pathSpec() {} | ||||||
|  | 
 | ||||||
| // manifestPathSpec describes the path elements used to build a manifest path.
 | // manifestPathSpec describes the path elements used to build a manifest path.
 | ||||||
| // The contents should be a signed manifest json file.
 | // The contents should be a signed manifest json file.
 | ||||||
| type manifestPathSpec struct { | type manifestPathSpec struct { | ||||||
|  |  | ||||||
|  | @ -52,6 +52,9 @@ func (ss *Services) Manifests() ManifestService { | ||||||
| 
 | 
 | ||||||
| // ManifestService provides operations on image manifests.
 | // ManifestService provides operations on image manifests.
 | ||||||
| type ManifestService interface { | type ManifestService interface { | ||||||
|  | 	// Tags lists the tags under the named repository.
 | ||||||
|  | 	Tags(name string) ([]string, error) | ||||||
|  | 
 | ||||||
| 	// Exists returns true if the layer exists.
 | 	// Exists returns true if the layer exists.
 | ||||||
| 	Exists(name, tag string) (bool, error) | 	Exists(name, tag string) (bool, error) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										31
									
								
								tags.go
								
								
								
								
							
							
						
						
									
										31
									
								
								tags.go
								
								
								
								
							|  | @ -1,8 +1,10 @@ | ||||||
| package registry | package registry | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/docker/docker-registry/storage" | ||||||
| 	"github.com/gorilla/handlers" | 	"github.com/gorilla/handlers" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -22,7 +24,34 @@ type tagsHandler struct { | ||||||
| 	*Context | 	*Context | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type tagsAPIResponse struct { | ||||||
|  | 	Name string   `json:"name"` | ||||||
|  | 	Tags []string `json:"tags"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // GetTags returns a json list of tags for a specific image name.
 | // GetTags returns a json list of tags for a specific image name.
 | ||||||
| func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) { | func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) { | ||||||
| 	// TODO(stevvooe): Implement this method.
 | 	defer r.Body.Close() | ||||||
|  | 	manifests := th.services.Manifests() | ||||||
|  | 
 | ||||||
|  | 	tags, err := manifests.Tags(th.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		switch err := err.(type) { | ||||||
|  | 		case storage.ErrUnknownRepository: | ||||||
|  | 			w.WriteHeader(404) | ||||||
|  | 			th.Errors.Push(ErrorCodeUnknownRepository, map[string]string{"name": th.Name}) | ||||||
|  | 		default: | ||||||
|  | 			th.Errors.PushErr(err) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	enc := json.NewEncoder(w) | ||||||
|  | 	if err := enc.Encode(tagsAPIResponse{ | ||||||
|  | 		Name: th.Name, | ||||||
|  | 		Tags: tags, | ||||||
|  | 	}); err != nil { | ||||||
|  | 		th.Errors.PushErr(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								urls.go
								
								
								
								
							
							
						
						
									
										14
									
								
								urls.go
								
								
								
								
							|  | @ -39,6 +39,20 @@ func newURLBuilderFromString(root string) (*urlBuilder, error) { | ||||||
| 	return newURLBuilder(u), nil | 	return newURLBuilder(u), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (ub *urlBuilder) buildTagsURL(name string) (string, error) { | ||||||
|  | 	route := clonedRoute(ub.router, routeNameTags) | ||||||
|  | 
 | ||||||
|  | 	tagsURL, err := route. | ||||||
|  | 		Schemes(ub.url.Scheme). | ||||||
|  | 		Host(ub.url.Host). | ||||||
|  | 		URL("name", name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return tagsURL.String(), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (ub *urlBuilder) forManifest(m *storage.Manifest) (string, error) { | func (ub *urlBuilder) forManifest(m *storage.Manifest) (string, error) { | ||||||
| 	return ub.buildManifestURL(m.Name, m.Tag) | 	return ub.buildManifestURL(m.Name, m.Tag) | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue