//PostBlobsV2Handler is //Initiate a resumable blob upload. If successful, an upload location will be provided to complete the upload. //Optionally, if the digest parameter is present, the request body will be used to complete the upload in a single request. func PostBlobsV2Handler(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.DockerV2) if err := r.Put(namespace, repository); err != nil { log.Errorf("Put or search repository error: %s", err.Error()) result, _ := module.EncodingError(module.UNKNOWN, map[string]string{"namespace": namespace, "repository": repository}) return http.StatusBadRequest, result } uuid := utils.MD5(uuid.NewV4().String()) state := utils.MD5(fmt.Sprintf("%s/%s/%d", namespace, repository, time.Now().UnixNano()/int64(time.Millisecond))) random := fmt.Sprintf("https://%s/v2/%s/%s/blobs/uploads/%s?_state=%s", setting.Domains, namespace, repository, uuid, state) ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") ctx.Resp.Header().Set("Docker-Upload-Uuid", uuid) ctx.Resp.Header().Set("Location", random) ctx.Resp.Header().Set("Range", "0-0") result, _ := json.Marshal(map[string]string{}) return http.StatusAccepted, result }
func PutRepositoryImagesV1Handler(ctx *macaron.Context, log *logs.BeeLogger) (int, []byte) { namespace := ctx.Params(":namespace") repository := ctx.Params(":repository") r := new(models.Repository) if err := r.PutImages(namespace, repository); err != nil { log.Error("[REGISTRY API V1] Failed to save images: %v", err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to save images"}) return http.StatusBadRequest, result } if ctx.Req.Header.Get("X-Docker-Token") == "true" { username, _, _ := utils.DecodeBasicAuth(ctx.Req.Header.Get("Authorization")) 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) } result, _ := json.Marshal(map[string]string{}) return http.StatusNoContent, result }
func PutRepositoryV1Handler(ctx *macaron.Context, log *logs.BeeLogger) (int, []byte) { username, _, _ := utils.DecodeBasicAuth(ctx.Req.Header.Get("Authorization")) namespace := ctx.Params(":namespace") repository := ctx.Params(":repository") body, err := ctx.Req.Body().String() if err != nil { log.Error("[REGISTRY API V1] Failed to get request body: %v", err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to get request body"}) return http.StatusBadRequest, result } r := new(models.Repository) if err := r.Put(namespace, repository, body, ctx.Req.Header.Get("User-Agent"), setting.APIVERSION_V1); err != nil { log.Error("[REGISTRY API V1] Failed to save repository %v/%v context: %v", namespace, repository, err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to save repository context"}) return http.StatusBadRequest, result } 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) } result, _ := json.Marshal(map[string]string{}) 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", setting.Domains) result, _ := json.Marshal(map[string]string{}) return http.StatusOK, result }
func (b *bridge) createEvent(action string) *Event { event := &Event{ ID: utils.MD5(uuid.NewV4().String()), Timestamp: time.Now(), Action: action, } //event.Source = b.source event.Actor = b.Actor event.Req = b.Req return event }
func PostBlobsV2Handler(ctx *macaron.Context, log *logs.BeeLogger) (int, []byte) { namespace := ctx.Params(":namespace") repository := ctx.Params(":repository") uuid := utils.MD5(uuid.NewV4().String()) state := utils.MD5(fmt.Sprintf("%s/%s/%d", namespace, repository, time.Now().UnixNano()/int64(time.Millisecond))) random := fmt.Sprintf("%s://%s/v2/%s/%s/blobs/uploads/%s?_state=%s", setting.ListenMode, setting.Domains, namespace, repository, uuid, state) ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") ctx.Resp.Header().Set("Docker-Upload-Uuid", uuid) ctx.Resp.Header().Set("Location", random) ctx.Resp.Header().Set("Range", "0-0") result, _ := json.Marshal(map[string]string{}) return http.StatusAccepted, result }
//PatchBlobsV2Handler is //Upload a chunk of data for the specified upload. //Docker 1.9.x above version saves layer in PATCH methord //Docker 1.9.x below version saves layer in PUT methord func PatchBlobsV2Handler(ctx *macaron.Context) (int, []byte) { repository := ctx.Params(":repository") namespace := ctx.Params(":namespace") desc := ctx.Params(":uuid") uuid := strings.Split(desc, "?")[0] if upload, err := module.CheckDockerVersion19(ctx.Req.Header.Get("User-Agent")); err != nil { log.Errorf("Decode docker version error: %s", err.Error()) result, _ := module.EncodingError(module.BLOB_UPLOAD_UNKNOWN, map[string]string{"namespace": namespace, "repository": repository}) return http.StatusBadRequest, result } else if upload == true { //It's run above docker 1.9.0 basePath := setting.DockerV2Storage uuidPath := fmt.Sprintf("%s/uuid/%s", basePath, uuid) uuidFile := fmt.Sprintf("%s/uuid/%s/%s", basePath, uuid, uuid) if !utils.IsDirExist(uuidPath) { os.MkdirAll(uuidPath, os.ModePerm) } if _, err := os.Stat(uuidFile); err == nil { os.Remove(uuidFile) } if file, err := os.Create(uuidFile); err != nil { log.Errorf("[%s] Create UUID file error: %s", ctx.Req.RequestURI, err.Error()) result, _ := module.EncodingError(module.BLOB_UPLOAD_UNKNOWN, map[string]string{"namespace": namespace, "repository": repository}) return http.StatusBadRequest, result } else { io.Copy(file, ctx.Req.Request.Body) size, _ := utils.GetFileSize(uuidFile) ctx.Resp.Header().Set("Range", fmt.Sprintf("0-%v", size-1)) } } state := utils.MD5(fmt.Sprintf("%s/%v", fmt.Sprintf("%s/%s", namespace, repository), time.Now().UnixNano()/int64(time.Millisecond))) random := fmt.Sprintf("https://%s/v2/%s/%s/blobs/uploads/%s?_state=%s", setting.Domains, namespace, repository, uuid, state) ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") ctx.Resp.Header().Set("Docker-Upload-Uuid", uuid) ctx.Resp.Header().Set("Location", random) result, _ := json.Marshal(map[string]string{}) return http.StatusOK, result }
func PatchBlobsV2Handler(ctx *macaron.Context, log *logs.BeeLogger) (int, []byte) { namespace := ctx.Params(":namespace") repository := ctx.Params(":repository") desc := ctx.Params(":uuid") uuid := strings.Split(desc, "?")[0] imagePathTmp := module.GetImagePath(uuid, setting.APIVERSION_V2) layerPathTmp := module.GetLayerPath(uuid, "layer", setting.APIVERSION_V2) //saving specific tarsum every times is in order to split the same tarsum in HEAD handler if !utils.IsDirExist(imagePathTmp) { os.MkdirAll(imagePathTmp, os.ModePerm) } if _, err := os.Stat(layerPathTmp); err == nil { os.Remove(layerPathTmp) } data, _ := ctx.Req.Body().Bytes() if err := ioutil.WriteFile(layerPathTmp, data, 0777); err != nil { log.Error("[REGISTRY API V2] Failed to save layer %v: %v", layerPathTmp, err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to save layer file"}) return http.StatusInternalServerError, result } state := utils.MD5(fmt.Sprintf("%s/%s/%d", namespace, repository, time.Now().UnixNano()/int64(time.Millisecond))) random := fmt.Sprintf("%s://%s/v2/%s/%s/blobs/uploads/%s?_state=%s", setting.ListenMode, setting.Domains, namespace, repository, uuid, state) ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") ctx.Resp.Header().Set("Docker-Upload-Uuid", uuid) ctx.Resp.Header().Set("Location", random) ctx.Resp.Header().Set("Range", fmt.Sprintf("0-%v", len(data)-1)) result, _ := json.Marshal(map[string]string{}) return http.StatusAccepted, result }
//Support to push acis func PostUploadHandler(ctx *macaron.Context, log *logs.BeeLogger) (int, []byte) { namespace := ctx.Params(":namespace") repository := ctx.Params(":repository") acifile := ctx.Params(":acifile") signfile := fmt.Sprintf("%v%v", acifile, ".asc") //TODO: only for testing,pubkey will be read and saved via user management module pubkeyspath := module.GetPubkeysPath(namespace, repository, setting.APIVERSION_ACI) if _, err := os.Stat(pubkeyspath); err != nil { if err := os.MkdirAll(pubkeyspath, os.ModePerm); err != nil { log.Error("[ACI API] Failed to create pubkeys path %v: %v", pubkeyspath, err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to create pubkeys path"}) return http.StatusInternalServerError, result } } imageId := utils.MD5(uuid.NewV4().String()) imagepath := module.GetImagePath(imageId, setting.APIVERSION_ACI) if err := os.MkdirAll(imagepath, os.ModePerm); err != nil { log.Error("[ACI API] Failed to create aci path %v: %v", imagepath, err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to create aci path"}) return http.StatusInternalServerError, result } prefix := fmt.Sprintf("%v://%v/ac/push/%v/%v/", setting.ListenMode, setting.Domains, namespace, repository) endpoint := models.UploadDetails{ ACIPushVersion: "0.0.1", //TODO: follow ACI push spec Multipart: false, ManifestURL: prefix + imageId + "/manifest", SignatureURL: prefix + imageId + "/signature/" + signfile, ACIURL: prefix + imageId + "/aci/" + acifile, CompletedURL: prefix + imageId + "/complete/" + acifile + "/" + signfile, } result, _ := json.Marshal(endpoint) return http.StatusOK, result }
func GetRepositoryImagesV1Handler(ctx *macaron.Context, log *logs.BeeLogger) (int, []byte) { namespace := ctx.Params(":namespace") repository := ctx.Params(":repository") r := new(models.Repository) if exists, err := r.Get(namespace, repository); err != nil { log.Error("[REGISTRY API V1] Failed to get repository %v/%v context: %v", namespace, repository, err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to get repository context"}) return http.StatusBadRequest, result } else if exists == false { log.Error("[REGISTRY API V1] Not found repository %v/%v", namespace, repository) result, _ := json.Marshal(map[string]string{"message": "Not found repository"}) return http.StatusNotFound, result } r.Download += 1 if err := r.Save(namespace, repository); err != nil { log.Error("[REGISTRY API V1] Failed to save repository %v/%v context: %v", namespace, repository, err.Error()) result, _ := json.Marshal(map[string]string{"message": "Failed to save repository context"}) return http.StatusBadRequest, result } username, _, _ := utils.DecodeBasicAuth(ctx.Req.Header.Get("Authorization")) 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(r.JSON))) return http.StatusOK, []byte(r.JSON) }
//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) } }
//PutBlobsV2Handler is //Complete the upload specified by uuid, optionally appending the body as the final chunk. func PutBlobsV2Handler(ctx *macaron.Context) (int, []byte) { var size int64 repository := ctx.Params(":repository") namespace := ctx.Params(":namespace") desc := ctx.Params(":uuid") uuid := strings.Split(desc, "?")[0] digest := ctx.Query("digest") tarsum := strings.Split(digest, ":")[1] basePath := setting.DockerV2Storage imagePath := fmt.Sprintf("%s/image/%s", basePath, tarsum) imageFile := fmt.Sprintf("%s/image/%s/%s", basePath, tarsum, tarsum) //Save from uuid path save too image path if !utils.IsDirExist(imagePath) { os.MkdirAll(imagePath, os.ModePerm) } if _, err := os.Stat(imageFile); err == nil { os.Remove(imageFile) } if upload, err := module.CheckDockerVersion19(ctx.Req.Header.Get("User-Agent")); err != nil { log.Errorf("Decode docker version error: %s", err.Error()) result, _ := module.EncodingError(module.BLOB_UPLOAD_INVALID, map[string]string{"namespace": namespace, "repository": repository}) return http.StatusBadRequest, result } else if upload == true { //Docker 1.9.x above version saves layer in PATCH method, in PUT method move from uuid to image:sha256 uuidPath := fmt.Sprintf("%s/uuid/%s", basePath, uuid) uuidFile := fmt.Sprintf("%s/uuid/%s/%s", basePath, uuid, uuid) if _, err := os.Stat(uuidFile); err == nil { if err := os.Rename(uuidFile, imageFile); err != nil { log.Errorf("Move the temp file to image folder %s error: %s", imageFile, err.Error()) result, _ := module.EncodingError(module.BLOB_UPLOAD_INVALID, map[string]string{"namespace": namespace, "repository": repository}) return http.StatusBadRequest, result } size, _ = utils.GetFileSize(imagePath) os.RemoveAll(uuidFile) os.RemoveAll(uuidPath) } } else if upload == false { //Docker 1.9.x below version saves layer in PUT methord, save data to file directly. if file, err := os.Create(imageFile); err != nil { log.Errorf("Save the file %s error: %s", imageFile, err.Error()) result, _ := module.EncodingError(module.BLOB_UPLOAD_INVALID, map[string]string{"namespace": namespace, "repository": repository}) return http.StatusBadRequest, result } else { io.Copy(file, ctx.Req.Request.Body) size, _ = utils.GetFileSize(imagePath) } } i := new(models.DockerImageV2) if err := i.Put(tarsum, imageFile, size); err != nil { log.Errorf("Save the iamge data %s error: %s", tarsum, err.Error()) result, _ := module.EncodingError(module.BLOB_UPLOAD_INVALID, map[string]string{"namespace": namespace, "repository": repository}) return http.StatusBadRequest, result } state := utils.MD5(fmt.Sprintf("%s/%v", fmt.Sprintf("%s/%s", namespace, repository), time.Now().UnixNano()/int64(time.Millisecond))) random := fmt.Sprintf("https://%s/v2/%s/%s/blobs/uploads/%s?_state=%s", setting.Domains, namespace, repository, uuid, state) ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8") ctx.Resp.Header().Set("Docker-Content-Digest", digest) ctx.Resp.Header().Set("Location", random) result, _ := json.Marshal(map[string]string{}) return http.StatusOK, result }
// file : the path of file to save func (d *radosdesc) Save(file string) (string, error) { fp, err := os.Open(file) if err != nil { return "", err } defer fp.Close() info, err := fp.Stat() if err != nil { return "", err } fileSize := uint64(info.Size()) buf := make([]byte, d.Chunksize) writeUp := false totalRead := uint64(0) //get object id from Omap oid, err := d.getOid(file) if err != nil { oid = objectBlobPrefix + utils.MD5(uuid.NewV4().String()) if err = d.setOid(file, oid); err != nil { return "", err } } else { // Check total object size only for existing ones totalSize, err := d.getXattrTotalSize(oid) if err != nil { return "", err } // If new object is smaller, delete old one if totalSize > fileSize { for offset := uint64(0); offset < totalSize; offset += d.Chunksize { chunkName := d.getChunkName(oid, offset) err = d.Ioctx.Delete(chunkName) if err != nil { return "", err } } } } // Write for { sizeRead := uint64(0) // Read from fp for i := 3; sizeRead < d.Chunksize; i-- { n, err := fp.Read(buf[sizeRead:]) sizeRead += uint64(n) if err != nil { if err != io.EOF { return "", err } writeUp = true break } if i == 0 { return "", fmt.Errorf("Not read enough data") } } // End of file and nothing was read if sizeRead == 0 { break } // Write chunk object chunkName := d.getChunkName(oid, totalRead) if err = d.Ioctx.Write(chunkName, buf[:sizeRead], 0); err != nil { return "", err } totalRead += sizeRead // Update total object size as xattr in the first chunk of the object err = d.setXattrTotalSize(oid, uint64(totalRead)) if err != nil { return "", err } // End of file if writeUp { break } } return "", nil }