remotes: support cross-repo-push

With distribution source label in content store, select the longest
common prefix components as condidate mount blob source and try to push
with mount blob.

Fix #2964

Signed-off-by: Wei Fu <fuweid89@gmail.com>
This commit is contained in:
Wei Fu
2019-04-17 17:31:03 +08:00
parent 545e79ae11
commit dd7c0aabcc
6 changed files with 398 additions and 88 deletions

View File

@@ -116,8 +116,6 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
}
}
// TODO: Lookup related objects for cross repository push
if isManifest {
var putPath string
if p.tag != "" {
@@ -132,21 +130,57 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
}
req.Header.Add("Content-Type", desc.MediaType)
} else {
// TODO: Do monolithic upload if size is small
// Start upload request
req, err = http.NewRequest(http.MethodPost, p.url("blobs", "uploads")+"/", nil)
if err != nil {
return nil, err
}
resp, err := p.doRequestWithRetries(ctx, req, nil)
if err != nil {
return nil, err
var resp *http.Response
if fromRepo := selectRepositoryMountCandidate(p.refspec, desc.Annotations); fromRepo != "" {
req = requestWithMountFrom(req, desc.Digest.String(), fromRepo)
pctx := contextWithAppendPullRepositoryScope(ctx, fromRepo)
// NOTE: the fromRepo might be private repo and
// auth service still can grant token without error.
// but the post request will fail because of 401.
//
// for the private repo, we should remove mount-from
// query and send the request again.
resp, err = p.doRequest(pctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusUnauthorized {
log.G(ctx).Debugf("failed to mount from repository %s", fromRepo)
resp.Body.Close()
resp = nil
req, err = removeMountFromQuery(req)
if err != nil {
return nil, err
}
}
}
if resp == nil {
resp, err = p.doRequestWithRetries(ctx, req, nil)
if err != nil {
return nil, err
}
}
switch resp.StatusCode {
case http.StatusOK, http.StatusAccepted, http.StatusNoContent:
case http.StatusCreated:
p.tracker.SetStatus(ref, Status{
Status: content.Status{
Ref: ref,
},
})
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v on remote", desc.Digest)
default:
// TODO: log error
return nil, errors.Errorf("unexpected response: %s", resp.Status)
@@ -320,3 +354,25 @@ func (pw *pushWriter) Truncate(size int64) error {
// TODO: always error on manifest
return errors.New("cannot truncate remote upload")
}
func requestWithMountFrom(req *http.Request, mount, from string) *http.Request {
q := req.URL.Query()
q.Set("mount", mount)
q.Set("from", from)
req.URL.RawQuery = q.Encode()
return req
}
func removeMountFromQuery(req *http.Request) (*http.Request, error) {
req, err := copyRequest(req)
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Del("mount")
q.Del("from")
req.URL.RawQuery = q.Encode()
return req, nil
}