Skip to content

Commit 4495e17

Browse files
authored
feat: support bos as plugin storage (#735)
1 parent 9c50081 commit 4495e17

7 files changed

Lines changed: 664 additions & 0 deletions

File tree

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ HUAWEI_OBS_SECRET_KEY=
5555
HUAWEI_OBS_SERVER=
5656
HUAWEI_OBS_PATH_STYLE=false
5757

58+
# baidu bos credentials
59+
BAIDU_BOS_ENDPOINT=
60+
BAIDU_BOS_ACCESS_KEY=
61+
BAIDU_BOS_SECRET_KEY=
62+
BAIDU_BOS_REGION=
63+
5864
# services storage
5965
# https://github.com/langgenius/dify-cloud-kit/blob/main/oss/factory/factory.go
6066
PLUGIN_STORAGE_TYPE=local

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ require (
135135
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
136136
github.com/aws/smithy-go v1.24.2 // indirect
137137
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
138+
github.com/baidu/baiducloud-sdk-go v0.0.0-20191022083902-5b6f2726970b // indirect
138139
github.com/beorn7/perks v1.0.1 // indirect
139140
github.com/bytedance/gopkg v0.1.3 // indirect
140141
github.com/cenkalti/backoff/v5 v5.0.3 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
9595
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
9696
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
9797
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
98+
github.com/baidu/baiducloud-sdk-go v0.0.0-20191022083902-5b6f2726970b h1:4o+MOlA6Oliau3TA4fjE2v8KYYhfh0796fI9l61+WuU=
99+
github.com/baidu/baiducloud-sdk-go v0.0.0-20191022083902-5b6f2726970b/go.mod h1:YpbdNTGpFZY7AH5EkjRsd1QmLsK8aZ0uSCOeiPG8h+8=
98100
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
99101
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
100102
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=

internal/server/server.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/langgenius/dify-plugin-daemon/internal/core/persistence"
99
"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_manager"
1010
"github.com/langgenius/dify-plugin-daemon/internal/db"
11+
"github.com/langgenius/dify-plugin-daemon/internal/storage/baidubos"
1112
"github.com/langgenius/dify-plugin-daemon/internal/tasks"
1213
"github.com/langgenius/dify-plugin-daemon/internal/types/app"
1314
"github.com/langgenius/dify-plugin-daemon/pkg/utils/cache"
@@ -19,6 +20,22 @@ func initOSS(config *app.Config) oss.OSS {
1920
// init storage
2021
var storage oss.OSS
2122
var err error
23+
24+
// baidu bos is implemented locally, not in dify-cloud-kit
25+
if config.PluginStorageType == baidubos.OSS_TYPE_BAIDU_BOS {
26+
storage, err = baidubos.NewBaiduBOSStorage(baidubos.BaiduBOSConfig{
27+
Endpoint: config.BaiduBOSEndpoint,
28+
AccessKey: config.BaiduBOSAccessKey,
29+
SecretKey: config.BaiduBOSSecretKey,
30+
Region: config.BaiduBOSRegion,
31+
Bucket: config.PluginStorageOSSBucket,
32+
})
33+
if err != nil {
34+
log.Panic("failed to create baidu bos storage", "error", err)
35+
}
36+
return storage
37+
}
38+
2239
storage, err = factory.Load(config.PluginStorageType, oss.OSSArgs{
2340
Local: &oss.Local{
2441
Path: config.PluginStorageLocalRoot,

internal/storage/baidubos/bos.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package baidubos
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"strings"
8+
9+
"github.com/baidu/baiducloud-sdk-go/bce"
10+
"github.com/baidu/baiducloud-sdk-go/bos"
11+
oss "github.com/langgenius/dify-cloud-kit/oss"
12+
)
13+
14+
const OSS_TYPE_BAIDU_BOS = "baidu_bos"
15+
16+
type bosClient interface {
17+
PutObject(bucketName string, objectKey string, data interface{}, metadata *bos.ObjectMetadata, option *bce.SignOption) (bos.PutObjectResponse, error)
18+
GetObject(bucketName string, objectKey string, option *bce.SignOption) (*bos.Object, error)
19+
GetObjectMetadata(bucketName string, objectKey string, option *bce.SignOption) (*bos.ObjectMetadata, error)
20+
ListObjectsFromRequest(req bos.ListObjectsRequest, option *bce.SignOption) (*bos.ListObjectsResponse, error)
21+
DeleteObject(bucketName string, objectKey string, option *bce.SignOption) error
22+
}
23+
24+
type BaiduBOSStorage struct {
25+
bucket string
26+
client bosClient
27+
}
28+
29+
type BaiduBOSConfig struct {
30+
Endpoint string
31+
AccessKey string
32+
SecretKey string
33+
Region string
34+
Bucket string
35+
}
36+
37+
func (c *BaiduBOSConfig) Validate() error {
38+
if c.Bucket == "" || c.AccessKey == "" || c.SecretKey == "" {
39+
return fmt.Errorf("bucket, accessKey, secretKey cannot be empty")
40+
}
41+
if c.Endpoint == "" && c.Region == "" {
42+
return fmt.Errorf("endpoint or region must be provided")
43+
}
44+
return nil
45+
}
46+
47+
func NewBaiduBOSStorage(config BaiduBOSConfig) (oss.OSS, error) {
48+
config.Endpoint = strings.TrimSpace(config.Endpoint)
49+
config.Region = strings.TrimSpace(config.Region)
50+
config.AccessKey = strings.TrimSpace(config.AccessKey)
51+
config.SecretKey = strings.TrimSpace(config.SecretKey)
52+
config.Bucket = strings.TrimSpace(config.Bucket)
53+
54+
if err := config.Validate(); err != nil {
55+
return nil, oss.ErrArgumentInvalid.WithDetail(err.Error())
56+
}
57+
58+
credentials := bce.NewCredentials(config.AccessKey, config.SecretKey)
59+
bceConfig := bce.NewConfig(credentials)
60+
61+
if config.Endpoint != "" {
62+
bceConfig.Endpoint = config.Endpoint
63+
}
64+
if config.Region != "" {
65+
bceConfig.Region = config.Region
66+
}
67+
68+
client := bos.NewClient(bceConfig)
69+
70+
return newBaiduBOSStorageWithClient(config.Bucket, client), nil
71+
}
72+
73+
func newBaiduBOSStorageWithClient(bucket string, client bosClient) *BaiduBOSStorage {
74+
return &BaiduBOSStorage{
75+
bucket: bucket,
76+
client: client,
77+
}
78+
}
79+
80+
func (b *BaiduBOSStorage) Save(key string, data []byte) error {
81+
_, err := b.client.PutObject(b.bucket, key, data, nil, nil)
82+
return err
83+
}
84+
85+
func (b *BaiduBOSStorage) Load(key string) ([]byte, error) {
86+
obj, err := b.client.GetObject(b.bucket, key, nil)
87+
if err != nil {
88+
return nil, err
89+
}
90+
defer obj.ObjectContent.Close()
91+
92+
return io.ReadAll(obj.ObjectContent)
93+
}
94+
95+
func (b *BaiduBOSStorage) Exists(key string) (bool, error) {
96+
_, err := b.client.GetObjectMetadata(b.bucket, key, nil)
97+
if err != nil {
98+
if isNotFoundError(err) {
99+
return false, nil
100+
}
101+
return false, err
102+
}
103+
return true, nil
104+
}
105+
106+
func (b *BaiduBOSStorage) State(key string) (oss.OSSState, error) {
107+
metadata, err := b.client.GetObjectMetadata(b.bucket, key, nil)
108+
if err != nil {
109+
return oss.OSSState{}, err
110+
}
111+
112+
return oss.OSSState{
113+
Size: metadata.ContentLength,
114+
}, nil
115+
}
116+
117+
func (b *BaiduBOSStorage) List(prefix string) ([]oss.OSSPath, error) {
118+
if prefix != "" && !strings.HasSuffix(prefix, "/") {
119+
prefix = prefix + "/"
120+
}
121+
122+
marker := ""
123+
paths := []oss.OSSPath{}
124+
for {
125+
req := bos.ListObjectsRequest{
126+
BucketName: b.bucket,
127+
Prefix: prefix,
128+
Marker: marker,
129+
MaxKeys: 1000,
130+
}
131+
resp, err := b.client.ListObjectsFromRequest(req, nil)
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
for _, obj := range resp.Contents {
137+
key := strings.TrimPrefix(obj.Key, prefix)
138+
key = strings.TrimPrefix(key, "/")
139+
140+
if key == "" {
141+
continue
142+
}
143+
paths = append(paths, oss.OSSPath{
144+
Path: key,
145+
IsDir: false,
146+
})
147+
}
148+
149+
if !resp.IsTruncated {
150+
break
151+
}
152+
153+
if resp.NextMarker != "" {
154+
marker = resp.NextMarker
155+
} else if len(resp.Contents) > 0 {
156+
marker = resp.Contents[len(resp.Contents)-1].Key
157+
} else {
158+
break
159+
}
160+
}
161+
162+
return paths, nil
163+
}
164+
165+
func (b *BaiduBOSStorage) Delete(key string) error {
166+
return b.client.DeleteObject(b.bucket, key, nil)
167+
}
168+
169+
func (b *BaiduBOSStorage) Type() string {
170+
return OSS_TYPE_BAIDU_BOS
171+
}
172+
173+
func isNotFoundError(err error) bool {
174+
if bceErr, ok := err.(*bce.Error); ok {
175+
return bceErr.StatusCode == http.StatusNotFound
176+
}
177+
return false
178+
}

0 commit comments

Comments
 (0)