-
Notifications
You must be signed in to change notification settings - Fork 18.8k
Client Support for Docker Registry HTTP API V2 #9784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
41e20ce
ac8d964
188b56c
bcc0a34
3e4fd00
e9b590d
e233625
a0f92a2
dbb4b03
0336b0c
7d61255
d094eb6
1b43144
7eeda3f
213e3d1
25945a4
8ceb9d2
1a9cdb1
9a38aa0
ef96c28
2fc2862
f138f7b
dbec231
92d5eaf
750b41c
9c6f8e1
f11f3f6
dd914f9
f29aacb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -148,6 +148,17 @@ RUN set -x \ | |
&& git clone -b v1.2 https://github.com/russross/blackfriday.git /go/src/github.com/russross/blackfriday \ | ||
&& go install -v github.com/cpuguy83/go-md2man | ||
|
||
# Install registry | ||
COPY pkg/tarsum /go/src/github.com/docker/docker/pkg/tarsum | ||
# REGISTRY_COMMIT gives us the repeatability guarantees we need | ||
# (so that we're all testing the same version of the registry) | ||
ENV REGISTRY_COMMIT 21a69f53b5c7986b831f33849d551cd59ec8cbd1 | ||
RUN set -x \ | ||
&& git clone https://github.com/docker/distribution.git /go/src/github.com/docker/distribution \ | ||
&& (cd /go/src/github.com/docker/distribution && git checkout -q $REGISTRY_COMMIT) \ | ||
&& go get -d github.com/docker/distribution/cmd/registry \ | ||
&& go build -o /go/bin/registry-v2 github.com/docker/distribution/cmd/registry | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is only for tests right or are you using this in the code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could split adding the tests into a different PR from this which might be cool, only if they work on the current registry of course ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is only for tests (we exec the TBH before this PR there was basically no tests (only 4 written, 3 of them being There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok no bigs just wondering |
||
|
||
# Wrap all commands in the "docker-in-docker" script to allow nested containers | ||
ENTRYPOINT ["hack/dind"] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package graph | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"path" | ||
|
||
log "github.com/Sirupsen/logrus" | ||
"github.com/docker/docker/engine" | ||
"github.com/docker/docker/pkg/tarsum" | ||
"github.com/docker/docker/registry" | ||
"github.com/docker/docker/runconfig" | ||
"github.com/docker/libtrust" | ||
) | ||
|
||
func (s *TagStore) CmdManifest(job *engine.Job) engine.Status { | ||
if len(job.Args) != 1 { | ||
return job.Errorf("usage: %s NAME", job.Name) | ||
} | ||
name := job.Args[0] | ||
tag := job.Getenv("tag") | ||
if tag == "" { | ||
tag = "latest" | ||
} | ||
|
||
// Resolve the Repository name from fqn to endpoint + name | ||
repoInfo, err := registry.ParseRepositoryInfo(name) | ||
if err != nil { | ||
return job.Error(err) | ||
} | ||
|
||
manifestBytes, err := s.newManifest(name, repoInfo.RemoteName, tag) | ||
if err != nil { | ||
return job.Error(err) | ||
} | ||
|
||
_, err = job.Stdout.Write(manifestBytes) | ||
if err != nil { | ||
return job.Error(err) | ||
} | ||
|
||
return engine.StatusOK | ||
} | ||
|
||
func (s *TagStore) newManifest(localName, remoteName, tag string) ([]byte, error) { | ||
manifest := ®istry.ManifestData{ | ||
Name: remoteName, | ||
Tag: tag, | ||
SchemaVersion: 1, | ||
} | ||
localRepo, err := s.Get(localName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if localRepo == nil { | ||
return nil, fmt.Errorf("Repo does not exist: %s", localName) | ||
} | ||
|
||
// Get the top-most layer id which the tag points to | ||
layerId, exists := localRepo[tag] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explicit what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that would be the top-most layer which the tag is referring directly to. I will add a comment. |
||
if !exists { | ||
return nil, fmt.Errorf("Tag does not exist for %s: %s", localName, tag) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: I just noticed an extra space between name and tag |
||
} | ||
layersSeen := make(map[string]bool) | ||
|
||
layer, err := s.graph.Get(layerId) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it's "history", but I'm really confused by all this :-( We're passing a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is because the "tag" store and "layer" store (called graph) are separate the in the current implementation. These will be unified in a single "image" store which shouldn't allow for confusion and inconsistencies in the interface. Josh has started some work on the "image" store, but it is out of scope for this PR. |
||
if err != nil { | ||
return nil, err | ||
} | ||
if layer.Config == nil { | ||
return nil, errors.New("Missing layer configuration") | ||
} | ||
manifest.Architecture = layer.Architecture | ||
manifest.FSLayers = make([]*registry.FSLayer, 0, 4) | ||
manifest.History = make([]*registry.ManifestHistory, 0, 4) | ||
var metadata runconfig.Config | ||
metadata = *layer.Config | ||
|
||
for ; layer != nil; layer, err = layer.GetParent() { | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if layersSeen[layer.ID] { | ||
break | ||
} | ||
if layer.Config != nil && metadata.Image != layer.ID { | ||
err = runconfig.Merge(&metadata, layer.Config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
archive, err := layer.TarLayer() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
tarSum, err := tarsum.NewTarSum(archive, true, tarsum.Version1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, we need to calculate the tarSum for each layer at manifest creation time. What I'm not sure to understand is that a manifest refers to content-addressable layers, so why would we need to compute this for an already known layer? |
||
if err != nil { | ||
return nil, err | ||
} | ||
if _, err := io.Copy(ioutil.Discard, tarSum); err != nil { | ||
return nil, err | ||
} | ||
|
||
tarId := tarSum.Sum(nil) | ||
|
||
manifest.FSLayers = append(manifest.FSLayers, ®istry.FSLayer{BlobSum: tarId}) | ||
|
||
layersSeen[layer.ID] = true | ||
jsonData, err := ioutil.ReadFile(path.Join(s.graph.Root, layer.ID, "json")) | ||
if err != nil { | ||
return nil, fmt.Errorf("Cannot retrieve the path for {%s}: %s", layer.ID, err) | ||
} | ||
manifest.History = append(manifest.History, ®istry.ManifestHistory{V1Compatibility: string(jsonData)}) | ||
} | ||
|
||
manifestBytes, err := json.MarshalIndent(manifest, "", " ") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return manifestBytes, nil | ||
} | ||
|
||
func (s *TagStore) verifyManifest(eng *engine.Engine, manifestBytes []byte) (*registry.ManifestData, bool, error) { | ||
sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures") | ||
if err != nil { | ||
return nil, false, fmt.Errorf("error parsing payload: %s", err) | ||
} | ||
|
||
keys, err := sig.Verify() | ||
if err != nil { | ||
return nil, false, fmt.Errorf("error verifying payload: %s", err) | ||
} | ||
|
||
payload, err := sig.Payload() | ||
if err != nil { | ||
return nil, false, fmt.Errorf("error retrieving payload: %s", err) | ||
} | ||
|
||
var manifest registry.ManifestData | ||
if err := json.Unmarshal(payload, &manifest); err != nil { | ||
return nil, false, fmt.Errorf("error unmarshalling manifest: %s", err) | ||
} | ||
if manifest.SchemaVersion != 1 { | ||
return nil, false, fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion) | ||
} | ||
|
||
var verified bool | ||
for _, key := range keys { | ||
job := eng.Job("trust_key_check") | ||
b, err := key.MarshalJSON() | ||
if err != nil { | ||
return nil, false, fmt.Errorf("error marshalling public key: %s", err) | ||
} | ||
namespace := manifest.Name | ||
if namespace[0] != '/' { | ||
namespace = "/" + namespace | ||
} | ||
stdoutBuffer := bytes.NewBuffer(nil) | ||
|
||
job.Args = append(job.Args, namespace) | ||
job.Setenv("PublicKey", string(b)) | ||
// Check key has read/write permission (0x03) | ||
job.SetenvInt("Permission", 0x03) | ||
job.Stdout.Add(stdoutBuffer) | ||
if err = job.Run(); err != nil { | ||
return nil, false, fmt.Errorf("error running key check: %s", err) | ||
} | ||
result := engine.Tail(stdoutBuffer, 1) | ||
log.Debugf("Key check result: %q", result) | ||
if result == "verified" { | ||
verified = true | ||
} | ||
} | ||
|
||
return &manifest, verified, nil | ||
} | ||
|
||
func checkValidManifest(manifest *registry.ManifestData) error { | ||
if len(manifest.FSLayers) != len(manifest.History) { | ||
return fmt.Errorf("length of history not equal to number of layers") | ||
} | ||
|
||
if len(manifest.FSLayers) == 0 { | ||
return fmt.Errorf("no FSLayers in manifest") | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldnt this just work? without this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try it, Jess. ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but but but je suis le lazyyyy, jk jk i trust you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then I'm not doing my job right. It's always my fault, so you really
shouldn't trust me. ;)