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 }