github.com/cornelk/go-cloud@v0.17.1/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 "github.com/cornelk/go-cloud/aws" 27 "github.com/cornelk/go-cloud/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 // 74 // See https://godoc.org/github.com/cornelk/go-cloud/aws#ConfigFromURLParams for supported query 75 // parameters for overriding the aws.Session from the URL. 76 type URLOpener struct { 77 // ConfigProvider must be set to a non-nil value. 78 ConfigProvider client.ConfigProvider 79 } 80 81 // OpenCollectionURL opens the collection at the URL's path. See the package doc for more details. 82 func (o *URLOpener) OpenCollectionURL(_ context.Context, u *url.URL) (*docstore.Collection, error) { 83 db, tableName, partitionKey, sortKey, opts, err := o.processURL(u) 84 if err != nil { 85 return nil, err 86 } 87 return OpenCollection(db, tableName, partitionKey, sortKey, opts) 88 } 89 90 func (o *URLOpener) processURL(u *url.URL) (db *dyn.DynamoDB, tableName, partitionKey, sortKey string, opts *Options, err error) { 91 q := u.Query() 92 93 partitionKey = q.Get("partition_key") 94 if partitionKey == "" { 95 return nil, "", "", "", nil, fmt.Errorf("open collection %s: partition_key is required to open a table", u) 96 } 97 q.Del("partition_key") 98 sortKey = q.Get("sort_key") 99 q.Del("sort_key") 100 opts = &Options{ 101 AllowScans: q.Get("allow_scans") == "true", 102 RevisionField: q.Get("revision_field"), 103 } 104 q.Del("allow_scans") 105 q.Del("revision_field") 106 107 tableName = u.Host 108 if tableName == "" { 109 return nil, "", "", "", nil, fmt.Errorf("open collection %s: URL's host cannot be empty (the table name)", u) 110 } 111 if u.Path != "" { 112 return nil, "", "", "", nil, fmt.Errorf("open collection %s: URL path must be empty, only the host is needed", u) 113 } 114 115 configProvider := &gcaws.ConfigOverrider{ 116 Base: o.ConfigProvider, 117 } 118 overrideCfg, err := gcaws.ConfigFromURLParams(q) 119 if err != nil { 120 return nil, "", "", "", nil, fmt.Errorf("open collection %s: %v", u, err) 121 } 122 configProvider.Configs = append(configProvider.Configs, overrideCfg) 123 db, err = Dial(configProvider) 124 if err != nil { 125 return nil, "", "", "", nil, fmt.Errorf("open collection %s: %v", u, err) 126 } 127 return db, tableName, partitionKey, sortKey, opts, nil 128 } 129 130 // Dial gets an AWS DynamoDB service client. 131 func Dial(p client.ConfigProvider) (*dyn.DynamoDB, error) { 132 if p == nil { 133 return nil, errors.New("getting Dynamo service: no AWS session provided") 134 } 135 return dyn.New(p), nil 136 }