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  }