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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user