This repository was archived by the owner on Nov 10, 2021. It is now read-only.
forked from nokia/docker-registry-client
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathblob.go
More file actions
176 lines (151 loc) · 5.52 KB
/
Copy pathblob.go
File metadata and controls
176 lines (151 loc) · 5.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package registry
import (
"fmt"
"io"
"net/http"
"net/url"
"github.com/docker/distribution"
digest "github.com/opencontainers/go-digest"
)
func (registry *Registry) DownloadBlob(repository string, digest digest.Digest) (io.ReadCloser, error) {
url := registry.url("/v2/%s/blobs/%s", repository, digest)
registry.Logf("registry.blob.download url=%s repository=%s digest=%s", url, repository, digest)
resp, err := registry.Client.Get(url)
if err != nil {
return nil, err
}
return resp.Body, nil
}
// Sending Monolithic chunked upload - following docker API specification for Chunked uploads : https://docs.docker.com/registry/spec/api/#listing-repositories
// See UploadBlob for more info about getBody
func (registry *Registry) UploadBlobToArtifactory(repository string, digest digest.Digest, content io.Reader, getBody func() (io.ReadCloser, error)) error {
uploadUrl, err := registry.initiateUpload(repository)
if err != nil {
return err
}
q := uploadUrl.Query()
q.Set("digest", digest.String())
uploadUrl.RawQuery = q.Encode()
registry.Logf("registry.blob.uploadToArtifactory url=%s repository=%s digest=%s", uploadUrl, repository, digest)
uploadStep1, err := http.NewRequest("PATCH", uploadUrl.String(), content)
if err != nil {
return err
}
uploadStep1.Header.Set("Content-Type", "application/octet-stream")
if getBody != nil {
uploadStep1.GetBody = getBody
}
resp1, err := registry.Client.Do(uploadStep1)
if resp1 != nil {
defer resp1.Body.Close()
}
// TODO: retry upload more than 0 bytes were successfully transferred
// (HEAD upload UUID, adn check the Range header)
if err != nil {
if resp1 == nil {
return fmt.Errorf("error while uploading blob to %s, digest: %s: %s", repository, digest, err)
} else {
return fmt.Errorf("error while uploading blob to %s: %v %v: digest: %s: %s", repository, resp1.StatusCode, resp1.Status, digest, err)
}
}
if resp1.StatusCode != 202 {
return fmt.Errorf("unexpected PATCH response while uploading blob to %s: %v %v: digest: %s", repository, resp1.StatusCode, resp1.Status, digest)
}
uploadStep2, err := http.NewRequest("PUT", uploadUrl.String(), nil)
if err != nil {
return err
}
uploadStep2.Header.Set("Content-Type", "application/octet-stream")
if getBody != nil {
uploadStep2.GetBody = getBody
}
_, err = registry.Client.Do(uploadStep2)
return err
}
// UploadBlob can be used to upload an FS layer or an image config file into the given repository.
// It uploads the bytes read from content. Digest must match with the hash of those bytes.
// In case of token authentication the HTTP request must be retried after a 401 Unauthorized response
// (see https://docs.docker.com/registry/spec/auth/token/). In this case the getBody function is called
// in order to retrieve a fresh instance of the content reader. This behaviour matches exactly of the
// GetBody parameter of http.Client. This also means that if content is of type *bytes.Buffer,
// *bytes.Reader or *strings.Reader, then GetBody is populated automatically (as explained in the
// documentation of http.NewRequest()), so nil can be passed as the getBody parameter.
func (registry *Registry) UploadBlob(repository string, digest digest.Digest, content io.Reader, getBody func() (io.ReadCloser, error)) error {
uploadUrl, err := registry.initiateUpload(repository)
if err != nil {
return err
}
q := uploadUrl.Query()
q.Set("digest", digest.String())
uploadUrl.RawQuery = q.Encode()
registry.Logf("registry.blob.upload url=%s repository=%s digest=%s", uploadUrl, repository, digest)
upload, err := http.NewRequest("PUT", uploadUrl.String(), content)
if err != nil {
return err
}
upload.Header.Set("Content-Type", "application/octet-stream")
if getBody != nil {
upload.GetBody = getBody
}
resp, err := registry.Client.Do(upload)
if err != nil {
return err
}
_ = resp.Body.Close()
return nil
}
func (registry *Registry) HasBlob(repository string, digest digest.Digest) (bool, error) {
checkUrl := registry.url("/v2/%s/blobs/%s", repository, digest)
registry.Logf("registry.blob.check url=%s repository=%s digest=%s", checkUrl, repository, digest)
resp, err := registry.Client.Head(checkUrl)
if resp != nil {
defer resp.Body.Close()
}
if err == nil {
return resp.StatusCode == http.StatusOK, nil
}
urlErr, ok := err.(*url.Error)
if !ok {
return false, err
}
httpErr, ok := urlErr.Err.(*HttpStatusError)
if !ok {
return false, err
}
if httpErr.Response.StatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
func (registry *Registry) BlobMetadata(repository string, digest digest.Digest) (distribution.Descriptor, error) {
checkUrl := registry.url("/v2/%s/blobs/%s", repository, digest)
registry.Logf("registry.blob.check url=%s repository=%s digest=%s", checkUrl, repository, digest)
resp, err := registry.Client.Head(checkUrl)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return distribution.Descriptor{}, err
}
return distribution.Descriptor{
Digest: digest,
Size: resp.ContentLength,
}, nil
}
func (registry *Registry) initiateUpload(repository string) (*url.URL, error) {
initiateUrl := registry.url("/v2/%s/blobs/uploads/", repository)
registry.Logf("registry.blob.initiate-upload url=%s repository=%s", initiateUrl, repository)
resp, err := registry.Client.Post(initiateUrl, "application/octet-stream", nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, err
}
location := resp.Header.Get("Location")
locationUrl, err := url.Parse(location)
if err != nil {
return nil, err
}
return locationUrl, nil
}