github.com/thiagoyeds/go-cloud@v0.26.0/docstore/awsdynamodb/urls.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 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 // https://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 awsdynamodb 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net/url" 22 "sync" 23 24 "github.com/aws/aws-sdk-go/aws/client" 25 dyn "github.com/aws/aws-sdk-go/service/dynamodb" 26 gcaws "gocloud.dev/aws" 27 "gocloud.dev/docstore" 28 ) 29 30 func init() { 31 docstore.DefaultURLMux().RegisterCollection(Scheme, new(lazySessionOpener)) 32 } 33 34 type lazySessionOpener struct { 35 init sync.Once 36 opener *URLOpener 37 err error 38 } 39 40 func (o *lazySessionOpener) OpenCollectionURL(ctx context.Context, u *url.URL) (*docstore.Collection, error) { 41 o.init.Do(func() { 42 sess, err := gcaws.NewDefaultSession() 43 if err != nil { 44 o.err = err 45 return 46 } 47 o.opener = &URLOpener{ 48 ConfigProvider: sess, 49 } 50 }) 51 if o.err != nil { 52 return nil, fmt.Errorf("open collection %s: %v", u, o.err) 53 } 54 return o.opener.OpenCollectionURL(ctx, u) 55 } 56 57 // Scheme is the URL scheme dynamodb registers its URLOpener under on 58 // docstore.DefaultMux. 59 const Scheme = "dynamodb" 60 61 // URLOpener opens dynamodb URLs like 62 // "dynamodb://mytable?partition_key=partkey&sort_key=sortkey". 63 // 64 // The URL Host is used as the table name. See 65 // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html 66 // for more details. 67 // 68 // The following query parameters are supported: 69 // 70 // - partition_key (required): the path to the partition key of a table or an index. 71 // - sort_key: the path to the sort key of a table or an index. 72 // - allow_scans: if "true", allow table scans to be used for queries 73 // - consistent_read: if "true", a strongly consistent read is used whenever possible. 74 // 75 // See https://godoc.org/gocloud.dev/aws#ConfigFromURLParams for supported query 76 // parameters for overriding the aws.Session from the URL. 77 type URLOpener struct { 78 // ConfigProvider must be set to a non-nil value. 79 ConfigProvider client.ConfigProvider 80 } 81 82 // OpenCollectionURL opens the collection at the URL's path. See the package doc for more details. 83 func (o *URLOpener) OpenCollectionURL(_ context.Context, u *url.URL) (*docstore.Collection, error) { 84 db, tableName, partitionKey, sortKey, opts, err := o.processURL(u) 85 if err != nil { 86 return nil, err 87 } 88 return OpenCollection(db, tableName, partitionKey, sortKey, opts) 89 } 90 91 func (o *URLOpener) processURL(u *url.URL) (db *dyn.DynamoDB, tableName, partitionKey, sortKey string, opts *Options, err error) { 92 q := u.Query() 93 94 partitionKey = q.Get("partition_key") 95 if partitionKey == "" { 96 return nil, "", "", "", nil, fmt.Errorf("open collection %s: partition_key is required to open a table", u) 97 } 98 q.Del("partition_key") 99 sortKey = q.Get("sort_key") 100 q.Del("sort_key") 101 opts = &Options{ 102 AllowScans: q.Get("allow_scans") == "true", 103 RevisionField: q.Get("revision_field"), 104 ConsistentRead: q.Get("consistent_read") == "true", 105 } 106 q.Del("allow_scans") 107 q.Del("revision_field") 108 q.Del("consistent_read") 109 110 tableName = u.Host 111 if tableName == "" { 112 return nil, "", "", "", nil, fmt.Errorf("open collection %s: URL's host cannot be empty (the table name)", u) 113 } 114 if u.Path != "" { 115 return nil, "", "", "", nil, fmt.Errorf("open collection %s: URL path must be empty, only the host is needed", u) 116 } 117 118 configProvider := &gcaws.ConfigOverrider{ 119 Base: o.ConfigProvider, 120 } 121 overrideCfg, err := gcaws.ConfigFromURLParams(q) 122 if err != nil { 123 return nil, "", "", "", nil, fmt.Errorf("open collection %s: %v", u, err) 124 } 125 configProvider.Configs = append(configProvider.Configs, overrideCfg) 126 db, err = Dial(configProvider) 127 if err != nil { 128 return nil, "", "", "", nil, fmt.Errorf("open collection %s: %v", u, err) 129 } 130 return db, tableName, partitionKey, sortKey, opts, nil 131 } 132 133 // Dial gets an AWS DynamoDB service client. 134 func Dial(p client.ConfigProvider) (*dyn.DynamoDB, error) { 135 if p == nil { 136 return nil, errors.New("getting Dynamo service: no AWS session provided") 137 } 138 return dyn.New(p), nil 139 }