forked from TrueCloudLab/distribution
Move Manifest type into storage package
This changeset move the Manifest type into the storage package to make the type accessible to client and registry without import cycles. The structure of the manifest was also changed to accuratle reflect the stages of the signing process. A straw man Manifest.Sign method has been added to start testing this concept out but will probably be accompanied by the more import SignedManifest.Verify method as the security model develops. This is probably the start of a concerted effort to consolidate types across the client and server portions of the code base but we may want to see how such a handy type, like the Manifest and SignedManifest, would work in docker core.
This commit is contained in:
parent
4bbabc6e36
commit
eaadb82e1e
9 changed files with 179 additions and 108 deletions
|
@ -12,17 +12,18 @@ import (
|
|||
|
||||
"github.com/docker/docker-registry"
|
||||
"github.com/docker/docker-registry/digest"
|
||||
"github.com/docker/docker-registry/storage"
|
||||
)
|
||||
|
||||
// Client implements the client interface to the registry http api
|
||||
type Client interface {
|
||||
// GetImageManifest returns an image manifest for the image at the given
|
||||
// name, tag pair.
|
||||
GetImageManifest(name, tag string) (*registry.ImageManifest, error)
|
||||
GetImageManifest(name, tag string) (*storage.SignedManifest, error)
|
||||
|
||||
// PutImageManifest uploads an image manifest for the image at the given
|
||||
// name, tag pair.
|
||||
PutImageManifest(name, tag string, imageManifest *registry.ImageManifest) error
|
||||
PutImageManifest(name, tag string, imageManifest *storage.SignedManifest) error
|
||||
|
||||
// DeleteImage removes the image at the given name, tag pair.
|
||||
DeleteImage(name, tag string) error
|
||||
|
@ -81,7 +82,7 @@ type clientImpl struct {
|
|||
|
||||
// TODO(bbland): use consistent route generation between server and client
|
||||
|
||||
func (r *clientImpl) GetImageManifest(name, tag string) (*registry.ImageManifest, error) {
|
||||
func (r *clientImpl) GetImageManifest(name, tag string) (*storage.SignedManifest, error) {
|
||||
response, err := http.Get(r.imageManifestURL(name, tag))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -108,7 +109,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*registry.ImageManifest
|
|||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
manifest := new(registry.ImageManifest)
|
||||
manifest := new(storage.SignedManifest)
|
||||
err = decoder.Decode(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -116,7 +117,7 @@ func (r *clientImpl) GetImageManifest(name, tag string) (*registry.ImageManifest
|
|||
return manifest, nil
|
||||
}
|
||||
|
||||
func (r *clientImpl) PutImageManifest(name, tag string, manifest *registry.ImageManifest) error {
|
||||
func (r *clientImpl) PutImageManifest(name, tag string, manifest *storage.SignedManifest) error {
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-registry"
|
||||
"github.com/docker/docker-registry/common/testutil"
|
||||
"github.com/docker/docker-registry/digest"
|
||||
"github.com/docker/docker-registry/storage"
|
||||
)
|
||||
|
||||
type testBlob struct {
|
||||
|
@ -33,8 +33,8 @@ func TestPush(t *testing.T) {
|
|||
},
|
||||
}
|
||||
uploadLocations := make([]string, len(testBlobs))
|
||||
blobs := make([]registry.FSLayer, len(testBlobs))
|
||||
history := make([]registry.ManifestHistory, len(testBlobs))
|
||||
blobs := make([]storage.FSLayer, len(testBlobs))
|
||||
history := make([]storage.ManifestHistory, len(testBlobs))
|
||||
|
||||
for i, blob := range testBlobs {
|
||||
// TODO(bbland): this is returning the same location for all uploads,
|
||||
|
@ -42,17 +42,21 @@ func TestPush(t *testing.T) {
|
|||
// It's sort of okay because we're using unique digests, but this needs
|
||||
// to change at some point.
|
||||
uploadLocations[i] = fmt.Sprintf("/v2/%s/blob/test-uuid", name)
|
||||
blobs[i] = registry.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = registry.ManifestHistory{V1Compatibility: blob.digest.String()}
|
||||
blobs[i] = storage.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = storage.ManifestHistory{V1Compatibility: blob.digest.String()}
|
||||
}
|
||||
|
||||
manifest := ®istry.ImageManifest{
|
||||
manifest := &storage.SignedManifest{
|
||||
Manifest: storage.Manifest{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: "x86",
|
||||
FSLayers: blobs,
|
||||
History: history,
|
||||
Versioned: storage.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
|
||||
|
@ -102,7 +106,7 @@ func TestPush(t *testing.T) {
|
|||
client := New(server.URL)
|
||||
objectStore := &memoryObjectStore{
|
||||
mutex: new(sync.Mutex),
|
||||
manifestStorage: make(map[string]*registry.ImageManifest),
|
||||
manifestStorage: make(map[string]*storage.SignedManifest),
|
||||
layerStorage: make(map[digest.Digest]Layer),
|
||||
}
|
||||
|
||||
|
@ -142,21 +146,25 @@ func TestPull(t *testing.T) {
|
|||
contents: []byte("some other contents"),
|
||||
},
|
||||
}
|
||||
blobs := make([]registry.FSLayer, len(testBlobs))
|
||||
history := make([]registry.ManifestHistory, len(testBlobs))
|
||||
blobs := make([]storage.FSLayer, len(testBlobs))
|
||||
history := make([]storage.ManifestHistory, len(testBlobs))
|
||||
|
||||
for i, blob := range testBlobs {
|
||||
blobs[i] = registry.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = registry.ManifestHistory{V1Compatibility: blob.digest.String()}
|
||||
blobs[i] = storage.FSLayer{BlobSum: blob.digest}
|
||||
history[i] = storage.ManifestHistory{V1Compatibility: blob.digest.String()}
|
||||
}
|
||||
|
||||
manifest := ®istry.ImageManifest{
|
||||
manifest := &storage.SignedManifest{
|
||||
Manifest: storage.Manifest{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: "x86",
|
||||
FSLayers: blobs,
|
||||
History: history,
|
||||
Versioned: storage.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
manifestBytes, err := json.Marshal(manifest)
|
||||
|
||||
|
@ -190,7 +198,7 @@ func TestPull(t *testing.T) {
|
|||
client := New(server.URL)
|
||||
objectStore := &memoryObjectStore{
|
||||
mutex: new(sync.Mutex),
|
||||
manifestStorage: make(map[string]*registry.ImageManifest),
|
||||
manifestStorage: make(map[string]*storage.SignedManifest),
|
||||
layerStorage: make(map[digest.Digest]Layer),
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker-registry"
|
||||
"github.com/docker/docker-registry/digest"
|
||||
"github.com/docker/docker-registry/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -28,11 +28,11 @@ var (
|
|||
type ObjectStore interface {
|
||||
// Manifest retrieves the image manifest stored at the given repository name
|
||||
// and tag
|
||||
Manifest(name, tag string) (*registry.ImageManifest, error)
|
||||
Manifest(name, tag string) (*storage.SignedManifest, error)
|
||||
|
||||
// WriteManifest stores an image manifest at the given repository name and
|
||||
// tag
|
||||
WriteManifest(name, tag string, manifest *registry.ImageManifest) error
|
||||
WriteManifest(name, tag string, manifest *storage.SignedManifest) error
|
||||
|
||||
// Layer returns a handle to a layer for reading and writing
|
||||
Layer(dgst digest.Digest) (Layer, error)
|
||||
|
@ -56,11 +56,11 @@ type Layer interface {
|
|||
// memoryObjectStore is an in-memory implementation of the ObjectStore interface
|
||||
type memoryObjectStore struct {
|
||||
mutex *sync.Mutex
|
||||
manifestStorage map[string]*registry.ImageManifest
|
||||
manifestStorage map[string]*storage.SignedManifest
|
||||
layerStorage map[digest.Digest]Layer
|
||||
}
|
||||
|
||||
func (objStore *memoryObjectStore) Manifest(name, tag string) (*registry.ImageManifest, error) {
|
||||
func (objStore *memoryObjectStore) Manifest(name, tag string) (*storage.SignedManifest, error) {
|
||||
objStore.mutex.Lock()
|
||||
defer objStore.mutex.Unlock()
|
||||
|
||||
|
@ -71,7 +71,7 @@ func (objStore *memoryObjectStore) Manifest(name, tag string) (*registry.ImageMa
|
|||
return manifest, nil
|
||||
}
|
||||
|
||||
func (objStore *memoryObjectStore) WriteManifest(name, tag string, manifest *registry.ImageManifest) error {
|
||||
func (objStore *memoryObjectStore) WriteManifest(name, tag string, manifest *storage.SignedManifest) error {
|
||||
objStore.mutex.Lock()
|
||||
defer objStore.mutex.Unlock()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker-registry"
|
||||
"github.com/docker/docker-registry/storage"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
@ -77,7 +77,7 @@ func Pull(c Client, objectStore ObjectStore, name, tag string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func pullLayer(c Client, objectStore ObjectStore, name string, fsLayer registry.FSLayer) error {
|
||||
func pullLayer(c Client, objectStore ObjectStore, name string, fsLayer storage.FSLayer) error {
|
||||
log.WithField("layer", fsLayer).Info("Pulling layer")
|
||||
|
||||
layer, err := objectStore.Layer(fsLayer.BlobSum)
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/docker/docker-registry"
|
||||
"github.com/docker/docker-registry/storage"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ import (
|
|||
// push window has been successfully pushed.
|
||||
const simultaneousLayerPushWindow = 4
|
||||
|
||||
type pushFunction func(fsLayer registry.FSLayer) error
|
||||
type pushFunction func(fsLayer storage.FSLayer) error
|
||||
|
||||
// Push implements a client push workflow for the image defined by the given
|
||||
// name and tag pair, using the given ObjectStore for local manifest and layer
|
||||
|
@ -74,7 +74,7 @@ func Push(c Client, objectStore ObjectStore, name, tag string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func pushLayer(c Client, objectStore ObjectStore, name string, fsLayer registry.FSLayer) error {
|
||||
func pushLayer(c Client, objectStore ObjectStore, name string, fsLayer storage.FSLayer) error {
|
||||
log.WithField("layer", fsLayer).Info("Pushing layer")
|
||||
|
||||
layer, err := objectStore.Layer(fsLayer.BlobSum)
|
||||
|
|
|
@ -212,7 +212,7 @@ type DetailUnknownLayer struct {
|
|||
|
||||
// Unknown should contain the contents of a layer descriptor, which is a
|
||||
// single FSLayer currently.
|
||||
Unknown FSLayer `json:"unknown"`
|
||||
Unknown storage.FSLayer `json:"unknown"`
|
||||
}
|
||||
|
||||
// RepositoryNotFoundError is returned when making an operation against a
|
||||
|
|
67
images.go
67
images.go
|
@ -2,76 +2,13 @@ package registry
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker-registry/digest"
|
||||
"github.com/docker/docker-registry/storage"
|
||||
"github.com/gorilla/handlers"
|
||||
)
|
||||
|
||||
// ImageManifest defines the structure of an image manifest
|
||||
type ImageManifest struct {
|
||||
// Name is the name of the image's repository
|
||||
Name string `json:"name"`
|
||||
|
||||
// Tag is the tag of the image specified by this manifest
|
||||
Tag string `json:"tag"`
|
||||
|
||||
// Architecture is the host architecture on which this image is intended to
|
||||
// run
|
||||
Architecture string `json:"architecture"`
|
||||
|
||||
// FSLayers is a list of filesystem layer blobSums contained in this image
|
||||
FSLayers []FSLayer `json:"fsLayers"`
|
||||
|
||||
// History is a list of unstructured historical data for v1 compatibility
|
||||
History []ManifestHistory `json:"history"`
|
||||
|
||||
// SchemaVersion is the image manifest schema that this image follows
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
|
||||
// Raw is the byte representation of the ImageManifest, used for signature
|
||||
// verification
|
||||
Raw []byte `json:"-"`
|
||||
}
|
||||
|
||||
// imageManifest is used to avoid recursion in unmarshaling
|
||||
type imageManifest ImageManifest
|
||||
|
||||
// UnmarshalJSON populates a new ImageManifest struct from JSON data.
|
||||
func (m *ImageManifest) UnmarshalJSON(b []byte) error {
|
||||
var manifest imageManifest
|
||||
err := json.Unmarshal(b, &manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*m = ImageManifest(manifest)
|
||||
m.Raw = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// FSLayer is a container struct for BlobSums defined in an image manifest
|
||||
type FSLayer struct {
|
||||
// BlobSum is the tarsum of the referenced filesystem image layer
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
// ManifestHistory stores unstructured v1 compatibility information
|
||||
type ManifestHistory struct {
|
||||
// V1Compatibility is the raw v1 compatibility information
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
||||
|
||||
// Checksum is a container struct for an image checksum
|
||||
type Checksum struct {
|
||||
// HashAlgorithm is the algorithm used to compute the checksum
|
||||
// Supported values: md5, sha1, sha256, sha512
|
||||
HashAlgorithm string
|
||||
|
||||
// Sum is the actual checksum value for the given HashAlgorithm
|
||||
Sum string
|
||||
}
|
||||
|
||||
// imageManifestDispatcher takes the request context and builds the
|
||||
// appropriate handler for handling image manifest requests.
|
||||
func imageManifestDispatcher(ctx *Context, r *http.Request) http.Handler {
|
||||
|
|
2
layer.go
2
layer.go
|
@ -52,7 +52,7 @@ func (lh *layerHandler) GetLayer(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusNotFound)
|
||||
lh.Errors.Push(ErrorCodeUnknownLayer,
|
||||
map[string]interface{}{
|
||||
"unknown": FSLayer{BlobSum: lh.Digest},
|
||||
"unknown": storage.FSLayer{BlobSum: lh.Digest},
|
||||
})
|
||||
return
|
||||
default:
|
||||
|
|
125
storage/manifest.go
Normal file
125
storage/manifest.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/libtrust"
|
||||
|
||||
"github.com/docker/docker-registry/digest"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrManifestUnknown is returned if the manifest is not known by the
|
||||
// registry.
|
||||
ErrManifestUnknown = fmt.Errorf("unknown manifest")
|
||||
|
||||
// ErrManifestUnverified is returned when the registry is unable to verify
|
||||
// the manifest.
|
||||
ErrManifestUnverified = fmt.Errorf("unverified manifest")
|
||||
)
|
||||
|
||||
// Versioned provides a struct with just the manifest schemaVersion. Incoming
|
||||
// content with unknown schema version can be decoded against this struct to
|
||||
// check the version.
|
||||
type Versioned struct {
|
||||
// SchemaVersion is the image manifest schema that this image follows
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
}
|
||||
|
||||
// Manifest provides the base accessible fields for working with V2 image
|
||||
// format in the registry.
|
||||
type Manifest struct {
|
||||
Versioned
|
||||
|
||||
// Name is the name of the image's repository
|
||||
Name string `json:"name"`
|
||||
|
||||
// Tag is the tag of the image specified by this manifest
|
||||
Tag string `json:"tag"`
|
||||
|
||||
// Architecture is the host architecture on which this image is intended to
|
||||
// run
|
||||
Architecture string `json:"architecture"`
|
||||
|
||||
// FSLayers is a list of filesystem layer blobSums contained in this image
|
||||
FSLayers []FSLayer `json:"fsLayers"`
|
||||
|
||||
// History is a list of unstructured historical data for v1 compatibility
|
||||
History []ManifestHistory `json:"history"`
|
||||
}
|
||||
|
||||
// Sign signs the manifest with the provided private key, returning a
|
||||
// SignedManifest. This typically won't be used within the registry, except
|
||||
// for testing.
|
||||
func (m *Manifest) Sign(pk libtrust.PrivateKey) (*SignedManifest, error) {
|
||||
p, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
js, err := libtrust.NewJSONSignature(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := js.Sign(pk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pretty, err := js.PrettySignature("signatures")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SignedManifest{
|
||||
Manifest: *m,
|
||||
Raw: pretty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignedManifest provides an envelope for
|
||||
type SignedManifest struct {
|
||||
Manifest
|
||||
|
||||
// Raw is the byte representation of the ImageManifest, used for signature
|
||||
// verification. The manifest byte representation cannot change or it will
|
||||
// have to be re-signed.
|
||||
Raw []byte `json:"-"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates a new ImageManifest struct from JSON data.
|
||||
func (m *SignedManifest) UnmarshalJSON(b []byte) error {
|
||||
var manifest Manifest
|
||||
if err := json.Unmarshal(b, &manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Manifest = manifest
|
||||
m.Raw = b
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner
|
||||
// contents.
|
||||
func (m *SignedManifest) MarshalJSON() ([]byte, error) {
|
||||
if len(m.Raw) > 0 {
|
||||
return m.Raw, nil
|
||||
}
|
||||
|
||||
// If the raw data is not available, just dump the inner content.
|
||||
return json.Marshal(&m.Manifest)
|
||||
}
|
||||
|
||||
// FSLayer is a container struct for BlobSums defined in an image manifest
|
||||
type FSLayer struct {
|
||||
// BlobSum is the tarsum of the referenced filesystem image layer
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
// ManifestHistory stores unstructured v1 compatibility information
|
||||
type ManifestHistory struct {
|
||||
// V1Compatibility is the raw v1 compatibility information
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
Loading…
Reference in a new issue