github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/dbfactory/oss.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dbfactory 16 17 import ( 18 "context" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "net/url" 23 "os" 24 "path/filepath" 25 26 "github.com/aliyun/aliyun-oss-go-sdk/oss" 27 28 "github.com/dolthub/dolt/go/libraries/doltcore/dconfig" 29 "github.com/dolthub/dolt/go/store/blobstore" 30 "github.com/dolthub/dolt/go/store/chunks" 31 "github.com/dolthub/dolt/go/store/datas" 32 "github.com/dolthub/dolt/go/store/nbs" 33 "github.com/dolthub/dolt/go/store/prolly/tree" 34 "github.com/dolthub/dolt/go/store/types" 35 ) 36 37 const ( 38 39 // OSSCredsFileParam is a creation parameter that can be used to specify a credential file to use. 40 OSSCredsFileParam = "oss-creds-file" 41 42 // OSSCredsProfile is a creation parameter that can be used to specify which OSS profile to use. 43 OSSCredsProfile = "oss-creds-profile" 44 ) 45 46 var ( 47 emptyOSSCredential = ossCredential{} 48 ) 49 50 type ossParams map[string]interface{} 51 type ossCredentials map[string]ossCredential 52 53 type ossCredential struct { 54 Endpoint string `json:"endpoint,omitempty"` 55 AccessKeyID string `json:"accessKeyID,omitempty"` 56 AccessKeySecret string `json:"accessKeySecret,omitempty"` 57 } 58 59 // OSSFactory is a DBFactory implementation for creating OSS backed databases 60 type OSSFactory struct { 61 } 62 63 // PrepareDB prepares an OSS backed database 64 func (fact OSSFactory) PrepareDB(ctx context.Context, nbf *types.NomsBinFormat, urlObj *url.URL, params map[string]interface{}) error { 65 // nothing to prepare 66 return nil 67 } 68 69 // CreateDB creates an OSS backed database 70 func (fact OSSFactory) CreateDB(ctx context.Context, nbf *types.NomsBinFormat, urlObj *url.URL, params map[string]interface{}) (datas.Database, types.ValueReadWriter, tree.NodeStore, error) { 71 ossStore, err := fact.newChunkStore(ctx, nbf, urlObj, params) 72 if err != nil { 73 return nil, nil, nil, err 74 } 75 76 vrw := types.NewValueStore(ossStore) 77 ns := tree.NewNodeStore(ossStore) 78 db := datas.NewTypesDatabase(vrw, ns) 79 80 return db, vrw, ns, nil 81 } 82 83 func (fact OSSFactory) newChunkStore(ctx context.Context, nbf *types.NomsBinFormat, urlObj *url.URL, params map[string]interface{}) (chunks.ChunkStore, error) { 84 // oss://[bucket]/[key] 85 bucket := urlObj.Hostname() 86 prefix := urlObj.Path 87 88 opts := ossConfigFromParams(params) 89 90 ossClient, err := getOSSClient(opts) 91 if err != nil { 92 return nil, fmt.Errorf("failed to initialize oss err: %s", err) 93 } 94 bs, err := blobstore.NewOSSBlobstore(ossClient, bucket, prefix) 95 if err != nil { 96 return nil, errors.New("failed to initialize oss blob store") 97 } 98 99 q := nbs.NewUnlimitedMemQuotaProvider() 100 return nbs.NewBSStore(ctx, nbf.VersionString(), bs, defaultMemTableSize, q) 101 } 102 103 func ossConfigFromParams(params map[string]interface{}) ossCredential { 104 // then we look for config from oss-creds-file 105 p := ossParams(params) 106 credFile, err := p.getCredFile() 107 if err != nil { 108 return emptyOSSCredential 109 } 110 creds, err := readOSSCredentialsFromFile(credFile) 111 if err != nil { 112 return emptyOSSCredential 113 } 114 // if there is only 1 cred in the file, just use this cred regardless the profile is 115 if len(creds) == 1 { 116 return creds.First() 117 } 118 // otherwise, we try to get cred by profile from cred file 119 if res, ok := creds[p.getCredProfile()]; ok { 120 return res 121 } 122 return emptyOSSCredential 123 } 124 125 func getOSSClient(opts ossCredential) (*oss.Client, error) { 126 var ( 127 endpoint, accessKeyID, accessKeySecret string 128 err error 129 ) 130 if endpoint, err = opts.getEndPoint(); err != nil { 131 return nil, err 132 } 133 if accessKeyID, err = opts.getAccessKeyID(); err != nil { 134 return nil, err 135 } 136 if accessKeySecret, err = opts.getAccessKeySecret(); err != nil { 137 return nil, err 138 } 139 return oss.New( 140 endpoint, 141 accessKeyID, 142 accessKeySecret, 143 ) 144 } 145 146 func (opt ossCredential) getEndPoint() (string, error) { 147 if opt.Endpoint != "" { 148 return opt.Endpoint, nil 149 } 150 if v := os.Getenv(dconfig.EnvOssEndpoint); v != "" { 151 return v, nil 152 } 153 return "", fmt.Errorf("failed to find endpoint from cred file or env %s", dconfig.EnvOssEndpoint) 154 } 155 156 func (opt ossCredential) getAccessKeyID() (string, error) { 157 if opt.AccessKeyID != "" { 158 return opt.AccessKeyID, nil 159 } 160 if v := os.Getenv(dconfig.EnvOssAccessKeyID); v != "" { 161 return v, nil 162 } 163 return "", fmt.Errorf("failed to find accessKeyID from cred file or env %s", dconfig.EnvOssAccessKeyID) 164 } 165 166 func (opt ossCredential) getAccessKeySecret() (string, error) { 167 if opt.AccessKeySecret != "" { 168 return opt.AccessKeySecret, nil 169 } 170 if v := os.Getenv(dconfig.EnvOssAccessKeySecret); v != "" { 171 return v, nil 172 } 173 return "", fmt.Errorf("failed to find accessKeySecret from cred file or env %s", dconfig.EnvOssAccessKeySecret) 174 } 175 176 func readOSSCredentialsFromFile(credFile string) (ossCredentials, error) { 177 data, err := os.ReadFile(credFile) 178 if err != nil { 179 return nil, fmt.Errorf("failed to read oss cred file %s, err: %s", credFile, err) 180 } 181 var res map[string]ossCredential 182 if err = json.Unmarshal(data, &res); err != nil { 183 return nil, fmt.Errorf("invalid oss credential file %s, err: %s", credFile, err) 184 } 185 if len(res) == 0 { 186 return nil, errors.New("empty credential file is not allowed") 187 } 188 return res, nil 189 } 190 191 func (oc ossCredentials) First() ossCredential { 192 var res ossCredential 193 for _, c := range oc { 194 res = c 195 break 196 } 197 return res 198 } 199 200 func (p ossParams) getCredFile() (string, error) { 201 // then we look for config from oss-creds-file 202 credFile, ok := p[OSSCredsFileParam] 203 if !ok { 204 // if oss-creds-files is 205 homeDir, err := os.UserHomeDir() 206 if err != nil { 207 return "", fmt.Errorf("failed to find oss cred file from home dir, err: %s", err) 208 } 209 credFile = filepath.Join(homeDir, ".oss", "dolt_oss_credentials") 210 } 211 return credFile.(string), nil 212 } 213 214 func (p ossParams) getCredProfile() string { 215 credProfile, ok := p[OSSCredsProfile] 216 if !ok { 217 credProfile = "default" 218 } 219 return credProfile.(string) 220 }