get.porter.sh/porter@v1.3.0/pkg/storage/pluginstore/grpc.go (about)

     1  package pluginstore
     2  
     3  import (
     4  	"context"
     5  
     6  	"get.porter.sh/porter/pkg/portercontext"
     7  	"get.porter.sh/porter/pkg/storage/plugins"
     8  	"get.porter.sh/porter/pkg/storage/plugins/proto"
     9  	"go.mongodb.org/mongo-driver/bson"
    10  	"google.golang.org/protobuf/types/known/structpb"
    11  )
    12  
    13  var _ plugins.StorageProtocol = &GClient{}
    14  
    15  // GClient is a gRPC implementation of the storage client.
    16  type GClient struct {
    17  	client proto.StorageProtocolClient
    18  }
    19  
    20  func NewClient(client proto.StorageProtocolClient) *GClient {
    21  	return &GClient{client}
    22  }
    23  
    24  func (m *GClient) EnsureIndex(ctx context.Context, opts plugins.EnsureIndexOptions) error {
    25  	req := &proto.EnsureIndexRequest{
    26  		Indices: make([]*proto.Index, len(opts.Indices)),
    27  	}
    28  
    29  	for i, index := range opts.Indices {
    30  		req.Indices[i] = &proto.Index{
    31  			Collection: index.Collection,
    32  			Keys:       FromOrderedMap(index.Keys),
    33  			Unique:     index.Unique,
    34  		}
    35  	}
    36  
    37  	_, err := m.client.EnsureIndex(ctx, req)
    38  	return err
    39  }
    40  
    41  func (m *GClient) Aggregate(ctx context.Context, opts plugins.AggregateOptions) ([]bson.Raw, error) {
    42  	req := &proto.AggregateRequest{
    43  		Collection: opts.Collection,
    44  		Pipeline:   NewPipeline(opts.Pipeline),
    45  	}
    46  	resp, err := m.client.Aggregate(ctx, req)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	results := convertToBsonRawList(resp.Results)
    52  	return results, nil
    53  }
    54  
    55  func (m *GClient) Count(ctx context.Context, opts plugins.CountOptions) (int64, error) {
    56  	resp, err := m.client.Count(ctx, &proto.CountRequest{
    57  		Collection: opts.Collection,
    58  		Filter:     FromMap(opts.Filter),
    59  	})
    60  	if err != nil {
    61  		return 0, err
    62  	}
    63  	return resp.Count, nil
    64  }
    65  
    66  func (m *GClient) Find(ctx context.Context, opts plugins.FindOptions) ([]bson.Raw, error) {
    67  	req := &proto.FindRequest{
    68  		Collection: opts.Collection,
    69  		Sort:       FromOrderedMap(opts.Sort),
    70  		Skip:       opts.Skip,
    71  		Limit:      opts.Limit,
    72  		Select:     FromOrderedMap(opts.Select),
    73  		Filter:     FromMap(opts.Filter),
    74  	}
    75  	resp, err := m.client.Find(ctx, req)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	results := convertToBsonRawList(resp.Results)
    81  	return results, nil
    82  }
    83  
    84  func (m *GClient) Insert(ctx context.Context, opts plugins.InsertOptions) error {
    85  	req := &proto.InsertRequest{
    86  		Collection: opts.Collection,
    87  		Documents:  FromMapList(opts.Documents),
    88  	}
    89  	_, err := m.client.Insert(ctx, req)
    90  	return err
    91  }
    92  
    93  func (m *GClient) Patch(ctx context.Context, opts plugins.PatchOptions) error {
    94  	req := &proto.PatchRequest{
    95  		Collection:     opts.Collection,
    96  		QueryDocument:  FromMap(opts.QueryDocument),
    97  		Transformation: FromOrderedMap(opts.Transformation),
    98  	}
    99  	_, err := m.client.Patch(ctx, req)
   100  	return err
   101  }
   102  
   103  func (m *GClient) Remove(ctx context.Context, opts plugins.RemoveOptions) error {
   104  	req := &proto.RemoveRequest{
   105  		Collection: opts.Collection,
   106  		Filter:     FromMap(opts.Filter),
   107  		All:        opts.All,
   108  	}
   109  	_, err := m.client.Remove(ctx, req)
   110  	return err
   111  }
   112  
   113  func (m *GClient) Update(ctx context.Context, opts plugins.UpdateOptions) error {
   114  	req := &proto.UpdateRequest{
   115  		Collection: opts.Collection,
   116  		Filter:     FromMap(opts.Filter),
   117  		Upsert:     opts.Upsert,
   118  		Document:   FromMap(opts.Document),
   119  	}
   120  	_, err := m.client.Update(ctx, req)
   121  	return err
   122  }
   123  
   124  // GServer is a gRPC wrapper around a StorageProtocol plugin
   125  type GServer struct {
   126  	impl plugins.StorageProtocol
   127  	proto.UnsafeStorageProtocolServer
   128  	c *portercontext.Context
   129  }
   130  
   131  func NewServer(c *portercontext.Context, impl plugins.StorageProtocol) *GServer {
   132  	return &GServer{c: c, impl: impl}
   133  }
   134  
   135  func (m *GServer) EnsureIndex(ctx context.Context, request *proto.EnsureIndexRequest) (*proto.EnsureIndexResponse, error) {
   136  	opts := plugins.EnsureIndexOptions{
   137  		Indices: make([]plugins.Index, len(request.Indices)),
   138  	}
   139  
   140  	for i, index := range request.Indices {
   141  		opts.Indices[i] = plugins.Index{
   142  			Collection: index.Collection,
   143  			Keys:       AsOrderedMap(index.Keys),
   144  			Unique:     index.Unique,
   145  		}
   146  	}
   147  
   148  	err := m.impl.EnsureIndex(ctx, opts)
   149  	return &proto.EnsureIndexResponse{}, err
   150  }
   151  
   152  func (m *GServer) Aggregate(ctx context.Context, request *proto.AggregateRequest) (*proto.AggregateResponse, error) {
   153  	opts := plugins.AggregateOptions{
   154  		Collection: request.Collection,
   155  		Pipeline:   AsOrderedMapList(request.Pipeline),
   156  	}
   157  
   158  	results, err := m.impl.Aggregate(ctx, opts)
   159  	resp := &proto.AggregateResponse{Results: make([][]byte, len(results))}
   160  	for i := range results {
   161  		resp.Results[i] = results[i]
   162  	}
   163  	return resp, err
   164  }
   165  
   166  func (m *GServer) Count(ctx context.Context, req *proto.CountRequest) (*proto.CountResponse, error) {
   167  	opts := plugins.CountOptions{
   168  		Collection: req.Collection,
   169  		Filter:     AsMap(req.Filter),
   170  	}
   171  	count, err := m.impl.Count(ctx, opts)
   172  	return &proto.CountResponse{Count: count}, err
   173  }
   174  
   175  func (m *GServer) Find(ctx context.Context, request *proto.FindRequest) (*proto.FindResponse, error) {
   176  	opts := plugins.FindOptions{
   177  		Collection: request.Collection,
   178  		Sort:       AsOrderedMap(request.Sort),
   179  		Skip:       request.Skip,
   180  		Limit:      request.Limit,
   181  		Select:     AsOrderedMap(request.Select),
   182  		Filter:     AsMap(request.Filter),
   183  	}
   184  
   185  	results, err := m.impl.Find(ctx, opts)
   186  	resp := &proto.FindResponse{Results: make([][]byte, len(results))}
   187  	for i := range results {
   188  		resp.Results[i] = results[i]
   189  	}
   190  
   191  	return resp, err
   192  }
   193  
   194  func (m *GServer) Insert(ctx context.Context, request *proto.InsertRequest) (*proto.InsertResponse, error) {
   195  	opts := plugins.InsertOptions{
   196  		Collection: request.Collection,
   197  		Documents:  AsMapList(request.Documents),
   198  	}
   199  
   200  	err := m.impl.Insert(ctx, opts)
   201  	return &proto.InsertResponse{}, err
   202  }
   203  
   204  func (m *GServer) Patch(ctx context.Context, request *proto.PatchRequest) (*proto.PatchResponse, error) {
   205  	opts := plugins.PatchOptions{
   206  		Collection:     request.Collection,
   207  		QueryDocument:  AsMap(request.QueryDocument),
   208  		Transformation: AsOrderedMap(request.Transformation),
   209  	}
   210  
   211  	err := m.impl.Patch(ctx, opts)
   212  	return &proto.PatchResponse{}, err
   213  }
   214  
   215  func (m *GServer) Remove(ctx context.Context, request *proto.RemoveRequest) (*proto.RemoveResponse, error) {
   216  	opts := plugins.RemoveOptions{
   217  		Collection: request.Collection,
   218  		Filter:     AsMap(request.Filter),
   219  		All:        request.All,
   220  	}
   221  
   222  	err := m.impl.Remove(ctx, opts)
   223  	return &proto.RemoveResponse{}, err
   224  }
   225  
   226  func (m *GServer) Update(ctx context.Context, request *proto.UpdateRequest) (*proto.UpdateResponse, error) {
   227  	opts := plugins.UpdateOptions{
   228  		Collection: request.Collection,
   229  		Filter:     AsMap(request.Filter),
   230  		Upsert:     request.Upsert,
   231  		Document:   AsMap(request.Document),
   232  	}
   233  
   234  	err := m.impl.Update(ctx, opts)
   235  	return &proto.UpdateResponse{}, err
   236  }
   237  
   238  func NewPipeline(src []bson.D) []*proto.Stage {
   239  	pipeline := make([]*proto.Stage, len(src))
   240  	for i, srcStage := range src {
   241  		stage := &proto.Stage{
   242  			Steps: FromOrderedMap(srcStage),
   243  		}
   244  		pipeline[i] = stage
   245  	}
   246  
   247  	return pipeline
   248  }
   249  
   250  // ConvertBsonToPrimitives converts from bson primitives to pure Go primitives
   251  func ConvertBsonToPrimitives(src interface{}) interface{} {
   252  	switch t := src.(type) {
   253  	case bson.E:
   254  		rawValue := ConvertBsonToPrimitives(t.Value)
   255  		return map[string]interface{}{t.Key: rawValue}
   256  	case []bson.D:
   257  		raw := make([]interface{}, len(t))
   258  		for i, item := range t {
   259  			raw[i] = ConvertBsonToPrimitives(item)
   260  		}
   261  		return raw
   262  	case bson.D:
   263  		raw := make([]interface{}, len(t))
   264  		for i, item := range t {
   265  			raw[i] = ConvertBsonToPrimitives(item)
   266  		}
   267  		return raw
   268  	case []bson.M:
   269  		raw := make([]interface{}, len(t))
   270  		for i, item := range t {
   271  			raw[i] = ConvertBsonToPrimitives(item)
   272  		}
   273  		return raw
   274  	case bson.M:
   275  		raw := make(map[string]interface{}, len(t))
   276  		for k, v := range t {
   277  			raw[k] = ConvertBsonToPrimitives(v)
   278  		}
   279  		return raw
   280  	default:
   281  		return src
   282  	}
   283  }
   284  
   285  // ConvertSliceToBsonD converts go slices to bson primitive.
   286  // it also works around a weirdness in how numbers are represented
   287  // by structpb.Value, where integer values are stored in float64. When we
   288  // deserialize from protobuf, this walks the specified value, finds ints
   289  // that were encoded as floats, and converts them back to ints.
   290  func ConvertSliceToBsonD(src interface{}) interface{} {
   291  	switch tv := src.(type) {
   292  	case []interface{}:
   293  		toBson := make(bson.D, 0, len(tv))
   294  		for _, item := range tv {
   295  			converted := ConvertSliceToBsonD(item)
   296  			if m, ok := converted.(map[string]interface{}); ok {
   297  				for k, v := range m {
   298  					toBson = append(toBson, bson.E{Key: k, Value: v})
   299  				}
   300  			}
   301  		}
   302  		return toBson
   303  	case map[string]interface{}:
   304  		for k, v := range tv {
   305  			tv[k] = ConvertSliceToBsonD(v)
   306  		}
   307  		return tv
   308  	default:
   309  		return tv
   310  	}
   311  }
   312  
   313  // ConvertFloatToInt works around a weirdness in how numbers are represented
   314  // by structpb.Value, where integer values are stored in float64. When we
   315  // deserialize from protobuf, this walks the specified value, finds ints
   316  // that were encoded as floats, and converts them back to ints.
   317  func ConvertFloatToInt(src interface{}) interface{} {
   318  	switch tv := src.(type) {
   319  	case float64:
   320  		intVal := int64(tv)
   321  		if tv == float64(intVal) {
   322  			return intVal
   323  		}
   324  		return tv
   325  	case []interface{}:
   326  		for i, item := range tv {
   327  			tv[i] = ConvertFloatToInt(item)
   328  		}
   329  		return tv
   330  	case map[string]interface{}:
   331  		for k, v := range tv {
   332  			tv[k] = ConvertFloatToInt(v)
   333  		}
   334  		return tv
   335  	default:
   336  		return tv
   337  	}
   338  }
   339  
   340  // FromMap represents bson.M in a data structure that protobuf understands
   341  // (which is a plain struct).
   342  func FromMap(src bson.M) *structpb.Struct {
   343  	rawSrc := make(map[string]interface{}, len(src))
   344  	for k, v := range src {
   345  		rawSrc[k] = ConvertBsonToPrimitives(v)
   346  	}
   347  
   348  	dest, err := structpb.NewStruct(rawSrc)
   349  	if err != nil {
   350  		// panic because if we hit this, there's no recovering or handling possible
   351  		panic(err)
   352  	}
   353  
   354  	return dest
   355  }
   356  
   357  // FromMapList represents []bson.M in a data structure that protobuf understands
   358  // (an array of structs).
   359  func FromMapList(src []bson.M) []*structpb.Struct {
   360  	dest := make([]*structpb.Struct, len(src))
   361  	for i, item := range src {
   362  		dest[i] = FromMap(item)
   363  	}
   364  	return dest
   365  }
   366  
   367  // FromOrderedMap represents bson.D, an ordered map, in a data structure that
   368  // protobuf understands (an array of structs).
   369  func FromOrderedMap(src bson.D) []*structpb.Struct {
   370  	dest := make([]*structpb.Struct, len(src))
   371  	for i, item := range src {
   372  		rawValue := ConvertBsonToPrimitives(item.Value)
   373  		dest[i] = NewStruct(map[string]interface{}{item.Key: rawValue})
   374  	}
   375  	return dest
   376  }
   377  
   378  func NewStruct(src map[string]interface{}) *structpb.Struct {
   379  	dest, err := structpb.NewStruct(src)
   380  	if err != nil {
   381  		panic(err)
   382  	}
   383  	return dest
   384  }
   385  
   386  // AsMap converts a protobuf struct into its original representation, bson.M.
   387  func AsMap(src *structpb.Struct, c ...converter) bson.M {
   388  	dest := src.AsMap()
   389  	for k, v := range dest {
   390  		converted := ConvertFloatToInt(v)
   391  		for _, convert := range c {
   392  			converted = convert(v)
   393  		}
   394  		dest[k] = converted
   395  	}
   396  	return dest
   397  }
   398  
   399  func AsMapList(src []*structpb.Struct) []bson.M {
   400  	dest := make([]bson.M, len(src))
   401  	for i, item := range src {
   402  		dest[i] = AsMap(item)
   403  	}
   404  	return dest
   405  }
   406  
   407  type converter func(src interface{}) interface{}
   408  
   409  // AsOrderedMap converts an array of protobuf structs into its original
   410  // representation, bson.D.
   411  func AsOrderedMap(src []*structpb.Struct, c ...converter) bson.D {
   412  	dest := make(bson.D, 0, len(src))
   413  	for _, item := range src {
   414  		for k, v := range AsMap(item, c...) {
   415  			dest = append(dest, bson.E{Key: k, Value: v})
   416  		}
   417  	}
   418  	return dest
   419  }
   420  
   421  // AsOrderedMapList converts a protobuf Pipeline into its original
   422  // representation, []bson.D
   423  func AsOrderedMapList(src []*proto.Stage) []bson.D {
   424  	dest := make([]bson.D, len(src))
   425  	for i, item := range src {
   426  		dest[i] = AsOrderedMap(item.Steps, ConvertSliceToBsonD)
   427  	}
   428  	return dest
   429  }
   430  
   431  func convertToBsonRawList(src [][]byte) []bson.Raw {
   432  	dest := make([]bson.Raw, len(src))
   433  	for i := range src {
   434  		dest[i] = src[i]
   435  	}
   436  	return dest
   437  }