Skip to content

Commit 8a915d9

Browse files
sincereflyXudong Liu
andauthored
Feature/support storage signed put (#18)
* feat: Support Storage Put Signed URL (client upload) * fix: put pre-signed url with option header * add: pre-signed url add tagging header * update: object_storage example with put object * fix: alicloud option key_id and key_secret return AliCloudStorageOption GetSecretID() and GetSecretKey() --------- Co-authored-by: Xudong Liu <xudong.liu@ewp-group.com>
1 parent b61537c commit 8a915d9

File tree

5 files changed

+256
-23
lines changed

5 files changed

+256
-23
lines changed

cloud/examples/object_storage/object_storage.go

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package main
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"fmt"
8+
"github.com/aws/aws-sdk-go/aws"
9+
"io"
10+
"log"
11+
"net/http"
712
"time"
813

914
"github.com/byte-power/gorich/cloud"
@@ -29,11 +34,16 @@ func main() {
2934
object_storage_examples("aws_bucket_name_xxx", optionForAWS)
3035

3136
optionForAliOSS := object_storage.AliCloudStorageOption{
32-
CredentialType: "oidc_role_arn",
33-
EndPoint: "oss-cn-zhangjiakou.aliyuncs.com",
34-
SessionName: "test-rrsa-oidc-token",
37+
//CredentialType: "oidc_role_arn",
38+
//EndPoint: "oss-cn-zhangjiakou.aliyuncs.com",
39+
//SessionName: "test-rrsa-oidc-token",
40+
41+
CredentialType: "ak",
42+
EndPoint: "oss-cn-beijing.aliyuncs.com",
43+
AccessKeyID: "alicloud_access_key_id_xxx",
44+
AccessKeySecret: "alicloud_access_key_secret_xxx",
3545
}
36-
object_storage_examples("my-bucket", optionForAliOSS)
46+
object_storage_examples("alicloud_bucket_name_xxx", optionForAliOSS)
3747
}
3848

3949
func object_storage_examples(bucketName string, option cloud.Option) {
@@ -186,4 +196,80 @@ func object_storage_examples(bucketName string, option cloud.Option) {
186196
fmt.Printf("GetSignedURLForExistedKey %s %s\n", name, url)
187197
}
188198
}
199+
200+
// PutSignedURL examples
201+
for name, item := range files {
202+
// get pre-signed put url
203+
opt := object_storage.PutHeaderOption{
204+
ContentType: aws.String(item.ContentType),
205+
}
206+
url, err := service.PutSignedURL(name, 1*time.Hour, opt)
207+
if err != nil {
208+
fmt.Printf("GetSignedURL for object %s error %s\n", name, err)
209+
return
210+
}
211+
fmt.Printf("GetSignedURL for put object %s %s\n", name, url)
212+
213+
// put content to s3 with signed-url
214+
if err := uploadContent(url, string(item.Body), item.ContentType); err != nil {
215+
fmt.Printf("Error uploading content: %v\n", err)
216+
return
217+
}
218+
219+
// get pre-signed url for download and check
220+
getSignedURL, err := service.GetSignedURL(name, 1*time.Hour)
221+
if err != nil {
222+
fmt.Printf("GetSignedURL for object %s error %s\n", name, err)
223+
return
224+
}
225+
fmt.Printf("GetSignedURL for get object %s %s\n", name, getSignedURL)
226+
227+
// check content
228+
content, err := accessContentBySignedURL(getSignedURL)
229+
if err != nil {
230+
log.Fatalf("Get content err: %v", err)
231+
}
232+
fmt.Println("Get content:", content)
233+
}
234+
}
235+
236+
// uploadContent uploads content to S3 using the provided presigned URL.
237+
func uploadContent(signedURL, content, contentType string) error {
238+
req, err := http.NewRequest("PUT", signedURL, bytes.NewBuffer([]byte(content)))
239+
if err != nil {
240+
return fmt.Errorf("error creating PUT request: %w", err)
241+
}
242+
req.Header.Set("Content-Type", contentType)
243+
244+
client := &http.Client{}
245+
resp, err := client.Do(req)
246+
if err != nil {
247+
return fmt.Errorf("error executing PUT request: %w", err)
248+
}
249+
defer resp.Body.Close()
250+
251+
if resp.StatusCode != http.StatusOK {
252+
return fmt.Errorf("upload failed with status code %d", resp.StatusCode)
253+
}
254+
fmt.Println("Content uploaded successfully")
255+
return nil
256+
}
257+
258+
// accessContentBySignedURL access content by Signed URL
259+
func accessContentBySignedURL(url string) (string, error) {
260+
resp, err := http.Get(url)
261+
if err != nil {
262+
return "", fmt.Errorf("access content failed: %v", err)
263+
}
264+
defer resp.Body.Close()
265+
266+
if resp.StatusCode != http.StatusOK {
267+
return "", fmt.Errorf("access content failed, status code: %d", resp.StatusCode)
268+
}
269+
270+
body, err := io.ReadAll(resp.Body)
271+
if err != nil {
272+
return "", fmt.Errorf("get content failed: %v", err)
273+
}
274+
return string(body), nil
189275
}

