github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/dbfactory/aws.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 "errors" 20 "net/url" 21 "os" 22 "strings" 23 24 "github.com/aws/aws-sdk-go/aws" 25 "github.com/aws/aws-sdk-go/aws/credentials" 26 "github.com/aws/aws-sdk-go/aws/session" 27 "github.com/aws/aws-sdk-go/service/dynamodb" 28 "github.com/aws/aws-sdk-go/service/s3" 29 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/types" 34 ) 35 36 const ( 37 // AWSRegionParam is a creation parameter that can be used to set the AWS region 38 AWSRegionParam = "aws-region" 39 40 // AWSCredsTypeParam is a creation parameter that can be used to set the type of credentials that should be used. 41 // valid values are role, env, auto, and file 42 AWSCredsTypeParam = "aws-creds-type" 43 44 // AWSCredsFileParam is a creation parameter that can be used to specify a credential file to use. 45 AWSCredsFileParam = "aws-creds-file" 46 47 //AWSCredsProfile is a creation parameter that can be used to specify which AWS profile to use. 48 AWSCredsProfile = "aws-creds-profile" 49 ) 50 51 // AWSCredentialSource is an enum type representing the different credential sources (auto, role, env, file, or invalid) 52 type AWSCredentialSource int 53 54 const ( 55 InvalidCS AWSCredentialSource = iota - 1 56 57 // Auto will try env first and fall back to role (This is the default) 58 AutoCS 59 60 // Role Uses the AWS IAM role of the instance for auth 61 RoleCS 62 63 // Env uses the credentials stored in the environment variables AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY 64 EnvCS 65 66 // Uses credentials stored in a file 67 FileCS 68 ) 69 70 // String returns the string representation of the of an AWSCredentialSource 71 func (ct AWSCredentialSource) String() string { 72 switch ct { 73 case RoleCS: 74 return "role" 75 case EnvCS: 76 return "env" 77 case AutoCS: 78 return "auto" 79 case FileCS: 80 return "file" 81 default: 82 return "invalid" 83 } 84 } 85 86 // AWSCredentialSourceFromStr converts a string to an AWSCredentialSource 87 func AWSCredentialSourceFromStr(str string) AWSCredentialSource { 88 strlwr := strings.TrimSpace(strings.ToLower(str)) 89 switch strlwr { 90 case "", "auto": 91 return AutoCS 92 case "role": 93 return RoleCS 94 case "env": 95 return EnvCS 96 case "file": 97 return FileCS 98 default: 99 return InvalidCS 100 } 101 } 102 103 // AWSFactory is a DBFactory implementation for creating AWS backed databases 104 type AWSFactory struct { 105 } 106 107 // CreateDB creates an AWS backed database 108 func (fact AWSFactory) CreateDB(ctx context.Context, nbf *types.NomsBinFormat, urlObj *url.URL, params map[string]string) (datas.Database, error) { 109 var db datas.Database 110 cs, err := fact.newChunkStore(ctx, nbf, urlObj, params) 111 112 if err != nil { 113 return nil, err 114 } 115 116 db = datas.NewDatabase(cs) 117 118 return db, nil 119 } 120 121 func (fact AWSFactory) newChunkStore(ctx context.Context, nbf *types.NomsBinFormat, urlObj *url.URL, params map[string]string) (chunks.ChunkStore, error) { 122 parts := strings.SplitN(urlObj.Hostname(), ":", 2) // [table]:[bucket] 123 if len(parts) != 2 { 124 return nil, errors.New("aws url has an invalid format") 125 } 126 127 opts, err := awsConfigFromParams(params) 128 129 if err != nil { 130 return nil, err 131 } 132 133 dbName, err := validatePath(urlObj.Path) 134 135 if err != nil { 136 return nil, err 137 } 138 139 sess := session.Must(session.NewSessionWithOptions(opts)) 140 return nbs.NewAWSStore(ctx, nbf.VersionString(), parts[0], dbName, parts[1], s3.New(sess), dynamodb.New(sess), defaultMemTableSize) 141 } 142 143 func validatePath(path string) (string, error) { 144 for len(path) > 0 && path[0] == '/' { 145 path = path[1:] 146 } 147 148 pathLen := len(path) 149 for pathLen > 0 && path[pathLen-1] == '/' { 150 path = path[:pathLen-1] 151 pathLen-- 152 } 153 154 // Should probably have regex validation of a valid database name here once we decide what valid database names look 155 // like. 156 if len(path) == 0 || strings.Index(path, "/") != -1 { 157 return "", errors.New("invalid database name") 158 } 159 160 return path, nil 161 } 162 163 func awsConfigFromParams(params map[string]string) (session.Options, error) { 164 awsConfig := aws.NewConfig() 165 if val, ok := params[AWSRegionParam]; ok { 166 awsConfig = awsConfig.WithRegion(val) 167 } 168 169 awsCredsSource := RoleCS 170 if val, ok := params[AWSCredsTypeParam]; ok { 171 awsCredsSource = AWSCredentialSourceFromStr(val) 172 if awsCredsSource == InvalidCS { 173 return session.Options{}, errors.New("invalid value for aws-creds-source") 174 } 175 } 176 177 opts := session.Options{} 178 179 profile := "" 180 if val, ok := params[AWSCredsProfile]; ok { 181 profile = val 182 opts.Profile = val 183 } 184 185 switch awsCredsSource { 186 case EnvCS: 187 awsConfig = awsConfig.WithCredentials(credentials.NewEnvCredentials()) 188 case FileCS: 189 if filePath, ok := params[AWSCredsFileParam]; !ok { 190 return opts, os.ErrNotExist 191 } else { 192 creds := credentials.NewSharedCredentials(filePath, profile) 193 awsConfig = awsConfig.WithCredentials(creds) 194 } 195 case AutoCS: 196 // start by trying to get the credentials from the environment 197 envCreds := credentials.NewEnvCredentials() 198 if _, err := envCreds.Get(); err == nil { 199 awsConfig = awsConfig.WithCredentials(envCreds) 200 } else { 201 // if env credentials don't exist try looking for a credentials file 202 if filePath, ok := params[AWSCredsFileParam]; ok { 203 if _, err := os.Stat(filePath); err == nil { 204 creds := credentials.NewSharedCredentials(filePath, profile) 205 awsConfig = awsConfig.WithCredentials(creds) 206 } 207 } 208 209 // if file and env do not return valid credentials use the default credentials of the box (same as role) 210 } 211 case RoleCS: 212 default: 213 } 214 215 opts.Config.MergeIn(awsConfig) 216 217 return opts, nil 218 }