get.porter.sh/porter@v1.3.0/pkg/storage/query.go (about) 1 package storage 2 3 import ( 4 "encoding/json" 5 "strings" 6 7 "get.porter.sh/porter/pkg/storage/plugins" 8 "go.mongodb.org/mongo-driver/bson" 9 ) 10 11 // AggregateOptions is the set of options available to the Aggregate operation on any 12 // storage provider. 13 type AggregateOptions struct { 14 // Pipeline document to aggregate, filter, and shape the results. 15 // See https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/ 16 Pipeline []bson.D 17 } 18 19 func (o AggregateOptions) ToPluginOptions(collection string) plugins.AggregateOptions { 20 return plugins.AggregateOptions{ 21 Collection: collection, 22 Pipeline: o.Pipeline, 23 } 24 } 25 26 // EnsureIndexOptions is the set of options available to the EnsureIndex operation. 27 type EnsureIndexOptions struct { 28 // Indices to create if not found. 29 Indices []Index 30 } 31 32 // Index on a collection. 33 type Index struct { 34 // Collection name to which the index applies. 35 Collection string 36 37 // Keys describes the fields and their sort order. 38 // Example: ["namespace", "name", "-timestamp"] 39 Keys []string 40 41 // Unique specifies if the index should enforce that the indexed fields for each document are unique. 42 Unique bool 43 } 44 45 // Convert from a simplified sort specifier like []{"-key"} 46 // to a mongodb sort document like []{{Key: "key", Value: -1}} 47 func convertSortKeys(values []string) bson.D { 48 if len(values) == 0 { 49 return nil 50 } 51 52 keys := make(bson.D, len(values)) 53 for i, key := range values { 54 sortKey := key 55 sortOrder := 1 56 if strings.HasPrefix(key, "-") { 57 sortKey = strings.Trim(key, "-") 58 sortOrder = -1 59 } 60 keys[i] = bson.E{Key: sortKey, Value: sortOrder} 61 } 62 return keys 63 } 64 65 func (o EnsureIndexOptions) ToPluginOptions() plugins.EnsureIndexOptions { 66 opts := plugins.EnsureIndexOptions{ 67 Indices: make([]plugins.Index, len(o.Indices)), 68 } 69 for i, index := range o.Indices { 70 opts.Indices[i] = plugins.Index{ 71 Collection: index.Collection, 72 Keys: convertSortKeys(index.Keys), 73 Unique: index.Unique, 74 } 75 } 76 return opts 77 } 78 79 // CountOptions is the set of options available to the Count operation on any 80 // storage provider. 81 type CountOptions struct { 82 // Query is a query filter document 83 // See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter 84 Filter bson.M 85 } 86 87 func (o CountOptions) ToPluginOptions(collection string) plugins.CountOptions { 88 if o.Filter == nil { 89 o.Filter = map[string]interface{}{} 90 } 91 return plugins.CountOptions{ 92 Collection: collection, 93 Filter: o.Filter, 94 } 95 } 96 97 // FindOptions is the set of options available to the StorageProtocol.Find 98 // operation. 99 type FindOptions struct { 100 // Sort is a list of field names by which the results should be sorted. 101 // Prefix a field with "-" to sort in reverse order. 102 Sort []string 103 104 // Skip is the number of results to skip past and exclude from the results. 105 Skip int64 106 107 // Limit is the number of results to return. 108 Limit int64 109 110 // Filter specifies a filter the results. 111 // See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter 112 Filter bson.M 113 114 // Select is a projection document. The entire document is returned by default. 115 // See https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/ 116 Select bson.D 117 } 118 119 func (o FindOptions) ToPluginOptions(collection string) plugins.FindOptions { 120 if o.Filter == nil { 121 o.Filter = bson.M{} 122 } 123 return plugins.FindOptions{ 124 Collection: collection, 125 Sort: convertSortKeys(o.Sort), 126 Skip: o.Skip, 127 Limit: o.Limit, 128 Select: o.Select, 129 Filter: o.Filter, 130 } 131 } 132 133 // GetOptions is the set of options available for the Get operation. 134 // Documents can be retrieved by either ID or Namespace + Name. 135 type GetOptions struct { 136 // ID of the document to retrieve. 137 ID string 138 139 // Name of the document to retrieve. 140 Name string 141 142 // Namespace of the document to retrieve. 143 Namespace string 144 } 145 146 // ToFindOptions converts from the convenience method Get to FindOne. 147 func (o GetOptions) ToFindOptions() FindOptions { 148 var filter map[string]interface{} 149 if o.ID != "" { 150 filter = map[string]interface{}{"_id": o.ID} 151 } else if o.Name != "" { 152 filter = map[string]interface{}{"namespace": o.Namespace, "name": o.Name} 153 } 154 155 return FindOptions{ 156 Filter: filter, 157 } 158 } 159 160 // InsertOptions is the set of options for the StorageProtocol.Insert operation. 161 type InsertOptions struct { 162 // Documents is a set of documents to insert. 163 Documents []interface{} 164 } 165 166 func (o InsertOptions) ToPluginOptions(collection string) (plugins.InsertOptions, error) { 167 var docs []bson.M 168 err := convertToRawJsonDocument(o.Documents, &docs) 169 if err != nil { 170 return plugins.InsertOptions{}, nil 171 } 172 173 return plugins.InsertOptions{ 174 Collection: collection, 175 Documents: docs, 176 }, nil 177 } 178 179 // PatchOptions is the set of options for the StorageProtocol.Patch operation. 180 type PatchOptions struct { 181 // Query is a query filter document 182 // See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter 183 QueryDocument bson.M 184 185 // Transformation is set of instructions to modify matching 186 // documents. 187 Transformation bson.D 188 } 189 190 func (o PatchOptions) ToPluginOptions(collection string) plugins.PatchOptions { 191 return plugins.PatchOptions{ 192 Collection: collection, 193 QueryDocument: o.QueryDocument, 194 Transformation: o.Transformation, 195 } 196 } 197 198 // RemoveOptions is the set of options for the StorageProtocol.Remove operation. 199 type RemoveOptions struct { 200 // Filter is a query filter document 201 // See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter 202 Filter bson.M 203 204 // All matching documents should be removed. Defaults to false, which only 205 // removes the first matching document. 206 All bool 207 208 // ID of the document to remove. This sets the Filter to an _id match using the specified value. 209 ID string 210 211 // Name of the document to remove. 212 Name string 213 214 // Namespace of the document to remove. 215 Namespace string 216 } 217 218 func (o RemoveOptions) ToPluginOptions(collection string) plugins.RemoveOptions { 219 // If a custom filter wasn't specified, update the specified document 220 if o.Filter == nil { 221 if o.ID != "" { 222 o.Filter = map[string]interface{}{"_id": o.ID} 223 } else if o.Name != "" { 224 o.Filter = map[string]interface{}{"namespace": o.Namespace, "name": o.Name} 225 } 226 } 227 228 return plugins.RemoveOptions{ 229 Collection: collection, 230 Filter: o.Filter, 231 All: o.All, 232 } 233 } 234 235 // UpdateOptions is the set of options for the StorageProtocol.Update operation. 236 type UpdateOptions struct { 237 // Filter is a query filter document. Defaults to filtering by the document id. 238 // See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter 239 Filter bson.M 240 241 // Upsert indicates that the document should be inserted if not found 242 Upsert bool 243 244 // Document is the replacement document. 245 Document interface{} 246 } 247 248 func (o UpdateOptions) ToPluginOptions(collection string) (plugins.UpdateOptions, error) { 249 // If a custom filter wasn't specified, update the specified document 250 if o.Filter == nil { 251 if doc, ok := o.Document.(Document); ok { 252 o.Filter = doc.DefaultDocumentFilter() 253 } 254 } 255 256 var doc map[string]interface{} 257 err := convertToRawJsonDocument(o.Document, &doc) 258 if err != nil { 259 return plugins.UpdateOptions{}, nil 260 } 261 262 return plugins.UpdateOptions{ 263 Collection: collection, 264 Filter: o.Filter, 265 Upsert: o.Upsert, 266 Document: doc, 267 }, nil 268 } 269 270 // Document represents a stored Porter document with 271 // accessor methods to make persistence more straightforward. 272 type Document interface { 273 // DefaultDocumentFilter is the default filter to match the current document. 274 DefaultDocumentFilter() map[string]interface{} 275 } 276 277 // converts a set of typed documents to a raw representation using maps 278 // by way of the type's json representation. This ensures that any 279 // json marshal logic is used when serializing documents to the database. 280 // e.g. if a document has a calculated field such as _id (which is required 281 // when persisting the document), that it is included in the doc sent to the database. 282 func convertToRawJsonDocument(in interface{}, raw interface{}) error { 283 data, err := json.Marshal(in) 284 if err != nil { 285 return err 286 } 287 288 return json.Unmarshal(data, raw) 289 } 290 291 // ListOptions is the set of options available to the list operation 292 // on any storage provider. 293 type ListOptions struct { 294 // Namespace in which the particular result list is defined. 295 Namespace string 296 297 // Name specifies whether the result list name contain the specified substring. 298 Name string 299 300 // Labels is used to filter result list based on a key-value pair. 301 Labels map[string]string 302 303 // Skip is the number of results to skip past and exclude from the results. 304 Skip int64 305 306 // Limit is the number of results to return. 307 Limit int64 308 } 309 310 // ToFindOptions builds a query for a list of documents with these conditions: 311 // * sorted in ascending order by namespace first and then name 312 // * filtered by matching namespace, name contains substring, and labels contain all matches 313 // * skipped and limited to a certain number of result 314 func (o ListOptions) ToFindOptions() FindOptions { 315 filter := make(map[string]interface{}, 3) 316 if o.Namespace != "*" { 317 filter["namespace"] = o.Namespace 318 } 319 if o.Name != "" { 320 filter["name"] = map[string]interface{}{"$regex": o.Name} 321 } 322 for k, v := range o.Labels { 323 filter["labels."+k] = v 324 } 325 326 return FindOptions{ 327 Sort: []string{"namespace", "name"}, 328 Filter: filter, 329 Skip: o.Skip, 330 Limit: o.Limit, 331 } 332 }