/
dockerv1.go
407 lines (316 loc) · 15.3 KB
/
dockerv1.go
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/*
Copyright 2014 Huawei Technologies Co., Ltd. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package handler
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strconv"
log "github.com/Sirupsen/logrus"
"github.com/containerops/configure"
"github.com/jinzhu/gorm"
"gopkg.in/macaron.v1"
"github.com/Huawei/dockyard/models"
"github.com/Huawei/dockyard/utils"
)
//GetPingV1Handler returns http.StatusOK(200) when Dockyard provide the Docker Registry V1 support.
//TODO: Add a config option for provide Docker Registry V1.
func GetPingV1Handler(ctx *macaron.Context) (int, []byte) {
result, _ := json.Marshal(map[string]string{})
return http.StatusOK, result
}
//GetUsersV1Handler is Docker client login handler functoin, should be integration with [Crew](https://gitub.com/containerops/crew) project.
//TODO: Integration with Crew project.
func GetUsersV1Handler(ctx *macaron.Context) (int, []byte) {
result, _ := json.Marshal(map[string]string{})
return http.StatusOK, result
}
//PostUsersV1Handler In Docker Registry V1, the Docker client will POST /v1/users to create an user.
//If the Dockyard allow create user in the CLI, should be integration with [Crew](https://github.com/containerops/crew).
//If don't, Dockyard returns http.StatusUnauthorized(401) for forbidden.
//TODO: Add a config option for allow/forbidden create user in the CLI, and integrated with [Crew](https://github.com/containerops/crew).
func PostUsersV1Handler(ctx *macaron.Context) (int, []byte) {
result, _ := json.Marshal(map[string]string{})
return http.StatusUnauthorized, result
}
//PutTagV1Handler
func PutTagV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
//In Docker Registry V1, the repository json data in the body of `PUT /v1/:namespace/:repository`
if body, err := ctx.Req.Body().String(); err != nil {
log.Errorf("[%s] get tag from http body error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Tag JSON Error"})
return http.StatusBadRequest, result
} else {
rege, _ := regexp.Compile(`"([[:alnum:]]+)"`)
imageID := rege.FindStringSubmatch(body)
tag := ctx.Params(":tag")
namespace := ctx.Params(":namespace")
repository := ctx.Params(":repository")
t := new(models.DockerTagV1)
if err := t.Put(imageID[1], tag, namespace, repository); err != nil {
log.Errorf("[%s] put repository tag error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Put Repository Tag Error"})
return http.StatusBadRequest, result
}
}
result, _ := json.Marshal(map[string]string{})
return http.StatusOK, result
}
//PutRepositoryImagesV1Handler
func PutRepositoryImagesV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
namespace := ctx.Params(":namespace")
repository := ctx.Params(":repository")
r := new(models.DockerV1)
if err := r.Unlocked(namespace, repository); err != nil {
log.Errorf("[%s] unlock repository error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Unlock Repository Error"})
return http.StatusBadRequest, result
}
result, _ := json.Marshal(map[string]string{})
return http.StatusNoContent, result
}
//GetRepositoryImagesV1Handler will return images json data.
func GetRepositoryImagesV1Handler(ctx *macaron.Context) (int, []byte) {
var username string
var err error
if username, _, err = utils.DecodeBasicAuth(ctx.Req.Header.Get("Authorization")); err != nil {
log.Errorf("[%s] decode Authorization error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Decode Authorization Error"})
return http.StatusUnauthorized, result
}
namespace := ctx.Params(":namespace")
repository := ctx.Params(":repository")
r := new(models.DockerV1)
if v1, err := r.Get(namespace, repository); err != nil {
log.Errorf("[%s] get repository images data error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Repository Images Error"})
return http.StatusBadRequest, result
} else {
//If the Docker client use "X-Docker-Token", will return a randon token value.
if ctx.Req.Header.Get("X-Docker-Token") == "true" {
token := fmt.Sprintf("Token signature=%v,repository=\"%v/%v\",access=%v",
utils.MD5(username), namespace, repository, "read")
ctx.Resp.Header().Set("X-Docker-Token", token)
ctx.Resp.Header().Set("WWW-Authenticate", token)
}
ctx.Resp.Header().Set("Content-Length", fmt.Sprint(len(v1.JSON)))
return http.StatusOK, []byte(v1.JSON)
}
}
//GetTagV1Handler is
func GetTagV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
namespace := ctx.Params(":namespace")
repository := ctx.Params(":repository")
r := new(models.DockerV1)
if tags, err := r.GetTags(namespace, repository); err != nil {
log.Errorf("[%s] get repository tags data error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Repository Tags Error"})
return http.StatusBadRequest, result
} else {
result, _ := json.Marshal(tags)
ctx.Resp.Header().Set("Content-Length", fmt.Sprint(len(result)))
return http.StatusOK, result
}
}
//PutRepositoryV1Handler will create or update the repository, it's first step of Docker push.
//TODO: @1 When someone create or update the repository, it will be locked to forbidden others action include pull action.
//TODO: @2 Add a config option for allow/forbidden Docker client pull action when a repository is locked.
//TODO: @3 Intergated with [Crew](https://github.com/containerops/crew).
//TODO: @4 Token will be store in Redis, and link the push action with username@repository.
func PutRepositoryV1Handler(ctx *macaron.Context) (int, []byte) {
var username, body string
//var passwd string
var err error
if username, _, err = utils.DecodeBasicAuth(ctx.Req.Header.Get("Authorization")); err != nil {
log.Errorf("[%s] decode Authorization error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Decode Authorization Error"})
return http.StatusUnauthorized, result
}
//When integrated with crew, like this:
//@1: username, passwd, _ := utils.DecodeBasicAuth(ctx.Req.Header.Get("Authorization"))
//@2: username, passwd authorizated in Crew.
namespace := ctx.Params(":namespace")
repository := ctx.Params(":repository")
//When integrated the Crew, should be check the privilage.
if username != namespace {
}
//In Docker Registry V1, the repository json data in the body of `PUT /v1/:namespace/:repository`
if body, err = ctx.Req.Body().String(); err != nil {
log.Errorf("[%s] get repository json from http body error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Repository JSON Error"})
return http.StatusBadRequest, result
}
//Create or update the repository.
r := new(models.DockerV1)
if e := r.Put(namespace, repository, body, ctx.Req.Header.Get("User-Agent")); e != nil {
log.Errorf("[%s] put repository error: %s", ctx.Req.RequestURI, e.Error())
result, _ := json.Marshal(map[string]string{"Error": "PUT Repository Error"})
return http.StatusBadRequest, result
}
//If the Docker client use "X-Docker-Token", will return a randon token value.
if ctx.Req.Header.Get("X-Docker-Token") == "true" {
token := fmt.Sprintf("Token signature=%v,repository=\"%v/%v\",access=%v",
utils.MD5(username), namespace, repository, "write")
ctx.Resp.Header().Set("X-Docker-Token", token)
ctx.Resp.Header().Set("WWW-Authenticate", token)
}
//TODO: When deploy multi instances of dockyard, the endpoints will schedule comply all instances stauts and arithmetic.
ctx.Resp.Header().Set("X-Docker-Endpoints", configure.GetString("deployment.domains"))
result, _ := json.Marshal(map[string]string{})
return http.StatusOK, result
}
//GetImageAncestryV1Handler
func GetImageAncestryV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
imageID := ctx.Params(":image")
image := new(models.DockerImageV1)
if i, err := image.Get(imageID); err != nil {
log.Errorf("[%s] get image ancestry error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Image Ancestry Error"})
return http.StatusBadRequest, result
} else {
ctx.Resp.Header().Set("Content-Length", fmt.Sprint(len(i.Ancestry)))
return http.StatusOK, []byte(i.Ancestry)
}
}
//GetImageJSONV1Handler is getting image json data function.
//When docker client push an image, dockyard return http status code '400' or '404' if haven't it. Then the docker client will push the json data and layer file.
//If dockyard has the image and return 200, the docker client will ignore it and push another iamge.
func GetImageJSONV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
imageID := ctx.Params(":image")
image := new(models.DockerImageV1)
if i, err := image.Get(imageID); err != nil && err == gorm.ErrRecordNotFound {
log.WithFields(log.Fields{
"image": i.ImageID,
}).Info("Image Not Found.")
result, _ := json.Marshal(map[string]string{})
return http.StatusNotFound, result
} else if err != nil && err != gorm.ErrRecordNotFound {
log.Errorf("[%s] get image error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Image Error"})
return http.StatusBadRequest, result
} else {
ctx.Resp.Header().Set("X-Docker-Checksum-Payload", i.Checksum)
ctx.Resp.Header().Set("X-Docker-Size", fmt.Sprint(i.Size))
ctx.Resp.Header().Set("Content-Length", fmt.Sprint(len(i.JSON)))
return http.StatusOK, []byte(i.JSON)
}
}
//GetImageLayerV1Handler
func GetImageLayerV1Handler(ctx *macaron.Context) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
imageID := ctx.Params(":image")
image := new(models.DockerImageV1)
if i, err := image.Get(imageID); err != nil {
log.Errorf("[%s] get image ancestry error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Image Layer Error"})
ctx.Resp.WriteHeader(http.StatusBadRequest)
ctx.Resp.Write(result)
return
} else {
if file, err := os.Open(i.Path); err != nil {
log.Errorf("[%s] get image layer file status: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Image Layer File Status Error"})
ctx.Resp.WriteHeader(http.StatusBadRequest)
ctx.Resp.Write(result)
return
} else {
size := strconv.FormatInt(i.Size, 10)
ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", i.ImageID))
ctx.Resp.Header().Set("Content-Length", size)
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Resp.Header().Set("Pragma", "public")
file.Seek(0, 0)
defer file.Close()
io.Copy(ctx.Resp, file)
ctx.Resp.WriteHeader(http.StatusOK)
return
}
}
}
//PutImageJSONV1Handler is
func PutImageJSONV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
if body, err := ctx.Req.Body().String(); err != nil {
log.Errorf("[%s] get image json from http body error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Get Image JSON Error"})
return http.StatusBadRequest, result
} else if err == nil {
imageID := ctx.Params(":image")
image := new(models.DockerImageV1)
if err := image.PutJSON(imageID, body); err != nil {
log.Errorf("[%s] put image json error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"Error": "Put Image JSON Error"})
return http.StatusBadRequest, result
}
}
result, _ := json.Marshal(map[string]string{})
return http.StatusOK, result
}
//PutImageLayerV1Handler is save image layer file in the server.
func PutImageLayerV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
imageID := ctx.Params(":image")
basePath := configure.GetString("dockerv1.storage")
imagePath := fmt.Sprintf("%s/images/%s", basePath, imageID)
layerfile := fmt.Sprintf("%s/images/%s/%s", basePath, imageID, imageID)
if !utils.IsDirExist(imagePath) {
os.MkdirAll(imagePath, os.ModePerm)
}
if _, err := os.Stat(layerfile); err == nil {
os.Remove(layerfile)
}
if file, err := os.Create(layerfile); err != nil {
log.Errorf("[%s] Create image file error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"message": "Write Image File Error."})
return http.StatusBadRequest, result
} else {
io.Copy(file, ctx.Req.Request.Body)
}
size, _ := utils.GetFileSize(layerfile)
image := new(models.DockerImageV1)
if err := image.PutLayer(imageID, layerfile, size); err != nil {
log.Errorf("[%s] Failed to save image layer data error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"message": "Put Image Layer Data Error"})
return http.StatusBadRequest, result
}
result, _ := json.Marshal(map[string]string{})
return http.StatusOK, result
}
//PutImageChecksumV1Handler is put image checksum and payload value in the database.
func PutImageChecksumV1Handler(ctx *macaron.Context) (int, []byte) {
//TODO: If standalone == true, Dockyard will check HEADER Authorization; if standalone == false, Dockyard will check HEADER TOEKN.
imageID := ctx.Params(":image")
checksum := ctx.Req.Header.Get("X-Docker-Checksum")
payload := ctx.Req.Header.Get("X-Docker-Checksum-Payload")
image := new(models.DockerImageV1)
if err := image.PutChecksum(imageID, checksum, payload); err != nil {
log.Errorf("[%s] Failed to set image checksum and payload error: %s", ctx.Req.RequestURI, err.Error())
result, _ := json.Marshal(map[string]string{"message": "Put Image Checksum And Payload Data Error"})
return http.StatusBadRequest, result
}
//TODO: Verify the file's checksum.
result, _ := json.Marshal(map[string]string{})
return http.StatusOK, result
}