cloud/object_storage/alicloud_object_storage.go

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,33 @@ import (
1717
var ossClientMap = make(map[string]*oss.Client)
1818

1919
var (
20-
ErrAliCloudStorageServiceCredentialTypeEmpty = errors.New("credential_type for alicloud storage service is empty")
21-
ErrAliCloudStorageServiceEndPointEmpty = errors.New("endpoint for alicloud storage service is empty")
22-
ErrAliCloudStorageServiceSessionNameEmpty = errors.New("session_name for alicloud storage service is empty")
20+
ErrAliCloudStorageServiceCredentialTypeEmpty = errors.New("credential_type for alicloud storage service is empty")
21+
ErrAliCloudStorageServiceEndPointEmpty = errors.New("endpoint for alicloud storage service is empty")
22+
ErrAliCloudStorageServiceSessionNameEmpty = errors.New("session_name for alicloud storage service is empty")
23+
ErrAliCloudStorageServiceAccessKeyIDEmpty = errors.New("access_key_id for alicloud storage service is empty")
24+
ErrAliCloudStorageServiceAccessKeySecretEmpty = errors.New("access_key_secret for alicloud storage service is empty")
2325
)
2426

2527
type AliCloudStorageOption struct {
26-
CredentialType string // eg: "oidc_role_arn"
28+
CredentialType string // eg: "oidc_role_arn" or "ak"
2729
EndPoint string // eg: "oss-cn-zhangjiakou.aliyuncs.com"
2830
SessionName string // eg: "test-rrsa-oidc-token"
31+
32+
// "ak" required
33+
AccessKeyID string
34+
AccessKeySecret string
2935
}
3036

3137
func (option AliCloudStorageOption) GetProvider() cloud.Provider {
3238
return cloud.AliCloudStorageProvider
3339
}
3440

3541
func (option AliCloudStorageOption) GetSecretID() string {
36-
return ""
42+
return option.AccessKeyID
3743
}
3844

3945
func (option AliCloudStorageOption) GetSecretKey() string {
40-
return ""
46+
return option.AccessKeySecret
4147
}
4248

4349
func (option AliCloudStorageOption) GetAssumeRoleArn() string {
@@ -79,8 +85,18 @@ func (option AliCloudStorageOption) check() error {
7985
if option.EndPoint == "" {
8086
return ErrAliCloudStorageServiceEndPointEmpty
8187
}
82-
if option.SessionName == "" {
83-
return ErrAliCloudStorageServiceSessionNameEmpty
88+
89+
if option.CredentialType == "oidc_role_arn" {
90+
if option.SessionName == "" {
91+
return ErrAliCloudStorageServiceSessionNameEmpty
92+
}
93+
} else if option.CredentialType == "ak" {
94+
if option.AccessKeyID == "" {
95+
return ErrAliCloudStorageServiceAccessKeyIDEmpty
96+
}
97+
if option.AccessKeySecret == "" {
98+
return ErrAliCloudStorageServiceAccessKeySecretEmpty
99+
}
84100
}
85101
return nil
86102
}
@@ -109,17 +125,28 @@ func GetAliCloudObjectService(bucketName string, option cloud.Option) (ObjectSto
109125
return &AliCloudObjectStorageService{client: client, bucketName: bucketName}, nil
110126
}
111127

112-
cred, err := newOidcCredential(storageOption.CredentialType, storageOption.SessionName)
113-
if err != nil {
114-
return nil, err
115-
}
116-
117-
provider := &aliCloudCredentialsProvider{
118-
cred: cred,
119-
}
120-
client, err := oss.New(storageOption.EndPoint, "", "", oss.SetCredentialsProvider(provider))
121-
if err != nil {
122-
return nil, err
128+
var client *oss.Client
129+
if storageOption.CredentialType == "oidc_role_arn" {
130+
cred, err := newOidcCredential(storageOption.CredentialType, storageOption.SessionName)
131+
if err != nil {
132+
return nil, err
133+
}
134+
provider := &aliCloudCredentialsProvider{
135+
cred: cred,
136+
}
137+
ossClient, err := oss.New(storageOption.EndPoint, "", "", oss.SetCredentialsProvider(provider))
138+
if err != nil {
139+
return nil, err
140+
}
141+
client = ossClient
142+
} else if storageOption.CredentialType == "ak" {
143+
ossClient, err := oss.New(storageOption.EndPoint, storageOption.AccessKeyID, storageOption.AccessKeySecret)
144+
if err != nil {
145+
return nil, err
146+
}
147+
client = ossClient
148+
} else {
149+
return nil, fmt.Errorf("credential type '%s' unsupported", storageOption.CredentialType)
123150
}
124151

125152
// cache client
@@ -319,3 +346,15 @@ func (service *AliCloudObjectStorageService) GetSignedURLForExistedKey(ctx conte
319346
}
320347
return service.GetSignedURL(key, duration)
321348
}
349+
350+
func (service *AliCloudObjectStorageService) PutSignedURL(key string, duration time.Duration, option PutHeaderOption) (string, error) {
351+
if key == "" {
352+
return "", ErrObjectKeyEmpty
353+
}
354+
bucket, err := service.client.Bucket(service.bucketName)
355+
if err != nil {
356+
return "", err
357+
}
358+
options := option.ToAliCloudOptions()
359+
return bucket.SignURL(key, oss.HTTPPut, int64(duration.Seconds()), options...)
360+
}

cloud/object_storage/aws_object_storage.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,28 @@ func (service *AWSObjectStorageService) GetSignedURLForExistedKey(ctx context.Co
204204
return service.GetSignedURL(key, duration)
205205
}
206206

207+
func (service *AWSObjectStorageService) PutSignedURL(key string, duration time.Duration, option PutHeaderOption) (string, error) {
208+
if key == "" {
209+
return "", ErrObjectKeyEmpty
210+
}
211+
212+
request, _ := service.client.PutObjectRequest(&s3.PutObjectInput{
213+
Bucket: &service.bucketName,
214+
Key: &key,
215+
ContentDisposition: option.ContentDisposition,
216+
ContentEncoding: option.ContentEncoding,
217+
ContentMD5: option.ContentMD5,
218+
ContentType: option.ContentType,
219+
ContentLength: option.ContentLength,
220+
Tagging: option.Tagging,
221+
})
222+
url, err := request.Presign(duration)
223+
if err != nil {
224+
return "", err
225+
}
226+
return url, nil
227+
}
228+
207229
func isNotFoundErrorForAWS(err error) bool {
208230
awsErr, ok := err.(awserr.Error)
209231
if !ok {

cloud/object_storage/object_storage.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package object_storage
33
import (
44
"context"
55
"errors"
6+
"github.com/aliyun/aliyun-oss-go-sdk/oss"
7+
"github.com/tencentyun/cos-go-sdk-v5"
8+
"net/http"
9+
"net/url"
10+
"strconv"
611
"time"
712

813
"github.com/byte-power/gorich/cloud"
@@ -24,6 +29,7 @@ type ObjectStorageService interface {
2429
GetSignedURL(key string, duration time.Duration) (string, error)
2530
// GetSignedURLForExistedKey generates signed url if key exists. If key does not exist, return error
2631
GetSignedURLForExistedKey(ctx context.Context, key string, duration time.Duration) (string, error)
32+
PutSignedURL(key string, duration time.Duration, option PutHeaderOption) (string, error)
2733
}
2834

2935
type PutObjectInput struct {
@@ -75,3 +81,64 @@ func GetObjectStorageService(bucketName string, option cloud.Option) (ObjectStor
7581
}
7682
return nil, cloud.ErrUnsupportedCloudProvider
7783
}
84+
85+
type PutHeaderOption struct {
86+
ContentDisposition *string
87+
ContentEncoding *string
88+
ContentMD5 *string
89+
ContentType *string
90+
ContentLength *int64
91+
Tagging *string
92+
}
93+
94+
func (o *PutHeaderOption) ToAliCloudOptions() []oss.Option {
95+
96+
options := make([]oss.Option, 0)
97+
if o.ContentDisposition != nil {
98+
options = append(options, oss.ContentDisposition(*o.ContentDisposition))
99+
}
100+
if o.ContentEncoding != nil {
101+
options = append(options, oss.ContentEncoding(*o.ContentEncoding))
102+
}
103+
if o.ContentMD5 != nil {
104+
options = append(options, oss.ContentMD5(*o.ContentMD5))
105+
}
106+
if o.ContentType != nil {
107+
options = append(options, oss.ContentType(*o.ContentType))
108+
}
109+
if o.ContentLength != nil {
110+
options = append(options, oss.ContentLength(*o.ContentLength))
111+
}
112+
if o.Tagging != nil {
113+
options = append(options, oss.SetHeader(oss.HTTPHeaderOssTagging, *o.Tagging))
114+
}
115+
return options
116+
}
117+
118+
func (o *PutHeaderOption) ToTencentCloudOptions() *cos.PresignedURLOptions {
119+
120+
opt := &cos.PresignedURLOptions{
121+
Query: &url.Values{},
122+
Header: &http.Header{},
123+
}
124+
125+
if o.ContentDisposition != nil {
126+
opt.Header.Add("Content-Disposition", *o.ContentDisposition)
127+
}
128+
if o.ContentEncoding != nil {
129+
opt.Header.Add("Content-Encoding", *o.ContentEncoding)
130+
}
131+
if o.ContentMD5 != nil {
132+
opt.Header.Add("Content-MD5", *o.ContentMD5)
133+
}
134+
if o.ContentType != nil {
135+
opt.Header.Add("Content-Type", *o.ContentType)
136+
}
137+
if o.ContentLength != nil {
138+
opt.Header.Add("Content-Length", strconv.FormatInt(*o.ContentLength, 10))
139+
}
140+
if o.Tagging != nil {
141+
opt.Header.Add("x-cos-tagging", *o.Tagging)
142+
}
143+
return opt
144+
}

cloud/object_storage/tencentcloud_object_storage.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,22 @@ func (service *TencentCloudObjectStorageService) GetSignedURLForExistedKey(ctx c
257257
}
258258
return service.GetSignedURL(key, duration)
259259
}
260+
261+
func (service *TencentCloudObjectStorageService) PutSignedURL(key string, duration time.Duration, option PutHeaderOption) (string, error) {
262+
if key == "" {
263+
return "", ErrObjectKeyEmpty
264+
}
265+
266+
options := option.ToTencentCloudOptions()
267+
268+
url, err := service.client.Object.GetPresignedURL(
269+
context.Background(), http.MethodPut, key,
270+
service.client.GetCredential().SecretID,
271+
service.client.GetCredential().SecretKey,
272+
duration, options,
273+
)
274+
if err != nil {
275+
return "", err
276+
}
277+
return url.String(), nil
278+
}

0 commit comments

Comments
 (0)