├── Open Source Software Notice.md ├── obs ├── model_header.go ├── error.go ├── callback.go ├── client_other.go ├── trait_other.go ├── authV2.go ├── temporary_createSignedUrl.go ├── client_resume.go ├── client_base.go ├── model_other.go ├── model_response.go ├── progress.go ├── extension.go ├── temporary_other.go ├── trait_base.go ├── authV4.go ├── trait_part.go ├── model_part.go ├── provider.go ├── client_part.go ├── log.go ├── auth.go └── type.go ├── examples ├── create_folder_sample.go ├── object_meta_sample.go ├── delete_objects_sample.go ├── simple_multipart_upload_sample.go ├── restore_object_sample.go ├── custom_http_client_sample.go ├── download_sample.go ├── list_objects_sample.go ├── object_operations_sample.go ├── list_objects_in_folder_sample.go ├── concurrent_upload_part_sample.go ├── concurrent_download_object_sample.go ├── concurrent_copy_part_sample.go └── list_versions_sample.go ├── README_CN.MD ├── README.MD └── LICENSE /Open Source Software Notice.md: -------------------------------------------------------------------------------- 1 | ## OPEN SOURCE SOFTWARE NOTICE 2 | This document contains open source software notice for this product. And this document is confidential information of copyright holder. Recipient shall protect it in due care and shall not disseminate it without permission. 3 | 4 | ### Warranty Disclaimer 5 | THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. 6 | 7 | ### Copyright Notice and License Texts -------------------------------------------------------------------------------- /obs/model_header.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | // ISseHeader defines the sse encryption header 16 | type ISseHeader interface { 17 | GetEncryption() string 18 | GetKey() string 19 | } 20 | 21 | // SseKmsHeader defines the SseKms header 22 | type SseKmsHeader struct { 23 | Encryption string 24 | Key string 25 | isObs bool 26 | } 27 | 28 | // SseCHeader defines the SseC header 29 | type SseCHeader struct { 30 | Encryption string 31 | Key string 32 | KeyMD5 string 33 | } 34 | -------------------------------------------------------------------------------- /obs/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "encoding/xml" 17 | "fmt" 18 | ) 19 | 20 | // ObsError defines error response from OBS 21 | type ObsError struct { 22 | BaseModel 23 | Status string 24 | XMLName xml.Name `xml:"Error"` 25 | Code string `xml:"Code" json:"code"` 26 | Message string `xml:"Message" json:"message"` 27 | Resource string `xml:"Resource"` 28 | HostId string `xml:"HostId"` 29 | Indicator string 30 | } 31 | 32 | // Format print obs error's log 33 | func (err ObsError) Error() string { 34 | return fmt.Sprintf("obs: service returned error: Status=%s, Code=%s, Message=%s, RequestId=%s, Indicator=%s.", 35 | err.Status, err.Code, err.Message, err.RequestId, err.Indicator) 36 | } 37 | -------------------------------------------------------------------------------- /obs/callback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "errors" 17 | "io" 18 | ) 19 | 20 | type ICallbackReadCloser interface { 21 | setCallbackReadCloser(body io.ReadCloser) 22 | } 23 | 24 | func (output *PutObjectOutput) setCallbackReadCloser(body io.ReadCloser) { 25 | output.CallbackBody.data = body 26 | } 27 | 28 | func (output *CompleteMultipartUploadOutput) setCallbackReadCloser(body io.ReadCloser) { 29 | output.CallbackBody.data = body 30 | } 31 | 32 | // define CallbackBody 33 | type CallbackBody struct { 34 | data io.ReadCloser 35 | } 36 | 37 | func (output CallbackBody) ReadCallbackBody(p []byte) (int, error) { 38 | if output.data == nil { 39 | return 0, errors.New("have no callback data") 40 | } 41 | return output.data.Read(p) 42 | } 43 | 44 | func (output CallbackBody) CloseCallbackBody() error { 45 | if output.data == nil { 46 | return errors.New("have no callback data") 47 | } 48 | return output.data.Close() 49 | } 50 | -------------------------------------------------------------------------------- /obs/client_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "strings" 17 | ) 18 | 19 | // Refresh refreshes ak, sk and securityToken for obsClient. 20 | func (obsClient ObsClient) Refresh(ak, sk, securityToken string) { 21 | for _, sp := range obsClient.conf.securityProviders { 22 | if bsp, ok := sp.(*BasicSecurityProvider); ok { 23 | bsp.refresh(strings.TrimSpace(ak), strings.TrimSpace(sk), strings.TrimSpace(securityToken)) 24 | break 25 | } 26 | } 27 | } 28 | 29 | func (obsClient ObsClient) getSecurity() securityHolder { 30 | if obsClient.conf.securityProviders != nil { 31 | for _, sp := range obsClient.conf.securityProviders { 32 | if sp == nil { 33 | continue 34 | } 35 | sh := sp.getSecurity() 36 | if sh.ak != "" && sh.sk != "" { 37 | return sh 38 | } 39 | } 40 | } 41 | return emptySecurityHolder 42 | } 43 | 44 | // Close closes ObsClient. 45 | func (obsClient *ObsClient) Close() { 46 | obsClient.httpClient = nil 47 | obsClient.conf.transport.CloseIdleConnections() 48 | obsClient.conf = nil 49 | } 50 | -------------------------------------------------------------------------------- /obs/trait_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "bytes" 17 | "io" 18 | "os" 19 | "strings" 20 | ) 21 | 22 | type partSlice []Part 23 | 24 | func (parts partSlice) Len() int { 25 | return len(parts) 26 | } 27 | 28 | func (parts partSlice) Less(i, j int) bool { 29 | return parts[i].PartNumber < parts[j].PartNumber 30 | } 31 | 32 | func (parts partSlice) Swap(i, j int) { 33 | parts[i], parts[j] = parts[j], parts[i] 34 | } 35 | 36 | type readerWrapper struct { 37 | reader io.Reader 38 | mark int64 39 | totalCount int64 40 | readedCount int64 41 | } 42 | 43 | func (rw *readerWrapper) seek(offset int64, whence int) (int64, error) { 44 | if r, ok := rw.reader.(*strings.Reader); ok { 45 | return r.Seek(offset, whence) 46 | } else if r, ok := rw.reader.(*bytes.Reader); ok { 47 | return r.Seek(offset, whence) 48 | } else if r, ok := rw.reader.(*os.File); ok { 49 | return r.Seek(offset, whence) 50 | } 51 | return offset, nil 52 | } 53 | 54 | func (rw *readerWrapper) Read(p []byte) (n int, err error) { 55 | if rw.totalCount == 0 { 56 | return 0, io.EOF 57 | } 58 | if rw.totalCount > 0 { 59 | n, err = rw.reader.Read(p) 60 | readedOnce := int64(n) 61 | remainCount := rw.totalCount - rw.readedCount 62 | if remainCount > readedOnce { 63 | rw.readedCount += readedOnce 64 | return n, err 65 | } 66 | rw.readedCount += remainCount 67 | return int(remainCount), io.EOF 68 | } 69 | return rw.reader.Read(p) 70 | } 71 | 72 | type fileReaderWrapper struct { 73 | readerWrapper 74 | filePath string 75 | } 76 | -------------------------------------------------------------------------------- /obs/authV2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "strings" 17 | ) 18 | 19 | func getV2StringToSign(method, canonicalizedURL string, headers map[string][]string, isObs bool) string { 20 | stringToSign := strings.Join([]string{method, "\n", attachHeaders(headers, isObs), "\n", canonicalizedURL}, "") 21 | 22 | var isSecurityToken bool 23 | var securityToken []string 24 | if isObs { 25 | securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_OBS] 26 | } else { 27 | securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_AMZ] 28 | } 29 | var query []string 30 | if !isSecurityToken { 31 | parmas := strings.Split(canonicalizedURL, "?") 32 | if len(parmas) > 1 { 33 | query = strings.Split(parmas[1], "&") 34 | for _, value := range query { 35 | if strings.HasPrefix(value, HEADER_STS_TOKEN_AMZ+"=") || strings.HasPrefix(value, HEADER_STS_TOKEN_OBS+"=") { 36 | if value[len(HEADER_STS_TOKEN_AMZ)+1:] != "" { 37 | securityToken = []string{value[len(HEADER_STS_TOKEN_AMZ)+1:]} 38 | isSecurityToken = true 39 | } 40 | } 41 | } 42 | } 43 | } 44 | logStringToSign := stringToSign 45 | if isSecurityToken && len(securityToken) > 0 { 46 | logStringToSign = strings.Replace(logStringToSign, securityToken[0], "******", -1) 47 | } 48 | doLog(LEVEL_DEBUG, "The v2 auth stringToSign:\n%s", logStringToSign) 49 | return stringToSign 50 | } 51 | 52 | func v2Auth(ak, sk, method, canonicalizedURL string, headers map[string][]string, isObs bool) map[string]string { 53 | stringToSign := getV2StringToSign(method, canonicalizedURL, headers, isObs) 54 | return map[string]string{"Signature": Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign)))} 55 | } 56 | -------------------------------------------------------------------------------- /obs/temporary_createSignedUrl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "errors" 17 | "fmt" 18 | ) 19 | 20 | // CreateSignedUrl creates signed url with the specified CreateSignedUrlInput, and returns the CreateSignedUrlOutput and error 21 | func (obsClient ObsClient) CreateSignedUrl(input *CreateSignedUrlInput, extensions ...extensionOptions) (output *CreateSignedUrlOutput, err error) { 22 | if input == nil { 23 | return nil, errors.New("CreateSignedUrlInput is nil") 24 | } 25 | 26 | params := make(map[string]string, len(input.QueryParams)) 27 | for key, value := range input.QueryParams { 28 | params[key] = value 29 | } 30 | 31 | if input.SubResource != "" { 32 | params[string(input.SubResource)] = "" 33 | } 34 | 35 | headers := make(map[string][]string, len(input.Headers)) 36 | for key, value := range input.Headers { 37 | headers[key] = []string{value} 38 | } 39 | 40 | for _, extension := range extensions { 41 | if extensionHeader, ok := extension.(extensionHeaders); ok { 42 | _err := extensionHeader(headers, obsClient.conf.signature == SignatureObs) 43 | if _err != nil { 44 | doLog(LEVEL_INFO, fmt.Sprintf("set header with error: %v", _err)) 45 | } 46 | } else { 47 | doLog(LEVEL_INFO, "Unsupported extensionOptions") 48 | } 49 | } 50 | 51 | if input.Expires <= 0 { 52 | input.Expires = 300 53 | } 54 | 55 | requestURL, err := obsClient.doAuthTemporary(string(input.Method), input.Bucket, input.Key, input.Policy, params, headers, int64(input.Expires)) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | output = &CreateSignedUrlOutput{ 61 | SignedUrl: requestURL, 62 | ActualSignedRequestHeaders: headers, 63 | } 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /obs/client_resume.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | // UploadFile resume uploads. 16 | // 17 | // This API is an encapsulated and enhanced version of multipart upload, and aims to eliminate large file 18 | // upload failures caused by poor network conditions and program breakdowns. 19 | func (obsClient ObsClient) UploadFile(input *UploadFileInput, extensions ...extensionOptions) (output *CompleteMultipartUploadOutput, err error) { 20 | if input.EnableCheckpoint && input.CheckpointFile == "" { 21 | input.CheckpointFile = input.UploadFile + ".uploadfile_record" 22 | } 23 | 24 | if input.TaskNum <= 0 { 25 | input.TaskNum = 1 26 | } 27 | if input.PartSize < MIN_PART_SIZE { 28 | input.PartSize = MIN_PART_SIZE 29 | } else if input.PartSize > MAX_PART_SIZE { 30 | input.PartSize = MAX_PART_SIZE 31 | } 32 | 33 | output, err = obsClient.resumeUpload(input, extensions) 34 | return 35 | } 36 | 37 | // DownloadFile resume downloads. 38 | // 39 | // This API is an encapsulated and enhanced version of partial download, and aims to eliminate large file 40 | // download failures caused by poor network conditions and program breakdowns. 41 | func (obsClient ObsClient) DownloadFile(input *DownloadFileInput, extensions ...extensionOptions) (output *GetObjectMetadataOutput, err error) { 42 | if input.DownloadFile == "" { 43 | input.DownloadFile = input.Key 44 | } 45 | 46 | if input.EnableCheckpoint && input.CheckpointFile == "" { 47 | input.CheckpointFile = input.DownloadFile + ".downloadfile_record" 48 | } 49 | 50 | if input.TaskNum <= 0 { 51 | input.TaskNum = 1 52 | } 53 | if input.PartSize <= 0 { 54 | input.PartSize = DEFAULT_PART_SIZE 55 | } 56 | 57 | output, err = obsClient.resumeDownload(input, extensions) 58 | return 59 | } 60 | -------------------------------------------------------------------------------- /obs/client_base.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "fmt" 17 | "net/http" 18 | "strings" 19 | ) 20 | 21 | // ObsClient defines OBS client. 22 | type ObsClient struct { 23 | conf *config 24 | httpClient *http.Client 25 | } 26 | 27 | // New creates a new ObsClient instance. 28 | func New(ak, sk, endpoint string, configurers ...configurer) (*ObsClient, error) { 29 | conf := &config{endpoint: endpoint} 30 | conf.securityProviders = make([]securityProvider, 0, 3) 31 | conf.securityProviders = append(conf.securityProviders, NewBasicSecurityProvider(ak, sk, "")) 32 | 33 | conf.maxRetryCount = -1 34 | conf.maxRedirectCount = -1 35 | for _, configurer := range configurers { 36 | configurer(conf) 37 | } 38 | 39 | if err := conf.initConfigWithDefault(); err != nil { 40 | return nil, err 41 | } 42 | err := conf.getTransport() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | if isWarnLogEnabled() { 48 | info := make([]string, 3) 49 | info[0] = fmt.Sprintf("[OBS SDK Version=%s", OBS_SDK_VERSION) 50 | info[1] = fmt.Sprintf("Endpoint=%s", conf.endpoint) 51 | accessMode := "Virtual Hosting" 52 | if conf.pathStyle { 53 | accessMode = "Path" 54 | } 55 | info[2] = fmt.Sprintf("Access Mode=%s]", accessMode) 56 | doLog(LEVEL_WARN, strings.Join(info, "];[")) 57 | } 58 | 59 | if conf.httpClient != nil { 60 | doLog(LEVEL_DEBUG, "Create obsclient with config:\n%s\n", conf) 61 | obsClient := &ObsClient{conf: conf, httpClient: conf.httpClient} 62 | return obsClient, nil 63 | } 64 | 65 | doLog(LEVEL_DEBUG, "Create obsclient with config:\n%s\n", conf) 66 | obsClient := &ObsClient{conf: conf, httpClient: &http.Client{Transport: conf.transport, CheckRedirect: checkRedirectFunc}} 67 | return obsClient, nil 68 | } 69 | -------------------------------------------------------------------------------- /obs/model_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "net/http" 17 | ) 18 | 19 | // CreateSignedUrlInput is the input parameter of CreateSignedUrl function 20 | type CreateSignedUrlInput struct { 21 | Method HttpMethodType 22 | Bucket string 23 | Key string 24 | Policy string 25 | SubResource SubResourceType 26 | Expires int 27 | Headers map[string]string 28 | QueryParams map[string]string 29 | } 30 | 31 | // CreateSignedUrlOutput is the result of CreateSignedUrl function 32 | type CreateSignedUrlOutput struct { 33 | SignedUrl string 34 | ActualSignedRequestHeaders http.Header 35 | } 36 | 37 | // ConditionRange the specifying ranges in the conditions 38 | type ConditionRange struct { 39 | RangeName string 40 | Lower int64 41 | Upper int64 42 | } 43 | 44 | // CreateBrowserBasedSignatureInput is the input parameter of CreateBrowserBasedSignature function. 45 | type CreateBrowserBasedSignatureInput struct { 46 | Bucket string 47 | Key string 48 | Expires int 49 | FormParams map[string]string 50 | RangeParams []ConditionRange 51 | } 52 | 53 | // CreateBrowserBasedSignatureOutput is the result of CreateBrowserBasedSignature function. 54 | type CreateBrowserBasedSignatureOutput struct { 55 | OriginPolicy string 56 | Policy string 57 | Algorithm string 58 | Credential string 59 | Date string 60 | Signature string 61 | } 62 | 63 | // SetBucketRequestPaymentInput is the input parameter of SetBucketRequestPayment function 64 | type SetBucketRequestPaymentInput struct { 65 | Bucket string `xml:"-"` 66 | BucketPayer 67 | } 68 | 69 | // GetBucketRequestPaymentOutput is the result of GetBucketRequestPayment function 70 | type GetBucketRequestPaymentOutput struct { 71 | BaseModel 72 | BucketPayer 73 | } 74 | -------------------------------------------------------------------------------- /obs/model_response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "encoding/xml" 17 | ) 18 | 19 | // BaseModel defines base model response from OBS 20 | type BaseModel struct { 21 | StatusCode int `xml:"-"` 22 | RequestId string `xml:"RequestId" json:"request_id"` 23 | ResponseHeaders map[string][]string `xml:"-"` 24 | } 25 | 26 | // Error defines the error property in DeleteObjectsOutput 27 | type Error struct { 28 | XMLName xml.Name `xml:"Error"` 29 | Key string `xml:"Key"` 30 | VersionId string `xml:"VersionId"` 31 | Code string `xml:"Code"` 32 | Message string `xml:"Message"` 33 | } 34 | 35 | // FetchResponse defines the response fetch policy configuration 36 | type FetchResponse struct { 37 | Status FetchPolicyStatusType `json:"status"` 38 | Agency string `json:"agency"` 39 | } 40 | 41 | // SetBucketFetchJobResponse defines the response SetBucketFetchJob configuration 42 | type SetBucketFetchJobResponse struct { 43 | ID string `json:"id"` 44 | Wait int `json:"Wait"` 45 | } 46 | 47 | // GetBucketFetchJobResponse defines the response fetch job configuration 48 | type GetBucketFetchJobResponse struct { 49 | Err string `json:"err"` 50 | Code string `json:"code"` 51 | Status string `json:"status"` 52 | Job JobResponse `json:"job"` 53 | } 54 | 55 | // JobResponse defines the response job configuration 56 | type JobResponse struct { 57 | Bucket string `json:"bucket"` 58 | URL string `json:"url"` 59 | Host string `json:"host"` 60 | Key string `json:"key"` 61 | Md5 string `json:"md5"` 62 | CallBackURL string `json:"callbackurl"` 63 | CallBackBody string `json:"callbackbody"` 64 | CallBackBodyType string `json:"callbackbodytype"` 65 | CallBackHost string `json:"callbackhost"` 66 | FileType string `json:"file_type"` 67 | IgnoreSameKey bool `json:"ignore_same_key"` 68 | } 69 | -------------------------------------------------------------------------------- /obs/progress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "io" 17 | ) 18 | 19 | type ProgressEventType int 20 | 21 | type ProgressEvent struct { 22 | ConsumedBytes int64 23 | TotalBytes int64 24 | EventType ProgressEventType 25 | } 26 | 27 | const ( 28 | TransferStartedEvent ProgressEventType = 1 + iota 29 | TransferDataEvent 30 | TransferCompletedEvent 31 | TransferFailedEvent 32 | ) 33 | 34 | func newProgressEvent(eventType ProgressEventType, consumed, total int64) *ProgressEvent { 35 | return &ProgressEvent{ 36 | ConsumedBytes: consumed, 37 | TotalBytes: total, 38 | EventType: eventType, 39 | } 40 | } 41 | 42 | type ProgressListener interface { 43 | ProgressChanged(event *ProgressEvent) 44 | } 45 | 46 | type readerTracker struct { 47 | completedBytes int64 48 | } 49 | 50 | // publishProgress 51 | func publishProgress(listener ProgressListener, event *ProgressEvent) { 52 | if listener != nil && event != nil { 53 | listener.ProgressChanged(event) 54 | } 55 | } 56 | 57 | type teeReader struct { 58 | reader io.Reader 59 | consumedBytes int64 60 | totalBytes int64 61 | tracker *readerTracker 62 | listener ProgressListener 63 | } 64 | 65 | func TeeReader(reader io.Reader, totalBytes int64, listener ProgressListener, tracker *readerTracker) io.ReadCloser { 66 | return &teeReader{ 67 | reader: reader, 68 | consumedBytes: 0, 69 | totalBytes: totalBytes, 70 | tracker: tracker, 71 | listener: listener, 72 | } 73 | } 74 | 75 | func (t *teeReader) Read(p []byte) (n int, err error) { 76 | n, err = t.reader.Read(p) 77 | 78 | if err != nil && err != io.EOF { 79 | event := newProgressEvent(TransferFailedEvent, t.consumedBytes, t.totalBytes) 80 | publishProgress(t.listener, event) 81 | } 82 | 83 | if n > 0 { 84 | t.consumedBytes += int64(n) 85 | 86 | if t.listener != nil { 87 | event := newProgressEvent(TransferDataEvent, t.consumedBytes, t.totalBytes) 88 | publishProgress(t.listener, event) 89 | } 90 | 91 | if t.tracker != nil { 92 | t.tracker.completedBytes = t.consumedBytes 93 | } 94 | } 95 | 96 | return 97 | } 98 | 99 | func (r *teeReader) Size() int64 { 100 | return r.totalBytes 101 | } 102 | 103 | func (t *teeReader) Close() error { 104 | if rc, ok := t.reader.(io.ReadCloser); ok { 105 | return rc.Close() 106 | } 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /obs/extension.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "bytes" 17 | "crypto/sha256" 18 | "encoding/base64" 19 | "encoding/hex" 20 | "encoding/json" 21 | "fmt" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | type extensionOptions interface{} 27 | type extensionHeaders func(headers map[string][]string, isObs bool) error 28 | type extensionProgressListener func() ProgressListener 29 | 30 | func WithProgress(progressListener ProgressListener) extensionProgressListener { 31 | return func() ProgressListener { 32 | return progressListener 33 | } 34 | } 35 | 36 | func setHeaderPrefix(key string, value string) extensionHeaders { 37 | return func(headers map[string][]string, isObs bool) error { 38 | if strings.TrimSpace(value) == "" { 39 | return fmt.Errorf("set header %s with empty value", key) 40 | } 41 | setHeaders(headers, key, []string{value}, isObs) 42 | return nil 43 | } 44 | } 45 | 46 | // WithReqPaymentHeader sets header for requester-pays 47 | func WithReqPaymentHeader(requester PayerType) extensionHeaders { 48 | return setHeaderPrefix(REQUEST_PAYER, string(requester)) 49 | } 50 | 51 | func WithTrafficLimitHeader(trafficLimit int64) extensionHeaders { 52 | return setHeaderPrefix(TRAFFIC_LIMIT, strconv.FormatInt(trafficLimit, 10)) 53 | } 54 | 55 | func WithCallbackHeader(callback string) extensionHeaders { 56 | return setHeaderPrefix(CALLBACK, string(callback)) 57 | } 58 | 59 | func WithCustomHeader(key string, value string) extensionHeaders { 60 | return func(headers map[string][]string, isObs bool) error { 61 | if strings.TrimSpace(value) == "" { 62 | return fmt.Errorf("set header %s with empty value", key) 63 | } 64 | headers[key] = []string{value} 65 | return nil 66 | } 67 | } 68 | 69 | func PreprocessCallbackInputToSHA256(callbackInput *CallbackInput) (output string, err error) { 70 | 71 | if callbackInput == nil { 72 | return "", fmt.Errorf("the parameter can not be nil") 73 | } 74 | 75 | if callbackInput.CallbackUrl == "" { 76 | return "", fmt.Errorf("the parameter [CallbackUrl] can not be empty") 77 | } 78 | 79 | if callbackInput.CallbackBody == "" { 80 | return "", fmt.Errorf("the parameter [CallbackBody] can not be empty") 81 | } 82 | 83 | callbackBuffer := bytes.NewBuffer([]byte{}) 84 | callbackEncoder := json.NewEncoder(callbackBuffer) 85 | // 避免HTML字符转义 86 | callbackEncoder.SetEscapeHTML(false) 87 | err = callbackEncoder.Encode(callbackInput) 88 | if err != nil { 89 | return "", err 90 | } 91 | callbackVal := base64.StdEncoding.EncodeToString(removeEndNewlineCharacter(callbackBuffer)) 92 | // 计算SHA256哈希 93 | hash := sha256.Sum256([]byte(callbackVal)) 94 | 95 | // 将哈希转换为十六进制字符串 96 | return hex.EncodeToString(hash[:]), nil 97 | 98 | } 99 | 100 | // Encode函数会默认在json结尾增加换行符,导致base64转码结果与其他方式不一致,需要去掉末尾的换行符 101 | func removeEndNewlineCharacter(callbackBuffer *bytes.Buffer) []byte { 102 | return callbackBuffer.Bytes()[:callbackBuffer.Len()-1] 103 | } 104 | -------------------------------------------------------------------------------- /examples/create_folder_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to create an empty folder under 15 | * specified bucket to OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "obs" 22 | ) 23 | 24 | type CreateFolderSample struct { 25 | bucketName string 26 | location string 27 | obsClient *obs.ObsClient 28 | } 29 | 30 | func newCreateFolderSample(ak, sk, endpoint, bucketName, location string) *CreateFolderSample { 31 | obsClient, err := obs.New(ak, sk, endpoint) 32 | if err != nil { 33 | panic(err) 34 | } 35 | return &CreateFolderSample{obsClient: obsClient, bucketName: bucketName, location: location} 36 | } 37 | 38 | func (sample CreateFolderSample) CreateBucket() { 39 | input := &obs.CreateBucketInput{} 40 | input.Bucket = sample.bucketName 41 | input.Location = sample.location 42 | _, err := sample.obsClient.CreateBucket(input) 43 | if err != nil { 44 | panic(err) 45 | } 46 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 47 | fmt.Println() 48 | } 49 | 50 | func RunCreateFolderSample() { 51 | const ( 52 | endpoint = "https://your-endpoint" 53 | ak = "*** Provide your Access Key ***" 54 | sk = "*** Provide your Secret Key ***" 55 | bucketName = "bucket-test" 56 | location = "yourbucketlocation" 57 | ) 58 | sample := newCreateFolderSample(ak, sk, endpoint, bucketName, location) 59 | 60 | fmt.Println("Create a new bucket for demo") 61 | sample.CreateBucket() 62 | 63 | keySuffixWithSlash1 := "MyObjectKey1/" 64 | keySuffixWithSlash2 := "MyObjectKey2/" 65 | 66 | // Create two empty folder without request body, note that the key must be suffixed with a slash 67 | var input = &obs.PutObjectInput{} 68 | input.Bucket = bucketName 69 | input.Key = keySuffixWithSlash1 70 | 71 | _, err := sample.obsClient.PutObject(input) 72 | if err != nil { 73 | panic(err) 74 | } 75 | fmt.Printf("Create empty folder:%s successfully!\n", keySuffixWithSlash1) 76 | fmt.Println() 77 | 78 | input.Key = keySuffixWithSlash2 79 | _, err = sample.obsClient.PutObject(input) 80 | if err != nil { 81 | panic(err) 82 | } 83 | fmt.Printf("Create empty folder:%s successfully!\n", keySuffixWithSlash2) 84 | fmt.Println() 85 | 86 | // Verify whether the size of the empty folder is zero 87 | var input2 = &obs.GetObjectMetadataInput{} 88 | input2.Bucket = bucketName 89 | input2.Key = keySuffixWithSlash1 90 | output, err := sample.obsClient.GetObjectMetadata(input2) 91 | if err != nil { 92 | panic(err) 93 | } 94 | fmt.Printf("Size of the empty folder %s is %d \n", keySuffixWithSlash1, output.ContentLength) 95 | fmt.Println() 96 | 97 | input2.Key = keySuffixWithSlash2 98 | output, err = sample.obsClient.GetObjectMetadata(input2) 99 | if err != nil { 100 | panic(err) 101 | } 102 | fmt.Printf("Size of the empty folder %s is %d \n", keySuffixWithSlash2, output.ContentLength) 103 | fmt.Println() 104 | 105 | } 106 | -------------------------------------------------------------------------------- /examples/object_meta_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to set/get self-defined metadata for object 15 | * on OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "obs" 22 | "strings" 23 | ) 24 | 25 | type ObjectMetaSample struct { 26 | bucketName string 27 | objectKey string 28 | location string 29 | obsClient *obs.ObsClient 30 | } 31 | 32 | func newObjectMetaSample(ak, sk, endpoint, bucketName, objectKey, location string) *ObjectMetaSample { 33 | obsClient, err := obs.New(ak, sk, endpoint) 34 | if err != nil { 35 | panic(err) 36 | } 37 | return &ObjectMetaSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 38 | } 39 | 40 | func (sample ObjectMetaSample) CreateBucket() { 41 | input := &obs.CreateBucketInput{} 42 | input.Bucket = sample.bucketName 43 | input.Location = sample.location 44 | _, err := sample.obsClient.CreateBucket(input) 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 49 | fmt.Println() 50 | } 51 | 52 | func (sample ObjectMetaSample) SetObjectMeta() { 53 | input := &obs.PutObjectInput{} 54 | input.Bucket = sample.bucketName 55 | input.Key = sample.objectKey 56 | input.Body = strings.NewReader("Hello OBS") 57 | // Setting object mime type 58 | input.ContentType = "text/plain" 59 | // Setting self-defined metadata 60 | input.Metadata = map[string]string{"meta1": "value1", "meta2": "value2"} 61 | _, err := sample.obsClient.PutObject(input) 62 | if err != nil { 63 | panic(err) 64 | } 65 | fmt.Println("Set object meatdata successfully!") 66 | fmt.Println() 67 | } 68 | 69 | func (sample ObjectMetaSample) GetObjectMeta() { 70 | input := &obs.GetObjectMetadataInput{} 71 | input.Bucket = sample.bucketName 72 | input.Key = sample.objectKey 73 | output, err := sample.obsClient.GetObjectMetadata(input) 74 | if err != nil { 75 | panic(err) 76 | } 77 | fmt.Printf("Object content-type:%s\n", output.ContentType) 78 | for key, val := range output.Metadata { 79 | fmt.Printf("%s:%s\n", key, val) 80 | } 81 | fmt.Println() 82 | } 83 | func (sample ObjectMetaSample) DeleteObject() { 84 | input := &obs.DeleteObjectInput{} 85 | input.Bucket = sample.bucketName 86 | input.Key = sample.objectKey 87 | 88 | _, err := sample.obsClient.DeleteObject(input) 89 | if err != nil { 90 | panic(err) 91 | } 92 | fmt.Printf("Delete object:%s successfully!\n", sample.objectKey) 93 | fmt.Println() 94 | } 95 | 96 | func RunObjectMetaSample() { 97 | const ( 98 | endpoint = "https://your-endpoint" 99 | ak = "*** Provide your Access Key ***" 100 | sk = "*** Provide your Secret Key ***" 101 | bucketName = "bucket-test" 102 | objectKey = "object-test" 103 | location = "yourbucketlocation" 104 | ) 105 | sample := newObjectMetaSample(ak, sk, endpoint, bucketName, objectKey, location) 106 | 107 | fmt.Println("Create a new bucket for demo") 108 | sample.CreateBucket() 109 | 110 | sample.SetObjectMeta() 111 | 112 | sample.GetObjectMeta() 113 | 114 | sample.DeleteObject() 115 | } 116 | -------------------------------------------------------------------------------- /examples/delete_objects_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to delete objects under specified bucket 15 | * from OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "obs" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | const( 27 | MyObjectKey string = "MyObjectKey" 28 | ) 29 | 30 | type DeleteObjectsSample struct { 31 | bucketName string 32 | location string 33 | obsClient *obs.ObsClient 34 | } 35 | 36 | func newDeleteObjectsSample(ak, sk, endpoint, bucketName, location string) *DeleteObjectsSample { 37 | obsClient, err := obs.New(ak, sk, endpoint) 38 | if err != nil { 39 | panic(err) 40 | } 41 | return &DeleteObjectsSample{obsClient: obsClient, bucketName: bucketName, location: location} 42 | } 43 | 44 | func (sample DeleteObjectsSample) CreateBucket() { 45 | input := &obs.CreateBucketInput{} 46 | input.Bucket = sample.bucketName 47 | input.Location = sample.location 48 | _, err := sample.obsClient.CreateBucket(input) 49 | if err != nil { 50 | panic(err) 51 | } 52 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 53 | fmt.Println() 54 | } 55 | 56 | func (sample DeleteObjectsSample) BatchPutObjects() { 57 | content := "Thank you for using Object Storage Service" 58 | keyPrefix := MyObjectKey 59 | 60 | input := &obs.PutObjectInput{} 61 | input.Bucket = sample.bucketName 62 | input.Body = strings.NewReader(content) 63 | for i := 0; i < 100; i++ { 64 | input.Key = keyPrefix + strconv.Itoa(i) 65 | _, err := sample.obsClient.PutObject(input) 66 | if err != nil { 67 | panic(err) 68 | } 69 | fmt.Printf("Succeed to put object %s\n", input.Key) 70 | } 71 | } 72 | 73 | func (sample DeleteObjectsSample) BatchDeleteObjects() { 74 | input := &obs.ListObjectsInput{} 75 | input.Bucket = sample.bucketName 76 | output, err := sample.obsClient.ListObjects(input) 77 | if err != nil { 78 | panic(err) 79 | } 80 | objects := make([]obs.ObjectToDelete, 0, len(output.Contents)) 81 | for _, content := range output.Contents { 82 | objects = append(objects, obs.ObjectToDelete{Key: content.Key}) 83 | } 84 | deleteObjectsInput := &obs.DeleteObjectsInput{} 85 | deleteObjectsInput.Bucket = sample.bucketName 86 | deleteObjectsInput.Objects = objects[:] 87 | deleteObjectsOutput, err := sample.obsClient.DeleteObjects(deleteObjectsInput) 88 | if err != nil { 89 | panic(err) 90 | } 91 | for _, deleted := range deleteObjectsOutput.Deleteds { 92 | fmt.Printf("Delete %s successfully\n", deleted.Key) 93 | } 94 | fmt.Println() 95 | for _, deleteError := range deleteObjectsOutput.Errors { 96 | fmt.Printf("Delete %s failed, code:%s, message:%s\n", deleteError.Key, deleteError.Code, deleteError.Message) 97 | } 98 | fmt.Println() 99 | } 100 | 101 | func RunDeleteObjectsSample() { 102 | const ( 103 | endpoint = "https://your-endpoint" 104 | ak = "*** Provide your Access Key ***" 105 | sk = "*** Provide your Secret Key ***" 106 | bucketName = "bucket-test" 107 | location = "yourbucketlocation" 108 | ) 109 | sample := newDeleteObjectsSample(ak, sk, endpoint, bucketName, location) 110 | 111 | fmt.Println("Create a new bucket for demo") 112 | sample.CreateBucket() 113 | 114 | // Batch put objects into the bucket 115 | sample.BatchPutObjects() 116 | 117 | // Delete all objects uploaded recently under the bucket 118 | sample.BatchDeleteObjects() 119 | } 120 | -------------------------------------------------------------------------------- /examples/simple_multipart_upload_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to upload multiparts to OBS 15 | * using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "obs" 22 | "strings" 23 | ) 24 | 25 | type SimpleMultipartUploadSample struct { 26 | bucketName string 27 | objectKey string 28 | location string 29 | obsClient *obs.ObsClient 30 | } 31 | 32 | func newSimpleMultipartUploadSample(ak, sk, endpoint, bucketName, objectKey, location string) *SimpleMultipartUploadSample { 33 | obsClient, err := obs.New(ak, sk, endpoint) 34 | if err != nil { 35 | panic(err) 36 | } 37 | return &SimpleMultipartUploadSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 38 | } 39 | 40 | func (sample SimpleMultipartUploadSample) CreateBucket() { 41 | input := &obs.CreateBucketInput{} 42 | input.Bucket = sample.bucketName 43 | input.Location = sample.location 44 | _, err := sample.obsClient.CreateBucket(input) 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 49 | fmt.Println() 50 | } 51 | 52 | func (sample SimpleMultipartUploadSample) InitiateMultipartUpload() string { 53 | input := &obs.InitiateMultipartUploadInput{} 54 | input.Bucket = sample.bucketName 55 | input.Key = sample.objectKey 56 | output, err := sample.obsClient.InitiateMultipartUpload(input) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return output.UploadId 61 | } 62 | 63 | func (sample SimpleMultipartUploadSample) UploadPart(uploadId string) (string, int) { 64 | input := &obs.UploadPartInput{} 65 | input.Bucket = sample.bucketName 66 | input.Key = sample.objectKey 67 | input.UploadId = uploadId 68 | input.PartNumber = 1 69 | input.Body = strings.NewReader("Hello OBS") 70 | output, err := sample.obsClient.UploadPart(input) 71 | if err != nil { 72 | panic(err) 73 | } 74 | return output.ETag, output.PartNumber 75 | } 76 | 77 | func (sample SimpleMultipartUploadSample) CompleteMultipartUpload(uploadId, etag string, partNumber int) { 78 | input := &obs.CompleteMultipartUploadInput{} 79 | input.Bucket = sample.bucketName 80 | input.Key = sample.objectKey 81 | input.UploadId = uploadId 82 | input.Parts = []obs.Part{ 83 | obs.Part{PartNumber: partNumber, ETag: etag}, 84 | } 85 | _, err := sample.obsClient.CompleteMultipartUpload(input) 86 | if err != nil { 87 | panic(err) 88 | } 89 | fmt.Printf("Upload object %s successfully!\n", sample.objectKey) 90 | } 91 | 92 | func RunSimpleMultipartUploadSample() { 93 | const ( 94 | endpoint = "https://your-endpoint" 95 | ak = "*** Provide your Access Key ***" 96 | sk = "*** Provide your Secret Key ***" 97 | bucketName = "bucket-test" 98 | objectKey = "object-test" 99 | location = "yourbucketlocation" 100 | ) 101 | sample := newSimpleMultipartUploadSample(ak, sk, endpoint, bucketName, objectKey, location) 102 | 103 | fmt.Println("Create a new bucket for demo") 104 | sample.CreateBucket() 105 | 106 | // Step 1: initiate multipart upload 107 | fmt.Println("Step 1: initiate multipart upload") 108 | uploadId := sample.InitiateMultipartUpload() 109 | 110 | // Step 2: upload a part 111 | fmt.Println("Step 2: upload a part") 112 | 113 | etag, partNumber := sample.UploadPart(uploadId) 114 | 115 | // Step 3: complete multipart upload 116 | fmt.Println("Step 3: complete multipart upload") 117 | sample.CompleteMultipartUpload(uploadId, etag, partNumber) 118 | 119 | } 120 | -------------------------------------------------------------------------------- /examples/restore_object_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to download an cold object 15 | * from OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "obs" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | type RestoreObjectSample struct { 28 | bucketName string 29 | objectKey string 30 | location string 31 | obsClient *obs.ObsClient 32 | } 33 | 34 | func newRestoreObjectSample(ak, sk, endpoint, bucketName, objectKey, location string) *RestoreObjectSample { 35 | obsClient, err := obs.New(ak, sk, endpoint) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return &RestoreObjectSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 40 | } 41 | 42 | func (sample RestoreObjectSample) CreateColdBucket() { 43 | input := &obs.CreateBucketInput{} 44 | input.Bucket = sample.bucketName 45 | input.Location = sample.location 46 | input.StorageClass = obs.StorageClassCold 47 | _, err := sample.obsClient.CreateBucket(input) 48 | if err != nil { 49 | panic(err) 50 | } 51 | fmt.Printf("Create cold bucket:%s successfully!\n", sample.bucketName) 52 | fmt.Println() 53 | } 54 | 55 | func (sample RestoreObjectSample) CreateObject() { 56 | input := &obs.PutObjectInput{} 57 | input.Bucket = sample.bucketName 58 | input.Key = sample.objectKey 59 | input.Body = strings.NewReader("Hello OBS") 60 | 61 | _, err := sample.obsClient.PutObject(input) 62 | if err != nil { 63 | panic(err) 64 | } 65 | fmt.Printf("Create object:%s successfully!\n", sample.objectKey) 66 | fmt.Println() 67 | } 68 | 69 | func (sample RestoreObjectSample) RestoreObject() { 70 | input := &obs.RestoreObjectInput{} 71 | input.Bucket = sample.bucketName 72 | input.Key = sample.objectKey 73 | input.Days = 1 74 | input.Tier = obs.RestoreTierExpedited 75 | 76 | _, err := sample.obsClient.RestoreObject(input) 77 | if err != nil { 78 | panic(err) 79 | } 80 | fmt.Printf("Create object:%s successfully!\n", sample.objectKey) 81 | fmt.Println() 82 | } 83 | 84 | func (sample RestoreObjectSample) GetObject() { 85 | input := &obs.GetObjectInput{} 86 | input.Bucket = sample.bucketName 87 | input.Key = sample.objectKey 88 | 89 | output, err := sample.obsClient.GetObject(input) 90 | if err != nil { 91 | panic(err) 92 | } 93 | defer func(){ 94 | errMsg := output.Body.Close() 95 | if errMsg != nil{ 96 | panic(errMsg) 97 | } 98 | }() 99 | fmt.Println("Object content:") 100 | body, err := ioutil.ReadAll(output.Body) 101 | if err != nil{ 102 | panic(err) 103 | } 104 | fmt.Println(string(body)) 105 | fmt.Println() 106 | } 107 | 108 | func (sample RestoreObjectSample) DeleteObject() { 109 | input := &obs.DeleteObjectInput{} 110 | input.Bucket = sample.bucketName 111 | input.Key = sample.objectKey 112 | _, err := sample.obsClient.DeleteObject(input) 113 | if err != nil { 114 | panic(err) 115 | } 116 | fmt.Printf("Delete object:%s successfully!\n", input.Key) 117 | fmt.Println() 118 | } 119 | 120 | func RunRestoreObjectSample() { 121 | const ( 122 | endpoint = "https://your-endpoint" 123 | ak = "*** Provide your Access Key ***" 124 | sk = "*** Provide your Secret Key ***" 125 | bucketName = "bucket-test-cold" 126 | objectKey = "object-test" 127 | location = "yourbucketlocation" 128 | ) 129 | 130 | sample := newRestoreObjectSample(ak, sk, endpoint, bucketName, objectKey, location) 131 | 132 | fmt.Println("Create a new cold bucket for demo") 133 | sample.CreateColdBucket() 134 | 135 | sample.CreateObject() 136 | 137 | sample.RestoreObject() 138 | 139 | // Wait 6 minutes to get the object 140 | time.Sleep(time.Duration(6*60) * time.Second) 141 | 142 | sample.GetObject() 143 | 144 | sample.DeleteObject() 145 | } 146 | -------------------------------------------------------------------------------- /obs/temporary_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "errors" 17 | "fmt" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | func (obsClient ObsClient) isSecurityToken(params map[string]string, sh securityHolder) { 23 | if sh.securityToken != "" { 24 | if obsClient.conf.signature == SignatureObs { 25 | params[HEADER_STS_TOKEN_OBS] = sh.securityToken 26 | } else { 27 | params[HEADER_STS_TOKEN_AMZ] = sh.securityToken 28 | } 29 | } 30 | } 31 | 32 | // CreateBrowserBasedSignature gets the browser based signature with the specified CreateBrowserBasedSignatureInput, 33 | // and returns the CreateBrowserBasedSignatureOutput and error 34 | func (obsClient ObsClient) CreateBrowserBasedSignature(input *CreateBrowserBasedSignatureInput) (output *CreateBrowserBasedSignatureOutput, err error) { 35 | if input == nil { 36 | return nil, errors.New("CreateBrowserBasedSignatureInput is nil") 37 | } 38 | 39 | params := make(map[string]string, len(input.FormParams)) 40 | for key, value := range input.FormParams { 41 | params[key] = value 42 | } 43 | 44 | date := time.Now().UTC() 45 | shortDate := date.Format(SHORT_DATE_FORMAT) 46 | longDate := date.Format(LONG_DATE_FORMAT) 47 | sh := obsClient.getSecurity() 48 | 49 | credential, _ := getCredential(sh.ak, obsClient.conf.region, shortDate) 50 | 51 | if input.Expires <= 0 { 52 | input.Expires = 300 53 | } 54 | 55 | expiration := date.Add(time.Second * time.Duration(input.Expires)).Format(ISO8601_DATE_FORMAT) 56 | if obsClient.conf.signature == SignatureV4 { 57 | params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX 58 | params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential 59 | params[PARAM_DATE_AMZ_CAMEL] = longDate 60 | } 61 | 62 | obsClient.isSecurityToken(params, sh) 63 | 64 | matchAnyBucket := true 65 | matchAnyKey := true 66 | count := 5 67 | if bucket := strings.TrimSpace(input.Bucket); bucket != "" { 68 | params["bucket"] = bucket 69 | matchAnyBucket = false 70 | count-- 71 | } 72 | 73 | if key := strings.TrimSpace(input.Key); key != "" { 74 | params["key"] = key 75 | matchAnyKey = false 76 | count-- 77 | } 78 | 79 | originPolicySlice := make([]string, 0, len(params)+count) 80 | originPolicySlice = append(originPolicySlice, fmt.Sprintf("{\"expiration\":\"%s\",", expiration)) 81 | originPolicySlice = append(originPolicySlice, "\"conditions\":[") 82 | for key, value := range params { 83 | if _key := strings.TrimSpace(strings.ToLower(key)); _key != "" { 84 | originPolicySlice = append(originPolicySlice, fmt.Sprintf("{\"%s\":\"%s\"},", _key, value)) 85 | } 86 | } 87 | 88 | if matchAnyBucket { 89 | originPolicySlice = append(originPolicySlice, "[\"starts-with\", \"$bucket\", \"\"],") 90 | } 91 | 92 | if matchAnyKey { 93 | originPolicySlice = append(originPolicySlice, "[\"starts-with\", \"$key\", \"\"],") 94 | } 95 | 96 | for _, v := range input.RangeParams { 97 | originPolicySlice = append(originPolicySlice, fmt.Sprintf("[\"%s\", %d, %d],", v.RangeName, v.Lower, v.Upper)) 98 | } 99 | 100 | lastIndex := len(originPolicySlice) - 1 101 | originPolicySlice[lastIndex] = strings.TrimSuffix(originPolicySlice[lastIndex], ",") 102 | 103 | originPolicySlice = append(originPolicySlice, "]}") 104 | 105 | originPolicy := strings.Join(originPolicySlice, "") 106 | 107 | policy := Base64Encode([]byte(originPolicy)) 108 | var signature string 109 | if obsClient.conf.signature == SignatureV4 { 110 | signature = getSignature(policy, sh.sk, obsClient.conf.region, shortDate) 111 | } else { 112 | signature = Base64Encode(HmacSha1([]byte(sh.sk), []byte(policy))) 113 | } 114 | 115 | output = &CreateBrowserBasedSignatureOutput{ 116 | OriginPolicy: originPolicy, 117 | Policy: policy, 118 | Algorithm: params[PARAM_ALGORITHM_AMZ_CAMEL], 119 | Credential: params[PARAM_CREDENTIAL_AMZ_CAMEL], 120 | Date: params[PARAM_DATE_AMZ_CAMEL], 121 | Signature: signature, 122 | } 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /examples/custom_http_client_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to use custom http.Client to upload multiparts to OBS 15 | * using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | "obs" 23 | "strings" 24 | ) 25 | 26 | type SimpleCustumHttpClientSample struct { 27 | bucketName string 28 | objectKey string 29 | location string 30 | obsClient *obs.ObsClient 31 | } 32 | type OurCustomTransport struct { 33 | Transport http.RoundTripper 34 | } 35 | 36 | func (t *OurCustomTransport) transport() http.RoundTripper { 37 | if t.Transport != nil { 38 | return t.Transport 39 | } 40 | return http.DefaultTransport 41 | } 42 | func (t *OurCustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { 43 | fmt.Println("do someting") 44 | return t.transport().RoundTrip(req) 45 | } 46 | 47 | func newCustumHttpClientSample(ak, sk, endpoint, bucketName, objectKey, location string) *SimpleCustumHttpClientSample { 48 | 49 | t := &http.Client{ 50 | Transport: &OurCustomTransport{}, 51 | } 52 | obsClient, err := obs.New(ak, sk, endpoint, obs.WithHttpClient(t)) 53 | if err != nil { 54 | panic(err) 55 | } 56 | return &SimpleCustumHttpClientSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 57 | } 58 | 59 | func (sample SimpleCustumHttpClientSample) CreateBucket() { 60 | input := &obs.CreateBucketInput{} 61 | input.Bucket = sample.bucketName 62 | input.Location = sample.location 63 | _, err := sample.obsClient.CreateBucket(input) 64 | if err != nil { 65 | panic(err) 66 | } 67 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 68 | fmt.Println() 69 | } 70 | 71 | func (sample SimpleCustumHttpClientSample) InitiateMultipartUpload() string { 72 | input := &obs.InitiateMultipartUploadInput{} 73 | input.Bucket = sample.bucketName 74 | input.Key = sample.objectKey 75 | output, err := sample.obsClient.InitiateMultipartUpload(input) 76 | if err != nil { 77 | panic(err) 78 | } 79 | return output.UploadId 80 | } 81 | 82 | func (sample SimpleCustumHttpClientSample) UploadPart(uploadId string) (string, int) { 83 | input := &obs.UploadPartInput{} 84 | input.Bucket = sample.bucketName 85 | input.Key = sample.objectKey 86 | input.UploadId = uploadId 87 | input.PartNumber = 1 88 | input.Body = strings.NewReader("Hello OBS") 89 | output, err := sample.obsClient.UploadPart(input) 90 | if err != nil { 91 | panic(err) 92 | } 93 | return output.ETag, output.PartNumber 94 | } 95 | 96 | func (sample SimpleCustumHttpClientSample) CompleteMultipartUpload(uploadId, etag string, partNumber int) { 97 | input := &obs.CompleteMultipartUploadInput{} 98 | input.Bucket = sample.bucketName 99 | input.Key = sample.objectKey 100 | input.UploadId = uploadId 101 | input.Parts = []obs.Part{ 102 | obs.Part{PartNumber: partNumber, ETag: etag}, 103 | } 104 | _, err := sample.obsClient.CompleteMultipartUpload(input) 105 | if err != nil { 106 | panic(err) 107 | } 108 | fmt.Printf("Upload object %s successfully!\n", sample.objectKey) 109 | } 110 | 111 | func RunCustumHttpClientSample() { 112 | const ( 113 | endpoint = "https://your-endpoint" 114 | ak = "*** Provide your Access Key ***" 115 | sk = "*** Provide your Secret Key ***" 116 | bucketName = "bucket-test" 117 | objectKey = "object-test" 118 | location = "yourbucketlocation" 119 | ) 120 | 121 | sample := newCustumHttpClientSample(ak, sk, endpoint, bucketName, objectKey, location) 122 | 123 | fmt.Println("Create a new bucket for demo") 124 | sample.CreateBucket() 125 | 126 | // Step 1: initiate multipart upload 127 | fmt.Println("Step 1: initiate multipart upload") 128 | uploadId := sample.InitiateMultipartUpload() 129 | 130 | // Step 2: upload a part 131 | fmt.Println("Step 2: upload a part") 132 | 133 | etag, partNumber := sample.UploadPart(uploadId) 134 | 135 | // Step 3: complete multipart upload 136 | fmt.Println("Step 3: complete multipart upload") 137 | sample.CompleteMultipartUpload(uploadId, etag, partNumber) 138 | 139 | } 140 | -------------------------------------------------------------------------------- /examples/download_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to download an object 15 | * from OBS in different ways using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "obs" 23 | "os" 24 | "path/filepath" 25 | "strings" 26 | ) 27 | 28 | type DownloadSample struct { 29 | bucketName string 30 | objectKey string 31 | location string 32 | obsClient *obs.ObsClient 33 | } 34 | 35 | func newDownloadSample(ak, sk, endpoint, bucketName, objectKey, location string) *DownloadSample { 36 | obsClient, err := obs.New(ak, sk, endpoint) 37 | if err != nil { 38 | panic(err) 39 | } 40 | return &DownloadSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 41 | } 42 | 43 | func (sample DownloadSample) CreateBucket() { 44 | input := &obs.CreateBucketInput{} 45 | input.Bucket = sample.bucketName 46 | input.Location = sample.location 47 | _, err := sample.obsClient.CreateBucket(input) 48 | if err != nil { 49 | panic(err) 50 | } 51 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 52 | fmt.Println() 53 | } 54 | 55 | func (sample DownloadSample) PutObject() { 56 | input := &obs.PutObjectInput{} 57 | input.Bucket = sample.bucketName 58 | input.Key = sample.objectKey 59 | input.Body = strings.NewReader("Hello OBS") 60 | 61 | _, err := sample.obsClient.PutObject(input) 62 | if err != nil { 63 | panic(err) 64 | } 65 | fmt.Printf("Put object:%s successfully!\n", sample.objectKey) 66 | fmt.Println() 67 | } 68 | 69 | func (sample DownloadSample) GetObject() { 70 | input := &obs.GetObjectInput{} 71 | input.Bucket = sample.bucketName 72 | input.Key = sample.objectKey 73 | 74 | output, err := sample.obsClient.GetObject(input) 75 | if err != nil { 76 | panic(err) 77 | } 78 | defer func(){ 79 | errMsg := output.Body.Close() 80 | if errMsg != nil{ 81 | panic(errMsg) 82 | } 83 | }() 84 | fmt.Println("Object content:") 85 | body, err := ioutil.ReadAll(output.Body) 86 | if err != nil { 87 | panic(err) 88 | } 89 | fmt.Println(string(body)) 90 | fmt.Println() 91 | } 92 | 93 | func (sample DownloadSample) PutFile(sampleFilePath string) { 94 | input := &obs.PutFileInput{} 95 | input.Bucket = sample.bucketName 96 | input.Key = sample.objectKey 97 | input.SourceFile = sampleFilePath 98 | 99 | _, err := sample.obsClient.PutFile(input) 100 | if err != nil { 101 | panic(err) 102 | } 103 | fmt.Printf("Put object:%s with file:%s successfully!\n", sample.objectKey, sampleFilePath) 104 | fmt.Println() 105 | } 106 | 107 | func (sample DownloadSample) DeleteObject() { 108 | input := &obs.DeleteObjectInput{} 109 | input.Bucket = sample.bucketName 110 | input.Key = sample.objectKey 111 | 112 | _, err := sample.obsClient.DeleteObject(input) 113 | if err != nil { 114 | panic(err) 115 | } 116 | fmt.Printf("Delete object:%s successfully!\n", sample.objectKey) 117 | fmt.Println() 118 | } 119 | 120 | func (DownloadSample) createSampleFile(sampleFilePath string) { 121 | if err := os.MkdirAll(filepath.Dir(sampleFilePath), os.ModePerm); err != nil { 122 | panic(err) 123 | } 124 | 125 | if err := ioutil.WriteFile(sampleFilePath, []byte("Hello OBS from file"), os.ModePerm); err != nil { 126 | panic(err) 127 | } 128 | } 129 | 130 | func RunDownloadSample() { 131 | const ( 132 | endpoint = "https://your-endpoint" 133 | ak = "*** Provide your Access Key ***" 134 | sk = "*** Provide your Secret Key ***" 135 | bucketName = "bucket-test" 136 | objectKey = "object-test" 137 | location = "yourbucketlocation" 138 | ) 139 | sample := newDownloadSample(ak, sk, endpoint, bucketName, objectKey, location) 140 | 141 | fmt.Println("Create a new bucket for demo") 142 | sample.CreateBucket() 143 | 144 | fmt.Println("Uploading a new object to OBS from string") 145 | sample.PutObject() 146 | 147 | fmt.Println("Download object to string") 148 | sample.GetObject() 149 | 150 | fmt.Println("Uploading a new object to OBS from file") 151 | sampleFilePath := "/temp/text.txt" 152 | sample.createSampleFile(sampleFilePath) 153 | defer func(){ 154 | errMsg := os.Remove(sampleFilePath) 155 | if errMsg != nil{ 156 | panic(errMsg) 157 | } 158 | }() 159 | sample.PutFile(sampleFilePath) 160 | 161 | fmt.Println("Download file to string") 162 | sample.GetObject() 163 | 164 | sample.DeleteObject() 165 | } 166 | -------------------------------------------------------------------------------- /obs/trait_base.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "io" 17 | ) 18 | 19 | type IRepeatable interface { 20 | Reset() error 21 | } 22 | 23 | // IReadCloser defines interface with function: setReadCloser 24 | type IReadCloser interface { 25 | setReadCloser(body io.ReadCloser) 26 | } 27 | 28 | func setHeaders(headers map[string][]string, header string, headerValue []string, isObs bool) { 29 | if isObs { 30 | header = HEADER_PREFIX_OBS + header 31 | headers[header] = headerValue 32 | } else { 33 | header = HEADER_PREFIX + header 34 | headers[header] = headerValue 35 | } 36 | } 37 | 38 | func setHeadersNext(headers map[string][]string, header string, headerNext string, headerValue []string, isObs bool) { 39 | if isObs { 40 | headers[header] = headerValue 41 | } else { 42 | headers[headerNext] = headerValue 43 | } 44 | } 45 | 46 | // IBaseModel defines interface for base response model 47 | type IBaseModel interface { 48 | setStatusCode(statusCode int) 49 | 50 | setRequestID(requestID string) 51 | 52 | setResponseHeaders(responseHeaders map[string][]string) 53 | } 54 | 55 | // ISerializable defines interface with function: trans 56 | type ISerializable interface { 57 | trans(isObs bool) (map[string]string, map[string][]string, interface{}, error) 58 | } 59 | 60 | // DefaultSerializable defines default serializable struct 61 | type DefaultSerializable struct { 62 | params map[string]string 63 | headers map[string][]string 64 | data interface{} 65 | } 66 | 67 | func (input DefaultSerializable) trans(isObs bool) (map[string]string, map[string][]string, interface{}, error) { 68 | return input.params, input.headers, input.data, nil 69 | } 70 | 71 | var defaultSerializable = &DefaultSerializable{} 72 | 73 | func newSubResourceSerialV2(subResource SubResourceType, value string) *DefaultSerializable { 74 | return &DefaultSerializable{map[string]string{string(subResource): value}, nil, nil} 75 | } 76 | 77 | func newSubResourceSerial(subResource SubResourceType) *DefaultSerializable { 78 | return &DefaultSerializable{map[string]string{string(subResource): ""}, nil, nil} 79 | } 80 | 81 | func trans(subResource SubResourceType, input interface{}) (params map[string]string, headers map[string][]string, data interface{}, err error) { 82 | params = map[string]string{string(subResource): ""} 83 | data, err = ConvertRequestToIoReader(input) 84 | return 85 | } 86 | 87 | func (baseModel *BaseModel) setStatusCode(statusCode int) { 88 | baseModel.StatusCode = statusCode 89 | } 90 | 91 | func (baseModel *BaseModel) setRequestID(requestID string) { 92 | baseModel.RequestId = requestID 93 | } 94 | 95 | func (baseModel *BaseModel) setResponseHeaders(responseHeaders map[string][]string) { 96 | baseModel.ResponseHeaders = responseHeaders 97 | } 98 | 99 | // GetEncryption gets the Encryption field value from SseKmsHeader 100 | func (header SseKmsHeader) GetEncryption() string { 101 | if header.Encryption != "" { 102 | return header.Encryption 103 | } 104 | if !header.isObs { 105 | return DEFAULT_SSE_KMS_ENCRYPTION 106 | } 107 | return DEFAULT_SSE_KMS_ENCRYPTION_OBS 108 | } 109 | 110 | // GetKey gets the Key field value from SseKmsHeader 111 | func (header SseKmsHeader) GetKey() string { 112 | return header.Key 113 | } 114 | 115 | // GetEncryption gets the Encryption field value from SseCHeader 116 | func (header SseCHeader) GetEncryption() string { 117 | if header.Encryption != "" { 118 | return header.Encryption 119 | } 120 | return DEFAULT_SSE_C_ENCRYPTION 121 | } 122 | 123 | // GetKey gets the Key field value from SseCHeader 124 | func (header SseCHeader) GetKey() string { 125 | return header.Key 126 | } 127 | 128 | // GetKeyMD5 gets the KeyMD5 field value from SseCHeader 129 | func (header SseCHeader) GetKeyMD5() string { 130 | if header.KeyMD5 != "" { 131 | return header.KeyMD5 132 | } 133 | 134 | if ret, err := Base64Decode(header.GetKey()); err == nil { 135 | return Base64Md5(ret) 136 | } 137 | return "" 138 | } 139 | 140 | func setSseHeader(headers map[string][]string, sseHeader ISseHeader, sseCOnly bool, isObs bool) { 141 | if sseHeader != nil { 142 | if sseCHeader, ok := sseHeader.(SseCHeader); ok { 143 | setHeaders(headers, HEADER_SSEC_ENCRYPTION, []string{sseCHeader.GetEncryption()}, isObs) 144 | setHeaders(headers, HEADER_SSEC_KEY, []string{sseCHeader.GetKey()}, isObs) 145 | setHeaders(headers, HEADER_SSEC_KEY_MD5, []string{sseCHeader.GetKeyMD5()}, isObs) 146 | } else if sseKmsHeader, ok := sseHeader.(SseKmsHeader); !sseCOnly && ok { 147 | sseKmsHeader.isObs = isObs 148 | setHeaders(headers, HEADER_SSEKMS_ENCRYPTION, []string{sseKmsHeader.GetEncryption()}, isObs) 149 | if sseKmsHeader.GetKey() != "" { 150 | setHeadersNext(headers, HEADER_SSEKMS_KEY_OBS, HEADER_SSEKMS_KEY_AMZ, []string{sseKmsHeader.GetKey()}, isObs) 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /obs/authV4.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func getV4StringToSign(method, canonicalizedURL, queryURL, scope, longDate, payload string, signedHeaders []string, headers map[string][]string) string { 21 | canonicalRequest := make([]string, 0, 10+len(signedHeaders)*4) 22 | canonicalRequest = append(canonicalRequest, method) 23 | canonicalRequest = append(canonicalRequest, "\n") 24 | canonicalRequest = append(canonicalRequest, canonicalizedURL) 25 | canonicalRequest = append(canonicalRequest, "\n") 26 | canonicalRequest = append(canonicalRequest, queryURL) 27 | canonicalRequest = append(canonicalRequest, "\n") 28 | 29 | for _, signedHeader := range signedHeaders { 30 | values, _ := headers[signedHeader] 31 | for _, value := range values { 32 | canonicalRequest = append(canonicalRequest, signedHeader) 33 | canonicalRequest = append(canonicalRequest, ":") 34 | canonicalRequest = append(canonicalRequest, value) 35 | canonicalRequest = append(canonicalRequest, "\n") 36 | } 37 | } 38 | canonicalRequest = append(canonicalRequest, "\n") 39 | canonicalRequest = append(canonicalRequest, strings.Join(signedHeaders, ";")) 40 | canonicalRequest = append(canonicalRequest, "\n") 41 | canonicalRequest = append(canonicalRequest, payload) 42 | 43 | _canonicalRequest := strings.Join(canonicalRequest, "") 44 | 45 | var isSecurityToken bool 46 | var securityToken []string 47 | if securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_OBS]; !isSecurityToken { 48 | securityToken, isSecurityToken = headers[HEADER_STS_TOKEN_AMZ] 49 | } 50 | var query []string 51 | if !isSecurityToken { 52 | query = strings.Split(queryURL, "&") 53 | for _, value := range query { 54 | if strings.HasPrefix(value, HEADER_STS_TOKEN_AMZ+"=") || strings.HasPrefix(value, HEADER_STS_TOKEN_OBS+"=") { 55 | if value[len(HEADER_STS_TOKEN_AMZ)+1:] != "" { 56 | securityToken = []string{value[len(HEADER_STS_TOKEN_AMZ)+1:]} 57 | isSecurityToken = true 58 | } 59 | } 60 | } 61 | } 62 | logCanonicalRequest := _canonicalRequest 63 | if isSecurityToken && len(securityToken) > 0 { 64 | logCanonicalRequest = strings.Replace(logCanonicalRequest, securityToken[0], "******", -1) 65 | } 66 | doLog(LEVEL_DEBUG, "The v4 auth canonicalRequest:\n%s", logCanonicalRequest) 67 | 68 | stringToSign := make([]string, 0, 7) 69 | stringToSign = append(stringToSign, V4_HASH_PREFIX) 70 | stringToSign = append(stringToSign, "\n") 71 | stringToSign = append(stringToSign, longDate) 72 | stringToSign = append(stringToSign, "\n") 73 | stringToSign = append(stringToSign, scope) 74 | stringToSign = append(stringToSign, "\n") 75 | stringToSign = append(stringToSign, HexSha256([]byte(_canonicalRequest))) 76 | 77 | _stringToSign := strings.Join(stringToSign, "") 78 | 79 | return _stringToSign 80 | } 81 | 82 | // V4Auth is a wrapper for v4Auth 83 | func V4Auth(ak, sk, region, method, canonicalizedURL, queryURL string, headers map[string][]string) map[string]string { 84 | return v4Auth(ak, sk, region, method, canonicalizedURL, queryURL, headers) 85 | } 86 | 87 | func v4Auth(ak, sk, region, method, canonicalizedURL, queryURL string, headers map[string][]string) map[string]string { 88 | var t time.Time 89 | if val, ok := headers[HEADER_DATE_AMZ]; ok { 90 | var err error 91 | t, err = time.Parse(LONG_DATE_FORMAT, val[0]) 92 | if err != nil { 93 | t = time.Now().UTC() 94 | } 95 | } else if val, ok := headers[PARAM_DATE_AMZ_CAMEL]; ok { 96 | var err error 97 | t, err = time.Parse(LONG_DATE_FORMAT, val[0]) 98 | if err != nil { 99 | t = time.Now().UTC() 100 | } 101 | } else if val, ok := headers[HEADER_DATE_CAMEL]; ok { 102 | var err error 103 | t, err = time.Parse(RFC1123_FORMAT, val[0]) 104 | if err != nil { 105 | t = time.Now().UTC() 106 | } 107 | } else if val, ok := headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok { 108 | var err error 109 | t, err = time.Parse(RFC1123_FORMAT, val[0]) 110 | if err != nil { 111 | t = time.Now().UTC() 112 | } 113 | } else { 114 | t = time.Now().UTC() 115 | } 116 | shortDate := t.Format(SHORT_DATE_FORMAT) 117 | longDate := t.Format(LONG_DATE_FORMAT) 118 | 119 | signedHeaders, _headers := getSignedHeaders(headers) 120 | 121 | credential, scope := getCredential(ak, region, shortDate) 122 | 123 | payload := UNSIGNED_PAYLOAD 124 | if val, ok := headers[HEADER_CONTENT_SHA256_AMZ]; ok { 125 | payload = val[0] 126 | } 127 | stringToSign := getV4StringToSign(method, canonicalizedURL, queryURL, scope, longDate, payload, signedHeaders, _headers) 128 | 129 | signature := getSignature(stringToSign, sk, region, shortDate) 130 | 131 | ret := make(map[string]string, 3) 132 | ret["Credential"] = credential 133 | ret["SignedHeaders"] = strings.Join(signedHeaders, ";") 134 | ret["Signature"] = signature 135 | return ret 136 | } 137 | -------------------------------------------------------------------------------- /examples/list_objects_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to list objects under specified bucket 15 | * from OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "obs" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | type ListObjectsSample struct { 27 | bucketName string 28 | location string 29 | obsClient *obs.ObsClient 30 | } 31 | 32 | func newListObjectsSample(ak, sk, endpoint, bucketName, location string) *ListObjectsSample { 33 | obsClient, err := obs.New(ak, sk, endpoint) 34 | if err != nil { 35 | panic(err) 36 | } 37 | return &ListObjectsSample{obsClient: obsClient, bucketName: bucketName, location: location} 38 | } 39 | 40 | func (sample ListObjectsSample) CreateBucket() { 41 | input := &obs.CreateBucketInput{} 42 | input.Bucket = sample.bucketName 43 | input.Location = sample.location 44 | _, err := sample.obsClient.CreateBucket(input) 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 49 | fmt.Println() 50 | } 51 | 52 | func (sample ListObjectsSample) DoInsertObjects() []string { 53 | 54 | keyPrefix := "MyObjectKey" 55 | 56 | input := &obs.PutObjectInput{} 57 | input.Bucket = sample.bucketName 58 | input.Body = strings.NewReader("Hello OBS") 59 | keys := make([]string, 0, 100) 60 | for i := 0; i < 100; i++ { 61 | input.Key = keyPrefix + strconv.Itoa(i) 62 | _, err := sample.obsClient.PutObject(input) 63 | if err != nil { 64 | panic(err) 65 | } 66 | fmt.Printf("Succeed to put object %s\n", input.Key) 67 | keys = append(keys, input.Key) 68 | } 69 | fmt.Println() 70 | return keys 71 | } 72 | 73 | func (sample ListObjectsSample) ListObjects() { 74 | input := &obs.ListObjectsInput{} 75 | input.Bucket = sample.bucketName 76 | output, err := sample.obsClient.ListObjects(input) 77 | if err != nil { 78 | panic(err) 79 | } 80 | for index, val := range output.Contents { 81 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 82 | index, val.ETag, val.Key, val.Size) 83 | } 84 | fmt.Println() 85 | } 86 | 87 | func (sample ListObjectsSample) ListObjectsByMarker() { 88 | input := &obs.ListObjectsInput{} 89 | input.Bucket = sample.bucketName 90 | input.MaxKeys = 10 91 | output, err := sample.obsClient.ListObjects(input) 92 | if err != nil { 93 | panic(err) 94 | } 95 | fmt.Println("List the first 10 objects :") 96 | for index, val := range output.Contents { 97 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 98 | index, val.ETag, val.Key, val.Size) 99 | } 100 | fmt.Println() 101 | 102 | input.Marker = output.NextMarker 103 | output, err = sample.obsClient.ListObjects(input) 104 | if err != nil { 105 | panic(err) 106 | } 107 | fmt.Println("List the second 10 objects using marker:") 108 | for index, val := range output.Contents { 109 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 110 | index, val.ETag, val.Key, val.Size) 111 | } 112 | fmt.Println() 113 | } 114 | 115 | func (sample ListObjectsSample) ListObjectsByPage() { 116 | 117 | pageSize := 10 118 | pageNum := 1 119 | input := &obs.ListObjectsInput{} 120 | input.Bucket = sample.bucketName 121 | input.MaxKeys = pageSize 122 | 123 | for { 124 | output, err := sample.obsClient.ListObjects(input) 125 | if err != nil { 126 | panic(err) 127 | } 128 | fmt.Printf("Page:%d\n", pageNum) 129 | for index, val := range output.Contents { 130 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 131 | index, val.ETag, val.Key, val.Size) 132 | } 133 | if output.IsTruncated { 134 | input.Marker = output.NextMarker 135 | pageNum++ 136 | } else { 137 | break 138 | } 139 | } 140 | 141 | fmt.Println() 142 | } 143 | 144 | func (sample ListObjectsSample) DeleteObjects(keys []string) { 145 | input := &obs.DeleteObjectsInput{} 146 | input.Bucket = sample.bucketName 147 | 148 | objects := make([]obs.ObjectToDelete, 0, len(keys)) 149 | for _, key := range keys { 150 | objects = append(objects, obs.ObjectToDelete{Key: key}) 151 | } 152 | input.Objects = objects 153 | _, err := sample.obsClient.DeleteObjects(input) 154 | if err != nil { 155 | panic(err) 156 | } 157 | fmt.Println("Delete objects successfully!") 158 | } 159 | 160 | func RunListObjectsSample() { 161 | 162 | const ( 163 | endpoint = "https://your-endpoint" 164 | ak = "*** Provide your Access Key ***" 165 | sk = "*** Provide your Secret Key ***" 166 | bucketName = "bucket-test" 167 | location = "yourbucketlocation" 168 | ) 169 | 170 | sample := newListObjectsSample(ak, sk, endpoint, bucketName, location) 171 | 172 | fmt.Println("Create a new bucket for demo") 173 | sample.CreateBucket() 174 | 175 | // First insert 100 objects for demo 176 | keys := sample.DoInsertObjects() 177 | 178 | // List objects using default parameters, will return up to 1000 objects 179 | sample.ListObjects() 180 | 181 | // List the first 10 and second 10 objects 182 | sample.ListObjectsByMarker() 183 | 184 | // List objects in way of pagination 185 | sample.ListObjectsByPage() 186 | 187 | // Delete all the objects created 188 | sample.DeleteObjects(keys) 189 | } 190 | -------------------------------------------------------------------------------- /obs/trait_part.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "fmt" 17 | ) 18 | 19 | func (input ListMultipartUploadsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}, err error) { 20 | params = map[string]string{string(SubResourceUploads): ""} 21 | if input.Prefix != "" { 22 | params["prefix"] = input.Prefix 23 | } 24 | if input.Delimiter != "" { 25 | params["delimiter"] = input.Delimiter 26 | } 27 | if input.MaxUploads > 0 { 28 | params["max-uploads"] = IntToString(input.MaxUploads) 29 | } 30 | if input.KeyMarker != "" { 31 | params["key-marker"] = input.KeyMarker 32 | } 33 | if input.UploadIdMarker != "" { 34 | params["upload-id-marker"] = input.UploadIdMarker 35 | } 36 | if input.EncodingType != "" { 37 | params["encoding-type"] = input.EncodingType 38 | } 39 | return 40 | } 41 | 42 | func (input AbortMultipartUploadInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}, err error) { 43 | params = map[string]string{"uploadId": input.UploadId} 44 | return 45 | } 46 | 47 | func (input InitiateMultipartUploadInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}, err error) { 48 | params, headers, data, err = input.ObjectOperationInput.trans(isObs) 49 | if err != nil { 50 | return 51 | } 52 | if input.ContentType != "" { 53 | headers[HEADER_CONTENT_TYPE_CAML] = []string{input.ContentType} 54 | } 55 | if input.ContentEncoding != "" { 56 | headers[HEADER_CONTENT_ENCODING_CAMEL] = []string{input.ContentEncoding} 57 | } 58 | if input.CacheControl != "" { 59 | headers[HEADER_CACHE_CONTROL_CAMEL] = []string{input.CacheControl} 60 | } 61 | if input.ContentDisposition != "" { 62 | headers[HEADER_CONTENT_DISPOSITION_CAMEL] = []string{input.ContentDisposition} 63 | } 64 | if input.ContentLanguage != "" { 65 | headers[HEADER_CONTENT_LANGUAGE_CAMEL] = []string{input.ContentLanguage} 66 | } 67 | if input.HttpExpires != "" { 68 | headers[HEADER_EXPIRES_CAMEL] = []string{input.HttpExpires} 69 | } 70 | params[string(SubResourceUploads)] = "" 71 | if input.EncodingType != "" { 72 | params["encoding-type"] = input.EncodingType 73 | } 74 | return 75 | } 76 | 77 | func (input UploadPartInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}, err error) { 78 | params = map[string]string{"uploadId": input.UploadId, "partNumber": IntToString(input.PartNumber)} 79 | headers = make(map[string][]string) 80 | setSseHeader(headers, input.SseHeader, true, isObs) 81 | if input.ContentMD5 != "" { 82 | headers[HEADER_MD5_CAMEL] = []string{input.ContentMD5} 83 | } 84 | if input.ContentSHA256 != "" { 85 | setHeaders(headers, HEADER_SHA256, []string{input.ContentSHA256}, isObs) 86 | } 87 | if input.Body != nil { 88 | data = input.Body 89 | } 90 | return 91 | } 92 | 93 | func (input CompleteMultipartUploadInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}, err error) { 94 | params = map[string]string{"uploadId": input.UploadId} 95 | if input.EncodingType != "" { 96 | params["encoding-type"] = input.EncodingType 97 | } 98 | data, _ = ConvertCompleteMultipartUploadInputToXml(input, false) 99 | return 100 | } 101 | 102 | func (input ListPartsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}, err error) { 103 | params = map[string]string{"uploadId": input.UploadId} 104 | if input.MaxParts > 0 { 105 | params["max-parts"] = IntToString(input.MaxParts) 106 | } 107 | if input.PartNumberMarker > 0 { 108 | params["part-number-marker"] = IntToString(input.PartNumberMarker) 109 | } 110 | if input.EncodingType != "" { 111 | params["encoding-type"] = input.EncodingType 112 | } 113 | return 114 | } 115 | 116 | func (input CopyPartInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}, err error) { 117 | params = map[string]string{"uploadId": input.UploadId, "partNumber": IntToString(input.PartNumber)} 118 | headers = make(map[string][]string, 1) 119 | var copySource string 120 | if input.CopySourceVersionId != "" { 121 | copySource = fmt.Sprintf("%s/%s?versionId=%s", input.CopySourceBucket, UrlEncode(input.CopySourceKey, false), input.CopySourceVersionId) 122 | } else { 123 | copySource = fmt.Sprintf("%s/%s", input.CopySourceBucket, UrlEncode(input.CopySourceKey, false)) 124 | } 125 | setHeaders(headers, HEADER_COPY_SOURCE, []string{copySource}, isObs) 126 | if input.CopySourceRangeStart >= 0 && input.CopySourceRangeEnd > input.CopySourceRangeStart { 127 | setHeaders(headers, HEADER_COPY_SOURCE_RANGE, []string{fmt.Sprintf("bytes=%d-%d", input.CopySourceRangeStart, input.CopySourceRangeEnd)}, isObs) 128 | } 129 | if input.CopySourceRange != "" { 130 | setHeaders(headers, HEADER_COPY_SOURCE_RANGE, []string{input.CopySourceRange}, isObs) 131 | } 132 | setSseHeader(headers, input.SseHeader, true, isObs) 133 | if input.SourceSseHeader != nil { 134 | if sseCHeader, ok := input.SourceSseHeader.(SseCHeader); ok { 135 | setHeaders(headers, HEADER_SSEC_COPY_SOURCE_ENCRYPTION, []string{sseCHeader.GetEncryption()}, isObs) 136 | setHeaders(headers, HEADER_SSEC_COPY_SOURCE_KEY, []string{sseCHeader.GetKey()}, isObs) 137 | setHeaders(headers, HEADER_SSEC_COPY_SOURCE_KEY_MD5, []string{sseCHeader.GetKeyMD5()}, isObs) 138 | } 139 | 140 | } 141 | return 142 | } 143 | -------------------------------------------------------------------------------- /examples/object_operations_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to do object-related operations 15 | * (such as create/delete/get/copy object, do object ACL) 16 | * on OBS using the OBS SDK for Go. 17 | */ 18 | package examples 19 | 20 | import ( 21 | "fmt" 22 | "io/ioutil" 23 | "obs" 24 | "strings" 25 | ) 26 | 27 | type ObjectOperationsSample struct { 28 | bucketName string 29 | objectKey string 30 | location string 31 | obsClient *obs.ObsClient 32 | } 33 | 34 | func newObjectOperationsSample(ak, sk, endpoint, bucketName, objectKey, location string) *ObjectOperationsSample { 35 | obsClient, err := obs.New(ak, sk, endpoint) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return &ObjectOperationsSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 40 | } 41 | 42 | func (sample ObjectOperationsSample) CreateBucket() { 43 | input := &obs.CreateBucketInput{} 44 | input.Bucket = sample.bucketName 45 | input.Location = sample.location 46 | _, err := sample.obsClient.CreateBucket(input) 47 | if err != nil { 48 | panic(err) 49 | } 50 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 51 | fmt.Println() 52 | } 53 | 54 | func (sample ObjectOperationsSample) GetObjectMeta() { 55 | input := &obs.GetObjectMetadataInput{} 56 | input.Bucket = sample.bucketName 57 | input.Key = sample.objectKey 58 | output, err := sample.obsClient.GetObjectMetadata(input) 59 | if err != nil { 60 | panic(err) 61 | } 62 | fmt.Printf("Object content-type:%s\n", output.ContentType) 63 | fmt.Printf("Object content-length:%d\n", output.ContentLength) 64 | fmt.Println() 65 | } 66 | 67 | func (sample ObjectOperationsSample) CreateObject() { 68 | input := &obs.PutObjectInput{} 69 | input.Bucket = sample.bucketName 70 | input.Key = sample.objectKey 71 | input.Body = strings.NewReader("Hello OBS") 72 | 73 | _, err := sample.obsClient.PutObject(input) 74 | if err != nil { 75 | panic(err) 76 | } 77 | fmt.Printf("Create object:%s successfully!\n", sample.objectKey) 78 | fmt.Println() 79 | } 80 | 81 | func (sample ObjectOperationsSample) GetObject() { 82 | input := &obs.GetObjectInput{} 83 | input.Bucket = sample.bucketName 84 | input.Key = sample.objectKey 85 | 86 | output, err := sample.obsClient.GetObject(input) 87 | if err != nil { 88 | panic(err) 89 | } 90 | defer func(){ 91 | errMsg := output.Body.Close() 92 | if errMsg != nil{ 93 | panic(errMsg) 94 | } 95 | }() 96 | fmt.Println("Object content:") 97 | body, err := ioutil.ReadAll(output.Body) 98 | if err != nil{ 99 | panic(err) 100 | } 101 | fmt.Println(string(body)) 102 | fmt.Println() 103 | } 104 | 105 | func (sample ObjectOperationsSample) CopyObject() { 106 | input := &obs.CopyObjectInput{} 107 | input.Bucket = sample.bucketName 108 | input.Key = sample.objectKey + "-back" 109 | input.CopySourceBucket = sample.bucketName 110 | input.CopySourceKey = sample.objectKey 111 | 112 | _, err := sample.obsClient.CopyObject(input) 113 | if err != nil { 114 | panic(err) 115 | } 116 | fmt.Println("Copy object successfully!") 117 | fmt.Println() 118 | } 119 | 120 | func (sample ObjectOperationsSample) DoObjectAcl() { 121 | input := &obs.SetObjectAclInput{} 122 | input.Bucket = sample.bucketName 123 | input.Key = sample.objectKey 124 | input.ACL = obs.AclPublicRead 125 | 126 | _, err := sample.obsClient.SetObjectAcl(input) 127 | if err != nil { 128 | panic(err) 129 | } 130 | fmt.Println("Set object acl successfully!") 131 | fmt.Println() 132 | 133 | output, err := sample.obsClient.GetObjectAcl(&obs.GetObjectAclInput{Bucket: sample.bucketName, Key: sample.objectKey}) 134 | if err != nil { 135 | panic(err) 136 | } 137 | fmt.Printf("Object owner - ownerId:%s, ownerName:%s\n", output.Owner.ID, output.Owner.DisplayName) 138 | for index, grant := range output.Grants { 139 | fmt.Printf("Grant[%d]\n", index) 140 | fmt.Printf("GranteeUri:%s, GranteeId:%s, GranteeName:%s\n", grant.Grantee.URI, grant.Grantee.ID, grant.Grantee.DisplayName) 141 | fmt.Printf("Permission:%s\n", grant.Permission) 142 | } 143 | } 144 | 145 | func (sample ObjectOperationsSample) DeleteObject() { 146 | input := &obs.DeleteObjectInput{} 147 | input.Bucket = sample.bucketName 148 | input.Key = sample.objectKey 149 | 150 | _, err := sample.obsClient.DeleteObject(input) 151 | if err != nil { 152 | panic(err) 153 | } 154 | fmt.Printf("Delete object:%s successfully!\n", input.Key) 155 | fmt.Println() 156 | 157 | input.Key = sample.objectKey + "-back" 158 | 159 | _, err = sample.obsClient.DeleteObject(input) 160 | if err != nil { 161 | panic(err) 162 | } 163 | fmt.Printf("Delete object:%s successfully!\n", input.Key) 164 | fmt.Println() 165 | } 166 | 167 | func RunObjectOperationsSample() { 168 | const ( 169 | endpoint = "https://your-endpoint" 170 | ak = "*** Provide your Access Key ***" 171 | sk = "*** Provide your Secret Key ***" 172 | bucketName = "bucket-test" 173 | objectKey = "object-test" 174 | location = "yourbucketlocation" 175 | ) 176 | 177 | sample := newObjectOperationsSample(ak, sk, endpoint, bucketName, objectKey, location) 178 | 179 | fmt.Println("Create a new bucket for demo") 180 | sample.CreateBucket() 181 | 182 | sample.CreateObject() 183 | 184 | sample.GetObjectMeta() 185 | 186 | sample.GetObject() 187 | 188 | sample.CopyObject() 189 | 190 | sample.DoObjectAcl() 191 | 192 | sample.DeleteObject() 193 | } 194 | -------------------------------------------------------------------------------- /obs/model_part.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "encoding/xml" 17 | "io" 18 | "time" 19 | ) 20 | 21 | // ListMultipartUploadsInput is the input parameter of ListMultipartUploads function 22 | type ListMultipartUploadsInput struct { 23 | Bucket string 24 | Prefix string 25 | MaxUploads int 26 | Delimiter string 27 | KeyMarker string 28 | UploadIdMarker string 29 | EncodingType string 30 | } 31 | 32 | // ListMultipartUploadsOutput is the result of ListMultipartUploads function 33 | type ListMultipartUploadsOutput struct { 34 | BaseModel 35 | XMLName xml.Name `xml:"ListMultipartUploadsResult"` 36 | Bucket string `xml:"Bucket"` 37 | KeyMarker string `xml:"KeyMarker"` 38 | NextKeyMarker string `xml:"NextKeyMarker"` 39 | UploadIdMarker string `xml:"UploadIdMarker"` 40 | NextUploadIdMarker string `xml:"NextUploadIdMarker"` 41 | Delimiter string `xml:"Delimiter"` 42 | IsTruncated bool `xml:"IsTruncated"` 43 | MaxUploads int `xml:"MaxUploads"` 44 | Prefix string `xml:"Prefix"` 45 | Uploads []Upload `xml:"Upload"` 46 | CommonPrefixes []string `xml:"CommonPrefixes>Prefix"` 47 | EncodingType string `xml:"EncodingType,omitempty"` 48 | } 49 | 50 | // AbortMultipartUploadInput is the input parameter of AbortMultipartUpload function 51 | type AbortMultipartUploadInput struct { 52 | Bucket string 53 | Key string 54 | UploadId string 55 | } 56 | 57 | // InitiateMultipartUploadInput is the input parameter of InitiateMultipartUpload function 58 | type InitiateMultipartUploadInput struct { 59 | ObjectOperationInput 60 | HttpHeader 61 | EncodingType string 62 | } 63 | 64 | // InitiateMultipartUploadOutput is the result of InitiateMultipartUpload function 65 | type InitiateMultipartUploadOutput struct { 66 | BaseModel 67 | XMLName xml.Name `xml:"InitiateMultipartUploadResult"` 68 | Bucket string `xml:"Bucket"` 69 | Key string `xml:"Key"` 70 | UploadId string `xml:"UploadId"` 71 | SseHeader ISseHeader 72 | EncodingType string `xml:"EncodingType,omitempty"` 73 | } 74 | 75 | // UploadPartInput is the input parameter of UploadPart function 76 | type UploadPartInput struct { 77 | Bucket string 78 | Key string 79 | PartNumber int 80 | UploadId string 81 | ContentMD5 string 82 | ContentSHA256 string 83 | SseHeader ISseHeader 84 | Body io.Reader 85 | SourceFile string 86 | Offset int64 87 | PartSize int64 88 | } 89 | 90 | // UploadPartOutput is the result of UploadPart function 91 | type UploadPartOutput struct { 92 | BaseModel 93 | PartNumber int 94 | ETag string 95 | SseHeader ISseHeader 96 | } 97 | 98 | // CompleteMultipartUploadInput is the input parameter of CompleteMultipartUpload function 99 | type CompleteMultipartUploadInput struct { 100 | Bucket string `xml:"-"` 101 | Key string `xml:"-"` 102 | UploadId string `xml:"-"` 103 | XMLName xml.Name `xml:"CompleteMultipartUpload"` 104 | Parts []Part `xml:"Part"` 105 | EncodingType string `xml:"-"` 106 | } 107 | 108 | // CompleteMultipartUploadOutput is the result of CompleteMultipartUpload function 109 | type CompleteMultipartUploadOutput struct { 110 | BaseModel 111 | VersionId string `xml:"-"` 112 | SseHeader ISseHeader `xml:"-"` 113 | XMLName xml.Name `xml:"CompleteMultipartUploadResult"` 114 | Location string `xml:"Location"` 115 | Bucket string `xml:"Bucket"` 116 | Key string `xml:"Key"` 117 | ETag string `xml:"ETag"` 118 | EncodingType string `xml:"EncodingType,omitempty"` 119 | CallbackBody 120 | } 121 | 122 | // ListPartsInput is the input parameter of ListParts function 123 | type ListPartsInput struct { 124 | Bucket string 125 | Key string 126 | UploadId string 127 | MaxParts int 128 | PartNumberMarker int 129 | EncodingType string 130 | } 131 | 132 | // ListPartsOutput is the result of ListParts function 133 | type ListPartsOutput struct { 134 | BaseModel 135 | XMLName xml.Name `xml:"ListPartsResult"` 136 | Bucket string `xml:"Bucket"` 137 | Key string `xml:"Key"` 138 | UploadId string `xml:"UploadId"` 139 | PartNumberMarker int `xml:"PartNumberMarker"` 140 | NextPartNumberMarker int `xml:"NextPartNumberMarker"` 141 | MaxParts int `xml:"MaxParts"` 142 | IsTruncated bool `xml:"IsTruncated"` 143 | StorageClass StorageClassType `xml:"StorageClass"` 144 | Initiator Initiator `xml:"Initiator"` 145 | Owner Owner `xml:"Owner"` 146 | Parts []Part `xml:"Part"` 147 | EncodingType string `xml:"EncodingType,omitempty"` 148 | } 149 | 150 | // CopyPartInput is the input parameter of CopyPart function 151 | type CopyPartInput struct { 152 | Bucket string 153 | Key string 154 | UploadId string 155 | PartNumber int 156 | CopySourceBucket string 157 | CopySourceKey string 158 | CopySourceVersionId string 159 | CopySourceRangeStart int64 160 | CopySourceRangeEnd int64 161 | CopySourceRange string 162 | SseHeader ISseHeader 163 | SourceSseHeader ISseHeader 164 | } 165 | 166 | // CopyPartOutput is the result of CopyPart function 167 | type CopyPartOutput struct { 168 | BaseModel 169 | XMLName xml.Name `xml:"CopyPartResult"` 170 | PartNumber int `xml:"-"` 171 | ETag string `xml:"ETag"` 172 | LastModified time.Time `xml:"LastModified"` 173 | SseHeader ISseHeader `xml:"-"` 174 | } 175 | -------------------------------------------------------------------------------- /README_CN.MD: -------------------------------------------------------------------------------- 1 | Version 3.25.9 2 | 3 | 新特性: 4 | 5 | 资料 & demo: 6 | 7 | 修复问题: 8 | 1. 修复range无法下载首字节的问题 9 | ----------------------------------------------------------------------------------- 10 | 11 | Version 3.25.4 12 | 13 | 新特性: 14 | 1. 支持在使用UploadFile方法时传递元数据信息。 15 | 16 | 资料 & demo: 17 | 18 | 修复问题: 19 | 20 | ----------------------------------------------------------------------------------- 21 | 22 | Version 3.25.3 23 | 24 | 新特性: 25 | 1. 新增BPA相关接口。 26 | 2. 支持智能分级特性。 27 | 28 | 资料 & demo: 29 | 1. 补充BPA相关接口描述。 30 | 31 | 修复问题: 32 | 33 | ----------------------------------------------------------------------------------- 34 | 35 | Version 3.24.9 36 | 37 | 新特性: 38 | 1. 新增listPosixObject接口。 39 | 40 | 资料 & demo: 41 | 42 | 修复问题: 43 | 44 | 1. 优化错误日志打印。 45 | 46 | ----------------------------------------------------------------------------------- 47 | 48 | Version 3.24.6 49 | 50 | 新特性: 51 | 1. 支持在使用CopyObject和InitiateMultipartUpload方法时传递元数据信息。 52 | 53 | 资料 & demo: 54 | 55 | 修复问题: 56 | 57 | 1. 修复部分场景下bucket acl配置失败的问题。 58 | 2. 修复重试场景下数据传输异常的问题。 59 | 60 | ----------------------------------------------------------------------------------- 61 | 62 | Version 3.24.3 63 | 64 | 新特性: 65 | 1. 新增accesslabel相关接口:设置对象accesslabel(SetDirAccesslabel), 获取对象accesslabel(GetDirAccesslabel), 删除对象accesslabel(DeleteDirAccesslabel); 66 | 67 | 资料&demo: 68 | 1. 补充accesslabel相关接口描述; 69 | 70 | 修复问题: 71 | 72 | ----------------------------------------------------------------------------------- 73 | 74 | Version 3.23.12 75 | 76 | 新特性: 77 | 1. 新增obs.WithDisableKeepAlive方法实现使用短连接功能。 78 | 2. 新增深度归档存储类别。 79 | 80 | 资料&demo: 81 | 82 | 修复问题: 83 | 1. 修复部分场景下设置桶的生命周期配置失败的问题。 84 | ----------------------------------------------------------------------------------- 85 | 86 | Version 3.23.9 87 | 88 | 新特性: 89 | 1. 新增obs.WithProgress方法支持进度条回调。 90 | 2. 新增配置生命周期管理规则中的碎片过期时间。 91 | 92 | 资料&demo: 93 | 94 | ----------------------------------------------------------------------------------- 95 | Version 3.23.4 96 | 97 | 新特性: 98 | 1. 新增obs.WithCustomHeader方法。 99 | 2. 新增自定义域名相关API,包括SetBucketCustomDomain,GetBucketCustomDomain,DeleteBucketCustomDomain。 100 | 3. 新增镜像回源相关API,包括SetBucketMirrorBackToSource、GetBucketMirrorBackToSource、DeleteBucketMirrorBackToSource。 101 | 102 | 资料&demo: 103 | 1. 补充自定义域名相关API的描述。 104 | 2. 补充镜像回源相关API的描述。 105 | 106 | ----------------------------------------------------------------------------------- 107 | Version 3.23.3 108 | 109 | 新特性: 110 | 1. 新增obs.WithCallbackHeader方法; 111 | 112 | 资料&demo: 113 | 1. 补充上传时回调的说明。 114 | 2. 补充SDK API扩展配置说明。 115 | 116 | 修复问题: 117 | 1. 优化部分代码实现 118 | 119 | ----------------------------------------------------------------------------------- 120 | Version 3.21.12 121 | 122 | 新特性: 123 | 1. 新增obs.WithTrafficLimitHeader方法实现单链接限速功能; 124 | 125 | 资料&demo: 126 | 1. 补充单链接限速描述; 127 | 2. 补充SDK接口扩展配置描述; 128 | 129 | 修复问题: 130 | 1. 优化部分代码实现 131 | 132 | ----------------------------------------------------------------------------------- 133 | 134 | Version 3.21.8 135 | 136 | 新特性: 137 | 1. 新增桶加密相关接口:设置桶加密配置(SetBucketEncryption), 获取桶加密配置(GetBucketEncryption), 删除桶加密配置(DeleteBucketEncryption); 138 | 2. 新增追加上传接口(AppendObject); 139 | 3. 新增修改写接口(ModifyObject); 140 | 4. 列举桶接口(ListBuckets)支持指定桶类型列举; 141 | 142 | 资料&demo: 143 | 1. 补充桶加密相关接口描述; 144 | 2. 补充追加上传接口描述; 145 | 3. 补充修改写接口描述; 146 | 4. 补充列举桶新增参数说明; 147 | 148 | 修复问题: 149 | 150 | ----------------------------------------------------------------------------------- 151 | 152 | Version 3.21.1 153 | 154 | 新特性: 155 | 156 | 资料&demo: 157 | 158 | 修复问题: 159 | 1. 修复代码在指定重试次数内重试成功时仍会返回失败错误信息的问题; 160 | 2. 修复代码在解析响应失败时未返回错误信息的问题; 161 | 162 | ----------------------------------------------------------------------------------- 163 | 164 | Version 3.20.7.1 165 | 166 | 新特性: 167 | 1. 新增断点续传上传接口(ObsClient.UploadFile)及断点续传下载接口(ObsClient.DownloadFile); 168 | 169 | 资料&demo: 170 | 1. 开发指南上传对象章节新增断点续传上传接口的相关描述,下载对象章节新增断点续传下载接口的相关描述; 171 | 2. 接口参考其他接口章节新增断点续传上传及断点续传下载的相关描述; 172 | 173 | 修复问题: 174 | 1. 优化部分代码实现; 175 | 176 | ----------------------------------------------------------------------------------- 177 | 178 | Version 3.20.7 179 | 180 | 新特性: 181 | 182 | 资料&demo: 183 | 184 | 修复问题: 185 | 1. 优化部分代码实现; 186 | 187 | ----------------------------------------------------------------------------------- 188 | Version 3.20.3 189 | 190 | 新特性: 191 | 1. 新增判断对象是否存在的接口(ObsClient.HeadObject); 192 | 2. 新增设置桶请求者付费配置和获取桶请求者付费配置的接口(ObsClient.SetBucketRequestPayment/ObsClient.GetBucketRequestPayment); 193 | 3. 新增对请求者付费头域的支持(obs.WithReqPaymentHeader),可以在调用接口时指定配置携带请求者付费头域; 194 | 195 | 资料&demo: 196 | 1. 开发指南文档管理对象章节新增判断对象是否存在,接口参考文档对象相关接口章节新增判断对象是否存在,数据类型章节新增判断对象是否存在请求参数; 197 | 2. 开发指南文档管理桶章节新增管理桶请求者付费,接口参考文档桶相关接口章节新增设置桶请求者付费配置和获取桶请求者付费配置,数据类型章节新增设置桶请求者付费配置请求参数及获取桶请求者付费配置响应结果; 198 | 3. 接口参考文档初始化章节新增SDK接口拓展配置; 199 | 4. 接口参考文档枚举常量章节新增请求者付费配置; 200 | 5. 接口参考文档各接口方法定义中新增对拓展参数的描述; 201 | 202 | 修复问题: 203 | 1. 优化部分代码实现; 204 | 2. 修复CreateBucket接口在未指定StorageClass参数情况下指定Epid参数不生效的问题; 205 | 3. CreateBucket接口支持指定az-redundancy参数; 206 | 207 | ----------------------------------------------------------------------------------- 208 | 209 | Version 3.20.1 210 | 211 | 新特性: 212 | 213 | 资料&demo: 214 | 215 | 修复问题: 216 | 1. 优化部分代码实现; 217 | 218 | ----------------------------------------------------------------------------------- 219 | 220 | Version 3.19.11 221 | 更新发布版本号,新的版本号命名方式:主版本号.年标识.月标识。 222 | 223 | 新特性: 224 | 225 | 资料&demo: 226 | 1. 接口参考ObsClient初始化章节,新增WithMaxRedirectCount及WithSecurityToken的相关描述; 227 | 2. 开发指南配置OBS客户端章节,新增WithMaxRedirectCount及WithSecurityToken的相关描述; 228 | 3. 开发指南常见问题章节,新增HTTP状态码为405时的相关处理; 229 | 230 | 修复问题: 231 | 1. 修复携带DisPlayName参数可能造成返回400错误的问题; 232 | 2. 修复SetBucketNotification接口返回400错误的问题; 233 | 3. 修复SetObjectAcl接口不支持指定Delivered参数的问题; 234 | 4. 修复SetBucketLoggingConfiguration接口在不同协议下对Agency字段的兼容问题; 235 | 5. 修复SetBucketLoggingConfiguration接口对Delivered字段的处理问题; 236 | 6. 修复UploadPart接口设置ContentMD5不生效的问题; 237 | 7. 修复SetBucketStoragePolicy接口在不同协议下对错误存储类型处理行为不一致的问题; 238 | 8. 修复CreateBucket接口未区分协议造成返回400错误的问题; 239 | 9. 修复getBucketAclObs接口的语法问题; 240 | 10. 修复SetBucketWebsiteConfiguration接口的错误; 241 | 11. 修复临时鉴权接口在不同协议下的兼容问题; 242 | 12. 修复在GET请求返回302情况下进行重定向会添加鉴权信息头的问题; 243 | 13. 修复根据文件名后缀获取content-type类型时不支持大写文件名后缀的问题; 244 | 14. 修改svg格式后缀的文件对应获取的content-type类型为image/svg+xml; 245 | 15. 修复examples/bucket_operations_sample.go中删除桶示例调用错误接口的问题。 246 | 16. 修复通过临时访问密钥访问存在的计算鉴权问题; 247 | 17. 修复匿名访问方式下,部分响应字段为空的问题。 248 | 249 | ----------------------------------------------------------------------------------- 250 | 251 | Version 3.1.2 252 | 253 | 新特性: 254 | 255 | 资料&demo: 256 | 257 | 修复问题: 258 | 1. 修复Obs.InitiateMultipartUpload设置ContentType不生效的问题; 259 | 260 | ----------------------------------------------------------------------------------- 261 | 262 | Version 3.1.1 263 | 264 | 新特性: 265 | 1. 支持配置自定义域名访问OBS服务(obs.WithCustomDomainName); 266 | 2. 上传对象ObsClient.PutObject支持自动识别更广泛的MIME类型; 267 | 268 | 修复问题: 269 | 1. 修复ObsClient.GetBucketStoragePolicy报错空指针的问题; 270 | 2. 修复ObsClient.SetBucketStoragePolicy报错的400的问题; 271 | -------------------------------------------------------------------------------- /examples/list_objects_in_folder_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to list objects under a specified folder of a bucket 15 | * from OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "obs" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | type ListObjectsInFolderSample struct { 27 | bucketName string 28 | location string 29 | obsClient *obs.ObsClient 30 | } 31 | 32 | func newListObjectsInFolderSample(ak, sk, endpoint, bucketName, location string) *ListObjectsInFolderSample { 33 | obsClient, err := obs.New(ak, sk, endpoint) 34 | if err != nil { 35 | panic(err) 36 | } 37 | return &ListObjectsInFolderSample{obsClient: obsClient, bucketName: bucketName, location: location} 38 | } 39 | 40 | func (sample ListObjectsInFolderSample) CreateBucket() { 41 | input := &obs.CreateBucketInput{} 42 | input.Bucket = sample.bucketName 43 | input.Location = sample.location 44 | _, err := sample.obsClient.CreateBucket(input) 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 49 | fmt.Println() 50 | } 51 | 52 | func (sample ListObjectsInFolderSample) prepareObjects(input *obs.PutObjectInput){ 53 | _, err := sample.obsClient.PutObject(input) 54 | if err != nil { 55 | panic(err) 56 | } 57 | } 58 | 59 | func (sample ListObjectsInFolderSample) PrepareFoldersAndObjects() { 60 | 61 | keyPrefix := "MyObjectKeyFolders" 62 | folderPrefix := "src" 63 | subFolderPrefix := "test" 64 | 65 | input := &obs.PutObjectInput{} 66 | input.Bucket = sample.bucketName 67 | 68 | // First prepare folders and sub folders 69 | for i := 0; i < 5; i++ { 70 | key := folderPrefix + strconv.Itoa(i) + "/" 71 | input.Key = key 72 | sample.prepareObjects(input) 73 | for j := 0; j < 3; j++ { 74 | subKey := key + subFolderPrefix + strconv.Itoa(j) + "/" 75 | input.Key = subKey 76 | sample.prepareObjects(input) 77 | } 78 | } 79 | 80 | // Insert 2 objects in each folder 81 | input.Body = strings.NewReader("Hello OBS") 82 | listObjectsInput := &obs.ListObjectsInput{} 83 | listObjectsInput.Bucket = sample.bucketName 84 | output, err := sample.obsClient.ListObjects(listObjectsInput) 85 | if err != nil{ 86 | panic(err) 87 | } 88 | for _, content := range output.Contents { 89 | for i := 0; i < 2; i++ { 90 | objectKey := content.Key + keyPrefix + strconv.Itoa(i) 91 | input.Key = objectKey 92 | sample.prepareObjects(input) 93 | } 94 | } 95 | 96 | // Insert 2 objects in root path 97 | input.Key = keyPrefix + strconv.Itoa(0) 98 | sample.prepareObjects(input) 99 | input.Key = keyPrefix + strconv.Itoa(1) 100 | sample.prepareObjects(input) 101 | 102 | fmt.Println("Prepare folders and objects finished") 103 | fmt.Println() 104 | } 105 | 106 | func (sample ListObjectsInFolderSample) ListObjectsInFolders() { 107 | fmt.Println("List objects in folder src0/") 108 | input := &obs.ListObjectsInput{} 109 | input.Bucket = sample.bucketName 110 | input.Prefix = "src0/" 111 | output, err := sample.obsClient.ListObjects(input) 112 | if err != nil { 113 | panic(err) 114 | } 115 | for index, val := range output.Contents { 116 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 117 | index, val.ETag, val.Key, val.Size) 118 | } 119 | 120 | fmt.Println() 121 | 122 | fmt.Println("List objects in sub folder src0/test0/") 123 | 124 | input.Prefix = "src0/test0/" 125 | output, err = sample.obsClient.ListObjects(input) 126 | if err != nil { 127 | panic(err) 128 | } 129 | for index, val := range output.Contents { 130 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 131 | index, val.ETag, val.Key, val.Size) 132 | } 133 | 134 | fmt.Println() 135 | } 136 | 137 | func (sample ListObjectsInFolderSample) listObjectsByPrefixes(commonPrefixes []string) { 138 | input := &obs.ListObjectsInput{} 139 | input.Bucket = sample.bucketName 140 | input.Delimiter = "/" 141 | for _, prefix := range commonPrefixes { 142 | input.Prefix = prefix 143 | output, err := sample.obsClient.ListObjects(input) 144 | if err != nil { 145 | panic(err) 146 | } 147 | fmt.Printf("Folder %s:\n", prefix) 148 | for index, val := range output.Contents { 149 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 150 | index, val.ETag, val.Key, val.Size) 151 | } 152 | fmt.Println() 153 | sample.listObjectsByPrefixes(output.CommonPrefixes) 154 | } 155 | } 156 | 157 | func (sample ListObjectsInFolderSample) ListObjectsGroupByFolder() { 158 | fmt.Println("List objects group by folder") 159 | input := &obs.ListObjectsInput{} 160 | input.Bucket = sample.bucketName 161 | input.Delimiter = "/" 162 | output, err := sample.obsClient.ListObjects(input) 163 | if err != nil { 164 | panic(err) 165 | } 166 | fmt.Println("Root path:") 167 | for index, val := range output.Contents { 168 | fmt.Printf("Content[%d]-ETag:%s, Key:%s, Size:%d\n", 169 | index, val.ETag, val.Key, val.Size) 170 | } 171 | fmt.Println() 172 | sample.listObjectsByPrefixes(output.CommonPrefixes) 173 | } 174 | 175 | func (sample ListObjectsInFolderSample) BatchDeleteObjects() { 176 | input := &obs.ListObjectsInput{} 177 | input.Bucket = sample.bucketName 178 | output, err := sample.obsClient.ListObjects(input) 179 | if err != nil { 180 | panic(err) 181 | } 182 | objects := make([]obs.ObjectToDelete, 0, len(output.Contents)) 183 | for _, content := range output.Contents { 184 | objects = append(objects, obs.ObjectToDelete{Key: content.Key}) 185 | } 186 | deleteObjectsInput := &obs.DeleteObjectsInput{} 187 | deleteObjectsInput.Bucket = sample.bucketName 188 | deleteObjectsInput.Objects = objects[:] 189 | _, err = sample.obsClient.DeleteObjects(deleteObjectsInput) 190 | if err != nil { 191 | panic(err) 192 | } 193 | fmt.Println("Delete objects successfully!") 194 | fmt.Println() 195 | } 196 | 197 | func RunListObjectsInFolderSample() { 198 | const ( 199 | endpoint = "https://your-endpoint" 200 | ak = "*** Provide your Access Key ***" 201 | sk = "*** Provide your Secret Key ***" 202 | bucketName = "bucket-test" 203 | location = "yourbucketlocation" 204 | ) 205 | 206 | sample := newListObjectsInFolderSample(ak, sk, endpoint, bucketName, location) 207 | 208 | fmt.Println("Create a new bucket for demo") 209 | sample.CreateBucket() 210 | 211 | // First prepare folders and objects 212 | sample.PrepareFoldersAndObjects() 213 | 214 | // List objects in folders 215 | sample.ListObjectsInFolders() 216 | 217 | // List all objects group by folder 218 | sample.ListObjectsGroupByFolder() 219 | 220 | sample.BatchDeleteObjects() 221 | } 222 | -------------------------------------------------------------------------------- /examples/concurrent_upload_part_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to multipart upload an object concurrently 15 | * from OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "math/rand" 23 | "obs" 24 | "os" 25 | "path/filepath" 26 | "time" 27 | ) 28 | 29 | type ConcurrentUploadPartSample struct { 30 | bucketName string 31 | objectKey string 32 | location string 33 | obsClient *obs.ObsClient 34 | } 35 | 36 | func newConcurrentUploadPartSample(ak, sk, endpoint, bucketName, objectKey, location string) *ConcurrentUploadPartSample { 37 | obsClient, err := obs.New(ak, sk, endpoint) 38 | if err != nil { 39 | panic(err) 40 | } 41 | return &ConcurrentUploadPartSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 42 | } 43 | 44 | func (sample ConcurrentUploadPartSample) CreateBucket() { 45 | input := &obs.CreateBucketInput{} 46 | input.Bucket = sample.bucketName 47 | input.Location = sample.location 48 | _, err := sample.obsClient.CreateBucket(input) 49 | if err != nil { 50 | panic(err) 51 | } 52 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 53 | fmt.Println() 54 | } 55 | 56 | func (sample ConcurrentUploadPartSample) checkError(err error) { 57 | if err != nil { 58 | panic(err) 59 | } 60 | } 61 | 62 | func (sample ConcurrentUploadPartSample) createSampleFile(sampleFilePath string, byteCount int64) { 63 | if err := os.MkdirAll(filepath.Dir(sampleFilePath), os.ModePerm); err != nil { 64 | panic(err) 65 | } 66 | 67 | fd, err := os.OpenFile(sampleFilePath, os.O_CREATE|os.O_TRUNC, 0600) 68 | if err != nil { 69 | panic(errors.New("open file with error")) 70 | } 71 | 72 | const chunkSize = 1024 73 | b := [chunkSize]byte{} 74 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 75 | for i := 0; i < chunkSize; i++ { 76 | b[i] = uint8(r.Intn(255)) 77 | } 78 | 79 | var writedCount int64 80 | for { 81 | remainCount := byteCount - writedCount 82 | if remainCount <= 0 { 83 | break 84 | } 85 | if remainCount > chunkSize { 86 | _, errMsg := fd.Write(b[:]) 87 | sample.checkError(errMsg) 88 | writedCount += chunkSize 89 | } else { 90 | _, errMsg := fd.Write(b[:remainCount]) 91 | sample.checkError(errMsg) 92 | writedCount += remainCount 93 | } 94 | } 95 | 96 | defer func() { 97 | errMsg := fd.Close() 98 | sample.checkError(errMsg) 99 | }() 100 | err = fd.Sync() 101 | sample.checkError(err) 102 | } 103 | 104 | func (sample ConcurrentUploadPartSample) PutFile(sampleFilePath string) { 105 | input := &obs.PutFileInput{} 106 | input.Bucket = sample.bucketName 107 | input.Key = sample.objectKey 108 | input.SourceFile = sampleFilePath 109 | _, err := sample.obsClient.PutFile(input) 110 | if err != nil { 111 | panic(err) 112 | } 113 | } 114 | 115 | func (sample ConcurrentUploadPartSample) DoConcurrentUploadPart(sampleFilePath string) { 116 | // Claim a upload id firstly 117 | input := &obs.InitiateMultipartUploadInput{} 118 | input.Bucket = sample.bucketName 119 | input.Key = sample.objectKey 120 | output, err := sample.obsClient.InitiateMultipartUpload(input) 121 | if err != nil { 122 | panic(err) 123 | } 124 | uploadId := output.UploadId 125 | 126 | fmt.Printf("Claiming a new upload id %s\n", uploadId) 127 | fmt.Println() 128 | 129 | // Calculate how many blocks to be divided 130 | // 5MB 131 | var partSize int64 = 5 * 1024 * 1024 132 | 133 | stat, err := os.Stat(sampleFilePath) 134 | if err != nil { 135 | panic(err) 136 | } 137 | fileSize := stat.Size() 138 | 139 | partCount := int(fileSize / partSize) 140 | 141 | if fileSize%partSize != 0 { 142 | partCount++ 143 | } 144 | fmt.Printf("Total parts count %d\n", partCount) 145 | fmt.Println() 146 | 147 | // Upload parts 148 | fmt.Println("Begin to upload parts to OBS") 149 | 150 | partChan := make(chan obs.Part, 5) 151 | 152 | for i := 0; i < partCount; i++ { 153 | partNumber := i + 1 154 | offset := int64(i) * partSize 155 | currPartSize := partSize 156 | if i+1 == partCount { 157 | currPartSize = fileSize - offset 158 | } 159 | go func(index int, offset, partSize int64) { 160 | uploadPartInput := &obs.UploadPartInput{} 161 | uploadPartInput.Bucket = sample.bucketName 162 | uploadPartInput.Key = sample.objectKey 163 | uploadPartInput.UploadId = uploadId 164 | uploadPartInput.SourceFile = sampleFilePath 165 | uploadPartInput.PartNumber = index 166 | uploadPartInput.Offset = offset 167 | uploadPartInput.PartSize = partSize 168 | uploadPartInputOutput, errMsg := sample.obsClient.UploadPart(uploadPartInput) 169 | if errMsg == nil { 170 | fmt.Printf("%d finished\n", index) 171 | partChan <- obs.Part{ETag: uploadPartInputOutput.ETag, PartNumber: uploadPartInputOutput.PartNumber} 172 | } else { 173 | panic(errMsg) 174 | } 175 | }(partNumber, offset, currPartSize) 176 | } 177 | 178 | parts := make([]obs.Part, 0, partCount) 179 | 180 | for { 181 | part, ok := <-partChan 182 | if !ok { 183 | break 184 | } 185 | parts = append(parts, part) 186 | if len(parts) == partCount { 187 | close(partChan) 188 | } 189 | } 190 | 191 | fmt.Println() 192 | fmt.Println("Completing to upload multiparts") 193 | completeMultipartUploadInput := &obs.CompleteMultipartUploadInput{} 194 | completeMultipartUploadInput.Bucket = sample.bucketName 195 | completeMultipartUploadInput.Key = sample.objectKey 196 | completeMultipartUploadInput.UploadId = uploadId 197 | completeMultipartUploadInput.Parts = parts 198 | sample.doCompleteMultipartUpload(completeMultipartUploadInput) 199 | } 200 | 201 | func (sample ConcurrentUploadPartSample) doCompleteMultipartUpload(input *obs.CompleteMultipartUploadInput) { 202 | _, err := sample.obsClient.CompleteMultipartUpload(input) 203 | if err != nil { 204 | panic(err) 205 | } 206 | fmt.Println("Complete multiparts finished") 207 | } 208 | 209 | func RunConcurrentUploadPartSample() { 210 | const ( 211 | endpoint = "https://your-endpoint" 212 | ak = "*** Provide your Access Key ***" 213 | sk = "*** Provide your Secret Key ***" 214 | bucketName = "bucket-test" 215 | objectKey = "object-test" 216 | location = "yourbucketlocation" 217 | ) 218 | 219 | sample := newConcurrentUploadPartSample(ak, sk, endpoint, bucketName, objectKey, location) 220 | 221 | fmt.Println("Create a new bucket for demo") 222 | sample.CreateBucket() 223 | 224 | //60MB file 225 | sampleFilePath := "/temp/uploadText.txt" 226 | sample.createSampleFile(sampleFilePath, 1024*1024*60) 227 | 228 | sample.DoConcurrentUploadPart(sampleFilePath) 229 | } 230 | -------------------------------------------------------------------------------- /examples/concurrent_download_object_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to download an object concurrently 15 | * from OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "math/rand" 23 | "obs" 24 | "os" 25 | "path/filepath" 26 | "sync" 27 | "time" 28 | ) 29 | 30 | type ConcurrentDownloadObjectSample struct { 31 | bucketName string 32 | objectKey string 33 | location string 34 | obsClient *obs.ObsClient 35 | } 36 | 37 | func newConcurrentDownloadObjectSample(ak, sk, endpoint, bucketName, objectKey, location string) *ConcurrentDownloadObjectSample { 38 | obsClient, err := obs.New(ak, sk, endpoint, obs.WithPathStyle(true)) 39 | if err != nil { 40 | panic(err) 41 | } 42 | return &ConcurrentDownloadObjectSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 43 | } 44 | 45 | func (sample ConcurrentDownloadObjectSample) CreateBucket() { 46 | input := &obs.CreateBucketInput{} 47 | input.Bucket = sample.bucketName 48 | input.Location = sample.location 49 | _, err := sample.obsClient.CreateBucket(input) 50 | if err != nil { 51 | panic(err) 52 | } 53 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 54 | fmt.Println() 55 | } 56 | 57 | func (sample ConcurrentDownloadObjectSample) createSampleFile(sampleFilePath string, byteCount int64) { 58 | if err := os.MkdirAll(filepath.Dir(sampleFilePath), os.ModePerm); err != nil { 59 | panic(err) 60 | } 61 | 62 | fd, err := os.OpenFile(sampleFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) 63 | if err != nil { 64 | panic(errors.New("open file with error")) 65 | } 66 | 67 | const chunkSize = 1024 68 | b := [chunkSize]byte{} 69 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 70 | for i := 0; i < chunkSize; i++ { 71 | b[i] = uint8(r.Intn(255)) 72 | } 73 | 74 | var writedCount int64 75 | for { 76 | remainCount := byteCount - writedCount 77 | if remainCount <= 0 { 78 | break 79 | } 80 | if remainCount > chunkSize { 81 | _, errMsg := fd.Write(b[:]) 82 | sample.checkError(errMsg) 83 | writedCount += chunkSize 84 | } else { 85 | _, errMsg := fd.Write(b[:remainCount]) 86 | sample.checkError(errMsg) 87 | writedCount += remainCount 88 | } 89 | } 90 | 91 | defer func(){ 92 | errMsg := fd.Close() 93 | sample.checkError(errMsg) 94 | }() 95 | err = fd.Sync() 96 | sample.checkError(err) 97 | } 98 | 99 | func (sample ConcurrentDownloadObjectSample) PutFile(sampleFilePath string) { 100 | input := &obs.PutFileInput{} 101 | input.Bucket = sample.bucketName 102 | input.Key = sample.objectKey 103 | input.SourceFile = sampleFilePath 104 | _, err := sample.obsClient.PutFile(input) 105 | if err != nil { 106 | panic(err) 107 | } 108 | } 109 | 110 | func (sample ConcurrentDownloadObjectSample) checkError(err error){ 111 | if err != nil{ 112 | panic(err) 113 | } 114 | } 115 | 116 | func (sample ConcurrentDownloadObjectSample) DoConcurrentDownload(sampleFilePath string) { 117 | 118 | // Get size of the object 119 | getObjectMetadataInput := &obs.GetObjectMetadataInput{} 120 | getObjectMetadataInput.Bucket = sample.bucketName 121 | getObjectMetadataInput.Key = sample.objectKey 122 | getObjectMetadataOutput, err := sample.obsClient.GetObjectMetadata(getObjectMetadataInput) 123 | sample.checkError(err) 124 | 125 | objectSize := getObjectMetadataOutput.ContentLength 126 | 127 | // Calculate how many blocks to be divided 128 | // 5MB 129 | var partSize int64 = 1024 * 1024 * 5 130 | partCount := int(objectSize / partSize) 131 | 132 | if objectSize%partSize != 0 { 133 | partCount++ 134 | } 135 | 136 | fmt.Printf("Total parts count %d\n", partCount) 137 | fmt.Println() 138 | 139 | downloadFilePath := filepath.Dir(sampleFilePath) + "/" + sample.objectKey 140 | 141 | var wg sync.WaitGroup 142 | wg.Add(partCount) 143 | 144 | fd, err := os.OpenFile(downloadFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) 145 | if err != nil { 146 | panic(errors.New("open file with error")) 147 | } 148 | 149 | err = fd.Close() 150 | sample.checkError(err) 151 | 152 | //Download the object concurrently 153 | fmt.Printf("Start to download %s \n", sample.objectKey) 154 | 155 | for i := 0; i < partCount; i++ { 156 | index := i + 1 157 | rangeStart := int64(i) * partSize 158 | rangeEnd := rangeStart + partSize - 1 159 | if index == partCount { 160 | rangeEnd = objectSize - 1 161 | } 162 | go func(start, end int64, num int) { 163 | defer wg.Done() 164 | getObjectInput := &obs.GetObjectInput{} 165 | getObjectInput.Bucket = sample.bucketName 166 | getObjectInput.Key = sample.objectKey 167 | getObjectInput.RangeStart = start 168 | getObjectInput.RangeEnd = end 169 | getObjectOutput, err := sample.obsClient.GetObject(getObjectInput) 170 | if err == nil { 171 | defer func(){ 172 | errMsg := getObjectOutput.Body.Close() 173 | sample.checkError(errMsg) 174 | }() 175 | wfd, err := os.OpenFile(downloadFilePath, os.O_WRONLY, 0600) 176 | sample.checkError(err) 177 | b := make([]byte, 1024) 178 | for { 179 | n, err := getObjectOutput.Body.Read(b) 180 | if n > 0 { 181 | wcnt, err := wfd.WriteAt(b[0:n], start) 182 | sample.checkError(err) 183 | if n != wcnt { 184 | panic(fmt.Sprintf("wcnt %d, n %d", wcnt, n)) 185 | } 186 | start += int64(n) 187 | } 188 | 189 | if err != nil { 190 | break 191 | } 192 | } 193 | errMsg := wfd.Sync() 194 | sample.checkError(errMsg) 195 | errMsg = wfd.Close() 196 | sample.checkError(errMsg) 197 | fmt.Printf("%d finished\n", num) 198 | } else { 199 | panic(err) 200 | } 201 | }(rangeStart, rangeEnd, index) 202 | } 203 | wg.Wait() 204 | 205 | fmt.Printf("Download object finished, downloadPath:%s\n", downloadFilePath) 206 | } 207 | 208 | func RunConcurrentDownloadObjectSample() { 209 | const ( 210 | endpoint = "https://your-endpoint" 211 | ak = "*** Provide your Access Key ***" 212 | sk = "*** Provide your Secret Key ***" 213 | bucketName = "bucket-test" 214 | objectKey = "object-test" 215 | location = "yourbucketlocation" 216 | ) 217 | 218 | sample := newConcurrentDownloadObjectSample(ak, sk, endpoint, bucketName, objectKey, location) 219 | 220 | fmt.Println("Create a new bucket for demo") 221 | sample.CreateBucket() 222 | 223 | //60MB file 224 | sampleFilePath := "/temp/uploadText.txt" 225 | sample.createSampleFile(sampleFilePath, 1024*1024*60) 226 | //Upload an object to your source bucket 227 | sample.PutFile(sampleFilePath) 228 | 229 | sample.DoConcurrentDownload(sampleFilePath) 230 | 231 | } 232 | -------------------------------------------------------------------------------- /examples/concurrent_copy_part_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to multipart upload an object concurrently by copy mode 15 | * to OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "math/rand" 23 | "obs" 24 | "os" 25 | "path/filepath" 26 | "time" 27 | ) 28 | 29 | const ( 30 | filePathSample string = "/temp/text.txt" 31 | ) 32 | 33 | type ConcurrentCopyPartSample struct { 34 | bucketName string 35 | objectKey string 36 | location string 37 | obsClient *obs.ObsClient 38 | } 39 | 40 | func newConcurrentCopyPartSample(ak, sk, endpoint, bucketName, objectKey, location string) *ConcurrentCopyPartSample { 41 | obsClient, err := obs.New(ak, sk, endpoint) 42 | if err != nil { 43 | panic(err) 44 | } 45 | return &ConcurrentCopyPartSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location} 46 | } 47 | 48 | func (sample ConcurrentCopyPartSample) CreateBucket() { 49 | input := &obs.CreateBucketInput{} 50 | input.Bucket = sample.bucketName 51 | input.Location = sample.location 52 | _, err := sample.obsClient.CreateBucket(input) 53 | if err != nil { 54 | panic(err) 55 | } 56 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 57 | fmt.Println() 58 | } 59 | 60 | func (sample ConcurrentCopyPartSample) checkError(err error) { 61 | if err != nil { 62 | panic(err) 63 | } 64 | } 65 | 66 | func (sample ConcurrentCopyPartSample) createSampleFile(sampleFilePath string, byteCount int64) { 67 | if err := os.MkdirAll(filepath.Dir(sampleFilePath), os.ModePerm); err != nil { 68 | panic(err) 69 | } 70 | 71 | fd, err := os.OpenFile(sampleFilePath, os.O_CREATE|os.O_TRUNC, 0600) 72 | if err != nil { 73 | panic(errors.New("open file with error")) 74 | } 75 | 76 | const chunkSize = 1024 77 | b := [chunkSize]byte{} 78 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 79 | for i := 0; i < chunkSize; i++ { 80 | b[i] = uint8(r.Intn(255)) 81 | } 82 | 83 | var writedCount int64 84 | for { 85 | remainCount := byteCount - writedCount 86 | if remainCount <= 0 { 87 | break 88 | } 89 | if remainCount > chunkSize { 90 | _, errMsg := fd.Write(b[:]) 91 | sample.checkError(errMsg) 92 | writedCount += chunkSize 93 | } else { 94 | _, errMsg := fd.Write(b[:remainCount]) 95 | sample.checkError(errMsg) 96 | writedCount += remainCount 97 | } 98 | } 99 | 100 | defer func() { 101 | errMsg := fd.Close() 102 | sample.checkError(errMsg) 103 | }() 104 | 105 | err = fd.Sync() 106 | sample.checkError(err) 107 | } 108 | 109 | func (sample ConcurrentCopyPartSample) PutFile(sampleFilePath string) { 110 | input := &obs.PutFileInput{} 111 | input.Bucket = sample.bucketName 112 | input.Key = sample.objectKey 113 | input.SourceFile = sampleFilePath 114 | _, err := sample.obsClient.PutFile(input) 115 | if err != nil { 116 | panic(err) 117 | } 118 | } 119 | 120 | func (sample ConcurrentCopyPartSample) DoConcurrentCopyPart() { 121 | destBucketName := sample.bucketName 122 | destObjectKey := sample.objectKey + "-back" 123 | sourceBucketName := sample.bucketName 124 | sourceObjectKey := sample.objectKey 125 | // Claim a upload id firstly 126 | input := &obs.InitiateMultipartUploadInput{} 127 | input.Bucket = destBucketName 128 | input.Key = destObjectKey 129 | output, err := sample.obsClient.InitiateMultipartUpload(input) 130 | if err != nil { 131 | panic(err) 132 | } 133 | uploadId := output.UploadId 134 | 135 | fmt.Printf("Claiming a new upload id %s\n", uploadId) 136 | fmt.Println() 137 | 138 | // Get size of the object 139 | getObjectMetadataInput := &obs.GetObjectMetadataInput{} 140 | getObjectMetadataInput.Bucket = sourceBucketName 141 | getObjectMetadataInput.Key = sourceObjectKey 142 | getObjectMetadataOutput, err := sample.obsClient.GetObjectMetadata(getObjectMetadataInput) 143 | if err != nil { 144 | panic(err) 145 | } 146 | 147 | objectSize := getObjectMetadataOutput.ContentLength 148 | 149 | // Calculate how many blocks to be divided 150 | // 5MB 151 | var partSize int64 = 5 * 1024 * 1024 152 | partCount := int(objectSize / partSize) 153 | 154 | if objectSize%partSize != 0 { 155 | partCount++ 156 | } 157 | 158 | fmt.Printf("Total parts count %d\n", partCount) 159 | fmt.Println() 160 | 161 | // Upload multiparts by copy mode 162 | fmt.Println("Begin to upload multiparts to OBS by copy mode") 163 | 164 | partChan := make(chan obs.Part, 5) 165 | 166 | for i := 0; i < partCount; i++ { 167 | partNumber := i + 1 168 | rangeStart := int64(i) * partSize 169 | rangeEnd := rangeStart + partSize - 1 170 | if i+1 == partCount { 171 | rangeEnd = objectSize - 1 172 | } 173 | go func(start, end int64, index int) { 174 | copyPartInput := &obs.CopyPartInput{} 175 | copyPartInput.Bucket = destBucketName 176 | copyPartInput.Key = destObjectKey 177 | copyPartInput.UploadId = uploadId 178 | copyPartInput.PartNumber = index 179 | copyPartInput.CopySourceBucket = sourceBucketName 180 | copyPartInput.CopySourceKey = sourceObjectKey 181 | copyPartInput.CopySourceRangeStart = start 182 | copyPartInput.CopySourceRangeEnd = end 183 | copyPartOutput, errMsg := sample.obsClient.CopyPart(copyPartInput) 184 | if errMsg == nil { 185 | fmt.Printf("%d finished\n", index) 186 | partChan <- obs.Part{ETag: copyPartOutput.ETag, PartNumber: copyPartOutput.PartNumber} 187 | } else { 188 | panic(errMsg) 189 | } 190 | }(rangeStart, rangeEnd, partNumber) 191 | } 192 | 193 | parts := make([]obs.Part, 0, partCount) 194 | 195 | for { 196 | part, ok := <-partChan 197 | if !ok { 198 | break 199 | } 200 | parts = append(parts, part) 201 | if len(parts) == partCount { 202 | close(partChan) 203 | } 204 | } 205 | 206 | fmt.Println() 207 | fmt.Println("Completing to upload multiparts") 208 | completeMultipartUploadInput := &obs.CompleteMultipartUploadInput{} 209 | completeMultipartUploadInput.Bucket = destBucketName 210 | completeMultipartUploadInput.Key = destObjectKey 211 | completeMultipartUploadInput.UploadId = uploadId 212 | completeMultipartUploadInput.Parts = parts 213 | sample.doCompleteMultipartUpload(completeMultipartUploadInput) 214 | } 215 | 216 | func (sample ConcurrentCopyPartSample) doCompleteMultipartUpload(input *obs.CompleteMultipartUploadInput) { 217 | _, err := sample.obsClient.CompleteMultipartUpload(input) 218 | if err != nil { 219 | panic(err) 220 | } 221 | fmt.Println("Complete multiparts finished") 222 | } 223 | 224 | func RunConcurrentCopyPartSample() { 225 | const ( 226 | endpoint = "https://your-endpoint" 227 | ak = "*** Provide your Access Key ***" 228 | sk = "*** Provide your Secret Key ***" 229 | bucketName = "bucket-test" 230 | objectKey = "object-test" 231 | location = "yourbucketlocation" 232 | ) 233 | 234 | sample := newConcurrentCopyPartSample(ak, sk, endpoint, bucketName, objectKey, location) 235 | 236 | fmt.Println("Create a new bucket for demo") 237 | sample.CreateBucket() 238 | 239 | sampleFilePath := filePathSample 240 | //60MB file 241 | sample.createSampleFile(sampleFilePath, 1024*1024*60) 242 | //Upload an object to your source bucket 243 | sample.PutFile(sampleFilePath) 244 | 245 | sample.DoConcurrentCopyPart() 246 | } 247 | -------------------------------------------------------------------------------- /obs/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "encoding/json" 17 | "io/ioutil" 18 | "math/rand" 19 | "net" 20 | "net/http" 21 | "os" 22 | "strings" 23 | "sync" 24 | "sync/atomic" 25 | "time" 26 | ) 27 | 28 | const ( 29 | accessKeyEnv = "OBS_ACCESS_KEY_ID" 30 | securityKeyEnv = "OBS_SECRET_ACCESS_KEY" 31 | securityTokenEnv = "OBS_SECURITY_TOKEN" 32 | ecsRequestURL = "http://169.254.169.254/openstack/latest/securitykey" 33 | ) 34 | 35 | type securityHolder struct { 36 | ak string 37 | sk string 38 | securityToken string 39 | } 40 | 41 | var emptySecurityHolder = securityHolder{} 42 | 43 | type securityProvider interface { 44 | getSecurity() securityHolder 45 | } 46 | 47 | type BasicSecurityProvider struct { 48 | val atomic.Value 49 | } 50 | 51 | func (bsp *BasicSecurityProvider) getSecurity() securityHolder { 52 | if sh, ok := bsp.val.Load().(securityHolder); ok { 53 | return sh 54 | } 55 | return emptySecurityHolder 56 | } 57 | 58 | func (bsp *BasicSecurityProvider) refresh(ak, sk, securityToken string) { 59 | bsp.val.Store(securityHolder{ak: strings.TrimSpace(ak), sk: strings.TrimSpace(sk), securityToken: strings.TrimSpace(securityToken)}) 60 | } 61 | 62 | func NewBasicSecurityProvider(ak, sk, securityToken string) *BasicSecurityProvider { 63 | bsp := &BasicSecurityProvider{} 64 | bsp.refresh(ak, sk, securityToken) 65 | return bsp 66 | } 67 | 68 | type EnvSecurityProvider struct { 69 | sh securityHolder 70 | suffix string 71 | once sync.Once 72 | } 73 | 74 | func (esp *EnvSecurityProvider) getSecurity() securityHolder { 75 | //ensure run only once 76 | esp.once.Do(func() { 77 | esp.sh = securityHolder{ 78 | ak: strings.TrimSpace(os.Getenv(accessKeyEnv + esp.suffix)), 79 | sk: strings.TrimSpace(os.Getenv(securityKeyEnv + esp.suffix)), 80 | securityToken: strings.TrimSpace(os.Getenv(securityTokenEnv + esp.suffix)), 81 | } 82 | }) 83 | 84 | return esp.sh 85 | } 86 | 87 | func NewEnvSecurityProvider(suffix string) *EnvSecurityProvider { 88 | if suffix != "" { 89 | suffix = "_" + suffix 90 | } 91 | esp := &EnvSecurityProvider{ 92 | suffix: suffix, 93 | } 94 | return esp 95 | } 96 | 97 | type TemporarySecurityHolder struct { 98 | securityHolder 99 | expireDate time.Time 100 | } 101 | 102 | var emptyTemporarySecurityHolder = TemporarySecurityHolder{} 103 | 104 | type EcsSecurityProvider struct { 105 | val atomic.Value 106 | lock sync.Mutex 107 | httpClient *http.Client 108 | prefetch int32 109 | retryCount int 110 | } 111 | 112 | func (ecsSp *EcsSecurityProvider) loadTemporarySecurityHolder() (TemporarySecurityHolder, bool) { 113 | if sh := ecsSp.val.Load(); sh == nil { 114 | return emptyTemporarySecurityHolder, false 115 | } else if _sh, ok := sh.(TemporarySecurityHolder); !ok { 116 | return emptyTemporarySecurityHolder, false 117 | } else { 118 | return _sh, true 119 | } 120 | } 121 | 122 | func (ecsSp *EcsSecurityProvider) getAndSetSecurityWithOutLock() securityHolder { 123 | _sh := TemporarySecurityHolder{} 124 | _sh.expireDate = time.Now().Add(time.Minute * 5) 125 | retryCount := 0 126 | for { 127 | if req, err := http.NewRequest("GET", ecsRequestURL, nil); err == nil { 128 | start := GetCurrentTimestamp() 129 | res, err := ecsSp.httpClient.Do(req) 130 | if err == nil { 131 | if data, _err := ioutil.ReadAll(res.Body); _err == nil { 132 | temp := &struct { 133 | Credential struct { 134 | AK string `json:"access,omitempty"` 135 | SK string `json:"secret,omitempty"` 136 | SecurityToken string `json:"securitytoken,omitempty"` 137 | ExpireDate time.Time `json:"expires_at,omitempty"` 138 | } `json:"credential"` 139 | }{} 140 | 141 | doLog(LEVEL_DEBUG, "Get the json data from ecs succeed") 142 | 143 | if jsonErr := json.Unmarshal(data, temp); jsonErr == nil { 144 | _sh.ak = temp.Credential.AK 145 | _sh.sk = temp.Credential.SK 146 | _sh.securityToken = temp.Credential.SecurityToken 147 | _sh.expireDate = temp.Credential.ExpireDate.Add(time.Minute * -1) 148 | 149 | doLog(LEVEL_INFO, "Get security from ecs succeed, AK:xxxx, SK:xxxx, SecurityToken:xxxx, ExprireDate %s", _sh.expireDate) 150 | 151 | doLog(LEVEL_INFO, "Get security from ecs succeed, cost %d ms", (GetCurrentTimestamp() - start)) 152 | break 153 | } else { 154 | err = jsonErr 155 | } 156 | } else { 157 | err = _err 158 | } 159 | } 160 | 161 | doLog(LEVEL_WARN, "Try to get security from ecs failed, cost %d ms, err %s", (GetCurrentTimestamp() - start), err.Error()) 162 | } 163 | 164 | if retryCount >= ecsSp.retryCount { 165 | doLog(LEVEL_WARN, "Try to get security from ecs failed and exceed the max retry count") 166 | break 167 | } 168 | sleepTime := float64(retryCount+2) * rand.Float64() 169 | if sleepTime > 10 { 170 | sleepTime = 10 171 | } 172 | time.Sleep(time.Duration(sleepTime * float64(time.Second))) 173 | retryCount++ 174 | } 175 | 176 | ecsSp.val.Store(_sh) 177 | return _sh.securityHolder 178 | } 179 | 180 | func (ecsSp *EcsSecurityProvider) getAndSetSecurity() securityHolder { 181 | ecsSp.lock.Lock() 182 | defer ecsSp.lock.Unlock() 183 | tsh, succeed := ecsSp.loadTemporarySecurityHolder() 184 | if !succeed || time.Now().After(tsh.expireDate) { 185 | return ecsSp.getAndSetSecurityWithOutLock() 186 | } 187 | return tsh.securityHolder 188 | } 189 | 190 | func (ecsSp *EcsSecurityProvider) getSecurity() securityHolder { 191 | if tsh, succeed := ecsSp.loadTemporarySecurityHolder(); succeed { 192 | if time.Now().Before(tsh.expireDate) { 193 | //not expire 194 | if time.Now().Add(time.Minute*5).After(tsh.expireDate) && atomic.CompareAndSwapInt32(&ecsSp.prefetch, 0, 1) { 195 | //do prefetch 196 | sh := ecsSp.getAndSetSecurityWithOutLock() 197 | atomic.CompareAndSwapInt32(&ecsSp.prefetch, 1, 0) 198 | return sh 199 | } 200 | return tsh.securityHolder 201 | } 202 | return ecsSp.getAndSetSecurity() 203 | } 204 | 205 | return ecsSp.getAndSetSecurity() 206 | } 207 | 208 | func getInternalTransport() *http.Transport { 209 | timeout := 10 210 | transport := &http.Transport{ 211 | Dial: func(network, addr string) (net.Conn, error) { 212 | start := GetCurrentTimestamp() 213 | conn, err := (&net.Dialer{ 214 | Timeout: time.Second * time.Duration(timeout), 215 | Resolver: net.DefaultResolver, 216 | }).Dial(network, addr) 217 | 218 | if isInfoLogEnabled() { 219 | doLog(LEVEL_INFO, "Do http dial cost %d ms", (GetCurrentTimestamp() - start)) 220 | } 221 | if err != nil { 222 | return nil, err 223 | } 224 | return getConnDelegate(conn, timeout, timeout*10), nil 225 | }, 226 | MaxIdleConns: 10, 227 | MaxIdleConnsPerHost: 10, 228 | ResponseHeaderTimeout: time.Second * time.Duration(timeout), 229 | IdleConnTimeout: time.Second * time.Duration(DEFAULT_IDLE_CONN_TIMEOUT), 230 | DisableCompression: true, 231 | } 232 | 233 | return transport 234 | } 235 | 236 | func NewEcsSecurityProvider(retryCount int) *EcsSecurityProvider { 237 | ecsSp := &EcsSecurityProvider{ 238 | retryCount: retryCount, 239 | } 240 | ecsSp.httpClient = &http.Client{Transport: getInternalTransport(), CheckRedirect: checkRedirectFunc} 241 | return ecsSp 242 | } 243 | -------------------------------------------------------------------------------- /examples/list_versions_sample.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | /** 14 | * This sample demonstrates how to list versions under specified bucket 15 | * from OBS using the OBS SDK for Go. 16 | */ 17 | package examples 18 | 19 | import ( 20 | "fmt" 21 | "obs" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | type ListVersionsSample struct { 27 | bucketName string 28 | location string 29 | obsClient *obs.ObsClient 30 | } 31 | 32 | func newListVersionsSample(ak, sk, endpoint, bucketName, location string) *ListVersionsSample { 33 | obsClient, err := obs.New(ak, sk, endpoint) 34 | if err != nil { 35 | panic(err) 36 | } 37 | return &ListVersionsSample{obsClient: obsClient, bucketName: bucketName, location: location} 38 | } 39 | 40 | func (sample ListVersionsSample) CreateBucket() { 41 | input := &obs.CreateBucketInput{} 42 | input.Bucket = sample.bucketName 43 | input.Location = sample.location 44 | _, err := sample.obsClient.CreateBucket(input) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | setBucketVersioningInput := &obs.SetBucketVersioningInput{} 50 | setBucketVersioningInput.Bucket = sample.bucketName 51 | setBucketVersioningInput.Status = obs.VersioningStatusEnabled 52 | _, err = sample.obsClient.SetBucketVersioning(setBucketVersioningInput) 53 | 54 | if err != nil { 55 | panic(err) 56 | } 57 | fmt.Printf("Create bucket:%s successfully!\n", sample.bucketName) 58 | fmt.Println() 59 | } 60 | 61 | func (sample ListVersionsSample) preparePutObject(input *obs.PutObjectInput){ 62 | _, err := sample.obsClient.PutObject(input) 63 | if err != nil { 64 | panic(err) 65 | } 66 | } 67 | 68 | func (sample ListVersionsSample) PrepareFoldersAndObjects() { 69 | 70 | keyPrefix := "MyObjectKeyVersions" 71 | folderPrefix := "src" 72 | subFolderPrefix := "test" 73 | 74 | input := &obs.PutObjectInput{} 75 | input.Bucket = sample.bucketName 76 | 77 | // First prepare folders and sub folders 78 | for i := 0; i < 5; i++ { 79 | key := folderPrefix + strconv.Itoa(i) + "/" 80 | input.Key = key 81 | sample.preparePutObject(input) 82 | for j := 0; j < 3; j++ { 83 | subKey := key + subFolderPrefix + strconv.Itoa(j) + "/" 84 | input.Key = subKey 85 | sample.preparePutObject(input) 86 | } 87 | } 88 | 89 | // Insert 2 objects in each folder 90 | input.Body = strings.NewReader("Hello OBS") 91 | listVersionsInput := &obs.ListVersionsInput{} 92 | listVersionsInput.Bucket = sample.bucketName 93 | output, err := sample.obsClient.ListVersions(listVersionsInput) 94 | if err != nil{ 95 | panic(err) 96 | } 97 | for _, version := range output.Versions { 98 | for i := 0; i < 2; i++ { 99 | objectKey := version.Key + keyPrefix + strconv.Itoa(i) 100 | input.Key = objectKey 101 | sample.preparePutObject(input) 102 | } 103 | } 104 | 105 | // Insert 2 objects in root path 106 | input.Key = keyPrefix + strconv.Itoa(0) 107 | sample.preparePutObject(input) 108 | input.Key = keyPrefix + strconv.Itoa(1) 109 | sample.preparePutObject(input) 110 | 111 | fmt.Println("Prepare folders and objects finished") 112 | fmt.Println() 113 | } 114 | 115 | func (sample ListVersionsSample) ListVersionsInFolders() { 116 | fmt.Println("List versions in folder src0/") 117 | input := &obs.ListVersionsInput{} 118 | input.Bucket = sample.bucketName 119 | input.Prefix = "src0/" 120 | output, err := sample.obsClient.ListVersions(input) 121 | if err != nil { 122 | panic(err) 123 | } 124 | for index, val := range output.Versions { 125 | fmt.Printf("Version[%d]-ETag:%s, Key:%s, Size:%d, VersionId:%s\n", 126 | index, val.ETag, val.Key, val.Size, val.VersionId) 127 | } 128 | 129 | fmt.Println() 130 | 131 | fmt.Println("List versions in sub folder src0/test0/") 132 | 133 | input.Prefix = "src0/test0/" 134 | output, err = sample.obsClient.ListVersions(input) 135 | if err != nil { 136 | panic(err) 137 | } 138 | for index, val := range output.Versions { 139 | fmt.Printf("Version[%d]-ETag:%s, Key:%s, Size:%d, VersionId:%s\n", 140 | index, val.ETag, val.Key, val.Size, val.VersionId) 141 | } 142 | 143 | fmt.Println() 144 | } 145 | 146 | func (sample ListVersionsSample) ListVersionsByPage() { 147 | 148 | pageSize := 10 149 | pageNum := 1 150 | input := &obs.ListVersionsInput{} 151 | input.Bucket = sample.bucketName 152 | input.MaxKeys = pageSize 153 | 154 | for { 155 | output, err := sample.obsClient.ListVersions(input) 156 | if err != nil { 157 | panic(err) 158 | } 159 | fmt.Printf("Page:%d\n", pageNum) 160 | for index, val := range output.Versions { 161 | fmt.Printf("Version[%d]-ETag:%s, Key:%s, Size:%d, VersionId:%s\n", 162 | index, val.ETag, val.Key, val.Size, val.VersionId) 163 | } 164 | if output.IsTruncated { 165 | input.KeyMarker = output.NextKeyMarker 166 | input.VersionIdMarker = output.NextVersionIdMarker 167 | pageNum++ 168 | } else { 169 | break 170 | } 171 | } 172 | 173 | fmt.Println() 174 | } 175 | 176 | func (sample ListVersionsSample) listVersionsByPrefixes(commonPrefixes []string) { 177 | input := &obs.ListVersionsInput{} 178 | input.Bucket = sample.bucketName 179 | input.Delimiter = "/" 180 | for _, prefix := range commonPrefixes { 181 | input.Prefix = prefix 182 | output, err := sample.obsClient.ListVersions(input) 183 | if err != nil { 184 | panic(err) 185 | } 186 | fmt.Printf("Folder %s:\n", prefix) 187 | for index, val := range output.Versions { 188 | fmt.Printf("Version[%d]-ETag:%s, Key:%s, Size:%d, VersionId:%s\n", 189 | index, val.ETag, val.Key, val.Size, val.VersionId) 190 | } 191 | fmt.Println() 192 | sample.listVersionsByPrefixes(output.CommonPrefixes) 193 | } 194 | } 195 | 196 | func (sample ListVersionsSample) ListVersionsGroupByFolder() { 197 | fmt.Println("List versions group by folder") 198 | input := &obs.ListVersionsInput{} 199 | input.Bucket = sample.bucketName 200 | input.Delimiter = "/" 201 | output, err := sample.obsClient.ListVersions(input) 202 | if err != nil { 203 | panic(err) 204 | } 205 | fmt.Println("Root path:") 206 | for index, val := range output.Versions { 207 | fmt.Printf("Version[%d]-ETag:%s, Key:%s, Size:%d, VersionId:%s\n", 208 | index, val.ETag, val.Key, val.Size, val.VersionId) 209 | } 210 | fmt.Println() 211 | sample.listVersionsByPrefixes(output.CommonPrefixes) 212 | } 213 | 214 | func (sample ListVersionsSample) BatchDeleteVersions() { 215 | input := &obs.ListVersionsInput{} 216 | input.Bucket = sample.bucketName 217 | output, err := sample.obsClient.ListVersions(input) 218 | if err != nil { 219 | panic(err) 220 | } 221 | objects := make([]obs.ObjectToDelete, 0, len(output.Versions)) 222 | for _, val := range output.Versions { 223 | objects = append(objects, obs.ObjectToDelete{Key: val.Key, VersionId: val.VersionId}) 224 | } 225 | deleteObjectsInput := &obs.DeleteObjectsInput{} 226 | deleteObjectsInput.Bucket = sample.bucketName 227 | deleteObjectsInput.Objects = objects[:] 228 | _, err = sample.obsClient.DeleteObjects(deleteObjectsInput) 229 | if err != nil { 230 | panic(err) 231 | } 232 | fmt.Println("Delete versions successfully!") 233 | fmt.Println() 234 | } 235 | 236 | func RunListVersionsSample() { 237 | 238 | const ( 239 | endpoint = "https://your-endpoint" 240 | ak = "*** Provide your Access Key ***" 241 | sk = "*** Provide your Secret Key ***" 242 | bucketName = "bucket-test" 243 | location = "yourbucketlocation" 244 | ) 245 | 246 | sample := newListVersionsSample(ak, sk, endpoint, bucketName, location) 247 | 248 | fmt.Println("Create a new bucket for demo") 249 | sample.CreateBucket() 250 | 251 | // First prepare folders and objects 252 | sample.PrepareFoldersAndObjects() 253 | 254 | // List versions in folders 255 | sample.ListVersionsInFolders() 256 | 257 | // List versions in way of pagination 258 | sample.ListVersionsByPage() 259 | 260 | // List versions group by folder 261 | sample.ListVersionsGroupByFolder() 262 | 263 | sample.BatchDeleteVersions() 264 | } 265 | -------------------------------------------------------------------------------- /obs/client_part.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "errors" 17 | "io" 18 | "os" 19 | "sort" 20 | "strings" 21 | ) 22 | 23 | // ListMultipartUploads lists the multipart uploads. 24 | // 25 | // You can use this API to list the multipart uploads that are initialized but not combined or aborted in a specified bucket. 26 | func (obsClient ObsClient) ListMultipartUploads(input *ListMultipartUploadsInput, extensions ...extensionOptions) (output *ListMultipartUploadsOutput, err error) { 27 | if input == nil { 28 | return nil, errors.New("ListMultipartUploadsInput is nil") 29 | } 30 | output = &ListMultipartUploadsOutput{} 31 | err = obsClient.doActionWithBucket("ListMultipartUploads", HTTP_GET, input.Bucket, input, output, extensions) 32 | if err != nil { 33 | output = nil 34 | } else if output.EncodingType == "url" { 35 | err = decodeListMultipartUploadsOutput(output) 36 | if err != nil { 37 | doLog(LEVEL_ERROR, "Failed to get ListMultipartUploadsOutput with error: %v.", err) 38 | output = nil 39 | } 40 | } 41 | return 42 | } 43 | 44 | // AbortMultipartUpload aborts a multipart upload in a specified bucket by using the multipart upload ID. 45 | func (obsClient ObsClient) AbortMultipartUpload(input *AbortMultipartUploadInput, extensions ...extensionOptions) (output *BaseModel, err error) { 46 | if input == nil { 47 | return nil, errors.New("AbortMultipartUploadInput is nil") 48 | } 49 | if input.UploadId == "" { 50 | return nil, errors.New("UploadId is empty") 51 | } 52 | output = &BaseModel{} 53 | err = obsClient.doActionWithBucketAndKey("AbortMultipartUpload", HTTP_DELETE, input.Bucket, input.Key, input, output, extensions) 54 | if err != nil { 55 | output = nil 56 | } 57 | return 58 | } 59 | 60 | // InitiateMultipartUpload initializes a multipart upload. 61 | func (obsClient ObsClient) InitiateMultipartUpload(input *InitiateMultipartUploadInput, extensions ...extensionOptions) (output *InitiateMultipartUploadOutput, err error) { 62 | if input == nil { 63 | return nil, errors.New("InitiateMultipartUploadInput is nil") 64 | } 65 | 66 | if input.ContentType == "" && input.Key != "" { 67 | if contentType, ok := mimeTypes[strings.ToLower(input.Key[strings.LastIndex(input.Key, ".")+1:])]; ok { 68 | input.ContentType = contentType 69 | } 70 | } 71 | 72 | output = &InitiateMultipartUploadOutput{} 73 | err = obsClient.doActionWithBucketAndKey("InitiateMultipartUpload", HTTP_POST, input.Bucket, input.Key, input, output, extensions) 74 | if err != nil { 75 | output = nil 76 | } else { 77 | ParseInitiateMultipartUploadOutput(output) 78 | if output.EncodingType == "url" { 79 | err = decodeInitiateMultipartUploadOutput(output) 80 | if err != nil { 81 | doLog(LEVEL_ERROR, "Failed to get InitiateMultipartUploadOutput with error: %v.", err) 82 | output = nil 83 | } 84 | } 85 | } 86 | return 87 | } 88 | 89 | // UploadPart uploads a part to a specified bucket by using a specified multipart upload ID. 90 | // 91 | // After a multipart upload is initialized, you can use this API to upload a part to a specified bucket 92 | // by using the multipart upload ID. Except for the last uploaded part whose size ranges from 0 to 5 GB, 93 | // sizes of the other parts range from 100 KB to 5 GB. The upload part ID ranges from 1 to 10000. 94 | func (obsClient ObsClient) UploadPart(_input *UploadPartInput, extensions ...extensionOptions) (output *UploadPartOutput, err error) { 95 | if _input == nil { 96 | return nil, errors.New("UploadPartInput is nil") 97 | } 98 | 99 | if _input.UploadId == "" { 100 | return nil, errors.New("UploadId is empty") 101 | } 102 | 103 | input := &UploadPartInput{} 104 | input.Bucket = _input.Bucket 105 | input.Key = _input.Key 106 | input.PartNumber = _input.PartNumber 107 | input.UploadId = _input.UploadId 108 | input.ContentMD5 = _input.ContentMD5 109 | input.ContentSHA256 = _input.ContentSHA256 110 | input.SourceFile = _input.SourceFile 111 | input.Offset = _input.Offset 112 | input.PartSize = _input.PartSize 113 | input.SseHeader = _input.SseHeader 114 | input.Body = _input.Body 115 | 116 | output = &UploadPartOutput{} 117 | var repeatable bool 118 | if input.Body != nil { 119 | if _, ok := input.Body.(*strings.Reader); ok { 120 | repeatable = true 121 | } 122 | if _, ok := input.Body.(*readerWrapper); !ok && input.PartSize > 0 { 123 | input.Body = &readerWrapper{reader: input.Body, totalCount: input.PartSize} 124 | } 125 | } else if sourceFile := strings.TrimSpace(input.SourceFile); sourceFile != "" { 126 | fd, _err := os.Open(sourceFile) 127 | if _err != nil { 128 | err = _err 129 | return nil, err 130 | } 131 | defer func() { 132 | errMsg := fd.Close() 133 | if errMsg != nil { 134 | doLog(LEVEL_WARN, "Failed to close file with reason: %v", errMsg) 135 | } 136 | }() 137 | 138 | stat, _err := fd.Stat() 139 | if _err != nil { 140 | err = _err 141 | return nil, err 142 | } 143 | fileSize := stat.Size() 144 | fileReaderWrapper := &fileReaderWrapper{filePath: sourceFile} 145 | fileReaderWrapper.reader = fd 146 | 147 | if input.Offset < 0 || input.Offset > fileSize { 148 | input.Offset = 0 149 | } 150 | 151 | if input.PartSize <= 0 || input.PartSize > (fileSize-input.Offset) { 152 | input.PartSize = fileSize - input.Offset 153 | } 154 | fileReaderWrapper.totalCount = input.PartSize 155 | fileReaderWrapper.mark = input.Offset 156 | if _, err = fd.Seek(input.Offset, io.SeekStart); err != nil { 157 | return nil, err 158 | } 159 | input.Body = fileReaderWrapper 160 | repeatable = true 161 | } 162 | if repeatable { 163 | err = obsClient.doActionWithBucketAndKey("UploadPart", HTTP_PUT, input.Bucket, input.Key, input, output, extensions) 164 | } else { 165 | err = obsClient.doActionWithBucketAndKeyUnRepeatable("UploadPart", HTTP_PUT, input.Bucket, input.Key, input, output, extensions) 166 | } 167 | if err != nil { 168 | output = nil 169 | } else { 170 | ParseUploadPartOutput(output) 171 | output.PartNumber = input.PartNumber 172 | } 173 | return 174 | } 175 | 176 | // CompleteMultipartUpload combines the uploaded parts in a specified bucket by using the multipart upload ID. 177 | func (obsClient ObsClient) CompleteMultipartUpload(input *CompleteMultipartUploadInput, extensions ...extensionOptions) (output *CompleteMultipartUploadOutput, err error) { 178 | if input == nil { 179 | return nil, errors.New("CompleteMultipartUploadInput is nil") 180 | } 181 | 182 | if input.UploadId == "" { 183 | return nil, errors.New("UploadId is empty") 184 | } 185 | 186 | var parts partSlice = input.Parts 187 | sort.Sort(parts) 188 | 189 | output = &CompleteMultipartUploadOutput{} 190 | err = obsClient.doActionWithBucketAndKey("CompleteMultipartUpload", HTTP_POST, input.Bucket, input.Key, input, output, extensions) 191 | if err != nil { 192 | output = nil 193 | } else { 194 | ParseCompleteMultipartUploadOutput(output) 195 | if output.EncodingType == "url" { 196 | err = decodeCompleteMultipartUploadOutput(output) 197 | if err != nil { 198 | doLog(LEVEL_ERROR, "Failed to get CompleteMultipartUploadOutput with error: %v.", err) 199 | output = nil 200 | } 201 | } 202 | } 203 | return 204 | } 205 | 206 | // ListParts lists the uploaded parts in a bucket by using the multipart upload ID. 207 | func (obsClient ObsClient) ListParts(input *ListPartsInput, extensions ...extensionOptions) (output *ListPartsOutput, err error) { 208 | if input == nil { 209 | return nil, errors.New("ListPartsInput is nil") 210 | } 211 | if input.UploadId == "" { 212 | return nil, errors.New("UploadId is empty") 213 | } 214 | output = &ListPartsOutput{} 215 | err = obsClient.doActionWithBucketAndKey("ListParts", HTTP_GET, input.Bucket, input.Key, input, output, extensions) 216 | if err != nil { 217 | output = nil 218 | } else if output.EncodingType == "url" { 219 | err = decodeListPartsOutput(output) 220 | if err != nil { 221 | doLog(LEVEL_ERROR, "Failed to get ListPartsOutput with error: %v.", err) 222 | output = nil 223 | } 224 | } 225 | return 226 | } 227 | 228 | // CopyPart copy a part to a specified bucket by using a specified multipart upload ID. 229 | // 230 | // After a multipart upload is initialized, you can use this API to copy a part to a specified bucket by using the multipart upload ID. 231 | func (obsClient ObsClient) CopyPart(input *CopyPartInput, extensions ...extensionOptions) (output *CopyPartOutput, err error) { 232 | if input == nil { 233 | return nil, errors.New("CopyPartInput is nil") 234 | } 235 | if input.UploadId == "" { 236 | return nil, errors.New("UploadId is empty") 237 | } 238 | if strings.TrimSpace(input.CopySourceBucket) == "" { 239 | return nil, errors.New("Source bucket is empty") 240 | } 241 | if strings.TrimSpace(input.CopySourceKey) == "" { 242 | return nil, errors.New("Source key is empty") 243 | } 244 | if input.CopySourceRange != "" && !strings.HasPrefix(input.CopySourceRange, "bytes=") { 245 | return nil, errors.New("Source Range should start with [bytes=]") 246 | } 247 | 248 | output = &CopyPartOutput{} 249 | err = obsClient.doActionWithBucketAndKey("CopyPart", HTTP_PUT, input.Bucket, input.Key, input, output, extensions) 250 | if err != nil { 251 | output = nil 252 | } else { 253 | ParseCopyPartOutput(output) 254 | output.PartNumber = input.PartNumber 255 | } 256 | return 257 | } 258 | -------------------------------------------------------------------------------- /obs/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "fmt" 17 | "log" 18 | "net/http" 19 | "os" 20 | "path/filepath" 21 | "runtime" 22 | "strings" 23 | "sync" 24 | "time" 25 | ) 26 | 27 | // Level defines the level of the log 28 | type Level int 29 | 30 | const ( 31 | LEVEL_OFF Level = 500 32 | LEVEL_ERROR Level = 400 33 | LEVEL_WARN Level = 300 34 | LEVEL_INFO Level = 200 35 | LEVEL_DEBUG Level = 100 36 | ) 37 | 38 | var logLevelMap = map[Level]string{ 39 | LEVEL_OFF: "[OFF]: ", 40 | LEVEL_ERROR: "[ERROR]: ", 41 | LEVEL_WARN: "[WARN]: ", 42 | LEVEL_INFO: "[INFO]: ", 43 | LEVEL_DEBUG: "[DEBUG]: ", 44 | } 45 | 46 | type logConfType struct { 47 | level Level 48 | logToConsole bool 49 | logFullPath string 50 | maxLogSize int64 51 | backups int 52 | } 53 | 54 | func getDefaultLogConf() logConfType { 55 | return logConfType{ 56 | level: LEVEL_WARN, 57 | logToConsole: false, 58 | logFullPath: "", 59 | maxLogSize: 1024 * 1024 * 30, //30MB 60 | backups: 10, 61 | } 62 | } 63 | 64 | var logConf logConfType 65 | 66 | type loggerWrapper struct { 67 | fullPath string 68 | fd *os.File 69 | ch chan string 70 | wg sync.WaitGroup 71 | queue []string 72 | logger *log.Logger 73 | index int 74 | cacheCount int 75 | closed bool 76 | loc *time.Location 77 | } 78 | 79 | func (lw *loggerWrapper) doInit() { 80 | lw.queue = make([]string, 0, lw.cacheCount) 81 | lw.logger = log.New(lw.fd, "", 0) 82 | lw.ch = make(chan string, lw.cacheCount) 83 | if lw.loc == nil { 84 | lw.loc = time.FixedZone("UTC", 0) 85 | } 86 | lw.wg.Add(1) 87 | go lw.doWrite() 88 | } 89 | 90 | func (lw *loggerWrapper) rotate() { 91 | stat, err := lw.fd.Stat() 92 | if err != nil { 93 | _err := lw.fd.Close() 94 | if _err != nil { 95 | doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err) 96 | } 97 | panic(err) 98 | } 99 | if stat.Size() >= logConf.maxLogSize { 100 | _err := lw.fd.Sync() 101 | if _err != nil { 102 | panic(_err) 103 | } 104 | _err = lw.fd.Close() 105 | if _err != nil { 106 | doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err) 107 | } 108 | if lw.index > logConf.backups { 109 | lw.index = 1 110 | } 111 | _err = os.Rename(lw.fullPath, lw.fullPath+"."+IntToString(lw.index)) 112 | if _err != nil { 113 | panic(_err) 114 | } 115 | lw.index++ 116 | 117 | fd, err := os.OpenFile(lw.fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) 118 | if err != nil { 119 | panic(err) 120 | } 121 | lw.fd = fd 122 | lw.logger.SetOutput(lw.fd) 123 | } 124 | } 125 | 126 | func (lw *loggerWrapper) doFlush() { 127 | lw.rotate() 128 | for _, m := range lw.queue { 129 | lw.logger.Println(m) 130 | } 131 | err := lw.fd.Sync() 132 | if err != nil { 133 | panic(err) 134 | } 135 | } 136 | 137 | func (lw *loggerWrapper) doClose() { 138 | lw.closed = true 139 | close(lw.ch) 140 | lw.wg.Wait() 141 | } 142 | 143 | func (lw *loggerWrapper) doWrite() { 144 | defer lw.wg.Done() 145 | for { 146 | msg, ok := <-lw.ch 147 | if !ok { 148 | lw.doFlush() 149 | _err := lw.fd.Close() 150 | if _err != nil { 151 | doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err) 152 | } 153 | break 154 | } 155 | if len(lw.queue) >= lw.cacheCount { 156 | lw.doFlush() 157 | lw.queue = make([]string, 0, lw.cacheCount) 158 | } 159 | lw.queue = append(lw.queue, msg) 160 | } 161 | 162 | } 163 | 164 | func (lw *loggerWrapper) Printf(format string, v ...interface{}) { 165 | if !lw.closed { 166 | msg := fmt.Sprintf(format, v...) 167 | lw.ch <- msg 168 | } 169 | } 170 | 171 | var consoleLogger *log.Logger 172 | var fileLogger *loggerWrapper 173 | var lock = new(sync.RWMutex) 174 | 175 | func isDebugLogEnabled() bool { 176 | return logConf.level <= LEVEL_DEBUG 177 | } 178 | 179 | func isErrorLogEnabled() bool { 180 | return logConf.level <= LEVEL_ERROR 181 | } 182 | 183 | func isWarnLogEnabled() bool { 184 | return logConf.level <= LEVEL_WARN 185 | } 186 | 187 | func isInfoLogEnabled() bool { 188 | return logConf.level <= LEVEL_INFO 189 | } 190 | 191 | func reset() { 192 | if fileLogger != nil { 193 | fileLogger.doClose() 194 | fileLogger = nil 195 | } 196 | consoleLogger = nil 197 | logConf = getDefaultLogConf() 198 | } 199 | 200 | type logConfig func(lw *loggerWrapper) 201 | 202 | func WithLoggerTimeLoc(loc *time.Location) logConfig { 203 | return func(lw *loggerWrapper) { 204 | lw.loc = loc 205 | } 206 | } 207 | 208 | // InitLog enable logging function with default cacheCnt 209 | func InitLog(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool, logConfigs ...logConfig) error { 210 | 211 | return InitLogWithCacheCnt(logFullPath, maxLogSize, backups, level, logToConsole, 50, logConfigs...) 212 | } 213 | 214 | // InitLogWithCacheCnt enable logging function 215 | func InitLogWithCacheCnt(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool, cacheCnt int, logConfigs ...logConfig) error { 216 | lock.Lock() 217 | defer lock.Unlock() 218 | if cacheCnt <= 0 { 219 | cacheCnt = 50 220 | } 221 | reset() 222 | if fullPath := strings.TrimSpace(logFullPath); fullPath != "" { 223 | _fullPath, err := filepath.Abs(fullPath) 224 | if err != nil { 225 | return err 226 | } 227 | 228 | if !strings.HasSuffix(_fullPath, ".log") { 229 | _fullPath += ".log" 230 | } 231 | 232 | stat, fd, err := initLogFile(_fullPath) 233 | if err != nil { 234 | return err 235 | } 236 | 237 | prefix := stat.Name() + "." 238 | index := 1 239 | var timeIndex int64 = 0 240 | walkFunc := func(path string, info os.FileInfo, err error) error { 241 | if err == nil { 242 | if name := info.Name(); strings.HasPrefix(name, prefix) { 243 | if i := StringToInt(name[len(prefix):], 0); i >= index && info.ModTime().Unix() >= timeIndex { 244 | timeIndex = info.ModTime().Unix() 245 | index = i + 1 246 | } 247 | } 248 | } 249 | return err 250 | } 251 | 252 | if err = filepath.Walk(filepath.Dir(_fullPath), walkFunc); err != nil { 253 | _err := fd.Close() 254 | if _err != nil { 255 | doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err) 256 | } 257 | return err 258 | } 259 | 260 | fileLogger = &loggerWrapper{fullPath: _fullPath, fd: fd, index: index, cacheCount: cacheCnt, closed: false} 261 | for _, logConfig := range logConfigs { 262 | logConfig(fileLogger) 263 | } 264 | fileLogger.doInit() 265 | } 266 | if maxLogSize > 0 { 267 | logConf.maxLogSize = maxLogSize 268 | } 269 | if backups > 0 { 270 | logConf.backups = backups 271 | } 272 | logConf.level = level 273 | if logToConsole { 274 | consoleLogger = log.New(os.Stdout, "", log.LstdFlags) 275 | } 276 | return nil 277 | } 278 | 279 | func initLogFile(_fullPath string) (os.FileInfo, *os.File, error) { 280 | stat, err := os.Stat(_fullPath) 281 | if err == nil && stat.IsDir() { 282 | return nil, nil, fmt.Errorf("logFullPath:[%s] is a directory", _fullPath) 283 | } else if err = os.MkdirAll(filepath.Dir(_fullPath), os.ModePerm); err != nil { 284 | return nil, nil, err 285 | } 286 | 287 | fd, err := os.OpenFile(_fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) 288 | if err != nil { 289 | _err := fd.Close() 290 | if _err != nil { 291 | doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err) 292 | } 293 | return nil, nil, err 294 | } 295 | 296 | if stat == nil { 297 | stat, err = os.Stat(_fullPath) 298 | if err != nil { 299 | _err := fd.Close() 300 | if _err != nil { 301 | doLog(LEVEL_WARN, "Failed to close file with reason: %v", _err) 302 | } 303 | return nil, nil, err 304 | } 305 | } 306 | 307 | return stat, fd, nil 308 | } 309 | 310 | // CloseLog disable logging and synchronize cache data to log files 311 | func CloseLog() { 312 | if logEnabled() { 313 | lock.Lock() 314 | defer lock.Unlock() 315 | reset() 316 | } 317 | } 318 | 319 | func logEnabled() bool { 320 | return consoleLogger != nil || fileLogger != nil 321 | } 322 | 323 | // DoLog writes log messages to the logger 324 | func DoLog(level Level, format string, v ...interface{}) { 325 | doLog(level, format, v...) 326 | } 327 | 328 | func doLog(level Level, format string, v ...interface{}) { 329 | if logEnabled() && logConf.level <= level { 330 | msg := fmt.Sprintf(format, v...) 331 | if _, file, line, ok := runtime.Caller(1); ok { 332 | index := strings.LastIndex(file, "/") 333 | if index >= 0 { 334 | file = file[index+1:] 335 | } 336 | msg = fmt.Sprintf("%s:%d|%s", file, line, msg) 337 | } 338 | prefix := logLevelMap[level] 339 | defer func() { 340 | _ = recover() 341 | // ignore ch closed error 342 | }() 343 | nowDate := FormatNowWithLoc("2006-01-02T15:04:05.000ZZ", fileLogger.loc) 344 | 345 | if consoleLogger != nil { 346 | consoleLogger.Printf("%s%s", prefix, msg) 347 | } 348 | if fileLogger != nil { 349 | fileLogger.Printf("%s %s%s", nowDate, prefix, msg) 350 | } 351 | } 352 | } 353 | 354 | func checkAndLogErr(err error, level Level, format string, v ...interface{}) { 355 | if err != nil { 356 | doLog(level, format, v...) 357 | } 358 | } 359 | 360 | func logResponseHeader(respHeader http.Header) string { 361 | resp := make([]string, 0, len(respHeader)+1) 362 | for key, value := range respHeader { 363 | key = strings.TrimSpace(key) 364 | if key == "" { 365 | continue 366 | } 367 | _key := strings.ToLower(key) 368 | if strings.HasPrefix(_key, HEADER_PREFIX) || strings.HasPrefix(_key, HEADER_PREFIX_OBS) { 369 | _key = _key[len(HEADER_PREFIX):] 370 | } 371 | if _, ok := allowedLogResponseHTTPHeaderNames[_key]; ok { 372 | resp = append(resp, fmt.Sprintf("%s: [%s]", key, value[0])) 373 | } 374 | if _key == HEADER_REQUEST_ID { 375 | resp = append(resp, fmt.Sprintf("%s: [%s]", key, value[0])) 376 | } 377 | } 378 | return strings.Join(resp, " ") 379 | } 380 | 381 | func logRequestHeader(reqHeader http.Header) string { 382 | resp := make([]string, 0, len(reqHeader)+1) 383 | for key, value := range reqHeader { 384 | key = strings.TrimSpace(key) 385 | if key == "" { 386 | continue 387 | } 388 | _key := strings.ToLower(key) 389 | if _, ok := allowedRequestHTTPHeaderMetadataNames[_key]; ok { 390 | resp = append(resp, fmt.Sprintf("%s: [%s]", key, value[0])) 391 | } 392 | } 393 | return strings.Join(resp, " ") 394 | } 395 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | Version 3.25.9 2 | 3 | New Features: 4 | 5 | Documentation & Demo: 6 | 7 | Resolved Issues: 8 | 1. Fixed an issue where the first byte could not be downloaded using range. 9 | 10 | ----------------------------------------------------------------------------------- 11 | 12 | Version 3.25.4 13 | 14 | New Features: 15 | 1. Supports passing metadata when use UploadFile method. 16 | 17 | Documentation & Demo: 18 | 19 | Resolved Issues: 20 | 21 | ----------------------------------------------------------------------------------- 22 | 23 | Version 3.25.3 24 | 25 | New Features: 26 | 1. Added BPA feature. 27 | 2. Added intelligent tiering feature. 28 | 29 | Documentation & Demo: 30 | 1. Added descriptions about BPA APIs. 31 | 32 | Resolved Issues: 33 | 34 | ----------------------------------------------------------------------------------- 35 | 36 | Version 3.24.9 37 | 38 | New Features: 39 | 1. Added list POSIX Object API. 40 | 41 | Documentation & Demo: 42 | 43 | Resolved Issues: 44 | 1. Optimize error log printing. 45 | 46 | ----------------------------------------------------------------------------------- 47 | 48 | Version 3.24.6 49 | 50 | New Features: 51 | 1. Supports passing metadata when use CopyObject and InitiateMultipartUpload method. 52 | 53 | Documentation & Demo: 54 | 55 | Resolved Issues: 56 | 1. Fixed the issue where bucket acl configuration failed in some scenarios. 57 | 2. Fixed the issue where data transmission exception in retry scenarios. 58 | 59 | ----------------------------------------------------------------------------------- 60 | 61 | Version 3.24.3 62 | 63 | New Features: 64 | 1. Added directory accesslable-related APIs, including SetDirAccesslabel, GetDirAccesslabel, and DeleteDirAccesslabel. 65 | 66 | Documentation & Demo: 67 | 1. Added descriptions about directory accesslable-related APIs. 68 | 69 | Resolved Issues: 70 | 71 | ----------------------------------------------------------------------------------- 72 | 73 | Version 3.23.12 74 | 75 | New Features: 76 | 1. Added obs.WithDisableKeepAlive method to implement short connection function. 77 | 2. Added deep archive storage-class. 78 | 79 | Documentation & Demo: 80 | 81 | Resolved Issues: 82 | 1. Fixed the issue where bucket life cycle configuration failed in some scenarios. 83 | 84 | ----------------------------------------------------------------------------------- 85 | 86 | Version 3.23.9 87 | 88 | New Features: 89 | 1. Added the obs.WithProgress method to support progress bar callback. 90 | 2. Added fragment expiration time in configuration life cycle management rules. 91 | 92 | Documentation & Demo: 93 | 94 | ----------------------------------------------------------------------------------- 95 | 96 | Version 3.23.4 97 | 98 | New Features: 99 | 1. Added the obs.WithCustomHeader method when upload. 100 | 2. Added bucket customDomain-related APIs, including SetBucketCustomDomain, GetBucketCustomDomain, and DeleteBucketCustomDomain. 101 | 3. Added bucket mirrorBackToSource-related APIs, including SetBucketMirrorBackToSource, GetBucketMirrorBackToSource, and DeleteBucketMirrorBackToSource. 102 | 103 | Documentation & Demo: 104 | 1. Added descriptions about bucket customDomain APIs. 105 | 2. Added descriptions about bucket mirrorBackToSource APIs. 106 | 107 | ----------------------------------------------------------------------------------- 108 | Version 3.23.3 109 | 110 | New Features: 111 | 1. Added the obs.WithCallbackHeader method when upload. 112 | 113 | Documentation & Demo: 114 | 1. Added descriptions about callback when upload. 115 | 2. Added descriptions about extended configurations of SDK APIs. 116 | 117 | Resolved Issues: 118 | 1. Optimized some code. 119 | 120 | ----------------------------------------------------------------------------------- 121 | Version 3.21.12 122 | 123 | New Features: 124 | 1. Added obs.WithTrafficLimitHeader method to realize single-connection bandwidth throttling. 125 | 126 | Documentation & Demo: 127 | 1. Added descriptions about single-connection bandwidth throttling. 128 | 2. Added descriptions about the SDK interface extension configuration. 129 | 130 | Resolved Issues: 131 | 1. Optimized part of the code. 132 | 133 | ----------------------------------------------------------------------------------- 134 | 135 | Version 3.21.8 136 | 137 | New Features: 138 | 1. Added bucket encryption-related APIs, including SetBucketEncryption, GetBucketEncryption, and DeleteBucketEncryption. 139 | 2. Added the AppendObject API. 140 | 3. Added the ModifyObject API. 141 | 4. Allowed you to specify a type in the ListBuckets API to list the buckets of this type. 142 | 143 | Documentation & Demo: 144 | 1. Added descriptions about bucket encryption APIs. 145 | 2. Added descriptions about the AppendObject API. 146 | 3. Added descriptions about the ModifyObject API. 147 | 4. Added descriptions about the new parameter for specifying a bucket type in the ListBuckets API. 148 | 149 | Resolved Issues: 150 | 151 | ----------------------------------------------------------------------------------- 152 | 153 | Version 3.21.1 154 | 155 | New Features: 156 | 157 | Documentation & Demo: 158 | 159 | Resolved Issues: 160 | 1. Fixed the issue that a failure message is still returned even if code is successfully retried within allowed attempts. 161 | 2. Fixed the issue that no error messages are returned when a response message fails to be obtained. 162 | 163 | ----------------------------------------------------------------------------------- 164 | 165 | Version 3.20.7.1 166 | 167 | New Features: 168 | 1. Added the APIs for resumable upload (ObsClient.UploadFile) and resumable download (ObsClient.DownloadFile). 169 | 170 | Documentation & Demo: 171 | 1. Added the description of the API for resumable upload to section "Object Upload" and the description of the API for resumable download to section "Object Download" in the OBS Go SDK Developer Guide. 172 | 2. Added the topics of resumable upload and resumable download to section "Other APIs" in OBS Go SDK API Reference. 173 | 174 | Resolved Issues: 175 | 1. Optimized part of the code. 176 | 177 | ----------------------------------------------------------------------------------- 178 | 179 | Version 3.20.7 180 | 181 | New Features: 182 | 183 | Documentation & Demo: 184 | 185 | Resolved Issues: 186 | 1. Optimized part of the code. 187 | 188 | ----------------------------------------------------------------------------------- 189 | 190 | Version 3.20.3 191 | 192 | New Features: 193 | 1. Added the ObsClient.HeadObject API for determining whether an object exists. 194 | 2. Added the ObsClient.SetBucketRequestPayment and ObsClient.GetBucketRequestPayment APIs respectively for configuring the Requester Pays function and obtaining related configuration. 195 | 3. Supports configuration of the Requester Pays header (obs.WithReqPaymentHeader) when calling an API. 196 | 197 | Documentation & Demo: 198 | 1. Added the topic of checking whether an object exists to section "Object Management" in OBS Go SDK Developer Guide; added the API for checking whether an object exists to section "Bucket-Related APIs" in OBS Go SDK API Reference; added the request parameter for checking whether an object exists to section "Data Types" in OBS Go SDK API Reference. 199 | 2. Added the topic of Requester Pays to section "Bucket Management" in OBS Go SDK Developer Guide; added the APIs for configuring the Requester Pays function and obtaining related configuration to section "Bucket-Related APIs" in OBS Go SDK API Reference; added the request parameter for configuring Requester Pays and the response result of obtaining Requester Pays configuration to section "Data Types" in OBS Go SDK API Reference. 200 | 3. Added the topic of SDK API extended configurations to section "Initialization" in OBS Go SDK API Reference. 201 | 4. Added the topic of Requester Pays configuration to section "Enumeration Constants" in OBS Go SDK API Reference. 202 | 5. Added the description of extended parameters to the API method definitions in OBS Go SDK API Reference. 203 | 204 | Resolved Issues: 205 | 1. Optimized part of the code. 206 | 2. Fixed the issue that the Epid parameter does not take effect if StorageClass is not specified when calling the CreateBucket API. 207 | 3. Supports the az-redundancy parameter when calling the CreateBucket API. 208 | 209 | ----------------------------------------------------------------------------------- 210 | 211 | Version 3.20.1 212 | 213 | New Features: 214 | 215 | Documentation & Demo: 216 | 217 | Resolved Issues: 218 | 1. Optimized part of the code. 219 | 220 | ----------------------------------------------------------------------------------- 221 | 222 | Version 3.19.11 223 | Updated the version ID format. The new version ID is named in the following format: Main version ID.Year ID.Month ID. 224 | 225 | New Features: 226 | 227 | Documentation & Demo: 228 | 1. Added the description of WithMaxRedirectCount and WithSecurityToken in section "ObsClient Initialization" in the API Reference. 229 | 2. Added descriptions about WithMaxRedirectCount and WithSecurityToken in section "Configuring an Instance of OBSClient" in the Developer Guide. 230 | 3. Added methods for handling HTTP status code 405 in section "FAQs" in the Developer Guide. 231 | 232 | Resolved Issues: 233 | 1. Fixed the issue that error code 400 is returned when DisplayName is carried in some circumstances. 234 | 2. Fixed the issue that error code 400 is returned when the SetBucketNotification API is called. 235 | 3. Fixed the issue that the SetObjectAcl API does not support the specified Delivered parameter. 236 | 4. Fixed the issue that the SetBucketLoggingConfiguration API is incompatible with the Agency field in different protocols. 237 | 5. Fixed the issue that the SetBucketLoggingConfiguration API incorrectly processes the Delivered field. 238 | 6. Fixed the issue that the ContentMD5 configuration does not take effect when the UploadPart API is called. 239 | 7. Fixed the issue that the SetBucketStoragePolicy API processes incorrect storage classes inconsistently in different protocols. 240 | 8. Fixed the issue that error code 400 is returned because the CreateBucket API does not distinguish protocols. 241 | 9. Fixed the syntax issue of the getBucketAclObs API. 242 | 10. Rectified the error of the SetBucketWebsiteConfiguration API. 243 | 11. Fixed the compatibility issue of the temporary authentication API in different protocols. 244 | 12. Fixed the issue that the authentication information header is added when redirection is performed upon a 302 response returned for a GET request. 245 | 13. Fixed the issue that the content-type cannot be obtained based on the file name extension if the extension is in uppercase. 246 | 14. Changed the content-type of files with the .svg extension to image/svg+xml. 247 | 15. Fixed the issue that an incorrect API is called in the sample code for deleting a bucket in examples/bucket_operations_sample.go. 248 | 16. Fixed the issue in calculating the authentication value during the access using temporary access keys. 249 | 17. Fixed the issue that some response fields are empty in anonymous access. 250 | 251 | ----------------------------------------------------------------------------------- 252 | 253 | Version 3.1.2 254 | 255 | New Features: 256 | 257 | Documentation & Demo: 258 | 259 | Resolved Issues: 260 | 1. Fixed the issue that the configuration of ContentType does not take effect when Obs.InitiateMultipartUpload is called. 261 | 262 | ----------------------------------------------------------------------------------- 263 | 264 | Version 3.1.1 265 | 266 | New Features: 267 | 1. Supports access to OBS using a user-defined domain name (obs.WithCustomDomainName). 268 | 2. The API for object upload (ObsClient.PutObject) can automatically identify a wider MIME type. 269 | 270 | Resolved Issues: 271 | 1. Fixed the issue that a null pointer error is reported when ObsClient.GetBucketStoragePolicy is called. 272 | 2. Fixed the 400 error reported by ObsClient.SetBucketStoragePolicy. 273 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Huawei Technologies Co., Ltd. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /obs/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | import ( 16 | "fmt" 17 | "net/url" 18 | "sort" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | func setURLWithPolicy(bucketName, canonicalizedUrl string) string { 24 | if strings.HasPrefix(canonicalizedUrl, "/"+bucketName+"/") { 25 | canonicalizedUrl = canonicalizedUrl[len("/"+bucketName+"/"):] 26 | } else if strings.HasPrefix(canonicalizedUrl, "/"+bucketName) { 27 | canonicalizedUrl = canonicalizedUrl[len("/"+bucketName):] 28 | } 29 | return canonicalizedUrl 30 | } 31 | 32 | func (obsClient ObsClient) doAuthTemporary(method, bucketName, objectKey string, policy string, params map[string]string, 33 | headers map[string][]string, expires int64) (requestURL string, err error) { 34 | sh := obsClient.getSecurity() 35 | isAkSkEmpty := sh.ak == "" || sh.sk == "" 36 | if isAkSkEmpty == false && sh.securityToken != "" { 37 | if obsClient.conf.signature == SignatureObs { 38 | params[HEADER_STS_TOKEN_OBS] = sh.securityToken 39 | } else { 40 | params[HEADER_STS_TOKEN_AMZ] = sh.securityToken 41 | } 42 | } 43 | 44 | if policy != "" { 45 | objectKey = "" 46 | } 47 | 48 | requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true) 49 | parsedRequestURL, err := url.Parse(requestURL) 50 | if err != nil { 51 | return "", err 52 | } 53 | encodeHeaders(headers) 54 | hostName := parsedRequestURL.Host 55 | 56 | isV4 := obsClient.conf.signature == SignatureV4 57 | prepareHostAndDate(headers, hostName, isV4) 58 | 59 | if isAkSkEmpty { 60 | doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization") 61 | } else { 62 | if isV4 { 63 | date, parseDateErr := time.Parse(RFC1123_FORMAT, headers[HEADER_DATE_CAMEL][0]) 64 | if parseDateErr != nil { 65 | doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr) 66 | return "", parseDateErr 67 | } 68 | delete(headers, HEADER_DATE_CAMEL) 69 | shortDate := date.Format(SHORT_DATE_FORMAT) 70 | longDate := date.Format(LONG_DATE_FORMAT) 71 | if len(headers[HEADER_HOST_CAMEL]) != 0 { 72 | index := strings.LastIndex(headers[HEADER_HOST_CAMEL][0], ":") 73 | if index != -1 { 74 | port := headers[HEADER_HOST_CAMEL][0][index+1:] 75 | if port == "80" || port == "443" { 76 | headers[HEADER_HOST_CAMEL] = []string{headers[HEADER_HOST_CAMEL][0][:index]} 77 | } 78 | } 79 | 80 | } 81 | 82 | signedHeaders, _headers := getSignedHeaders(headers) 83 | 84 | credential, scope := getCredential(sh.ak, obsClient.conf.region, shortDate) 85 | params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX 86 | params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential 87 | params[PARAM_DATE_AMZ_CAMEL] = longDate 88 | params[PARAM_EXPIRES_AMZ_CAMEL] = Int64ToString(expires) 89 | params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = strings.Join(signedHeaders, ";") 90 | 91 | requestURL, canonicalizedURL = obsClient.conf.formatUrls(bucketName, objectKey, params, true) 92 | parsedRequestURL, _err := url.Parse(requestURL) 93 | if _err != nil { 94 | return "", _err 95 | } 96 | 97 | stringToSign := getV4StringToSign(method, canonicalizedURL, parsedRequestURL.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, signedHeaders, _headers) 98 | signature := getSignature(stringToSign, sh.sk, obsClient.conf.region, shortDate) 99 | 100 | requestURL += fmt.Sprintf("&%s=%s", PARAM_SIGNATURE_AMZ_CAMEL, UrlEncode(signature, false)) 101 | 102 | } else { 103 | originDate := headers[HEADER_DATE_CAMEL][0] 104 | date, parseDateErr := time.Parse(RFC1123_FORMAT, originDate) 105 | if parseDateErr != nil { 106 | doLog(LEVEL_WARN, "Failed to parse date with reason: %v", parseDateErr) 107 | return "", parseDateErr 108 | } 109 | expires += date.Unix() 110 | if policy == "" { 111 | headers[HEADER_DATE_CAMEL] = []string{Int64ToString(expires)} 112 | } else { 113 | policy = Base64Encode([]byte(policy)) 114 | headers[HEADER_DATE_CAMEL] = []string{policy} 115 | canonicalizedURL = setURLWithPolicy(bucketName, canonicalizedURL) 116 | } 117 | 118 | stringToSign := getV2StringToSign(method, canonicalizedURL, headers, obsClient.conf.signature == SignatureObs) 119 | signature := UrlEncode(Base64Encode(HmacSha1([]byte(sh.sk), []byte(stringToSign))), false) 120 | if strings.Index(requestURL, "?") < 0 { 121 | requestURL += "?" 122 | } else { 123 | requestURL += "&" 124 | } 125 | delete(headers, HEADER_DATE_CAMEL) 126 | 127 | if obsClient.conf.signature != SignatureObs { 128 | requestURL += "AWS" 129 | } 130 | if policy == "" { 131 | requestURL += fmt.Sprintf("AccessKeyId=%s&Expires=%d&Signature=%s", UrlEncode(sh.ak, false), 132 | expires, signature) 133 | return 134 | 135 | } 136 | requestURL += fmt.Sprintf("AccessKeyId=%s&Policy=%s&Signature=%s", UrlEncode(sh.ak, false), 137 | UrlEncode(policy, false), signature) 138 | } 139 | } 140 | 141 | return 142 | } 143 | 144 | func (obsClient ObsClient) doAuth(method, bucketName, objectKey string, params map[string]string, 145 | headers map[string][]string, hostName string) (requestURL string, err error) { 146 | sh := obsClient.getSecurity() 147 | isAkSkEmpty := sh.ak == "" || sh.sk == "" 148 | if isAkSkEmpty == false && sh.securityToken != "" { 149 | if obsClient.conf.signature == SignatureObs { 150 | headers[HEADER_STS_TOKEN_OBS] = []string{sh.securityToken} 151 | } else { 152 | headers[HEADER_STS_TOKEN_AMZ] = []string{sh.securityToken} 153 | } 154 | } 155 | isObs := obsClient.conf.signature == SignatureObs 156 | requestURL, canonicalizedURL := obsClient.conf.formatUrls(bucketName, objectKey, params, true) 157 | parsedRequestURL, err := url.Parse(requestURL) 158 | if err != nil { 159 | return "", err 160 | } 161 | encodeHeaders(headers) 162 | 163 | if hostName == "" { 164 | hostName = parsedRequestURL.Host 165 | } 166 | 167 | isV4 := obsClient.conf.signature == SignatureV4 168 | prepareHostAndDate(headers, hostName, isV4) 169 | 170 | if isAkSkEmpty { 171 | doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization") 172 | } else { 173 | ak := sh.ak 174 | sk := sh.sk 175 | var authorization string 176 | if isV4 { 177 | headers[HEADER_CONTENT_SHA256_AMZ] = []string{UNSIGNED_PAYLOAD} 178 | ret := v4Auth(ak, sk, obsClient.conf.region, method, canonicalizedURL, parsedRequestURL.RawQuery, headers) 179 | authorization = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"]) 180 | } else { 181 | ret := v2Auth(ak, sk, method, canonicalizedURL, headers, isObs) 182 | hashPrefix := V2_HASH_PREFIX 183 | if isObs { 184 | hashPrefix = OBS_HASH_PREFIX 185 | } 186 | authorization = fmt.Sprintf("%s %s:%s", hashPrefix, ak, ret["Signature"]) 187 | } 188 | headers[HEADER_AUTH_CAMEL] = []string{authorization} 189 | } 190 | return 191 | } 192 | 193 | func prepareHostAndDate(headers map[string][]string, hostName string, isV4 bool) { 194 | headers[HEADER_HOST_CAMEL] = []string{hostName} 195 | if date, ok := headers[HEADER_DATE_AMZ]; ok { 196 | flag := false 197 | if len(date) == 1 { 198 | if isV4 { 199 | if t, err := time.Parse(LONG_DATE_FORMAT, date[0]); err == nil { 200 | headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(t)} 201 | flag = true 202 | } 203 | } else { 204 | if strings.HasSuffix(date[0], "GMT") { 205 | headers[HEADER_DATE_CAMEL] = []string{date[0]} 206 | flag = true 207 | } 208 | } 209 | } 210 | if !flag { 211 | delete(headers, HEADER_DATE_AMZ) 212 | } 213 | } 214 | if _, ok := headers[HEADER_DATE_CAMEL]; !ok { 215 | headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(time.Now().UTC())} 216 | } 217 | 218 | } 219 | 220 | func encodeHeaders(headers map[string][]string) { 221 | for key, values := range headers { 222 | for index, value := range values { 223 | values[index] = UrlEncode(value, true) 224 | } 225 | headers[key] = values 226 | } 227 | } 228 | 229 | func prepareDateHeader(dataHeader, dateCamelHeader string, headers, _headers map[string][]string) { 230 | if _, ok := _headers[HEADER_DATE_CAMEL]; ok { 231 | if _, ok := _headers[dataHeader]; ok { 232 | _headers[HEADER_DATE_CAMEL] = []string{""} 233 | } else if _, ok := headers[dateCamelHeader]; ok { 234 | _headers[HEADER_DATE_CAMEL] = []string{""} 235 | } 236 | } else if _, ok := _headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok { 237 | if _, ok := _headers[dataHeader]; ok { 238 | _headers[HEADER_DATE_CAMEL] = []string{""} 239 | } else if _, ok := headers[dateCamelHeader]; ok { 240 | _headers[HEADER_DATE_CAMEL] = []string{""} 241 | } 242 | } 243 | } 244 | 245 | func getStringToSign(keys []string, isObs bool, _headers map[string][]string) []string { 246 | stringToSign := make([]string, 0, len(keys)) 247 | for _, key := range keys { 248 | var value string 249 | prefixHeader := HEADER_PREFIX 250 | prefixMetaHeader := HEADER_PREFIX_META 251 | if isObs { 252 | prefixHeader = HEADER_PREFIX_OBS 253 | prefixMetaHeader = HEADER_PREFIX_META_OBS 254 | } 255 | if strings.HasPrefix(key, prefixHeader) { 256 | if strings.HasPrefix(key, prefixMetaHeader) { 257 | for index, v := range _headers[key] { 258 | value += strings.TrimSpace(v) 259 | if index != len(_headers[key])-1 { 260 | value += "," 261 | } 262 | } 263 | } else { 264 | value = strings.Join(_headers[key], ",") 265 | } 266 | value = fmt.Sprintf("%s:%s", key, value) 267 | } else { 268 | value = strings.Join(_headers[key], ",") 269 | } 270 | stringToSign = append(stringToSign, value) 271 | } 272 | return stringToSign 273 | } 274 | 275 | func attachHeaders(headers map[string][]string, isObs bool) string { 276 | length := len(headers) 277 | _headers := make(map[string][]string, length) 278 | keys := make([]string, 0, length) 279 | 280 | for key, value := range headers { 281 | _key := strings.ToLower(strings.TrimSpace(key)) 282 | if _key != "" { 283 | prefixheader := HEADER_PREFIX 284 | if isObs { 285 | prefixheader = HEADER_PREFIX_OBS 286 | } 287 | if _key == "content-md5" || _key == "content-type" || _key == "date" || strings.HasPrefix(_key, prefixheader) { 288 | keys = append(keys, _key) 289 | _headers[_key] = value 290 | } 291 | } else { 292 | delete(headers, key) 293 | } 294 | } 295 | 296 | for _, interestedHeader := range interestedHeaders { 297 | if _, ok := _headers[interestedHeader]; !ok { 298 | _headers[interestedHeader] = []string{""} 299 | keys = append(keys, interestedHeader) 300 | } 301 | } 302 | dateCamelHeader := PARAM_DATE_AMZ_CAMEL 303 | dataHeader := HEADER_DATE_AMZ 304 | if isObs { 305 | dateCamelHeader = PARAM_DATE_OBS_CAMEL 306 | dataHeader = HEADER_DATE_OBS 307 | } 308 | prepareDateHeader(dataHeader, dateCamelHeader, headers, _headers) 309 | 310 | sort.Strings(keys) 311 | stringToSign := getStringToSign(keys, isObs, _headers) 312 | return strings.Join(stringToSign, "\n") 313 | } 314 | 315 | func getScope(region, shortDate string) string { 316 | return fmt.Sprintf("%s/%s/%s/%s", shortDate, region, V4_SERVICE_NAME, V4_SERVICE_SUFFIX) 317 | } 318 | 319 | func getCredential(ak, region, shortDate string) (string, string) { 320 | scope := getScope(region, shortDate) 321 | return fmt.Sprintf("%s/%s", ak, scope), scope 322 | } 323 | 324 | func getSignedHeaders(headers map[string][]string) ([]string, map[string][]string) { 325 | length := len(headers) 326 | _headers := make(map[string][]string, length) 327 | signedHeaders := make([]string, 0, length) 328 | for key, value := range headers { 329 | _key := strings.ToLower(strings.TrimSpace(key)) 330 | if _key != "" { 331 | signedHeaders = append(signedHeaders, _key) 332 | _headers[_key] = value 333 | } else { 334 | delete(headers, key) 335 | } 336 | } 337 | sort.Strings(signedHeaders) 338 | return signedHeaders, _headers 339 | } 340 | 341 | func getSignature(stringToSign, sk, region, shortDate string) string { 342 | key := HmacSha256([]byte(V4_HASH_PRE+sk), []byte(shortDate)) 343 | key = HmacSha256(key, []byte(region)) 344 | key = HmacSha256(key, []byte(V4_SERVICE_NAME)) 345 | key = HmacSha256(key, []byte(V4_SERVICE_SUFFIX)) 346 | return Hex(HmacSha256(key, []byte(stringToSign))) 347 | } 348 | -------------------------------------------------------------------------------- /obs/type.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Huawei Technologies Co.,Ltd. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use 3 | // this file except in compliance with the License. You may obtain a copy of the 4 | // License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software distributed 9 | // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | // specific language governing permissions and limitations under the License. 12 | 13 | package obs 14 | 15 | // SignatureType defines type of signature 16 | type SignatureType string 17 | 18 | const ( 19 | // SignatureV2 signature type v2 20 | SignatureV2 SignatureType = "v2" 21 | // SignatureV4 signature type v4 22 | SignatureV4 SignatureType = "v4" 23 | // SignatureObs signature type OBS 24 | SignatureObs SignatureType = "OBS" 25 | ) 26 | 27 | // HttpMethodType defines http method type 28 | type HttpMethodType string 29 | 30 | const ( 31 | HttpMethodGet HttpMethodType = HTTP_GET 32 | HttpMethodPut HttpMethodType = HTTP_PUT 33 | HttpMethodPost HttpMethodType = HTTP_POST 34 | HttpMethodDelete HttpMethodType = HTTP_DELETE 35 | HttpMethodHead HttpMethodType = HTTP_HEAD 36 | HttpMethodOptions HttpMethodType = HTTP_OPTIONS 37 | ) 38 | 39 | // SubResourceType defines the subResource value 40 | type SubResourceType string 41 | 42 | const ( 43 | // SubResourceStoragePolicy subResource value: storagePolicy 44 | SubResourceStoragePolicy SubResourceType = "storagePolicy" 45 | 46 | // SubResourceStorageClass subResource value: storageClass 47 | SubResourceStorageClass SubResourceType = "storageClass" 48 | 49 | // SubResourceQuota subResource value: quota 50 | SubResourceQuota SubResourceType = "quota" 51 | 52 | // SubResourceStorageInfo subResource value: storageinfo 53 | SubResourceStorageInfo SubResourceType = "storageinfo" 54 | 55 | // SubResourceLocation subResource value: location 56 | SubResourceLocation SubResourceType = "location" 57 | 58 | // SubResourceAcl subResource value: acl 59 | SubResourceAcl SubResourceType = "acl" 60 | 61 | // SubResourcePolicy subResource value: policy 62 | SubResourcePolicy SubResourceType = "policy" 63 | 64 | // SubResourceCors subResource value: cors 65 | SubResourceCors SubResourceType = "cors" 66 | 67 | // SubResourceVersioning subResource value: versioning 68 | SubResourceVersioning SubResourceType = "versioning" 69 | 70 | // SubResourceWebsite subResource value: website 71 | SubResourceWebsite SubResourceType = "website" 72 | 73 | // SubResourceLogging subResource value: logging 74 | SubResourceLogging SubResourceType = "logging" 75 | 76 | // SubResourceLifecycle subResource value: lifecycle 77 | SubResourceLifecycle SubResourceType = "lifecycle" 78 | 79 | // SubResourceNotification subResource value: notification 80 | SubResourceNotification SubResourceType = "notification" 81 | 82 | // SubResourceEncryption subResource value: encryption 83 | SubResourceEncryption SubResourceType = "encryption" 84 | 85 | // SubResourceTagging subResource value: tagging 86 | SubResourceTagging SubResourceType = "tagging" 87 | 88 | // SubResourceDelete subResource value: delete 89 | SubResourceDelete SubResourceType = "delete" 90 | 91 | // SubResourceVersions subResource value: versions 92 | SubResourceVersions SubResourceType = "versions" 93 | 94 | // SubResourceUploads subResource value: uploads 95 | SubResourceUploads SubResourceType = "uploads" 96 | 97 | // SubResourceRestore subResource value: restore 98 | SubResourceRestore SubResourceType = "restore" 99 | 100 | // SubResourceMetadata subResource value: metadata 101 | SubResourceMetadata SubResourceType = "metadata" 102 | 103 | // SubResourceRequestPayment subResource value: requestPayment 104 | SubResourceRequestPayment SubResourceType = "requestPayment" 105 | 106 | // SubResourceAppend subResource value: append 107 | SubResourceAppend SubResourceType = "append" 108 | 109 | // SubResourceModify subResource value: modify 110 | SubResourceModify SubResourceType = "modify" 111 | 112 | // SubResourceRename subResource value: rename 113 | SubResourceRename SubResourceType = "rename" 114 | 115 | // SubResourceCustomDomain subResource value: customdomain 116 | SubResourceCustomDomain SubResourceType = "customdomain" 117 | 118 | // SubResourceMirrorBackToSource subResource value: mirrorBackToSource 119 | SubResourceMirrorBackToSource SubResourceType = "mirrorBackToSource" 120 | 121 | // SubResourceMirrorBackToSource subResource value: mirrorBackToSource 122 | SubResourceAccesslabel SubResourceType = "x-obs-accesslabel" 123 | 124 | // SubResourceMirrorBackToSource subResource value: publicAccessBlock 125 | SubResourcePublicAccessBlock SubResourceType = "publicAccessBlock" 126 | 127 | // SubResourcePublicBucketStatus subResource value: bucketStatus 128 | SubResourceBucketPublicStatus SubResourceType = "bucketStatus" 129 | 130 | // SubResourcePublicPolicyStatus subResource value: policyStatus 131 | SubResourceBucketPolicyPublicStatus SubResourceType = "policyStatus" 132 | ) 133 | 134 | // objectKeyType defines the objectKey value 135 | type objectKeyType string 136 | 137 | const ( 138 | // objectKeyExtensionPolicy objectKey value: v1/extension_policy 139 | objectKeyExtensionPolicy objectKeyType = "v1/extension_policy" 140 | 141 | // objectKeyAsyncFetchJob objectKey value: v1/async-fetch/jobs 142 | objectKeyAsyncFetchJob objectKeyType = "v1/async-fetch/jobs" 143 | ) 144 | 145 | // AclType defines bucket/object acl type 146 | type AclType string 147 | 148 | const ( 149 | AclPrivate AclType = "private" 150 | AclPublicRead AclType = "public-read" 151 | AclPublicReadWrite AclType = "public-read-write" 152 | AclAuthenticatedRead AclType = "authenticated-read" 153 | AclBucketOwnerRead AclType = "bucket-owner-read" 154 | AclBucketOwnerFullControl AclType = "bucket-owner-full-control" 155 | AclLogDeliveryWrite AclType = "log-delivery-write" 156 | AclPublicReadDelivery AclType = "public-read-delivered" 157 | AclPublicReadWriteDelivery AclType = "public-read-write-delivered" 158 | ) 159 | 160 | // StorageClassType defines bucket storage class 161 | type StorageClassType string 162 | 163 | const ( 164 | //StorageClassStandard storage class: STANDARD 165 | StorageClassStandard StorageClassType = "STANDARD" 166 | 167 | //StorageClassWarm storage class: WARM 168 | StorageClassWarm StorageClassType = "WARM" 169 | 170 | //StorageClassCold storage class: COLD 171 | StorageClassCold StorageClassType = "COLD" 172 | 173 | //StorageClassDeepArchive storage class: DEEP_ARCHIVE 174 | StorageClassDeepArchive StorageClassType = "DEEP_ARCHIVE" 175 | 176 | //StorageClassIntelligentTiering storage class: INTELLIGENT_TIERING 177 | StorageClassIntelligentTiering StorageClassType = "INTELLIGENT_TIERING" 178 | 179 | storageClassStandardIA StorageClassType = "STANDARD_IA" 180 | storageClassGlacier StorageClassType = "GLACIER" 181 | ) 182 | 183 | // PermissionType defines permission type 184 | type PermissionType string 185 | 186 | const ( 187 | // PermissionRead permission type: READ 188 | PermissionRead PermissionType = "READ" 189 | 190 | // PermissionWrite permission type: WRITE 191 | PermissionWrite PermissionType = "WRITE" 192 | 193 | // PermissionReadAcp permission type: READ_ACP 194 | PermissionReadAcp PermissionType = "READ_ACP" 195 | 196 | // PermissionWriteAcp permission type: WRITE_ACP 197 | PermissionWriteAcp PermissionType = "WRITE_ACP" 198 | 199 | // PermissionFullControl permission type: FULL_CONTROL 200 | PermissionFullControl PermissionType = "FULL_CONTROL" 201 | ) 202 | 203 | // GranteeType defines grantee type 204 | type GranteeType string 205 | 206 | const ( 207 | // GranteeGroup grantee type: Group 208 | GranteeGroup GranteeType = "Group" 209 | 210 | // GranteeUser grantee type: CanonicalUser 211 | GranteeUser GranteeType = "CanonicalUser" 212 | ) 213 | 214 | // GroupUriType defines grantee uri type 215 | type GroupUriType string 216 | 217 | const ( 218 | // GroupAllUsers grantee uri type: AllUsers 219 | GroupAllUsers GroupUriType = "AllUsers" 220 | 221 | // GroupAuthenticatedUsers grantee uri type: AuthenticatedUsers 222 | GroupAuthenticatedUsers GroupUriType = "AuthenticatedUsers" 223 | 224 | // GroupLogDelivery grantee uri type: LogDelivery 225 | GroupLogDelivery GroupUriType = "LogDelivery" 226 | ) 227 | 228 | // VersioningStatusType defines bucket version status 229 | type VersioningStatusType string 230 | 231 | const ( 232 | // VersioningStatusEnabled version status: Enabled 233 | VersioningStatusEnabled VersioningStatusType = "Enabled" 234 | 235 | // VersioningStatusSuspended version status: Suspended 236 | VersioningStatusSuspended VersioningStatusType = "Suspended" 237 | ) 238 | 239 | // ProtocolType defines protocol type 240 | type ProtocolType string 241 | 242 | const ( 243 | // ProtocolHttp prorocol type: http 244 | ProtocolHttp ProtocolType = "http" 245 | 246 | // ProtocolHttps prorocol type: https 247 | ProtocolHttps ProtocolType = "https" 248 | ) 249 | 250 | // RuleStatusType defines lifeCycle rule status 251 | type RuleStatusType string 252 | 253 | const ( 254 | // RuleStatusEnabled rule status: Enabled 255 | RuleStatusEnabled RuleStatusType = "Enabled" 256 | 257 | // RuleStatusDisabled rule status: Disabled 258 | RuleStatusDisabled RuleStatusType = "Disabled" 259 | ) 260 | 261 | // RestoreTierType defines restore options 262 | type RestoreTierType string 263 | 264 | const ( 265 | // RestoreTierExpedited restore options: Expedited 266 | RestoreTierExpedited RestoreTierType = "Expedited" 267 | 268 | // RestoreTierStandard restore options: Standard 269 | RestoreTierStandard RestoreTierType = "Standard" 270 | 271 | // RestoreTierBulk restore options: Bulk 272 | RestoreTierBulk RestoreTierType = "Bulk" 273 | ) 274 | 275 | // MetadataDirectiveType defines metadata operation indicator 276 | type MetadataDirectiveType string 277 | 278 | const ( 279 | // CopyMetadata metadata operation: COPY 280 | CopyMetadata MetadataDirectiveType = "COPY" 281 | 282 | // ReplaceNew metadata operation: REPLACE_NEW 283 | ReplaceNew MetadataDirectiveType = "REPLACE_NEW" 284 | 285 | // ReplaceMetadata metadata operation: REPLACE 286 | ReplaceMetadata MetadataDirectiveType = "REPLACE" 287 | ) 288 | 289 | // EventType defines bucket notification type of events 290 | type EventType string 291 | 292 | const ( 293 | // ObjectCreatedAll type of events: ObjectCreated:* 294 | ObjectCreatedAll EventType = "ObjectCreated:*" 295 | 296 | // ObjectCreatedPut type of events: ObjectCreated:Put 297 | ObjectCreatedPut EventType = "ObjectCreated:Put" 298 | 299 | // ObjectCreatedPost type of events: ObjectCreated:Post 300 | ObjectCreatedPost EventType = "ObjectCreated:Post" 301 | 302 | // ObjectCreatedCopy type of events: ObjectCreated:Copy 303 | ObjectCreatedCopy EventType = "ObjectCreated:Copy" 304 | 305 | // ObjectCreatedCompleteMultipartUpload type of events: ObjectCreated:CompleteMultipartUpload 306 | ObjectCreatedCompleteMultipartUpload EventType = "ObjectCreated:CompleteMultipartUpload" 307 | 308 | // ObjectRemovedAll type of events: ObjectRemoved:* 309 | ObjectRemovedAll EventType = "ObjectRemoved:*" 310 | 311 | // ObjectRemovedDelete type of events: ObjectRemoved:Delete 312 | ObjectRemovedDelete EventType = "ObjectRemoved:Delete" 313 | 314 | // ObjectRemovedDeleteMarkerCreated type of events: ObjectRemoved:DeleteMarkerCreated 315 | ObjectRemovedDeleteMarkerCreated EventType = "ObjectRemoved:DeleteMarkerCreated" 316 | ) 317 | 318 | // PayerType defines type of payer 319 | type PayerType string 320 | 321 | const ( 322 | // BucketOwnerPayer type of payer: BucketOwner 323 | BucketOwnerPayer PayerType = "BucketOwner" 324 | 325 | // RequesterPayer type of payer: Requester 326 | RequesterPayer PayerType = "Requester" 327 | 328 | // Requester header for requester-Pays 329 | Requester PayerType = "requester" 330 | ) 331 | 332 | // FetchPolicyStatusType defines type of fetch policy status 333 | type FetchPolicyStatusType string 334 | 335 | const ( 336 | // FetchStatusOpen type of status: open 337 | FetchStatusOpen FetchPolicyStatusType = "open" 338 | 339 | // FetchStatusClosed type of status: closed 340 | FetchStatusClosed FetchPolicyStatusType = "closed" 341 | ) 342 | 343 | // AvailableZoneType defines type of az redundancy 344 | type AvailableZoneType string 345 | 346 | const ( 347 | AvailableZoneMultiAz AvailableZoneType = "3az" 348 | ) 349 | 350 | // FSStatusType defines type of file system status 351 | type FSStatusType string 352 | 353 | const ( 354 | FSStatusEnabled FSStatusType = "Enabled" 355 | FSStatusDisabled FSStatusType = "Disabled" 356 | ) 357 | 358 | // BucketType defines type of bucket 359 | type BucketType string 360 | 361 | const ( 362 | OBJECT BucketType = "OBJECT" 363 | POSIX BucketType = "POSIX" 364 | ) 365 | 366 | // RedundancyType defines type of redundancyType 367 | type BucketRedundancyType string 368 | 369 | const ( 370 | BucketRedundancyClassic BucketRedundancyType = "CLASSIC" 371 | BucketRedundancyFusion BucketRedundancyType = "FUSION" 372 | ) 373 | --------------------------------------------------------------------------------