mirror of https://github.com/octoleo/restic.git
332 lines
12 KiB
Go
332 lines
12 KiB
Go
/*
|
|
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
|
*
|
|
* 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 minio
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
)
|
|
|
|
// listMultipartUploadsRequest wrapper creates a new listMultipartUploads request
|
|
func (a apiCore) listMultipartUploadsRequest(bucket, keymarker, uploadIDMarker, prefix, delimiter string, maxuploads int) (*request, error) {
|
|
// resourceQuery - get resources properly escaped and lined up before using them in http request
|
|
resourceQuery := func() (string, error) {
|
|
switch {
|
|
case keymarker != "":
|
|
keymarker = fmt.Sprintf("&key-marker=%s", getURLEncodedPath(keymarker))
|
|
fallthrough
|
|
case uploadIDMarker != "":
|
|
uploadIDMarker = fmt.Sprintf("&upload-id-marker=%s", uploadIDMarker)
|
|
fallthrough
|
|
case prefix != "":
|
|
prefix = fmt.Sprintf("&prefix=%s", getURLEncodedPath(prefix))
|
|
fallthrough
|
|
case delimiter != "":
|
|
delimiter = fmt.Sprintf("&delimiter=%s", delimiter)
|
|
}
|
|
query := fmt.Sprintf("?uploads&max-uploads=%d", maxuploads) + keymarker + uploadIDMarker + prefix + delimiter
|
|
return query, nil
|
|
}
|
|
query, err := resourceQuery()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
op := &operation{
|
|
HTTPServer: a.config.Endpoint,
|
|
HTTPMethod: "GET",
|
|
HTTPPath: separator + bucket + query,
|
|
}
|
|
r, err := newRequest(op, a.config, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// listMultipartUploads - (List Multipart Uploads) - Lists some or all (up to 1000) in-progress multipart uploads in a bucket.
|
|
//
|
|
// You can use the request parameters as selection criteria to return a subset of the uploads in a bucket.
|
|
// request paramters :-
|
|
// ---------
|
|
// ?key-marker - Specifies the multipart upload after which listing should begin
|
|
// ?upload-id-marker - Together with key-marker specifies the multipart upload after which listing should begin
|
|
// ?delimiter - A delimiter is a character you use to group keys.
|
|
// ?prefix - Limits the response to keys that begin with the specified prefix.
|
|
// ?max-uploads - Sets the maximum number of multipart uploads returned in the response body.
|
|
func (a apiCore) listMultipartUploads(bucket, keymarker, uploadIDMarker, prefix, delimiter string, maxuploads int) (listMultipartUploadsResult, error) {
|
|
req, err := a.listMultipartUploadsRequest(bucket, keymarker, uploadIDMarker, prefix, delimiter, maxuploads)
|
|
if err != nil {
|
|
return listMultipartUploadsResult{}, err
|
|
}
|
|
resp, err := req.Do()
|
|
defer closeResp(resp)
|
|
if err != nil {
|
|
return listMultipartUploadsResult{}, err
|
|
}
|
|
if resp != nil {
|
|
if resp.StatusCode != http.StatusOK {
|
|
return listMultipartUploadsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
|
}
|
|
}
|
|
listMultipartUploadsResult := listMultipartUploadsResult{}
|
|
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listMultipartUploadsResult)
|
|
if err != nil {
|
|
return listMultipartUploadsResult, err
|
|
}
|
|
// close body while returning, along with any error
|
|
return listMultipartUploadsResult, nil
|
|
}
|
|
|
|
// initiateMultipartRequest wrapper creates a new initiateMultiPart request
|
|
func (a apiCore) initiateMultipartRequest(bucket, object string) (*request, error) {
|
|
op := &operation{
|
|
HTTPServer: a.config.Endpoint,
|
|
HTTPMethod: "POST",
|
|
HTTPPath: separator + bucket + separator + object + "?uploads",
|
|
}
|
|
return newRequest(op, a.config, nil)
|
|
}
|
|
|
|
// initiateMultipartUpload initiates a multipart upload and returns an upload ID
|
|
func (a apiCore) initiateMultipartUpload(bucket, object string) (initiateMultipartUploadResult, error) {
|
|
req, err := a.initiateMultipartRequest(bucket, object)
|
|
if err != nil {
|
|
return initiateMultipartUploadResult{}, err
|
|
}
|
|
resp, err := req.Do()
|
|
defer closeResp(resp)
|
|
if err != nil {
|
|
return initiateMultipartUploadResult{}, err
|
|
}
|
|
if resp != nil {
|
|
if resp.StatusCode != http.StatusOK {
|
|
return initiateMultipartUploadResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
|
}
|
|
}
|
|
initiateMultipartUploadResult := initiateMultipartUploadResult{}
|
|
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &initiateMultipartUploadResult)
|
|
if err != nil {
|
|
return initiateMultipartUploadResult, err
|
|
}
|
|
return initiateMultipartUploadResult, nil
|
|
}
|
|
|
|
// completeMultipartUploadRequest wrapper creates a new CompleteMultipartUpload request
|
|
func (a apiCore) completeMultipartUploadRequest(bucket, object, uploadID string, complete completeMultipartUpload) (*request, error) {
|
|
op := &operation{
|
|
HTTPServer: a.config.Endpoint,
|
|
HTTPMethod: "POST",
|
|
HTTPPath: separator + bucket + separator + object + "?uploadId=" + uploadID,
|
|
}
|
|
var completeMultipartUploadBytes []byte
|
|
var err error
|
|
switch {
|
|
case a.config.AcceptType == "application/xml":
|
|
completeMultipartUploadBytes, err = xml.Marshal(complete)
|
|
case a.config.AcceptType == "application/json":
|
|
completeMultipartUploadBytes, err = json.Marshal(complete)
|
|
default:
|
|
completeMultipartUploadBytes, err = xml.Marshal(complete)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
|
|
r, err := newRequest(op, a.config, completeMultipartUploadBuffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.req.ContentLength = int64(completeMultipartUploadBuffer.Len())
|
|
return r, nil
|
|
}
|
|
|
|
// completeMultipartUpload completes a multipart upload by assembling previously uploaded parts.
|
|
func (a apiCore) completeMultipartUpload(bucket, object, uploadID string, c completeMultipartUpload) (completeMultipartUploadResult, error) {
|
|
req, err := a.completeMultipartUploadRequest(bucket, object, uploadID, c)
|
|
if err != nil {
|
|
return completeMultipartUploadResult{}, err
|
|
}
|
|
resp, err := req.Do()
|
|
defer closeResp(resp)
|
|
if err != nil {
|
|
return completeMultipartUploadResult{}, err
|
|
}
|
|
if resp != nil {
|
|
if resp.StatusCode != http.StatusOK {
|
|
return completeMultipartUploadResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
|
}
|
|
}
|
|
completeMultipartUploadResult := completeMultipartUploadResult{}
|
|
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &completeMultipartUploadResult)
|
|
if err != nil {
|
|
return completeMultipartUploadResult, err
|
|
}
|
|
return completeMultipartUploadResult, nil
|
|
}
|
|
|
|
// abortMultipartUploadRequest wrapper creates a new AbortMultipartUpload request
|
|
func (a apiCore) abortMultipartUploadRequest(bucket, object, uploadID string) (*request, error) {
|
|
op := &operation{
|
|
HTTPServer: a.config.Endpoint,
|
|
HTTPMethod: "DELETE",
|
|
HTTPPath: separator + bucket + separator + object + "?uploadId=" + uploadID,
|
|
}
|
|
return newRequest(op, a.config, nil)
|
|
}
|
|
|
|
// abortMultipartUpload aborts a multipart upload for the given uploadID, all parts are deleted
|
|
func (a apiCore) abortMultipartUpload(bucket, object, uploadID string) error {
|
|
req, err := a.abortMultipartUploadRequest(bucket, object, uploadID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := req.Do()
|
|
defer closeResp(resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp != nil {
|
|
if resp.StatusCode != http.StatusNoContent {
|
|
// Abort has no response body, handle it
|
|
var errorResponse ErrorResponse
|
|
switch resp.StatusCode {
|
|
case http.StatusNotFound:
|
|
errorResponse = ErrorResponse{
|
|
Code: "NoSuchUpload",
|
|
Message: "The specified multipart upload does not exist.",
|
|
Resource: separator + bucket + separator + object,
|
|
RequestID: resp.Header.Get("x-amz-request-id"),
|
|
}
|
|
case http.StatusForbidden:
|
|
errorResponse = ErrorResponse{
|
|
Code: "AccessDenied",
|
|
Message: "Access Denied.",
|
|
Resource: separator + bucket + separator + object,
|
|
RequestID: resp.Header.Get("x-amz-request-id"),
|
|
}
|
|
default:
|
|
errorResponse = ErrorResponse{
|
|
Code: resp.Status,
|
|
Message: "Unknown error, please report this at https://github.com/minio/minio-go-legacy/issues.",
|
|
Resource: separator + bucket + separator + object,
|
|
RequestID: resp.Header.Get("x-amz-request-id"),
|
|
}
|
|
}
|
|
return errorResponse
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// listObjectPartsRequest wrapper creates a new ListObjectParts request
|
|
func (a apiCore) listObjectPartsRequest(bucket, object, uploadID string, partNumberMarker, maxParts int) (*request, error) {
|
|
// resourceQuery - get resources properly escaped and lined up before using them in http request
|
|
resourceQuery := func() string {
|
|
var partNumberMarkerStr string
|
|
switch {
|
|
case partNumberMarker != 0:
|
|
partNumberMarkerStr = fmt.Sprintf("&part-number-marker=%d", partNumberMarker)
|
|
}
|
|
return fmt.Sprintf("?uploadId=%s&max-parts=%d", uploadID, maxParts) + partNumberMarkerStr
|
|
}
|
|
op := &operation{
|
|
HTTPServer: a.config.Endpoint,
|
|
HTTPMethod: "GET",
|
|
HTTPPath: separator + bucket + separator + object + resourceQuery(),
|
|
}
|
|
return newRequest(op, a.config, nil)
|
|
}
|
|
|
|
// listObjectParts (List Parts) - lists some or all (up to 1000) parts that have been uploaded for a specific multipart upload
|
|
//
|
|
// You can use the request parameters as selection criteria to return a subset of the uploads in a bucket.
|
|
// request paramters :-
|
|
// ---------
|
|
// ?part-number-marker - Specifies the part after which listing should begin.
|
|
func (a apiCore) listObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (listObjectPartsResult, error) {
|
|
req, err := a.listObjectPartsRequest(bucket, object, uploadID, partNumberMarker, maxParts)
|
|
if err != nil {
|
|
return listObjectPartsResult{}, err
|
|
}
|
|
resp, err := req.Do()
|
|
defer closeResp(resp)
|
|
if err != nil {
|
|
return listObjectPartsResult{}, err
|
|
}
|
|
if resp != nil {
|
|
if resp.StatusCode != http.StatusOK {
|
|
return listObjectPartsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
|
}
|
|
}
|
|
listObjectPartsResult := listObjectPartsResult{}
|
|
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listObjectPartsResult)
|
|
if err != nil {
|
|
return listObjectPartsResult, err
|
|
}
|
|
return listObjectPartsResult, nil
|
|
}
|
|
|
|
// uploadPartRequest wrapper creates a new UploadPart request
|
|
func (a apiCore) uploadPartRequest(bucket, object, uploadID string, md5SumBytes []byte, partNumber int, size int64, body io.ReadSeeker) (*request, error) {
|
|
op := &operation{
|
|
HTTPServer: a.config.Endpoint,
|
|
HTTPMethod: "PUT",
|
|
HTTPPath: separator + bucket + separator + object + "?partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + uploadID,
|
|
}
|
|
r, err := newRequest(op, a.config, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// set Content-MD5 as base64 encoded md5
|
|
if md5SumBytes != nil {
|
|
r.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5SumBytes))
|
|
}
|
|
r.req.ContentLength = size
|
|
return r, nil
|
|
}
|
|
|
|
// uploadPart uploads a part in a multipart upload.
|
|
func (a apiCore) uploadPart(bucket, object, uploadID string, md5SumBytes []byte, partNumber int, size int64, body io.ReadSeeker) (completePart, error) {
|
|
req, err := a.uploadPartRequest(bucket, object, uploadID, md5SumBytes, partNumber, size, body)
|
|
if err != nil {
|
|
return completePart{}, err
|
|
}
|
|
cPart := completePart{}
|
|
cPart.PartNumber = partNumber
|
|
cPart.ETag = "\"" + hex.EncodeToString(md5SumBytes) + "\""
|
|
|
|
// initiate the request
|
|
resp, err := req.Do()
|
|
defer closeResp(resp)
|
|
if err != nil {
|
|
return completePart{}, err
|
|
}
|
|
if resp != nil {
|
|
if resp.StatusCode != http.StatusOK {
|
|
return completePart{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
|
|
}
|
|
}
|
|
return cPart, nil
|
|
}
|