• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / deployments / 731956009

pending completion
731956009

Pull #809

gitlab-ci

Fabio Tranchitella
feat: add support to filter device deployments by status `finished`
Pull Request #809: MEN-5911: feat: internal end-point to retrieve the device deployments history

95 of 131 new or added lines in 7 files covered. (72.52%)

1 existing line in 1 file now uncovered.

6112 of 7840 relevant lines covered (77.96%)

77.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.89
/store/mongo/datastore_mongo.go
1
// Copyright 2022 Northern.tech AS
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
//            http://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
package mongo
15

16
import (
17
        "context"
18
        "crypto/tls"
19
        "math"
20
        "regexp"
21
        "strings"
22
        "time"
23

24
        "github.com/pkg/errors"
25
        "go.mongodb.org/mongo-driver/bson"
26
        "go.mongodb.org/mongo-driver/bson/primitive"
27
        "go.mongodb.org/mongo-driver/mongo"
28
        mopts "go.mongodb.org/mongo-driver/mongo/options"
29

30
        "github.com/mendersoftware/go-lib-micro/config"
31
        mstore "github.com/mendersoftware/go-lib-micro/store"
32

33
        dconfig "github.com/mendersoftware/deployments/config"
34
        "github.com/mendersoftware/deployments/model"
35
        "github.com/mendersoftware/deployments/store"
36
)
37

38
const (
39
        DatabaseName                   = "deployment_service"
40
        CollectionLimits               = "limits"
41
        CollectionImages               = "images"
42
        CollectionDeployments          = "deployments"
43
        CollectionDeviceDeploymentLogs = "devices.logs"
44
        CollectionDevices              = "devices"
45
        CollectionStorageSettings      = "settings"
46
)
47

48
const DefaultDocumentLimit = 20
49
const maxCountDocuments = int64(10000)
50

51
// Internal status codes from
52
// https://github.com/mongodb/mongo/blob/4.4/src/mongo/base/error_codes.yml
53
const (
54
        errorCodeNamespaceNotFound = 26
55
        errorCodeIndexNotFound     = 27
56
)
57

58
var (
59
        // Indexes (version: 1.2.2)
60
        IndexUniqueNameAndDeviceTypeName          = "uniqueNameAndDeviceTypeIndex"
61
        IndexDeploymentArtifactName               = "deploymentArtifactNameIndex"
62
        IndexDeploymentDeviceStatusesName         = "deviceIdWithStatusByCreated"
63
        IndexDeploymentDeviceIdStatusName         = "devicesIdWithStatus"
64
        IndexDeploymentDeviceCreatedStatusName    = "devicesIdWithCreatedStatus"
65
        IndexDeploymentDeviceDeploymentIdName     = "devicesDeploymentId"
66
        IndexDeploymentStatusFinishedName         = "deploymentStatusFinished"
67
        IndexDeploymentStatusPendingName          = "deploymentStatusPending"
68
        IndexDeploymentCreatedName                = "deploymentCreated"
69
        IndexDeploymentDeviceStatusRebootingName  = "deploymentsDeviceStatusRebooting"
70
        IndexDeploymentDeviceStatusPendingName    = "deploymentsDeviceStatusPending"
71
        IndexDeploymentDeviceStatusInstallingName = "deploymentsDeviceStatusInstalling"
72
        IndexDeploymentDeviceStatusFinishedName   = "deploymentsFinished"
73

74
        // Indexes (version: 1.2.3)
75
        IndexArtifactNameDependsName = "artifactNameDepends"
76
        IndexNameAndDeviceTypeName   = "artifactNameAndDeviceTypeIndex"
77

78
        // Indexes (version: 1.2.4)
79
        IndexDeploymentStatus = "deploymentStatus"
80

81
        // Indexes 1.2.6
82
        IndexDeviceDeploymentStatusName = "deploymentid_status_deviceid"
83

84
        // Indexes 1.2.13
85
        IndexArtifactProvidesName = "artifact_provides"
86

87
        _false         = false
88
        _true          = true
89
        StorageIndexes = mongo.IndexModel{
90
                // NOTE: Keys should be bson.D as element
91
                //       order matters!
92
                Keys: bson.D{
93
                        {Key: StorageKeyDeploymentName,
94
                                Value: "text"},
95
                        {Key: StorageKeyDeploymentArtifactName,
96
                                Value: "text"},
97
                },
98
                Options: &mopts.IndexOptions{
99
                        Background: &_false,
100
                        Name:       &IndexDeploymentArtifactName,
101
                },
102
        }
103
        StatusIndexes = mongo.IndexModel{
104
                Keys: bson.D{
105
                        {Key: StorageKeyDeviceDeploymentDeviceId,
106
                                Value: 1},
107
                        {Key: StorageKeyDeviceDeploymentStatus,
108
                                Value: 1},
109
                        {Key: StorageKeyDeploymentStatsCreated,
110
                                Value: 1},
111
                },
112
                Options: &mopts.IndexOptions{
113
                        Background: &_false,
114
                        Name:       &IndexDeploymentDeviceStatusesName,
115
                },
116
        }
117
        DeploymentStatusIndex = mongo.IndexModel{
118
                Keys: bson.D{
119
                        {Key: StorageKeyDeviceDeploymentStatus,
120
                                Value: 1},
121
                },
122
                Options: &mopts.IndexOptions{
123
                        Background: &_false,
124
                        Name:       &IndexDeploymentStatus,
125
                },
126
        }
127
        DeviceIDStatusIndexes = mongo.IndexModel{
128
                Keys: bson.D{
129
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
130
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
131
                },
132
                Options: &mopts.IndexOptions{
133
                        Background: &_false,
134
                        Name:       &IndexDeploymentDeviceIdStatusName,
135
                },
136
        }
137
        DeviceIDCreatedStatusIndex = mongo.IndexModel{
138
                Keys: bson.D{
139
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
140
                        {Key: StorageKeyDeploymentStatsCreated, Value: 1},
141
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
142
                },
143
                Options: &mopts.IndexOptions{
144
                        Background: &_false,
145
                        Name:       &IndexDeploymentDeviceCreatedStatusName,
146
                },
147
        }
148
        DeploymentIdIndexes = mongo.IndexModel{
149
                Keys: bson.D{
150
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
151
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
152
                },
153
                Options: &mopts.IndexOptions{
154
                        Background: &_false,
155
                        Name:       &IndexDeploymentDeviceDeploymentIdName,
156
                },
157
        }
158
        DeviceDeploymentIdStatus = mongo.IndexModel{
159
                Keys: bson.D{
160
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
161
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
162
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
163
                },
164
                Options: mopts.Index().
165
                        SetName(IndexDeviceDeploymentStatusName),
166
        }
167
        DeploymentStatusFinishedIndex = mongo.IndexModel{
168
                Keys: bson.D{
169
                        {Key: "stats.downloading", Value: 1},
170
                        {Key: "stats.installing", Value: 1},
171
                        {Key: "stats.pending", Value: 1},
172
                        {Key: "stats.rebooting", Value: 1},
173
                        {Key: "created", Value: -1},
174
                },
175
                Options: &mopts.IndexOptions{
176
                        Background: &_false,
177
                        Name:       &IndexDeploymentStatusFinishedName,
178
                },
179
        }
180
        DeploymentStatusPendingIndex = mongo.IndexModel{
181
                Keys: bson.D{
182
                        {Key: "stats.aborted", Value: 1},
183
                        {Key: "stats.already-installed", Value: 1},
184
                        {Key: "stats.decommissioned", Value: 1},
185
                        {Key: "stats.downloading", Value: 1},
186
                        {Key: "stats.failure", Value: 1},
187
                        {Key: "stats.installing", Value: 1},
188
                        {Key: "stats.noartifact", Value: 1},
189
                        {Key: "stats.rebooting", Value: 1},
190
                        {Key: "stats.success", Value: 1},
191
                        {Key: "created", Value: -1},
192
                },
193
                Options: &mopts.IndexOptions{
194
                        Background: &_false,
195
                        Name:       &IndexDeploymentStatusPendingName,
196
                },
197
        }
198
        DeploymentCreatedIndex = mongo.IndexModel{
199
                Keys: bson.D{
200
                        {Key: "created", Value: -1},
201
                },
202
                Options: &mopts.IndexOptions{
203
                        Background: &_false,
204
                        Name:       &IndexDeploymentCreatedName,
205
                },
206
        }
207
        DeploymentDeviceStatusRebootingIndex = mongo.IndexModel{
208
                Keys: bson.D{
209
                        {Key: "stats.rebooting", Value: 1},
210
                },
211
                Options: &mopts.IndexOptions{
212
                        Background: &_false,
213
                        Name:       &IndexDeploymentDeviceStatusRebootingName,
214
                },
215
        }
216
        DeploymentDeviceStatusPendingIndex = mongo.IndexModel{
217
                Keys: bson.D{
218
                        {Key: "stats.pending", Value: 1},
219
                },
220
                Options: &mopts.IndexOptions{
221
                        Background: &_false,
222
                        Name:       &IndexDeploymentDeviceStatusPendingName,
223
                },
224
        }
225
        DeploymentDeviceStatusInstallingIndex = mongo.IndexModel{
226
                Keys: bson.D{
227
                        {Key: "stats.installing", Value: 1},
228
                },
229
                Options: &mopts.IndexOptions{
230
                        Background: &_false,
231
                        Name:       &IndexDeploymentDeviceStatusInstallingName,
232
                },
233
        }
234
        DeploymentDeviceStatusFinishedIndex = mongo.IndexModel{
235
                Keys: bson.D{
236
                        {Key: "finished", Value: 1},
237
                },
238
                Options: &mopts.IndexOptions{
239
                        Background: &_false,
240
                        Name:       &IndexDeploymentDeviceStatusFinishedName,
241
                },
242
        }
243
        UniqueNameVersionIndex = mongo.IndexModel{
244
                Keys: bson.D{
245
                        {Key: StorageKeyImageName,
246
                                Value: 1},
247
                        {Key: StorageKeyImageDeviceTypes,
248
                                Value: 1},
249
                },
250
                Options: &mopts.IndexOptions{
251
                        Background: &_false,
252
                        Name:       &IndexUniqueNameAndDeviceTypeName,
253
                        Unique:     &_true,
254
                },
255
        }
256

257
        // 1.2.3
258
        IndexArtifactNameDepends = mongo.IndexModel{
259
                Keys: bson.D{
260
                        {Key: StorageKeyImageName,
261
                                Value: 1},
262
                        {Key: StorageKeyImageDependsIdx,
263
                                Value: 1},
264
                },
265
                Options: &mopts.IndexOptions{
266
                        Background: &_false,
267
                        Name:       &IndexArtifactNameDependsName,
268
                        Unique:     &_true,
269
                },
270
        }
271

272
        // Indexes 1.2.7
273
        IndexImageMetaDescription      = "image_meta_description"
274
        IndexImageMetaDescriptionModel = mongo.IndexModel{
275
                Keys: bson.D{
276
                        {Key: StorageKeyImageDescription, Value: 1},
277
                },
278
                Options: &mopts.IndexOptions{
279
                        Background: &_false,
280
                        Name:       &IndexImageMetaDescription,
281
                },
282
        }
283

284
        IndexImageMetaArtifactDeviceTypeCompatible      = "image_meta_artifact_device_type_compatible"
285
        IndexImageMetaArtifactDeviceTypeCompatibleModel = mongo.IndexModel{
286
                Keys: bson.D{
287
                        {Key: StorageKeyImageDeviceTypes, Value: 1},
288
                },
289
                Options: &mopts.IndexOptions{
290
                        Background: &_false,
291
                        Name:       &IndexImageMetaArtifactDeviceTypeCompatible,
292
                },
293
        }
294

295
        // Indexes 1.2.8
296
        IndexDeploymentsActiveCreated      = "active_created"
297
        IndexDeploymentsActiveCreatedModel = mongo.IndexModel{
298
                Keys: bson.D{
299
                        {Key: StorageKeyDeploymentCreated, Value: 1},
300
                },
301
                Options: &mopts.IndexOptions{
302
                        Background: &_false,
303
                        Name:       &IndexDeploymentsActiveCreated,
304
                        PartialFilterExpression: bson.M{
305
                                StorageKeyDeploymentActive: true,
306
                        },
307
                },
308
        }
309

310
        // Index 1.2.9
311
        IndexDeviceDeploymentsActiveCreated      = "active_deviceid_created"
312
        IndexDeviceDeploymentsActiveCreatedModel = mongo.IndexModel{
313
                Keys: bson.D{
314
                        {Key: StorageKeyDeviceDeploymentActive, Value: 1},
315
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
316
                        {Key: StorageKeyDeviceDeploymentCreated, Value: 1},
317
                },
318
                Options: mopts.Index().
319
                        SetName(IndexDeviceDeploymentsActiveCreated),
320
        }
321

322
        // Index 1.2.11
323
        IndexDeviceDeploymentsLogs      = "devices_logs"
324
        IndexDeviceDeploymentsLogsModel = mongo.IndexModel{
325
                Keys: bson.D{
326
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
327
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
328
                },
329
                Options: mopts.Index().
330
                        SetName(IndexDeviceDeploymentsLogs),
331
        }
332

333
        // 1.2.13
334
        IndexArtifactProvides = mongo.IndexModel{
335
                Keys: bson.D{
336
                        {Key: model.StorageKeyImageProvidesKey,
337
                                Value: 1},
338
                        {Key: model.StorageKeyImageProvidesValue,
339
                                Value: 1},
340
                },
341
                Options: &mopts.IndexOptions{
342
                        Background: &_false,
343
                        Sparse:     &_true,
344
                        Name:       &IndexArtifactProvidesName,
345
                },
346
        }
347
)
348

349
// Errors
350
var (
351
        ErrImagesStorageInvalidID           = errors.New("Invalid id")
352
        ErrImagesStorageInvalidArtifactName = errors.New("Invalid artifact name")
353
        ErrImagesStorageInvalidName         = errors.New("Invalid name")
354
        ErrImagesStorageInvalidDeviceType   = errors.New("Invalid device type")
355
        ErrImagesStorageInvalidImage        = errors.New("Invalid image")
356

357
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
358

359
        ErrDeploymentStorageInvalidDeployment = errors.New("Invalid deployment")
360
        ErrStorageInvalidID                   = errors.New("Invalid id")
361
        ErrStorageNotFound                    = errors.New("Not found")
362
        ErrDeploymentStorageInvalidQuery      = errors.New("Invalid query")
363
        ErrDeploymentStorageCannotExecQuery   = errors.New("Cannot execute query")
364
        ErrStorageInvalidInput                = errors.New("invalid input")
365

366
        ErrLimitNotFound      = errors.New("limit not found")
367
        ErrDevicesCountFailed = errors.New("failed to count devices")
368
)
369

370
const (
371
        ErrMsgConflictingDepends = "An artifact with the same name has " +
372
                "conflicting depends"
373
)
374

375
// Database keys
376
const (
377
        // Need to be kept in sync with structure filed names
378
        StorageKeyId = "_id"
379

380
        StorageKeyImageProvides    = "meta_artifact.provides"
381
        StorageKeyImageDepends     = "meta_artifact.depends"
382
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
383
        StorageKeyImageSize        = "size"
384
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
385
        StorageKeyImageName        = "meta_artifact.name"
386
        StorageKeyImageDescription = "meta.description"
387

388
        StorageKeyDeviceDeploymentLogMessages = "messages"
389

390
        StorageKeyDeviceDeploymentAssignedImage   = "image"
391
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
392
                "." + StorageKeyId
393

394
        StorageKeyDeviceDeploymentActive         = "active"
395
        StorageKeyDeviceDeploymentCreated        = "created"
396
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
397
        StorageKeyDeviceDeploymentStatus         = "status"
398
        StorageKeyDeviceDeploymentSubState       = "substate"
399
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
400
        StorageKeyDeviceDeploymentFinished       = "finished"
401
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
402
        StorageKeyDeviceDeploymentArtifact       = "image"
403
        StorageKeyDeviceDeploymentRequest        = "request"
404
        StorageKeyDeviceDeploymentDeleted        = "deleted"
405

406
        StorageKeyDeploymentName         = "deploymentconstructor.name"
407
        StorageKeyDeploymentArtifactName = "deploymentconstructor.artifactname"
408
        StorageKeyDeploymentStats        = "stats"
409
        StorageKeyDeploymentActive       = "active"
410
        StorageKeyDeploymentStatus       = "status"
411
        StorageKeyDeploymentCreated      = "created"
412
        StorageKeyDeploymentStatsCreated = "created"
413
        StorageKeyDeploymentFinished     = "finished"
414
        StorageKeyDeploymentArtifacts    = "artifacts"
415
        StorageKeyDeploymentDeviceCount  = "device_count"
416
        StorageKeyDeploymentMaxDevices   = "max_devices"
417
        StorageKeyDeploymentType         = "type"
418

419
        StorageKeyStorageSettingsDefaultID      = "settings"
420
        StorageKeyStorageSettingsBucket         = "bucket"
421
        StorageKeyStorageSettingsRegion         = "region"
422
        StorageKeyStorageSettingsKey            = "key"
423
        StorageKeyStorageSettingsSecret         = "secret"
424
        StorageKeyStorageSettingsURI            = "uri"
425
        StorageKeyStorageSettingsExternalURI    = "external_uri"
426
        StorageKeyStorageSettingsToken          = "token"
427
        StorageKeyStorageSettingsForcePathStyle = "force_path_style"
428
        StorageKeyStorageSettingsUseAccelerate  = "use_accelerate"
429

430
        ArtifactDependsDeviceType = "device_type"
431
)
432

433
type DataStoreMongo struct {
434
        client *mongo.Client
435
}
436

437
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
541✔
438
        return &DataStoreMongo{
541✔
439
                client: client,
541✔
440
        }
541✔
441
}
541✔
442

443
func NewMongoClient(ctx context.Context, c config.Reader) (*mongo.Client, error) {
1✔
444

1✔
445
        clientOptions := mopts.Client()
1✔
446
        mongoURL := c.GetString(dconfig.SettingMongo)
1✔
447
        if !strings.Contains(mongoURL, "://") {
1✔
448
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
449
                        mongoURL)
×
450
        }
×
451
        clientOptions.ApplyURI(mongoURL)
1✔
452

1✔
453
        username := c.GetString(dconfig.SettingDbUsername)
1✔
454
        if username != "" {
1✔
455
                credentials := mopts.Credential{
×
456
                        Username: c.GetString(dconfig.SettingDbUsername),
×
457
                }
×
458
                password := c.GetString(dconfig.SettingDbPassword)
×
459
                if password != "" {
×
460
                        credentials.Password = password
×
461
                        credentials.PasswordSet = true
×
462
                }
×
463
                clientOptions.SetAuth(credentials)
×
464
        }
465

466
        if c.GetBool(dconfig.SettingDbSSL) {
1✔
467
                tlsConfig := &tls.Config{}
×
468
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
469
                clientOptions.SetTLSConfig(tlsConfig)
×
470
        }
×
471

472
        // Set 10s timeout
473
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
1✔
474
        defer cancel()
1✔
475
        client, err := mongo.Connect(ctx, clientOptions)
1✔
476
        if err != nil {
1✔
477
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
478
        }
×
479

480
        // Validate connection
481
        if err = client.Ping(ctx, nil); err != nil {
1✔
482
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
483
        }
×
484

485
        return client, nil
1✔
486
}
487

488
func (db *DataStoreMongo) Ping(ctx context.Context) error {
2✔
489
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
2✔
490
        return res.Err()
2✔
491
}
2✔
492

493
func (db *DataStoreMongo) GetReleases(
494
        ctx context.Context,
495
        filt *model.ReleaseOrImageFilter,
496
) ([]model.Release, int, error) {
19✔
497
        var pipe []bson.D
19✔
498

19✔
499
        pipe = []bson.D{}
19✔
500
        if filt != nil && filt.Name != "" {
24✔
501
                pipe = append(pipe, bson.D{
5✔
502
                        {Key: "$match", Value: bson.M{
5✔
503
                                StorageKeyImageName: bson.M{
5✔
504
                                        "$regex": primitive.Regex{
5✔
505
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
5✔
506
                                                Options: "i",
5✔
507
                                        },
5✔
508
                                },
5✔
509
                        }},
5✔
510
                })
5✔
511
        }
5✔
512

513
        pipe = append(pipe, bson.D{
19✔
514
                // Remove (possibly expensive) sub-document from pipeline
19✔
515
                {Key: "$project", Value: bson.M{StorageKeyImageDependsIdx: 0}},
19✔
516
        })
19✔
517

19✔
518
        pipe = append(pipe, bson.D{
19✔
519
                {Key: "$group", Value: bson.D{
19✔
520
                        {Key: "_id", Value: "$" + StorageKeyImageName},
19✔
521
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
19✔
522
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
19✔
523
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
19✔
524
                }},
19✔
525
        })
19✔
526

19✔
527
        if filt != nil && filt.Description != "" {
23✔
528
                pipe = append(pipe, bson.D{
4✔
529
                        {Key: "$match", Value: bson.M{
4✔
530
                                "artifacts." + StorageKeyImageDescription: bson.M{
4✔
531
                                        "$regex": primitive.Regex{
4✔
532
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
533
                                                Options: "i",
4✔
534
                                        },
4✔
535
                                },
4✔
536
                        }},
4✔
537
                })
4✔
538
        }
4✔
539
        if filt != nil && filt.DeviceType != "" {
21✔
540
                pipe = append(pipe, bson.D{
2✔
541
                        {Key: "$match", Value: bson.M{
2✔
542
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
2✔
543
                                        "$regex": primitive.Regex{
2✔
544
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
2✔
545
                                                Options: "i",
2✔
546
                                        },
2✔
547
                                },
2✔
548
                        }},
2✔
549
                })
2✔
550
        }
2✔
551

552
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
19✔
553
        if sortField == "" {
32✔
554
                sortField = "name"
13✔
555
        }
13✔
556
        if sortOrder == 0 {
32✔
557
                sortOrder = 1
13✔
558
        }
13✔
559

560
        page := 1
19✔
561
        perPage := math.MaxInt64
19✔
562
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
21✔
563
                page = filt.Page
2✔
564
                perPage = filt.PerPage
2✔
565
        }
2✔
566
        pipe = append(pipe,
19✔
567
                bson.D{{Key: "$facet", Value: bson.D{
19✔
568
                        {Key: "results", Value: []bson.D{
19✔
569
                                {
19✔
570
                                        {Key: "$sort", Value: bson.D{
19✔
571
                                                {Key: sortField, Value: sortOrder},
19✔
572
                                                {Key: "_id", Value: 1},
19✔
573
                                        }},
19✔
574
                                },
19✔
575
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
19✔
576
                                {{Key: "$limit", Value: int64(perPage)}},
19✔
577
                        }},
19✔
578
                        {Key: "count", Value: []bson.D{
19✔
579
                                {{Key: "$count", Value: "count"}},
19✔
580
                        }},
19✔
581
                }}},
19✔
582
        )
19✔
583

19✔
584
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
19✔
585
        collImg := database.Collection(CollectionImages)
19✔
586

19✔
587
        cursor, err := collImg.Aggregate(ctx, pipe)
19✔
588
        if err != nil {
19✔
589
                return nil, 0, err
×
590
        }
×
591
        defer cursor.Close(ctx)
19✔
592

19✔
593
        result := struct {
19✔
594
                Results []model.Release       `bson:"results"`
19✔
595
                Count   []struct{ Count int } `bson:"count"`
19✔
596
        }{}
19✔
597
        if !cursor.Next(ctx) {
19✔
598
                return nil, 0, nil
×
599
        } else if err = cursor.Decode(&result); err != nil {
19✔
600
                return nil, 0, err
×
601
        } else if len(result.Count) == 0 {
22✔
602
                return []model.Release{}, 0, err
3✔
603
        }
3✔
604
        return result.Results, result.Count[0].Count, nil
17✔
605
}
606

607
// limits
608
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
8✔
609

8✔
610
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
611
        collLim := database.Collection(CollectionLimits)
8✔
612

8✔
613
        limit := new(model.Limit)
8✔
614
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
8✔
615
                Decode(limit); err != nil {
12✔
616
                if err == mongo.ErrNoDocuments {
8✔
617
                        return nil, ErrLimitNotFound
4✔
618
                }
4✔
619
                return nil, err
×
620
        }
621

622
        return limit, nil
4✔
623
}
624

625
func (db *DataStoreMongo) ProvisionTenant(ctx context.Context, tenantId string) error {
9✔
626

9✔
627
        dbname := mstore.DbNameForTenant(tenantId, DbName)
9✔
628

9✔
629
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
9✔
630
}
9✔
631

632
//images
633

634
// Exists checks if object with ID exists
635
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
636
        var result interface{}
×
637

×
638
        if len(id) == 0 {
×
639
                return false, ErrImagesStorageInvalidID
×
640
        }
×
641

642
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
643
        collImg := database.Collection(CollectionImages)
×
644

×
645
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
646
                Decode(&result); err != nil {
×
647
                if err == mongo.ErrNoDocuments {
×
648
                        return false, nil
×
649
                }
×
650
                return false, err
×
651
        }
652

653
        return true, nil
×
654
}
655

656
// Update provided Image
657
// Return false if not found
658
func (db *DataStoreMongo) Update(ctx context.Context,
659
        image *model.Image) (bool, error) {
2✔
660

2✔
661
        if err := image.Validate(); err != nil {
2✔
662
                return false, err
×
663
        }
×
664

665
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
666
        collImg := database.Collection(CollectionImages)
2✔
667

2✔
668
        image.SetModified(time.Now())
2✔
669
        if res, err := collImg.ReplaceOne(
2✔
670
                ctx, bson.M{"_id": image.Id}, image,
2✔
671
        ); err != nil {
2✔
672
                return false, err
×
673
        } else if res.MatchedCount == 0 {
2✔
674
                return false, nil
×
675
        }
×
676

677
        return true, nil
2✔
678
}
679

680
// ImageByNameAndDeviceType finds image with specified application name and target device type
681
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
682
        name, deviceType string) (*model.Image, error) {
18✔
683

18✔
684
        if len(name) == 0 {
20✔
685
                return nil, ErrImagesStorageInvalidArtifactName
2✔
686
        }
2✔
687

688
        if len(deviceType) == 0 {
18✔
689
                return nil, ErrImagesStorageInvalidDeviceType
2✔
690
        }
2✔
691

692
        // equal to device type & software version (application name + version)
693
        query := bson.M{
14✔
694
                StorageKeyImageName:        name,
14✔
695
                StorageKeyImageDeviceTypes: deviceType,
14✔
696
        }
14✔
697

14✔
698
        // If multiple entries matches, pick the smallest one.
14✔
699
        findOpts := mopts.FindOne()
14✔
700
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
14✔
701

14✔
702
        dbName := mstore.DbFromContext(ctx, DatabaseName)
14✔
703
        database := db.client.Database(dbName)
14✔
704
        collImg := database.Collection(CollectionImages)
14✔
705

14✔
706
        // Both we lookup unique object, should be one or none.
14✔
707
        var image model.Image
14✔
708
        if err := collImg.FindOne(ctx, query, findOpts).
14✔
709
                Decode(&image); err != nil {
22✔
710
                if err == mongo.ErrNoDocuments {
16✔
711
                        return nil, nil
8✔
712
                }
8✔
713
                return nil, err
×
714
        }
715

716
        return &image, nil
6✔
717
}
718

719
// ImageByIdsAndDeviceType finds image with id from ids and target device type
720
func (db *DataStoreMongo) ImageByIdsAndDeviceType(ctx context.Context,
721
        ids []string, deviceType string) (*model.Image, error) {
1✔
722

1✔
723
        if len(deviceType) == 0 {
1✔
724
                return nil, ErrImagesStorageInvalidDeviceType
×
725
        }
×
726

727
        if len(ids) == 0 {
1✔
728
                return nil, ErrImagesStorageInvalidID
×
729
        }
×
730

731
        query := bson.D{
1✔
732
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
733
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
734
        }
1✔
735

1✔
736
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
737
        collImg := database.Collection(CollectionImages)
1✔
738

1✔
739
        // If multiple entries matches, pick the smallest one
1✔
740
        findOpts := mopts.FindOne()
1✔
741
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
742

1✔
743
        // Both we lookup unique object, should be one or none.
1✔
744
        var image model.Image
1✔
745
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
746
                Decode(&image); err != nil {
1✔
747
                if err == mongo.ErrNoDocuments {
×
748
                        return nil, nil
×
749
                }
×
750
                return nil, err
×
751
        }
752

753
        return &image, nil
1✔
754
}
755

756
// ImagesByName finds images with specified artifact name
757
func (db *DataStoreMongo) ImagesByName(
758
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
759

1✔
760
        var images []*model.Image
1✔
761

1✔
762
        if len(name) == 0 {
1✔
763
                return nil, ErrImagesStorageInvalidName
×
764
        }
×
765

766
        // equal to artifact name
767
        query := bson.M{
1✔
768
                StorageKeyImageName: name,
1✔
769
        }
1✔
770

1✔
771
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
772
        collImg := database.Collection(CollectionImages)
1✔
773
        cursor, err := collImg.Find(ctx, query)
1✔
774
        if err != nil {
1✔
775
                return nil, err
×
776
        }
×
777
        // Both we lookup unique object, should be one or none.
778
        if err = cursor.All(ctx, &images); err != nil {
1✔
779
                return nil, err
×
780
        }
×
781

782
        return images, nil
1✔
783
}
784

785
// Insert persists object
786
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
125✔
787

125✔
788
        if image == nil {
125✔
789
                return ErrImagesStorageInvalidImage
×
790
        }
×
791

792
        if err := image.Validate(); err != nil {
125✔
793
                return err
×
794
        }
×
795

796
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
125✔
797
        collImg := database.Collection(CollectionImages)
125✔
798

125✔
799
        _, err := collImg.InsertOne(ctx, image)
125✔
800
        if err != nil {
139✔
801
                if except, ok := err.(mongo.WriteException); ok {
28✔
802
                        var conflicts string
14✔
803
                        if len(except.WriteErrors) > 0 {
28✔
804
                                err := except.WriteErrors[0]
14✔
805
                                yamlStart := strings.IndexByte(err.Message, '{')
14✔
806
                                if yamlStart != -1 {
28✔
807
                                        conflicts = err.Message[yamlStart:]
14✔
808
                                }
14✔
809
                        }
810
                        conflictErr := model.NewConflictError(
14✔
811
                                ErrMsgConflictingDepends,
14✔
812
                                conflicts,
14✔
813
                        )
14✔
814
                        return conflictErr
14✔
815
                }
816
        }
817

818
        return nil
111✔
819
}
820

821
// FindImageByID search storage for image with ID, returns nil if not found
822
func (db *DataStoreMongo) FindImageByID(ctx context.Context,
823
        id string) (*model.Image, error) {
1✔
824

1✔
825
        if len(id) == 0 {
1✔
826
                return nil, ErrImagesStorageInvalidID
×
827
        }
×
828

829
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
830
        collImg := database.Collection(CollectionImages)
1✔
831

1✔
832
        var image model.Image
1✔
833
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
1✔
834
                Decode(&image); err != nil {
2✔
835
                if err == mongo.ErrNoDocuments {
2✔
836
                        return nil, nil
1✔
837
                }
1✔
838
                return nil, err
×
839
        }
840

841
        return &image, nil
1✔
842
}
843

844
// IsArtifactUnique checks if there is no artifact with the same artifactName
845
// supporting one of the device types from deviceTypesCompatible list.
846
// Returns true, nil if artifact is unique;
847
// false, nil if artifact is not unique;
848
// false, error in case of error.
849
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
850
        artifactName string, deviceTypesCompatible []string) (bool, error) {
11✔
851

11✔
852
        if len(artifactName) == 0 {
13✔
853
                return false, ErrImagesStorageInvalidArtifactName
2✔
854
        }
2✔
855

856
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
857
        collImg := database.Collection(CollectionImages)
9✔
858

9✔
859
        query := bson.M{
9✔
860
                "$and": []bson.M{
9✔
861
                        {
9✔
862
                                StorageKeyImageName: artifactName,
9✔
863
                        },
9✔
864
                        {
9✔
865
                                StorageKeyImageDeviceTypes: bson.M{
9✔
866
                                        "$in": deviceTypesCompatible},
9✔
867
                        },
9✔
868
                },
9✔
869
        }
9✔
870

9✔
871
        // do part of the job manually
9✔
872
        // if candidate images have any extra 'depends' - guaranteed non-overlap
9✔
873
        // otherwise it's a match
9✔
874
        cur, err := collImg.Find(ctx, query)
9✔
875
        if err != nil {
9✔
876
                return false, err
×
877
        }
×
878

879
        var images []model.Image
9✔
880
        err = cur.All(ctx, &images)
9✔
881
        if err != nil {
9✔
882
                return false, err
×
883
        }
×
884

885
        for _, i := range images {
11✔
886
                // the artifact already has same name and overlapping dev type
2✔
887
                // if there are no more depends than dev type - it's not unique
2✔
888
                if len(i.ArtifactMeta.Depends) == 1 {
4✔
889
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
4✔
890
                                return false, nil
2✔
891
                        }
2✔
892
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
893
                        return false, nil
×
894
                }
×
895
        }
896

897
        return true, nil
7✔
898
}
899

900
// Delete image specified by ID
901
// Noop on if not found.
902
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
1✔
903

1✔
904
        if len(id) == 0 {
1✔
905
                return ErrImagesStorageInvalidID
×
906
        }
×
907

908
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
909
        collImg := database.Collection(CollectionImages)
1✔
910

1✔
911
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
912
                if res.DeletedCount == 0 {
×
913
                        return nil
×
914
                }
×
915
                return err
×
916
        }
917

918
        return nil
1✔
919
}
920

921
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
47✔
922
        if filt != nil && filt.Sort != "" {
59✔
923
                sortParts := strings.SplitN(filt.Sort, ":", 2)
12✔
924
                if len(sortParts) == 2 && (sortParts[0] == "name" || sortParts[0] == "modified") {
24✔
925
                        sortField := sortParts[0]
12✔
926
                        sortOrder := 1
12✔
927
                        if sortParts[1] == model.SortDirectionDescending {
20✔
928
                                sortOrder = -1
8✔
929
                        }
8✔
930
                        return sortField, sortOrder
12✔
931
                }
932
        }
933
        return "", 0
35✔
934
}
935

936
// ListImages lists all images
937
func (db *DataStoreMongo) ListImages(
938
        ctx context.Context,
939
        filt *model.ReleaseOrImageFilter,
940
) ([]*model.Image, int, error) {
29✔
941

29✔
942
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
943
        collImg := database.Collection(CollectionImages)
29✔
944

29✔
945
        filters := bson.M{}
29✔
946
        if filt != nil {
48✔
947
                if filt.Name != "" {
27✔
948
                        filters[StorageKeyImageName] = bson.M{
8✔
949
                                "$regex": primitive.Regex{
8✔
950
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
8✔
951
                                        Options: "i",
8✔
952
                                },
8✔
953
                        }
8✔
954
                }
8✔
955
                if filt.Description != "" {
23✔
956
                        filters[StorageKeyImageDescription] = bson.M{
4✔
957
                                "$regex": primitive.Regex{
4✔
958
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
959
                                        Options: "i",
4✔
960
                                },
4✔
961
                        }
4✔
962
                }
4✔
963
                if filt.DeviceType != "" {
21✔
964
                        filters[StorageKeyImageDeviceTypes] = bson.M{
2✔
965
                                "$regex": primitive.Regex{
2✔
966
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
2✔
967
                                        Options: "i",
2✔
968
                                },
2✔
969
                        }
2✔
970
                }
2✔
971

972
        }
973

974
        findOptions := &mopts.FindOptions{}
29✔
975
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
31✔
976
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
2✔
977
                findOptions.SetLimit(int64(filt.PerPage))
2✔
978
        }
2✔
979

980
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
29✔
981
        if sortField == "" || sortField == "name" {
54✔
982
                sortField = StorageKeyImageName
25✔
983
        }
25✔
984
        if sortOrder == 0 {
52✔
985
                sortOrder = 1
23✔
986
        }
23✔
987
        findOptions.SetSort(bson.D{
29✔
988
                {Key: sortField, Value: sortOrder},
29✔
989
                {Key: "_id", Value: sortOrder},
29✔
990
        })
29✔
991

29✔
992
        cursor, err := collImg.Find(ctx, filters, findOptions)
29✔
993
        if err != nil {
29✔
994
                return nil, 0, err
×
995
        }
×
996

997
        // NOTE: cursor.All closes the cursor before returning
998
        var images []*model.Image
29✔
999
        if err := cursor.All(ctx, &images); err != nil {
29✔
1000
                if err == mongo.ErrNoDocuments {
×
1001
                        return nil, 0, nil
×
1002
                }
×
1003
                return nil, 0, err
×
1004
        }
1005

1006
        count, err := collImg.CountDocuments(ctx, filters)
29✔
1007
        if err != nil {
29✔
1008
                return nil, -1, ErrDevicesCountFailed
×
1009
        }
×
1010

1011
        return images, int(count), nil
29✔
1012
}
1013

1014
// device deployment log
1015
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1016
        log model.DeploymentLog) error {
17✔
1017

17✔
1018
        if err := log.Validate(); err != nil {
23✔
1019
                return err
6✔
1020
        }
6✔
1021

1022
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1023
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1024

11✔
1025
        query := bson.D{
11✔
1026
                {Key: StorageKeyDeviceDeploymentDeviceId,
11✔
1027
                        Value: log.DeviceID},
11✔
1028
                {Key: StorageKeyDeviceDeploymentDeploymentID,
11✔
1029
                        Value: log.DeploymentID},
11✔
1030
        }
11✔
1031

11✔
1032
        // update log messages
11✔
1033
        // if the deployment log is already present than messages will be overwritten
11✔
1034
        update := bson.D{
11✔
1035
                {Key: "$set", Value: bson.M{
11✔
1036
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
11✔
1037
                }},
11✔
1038
        }
11✔
1039
        updateOptions := mopts.Update()
11✔
1040
        updateOptions.SetUpsert(true)
11✔
1041
        if _, err := collLogs.UpdateOne(
11✔
1042
                ctx, query, update, updateOptions); err != nil {
11✔
1043
                return err
×
1044
        }
×
1045

1046
        return nil
11✔
1047
}
1048

1049
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1050
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
11✔
1051

11✔
1052
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1053
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1054

11✔
1055
        query := bson.M{
11✔
1056
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
11✔
1057
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1058
        }
11✔
1059

11✔
1060
        var depl model.DeploymentLog
11✔
1061
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
15✔
1062
                if err == mongo.ErrNoDocuments {
8✔
1063
                        return nil, nil
4✔
1064
                }
4✔
1065
                return nil, err
×
1066
        }
1067

1068
        return &depl, nil
7✔
1069
}
1070

1071
// device deployments
1072

1073
// Insert persists device deployment object
1074
func (db *DataStoreMongo) InsertDeviceDeployment(
1075
        ctx context.Context,
1076
        deviceDeployment *model.DeviceDeployment,
1077
        incrementDeviceCount bool,
1078
) error {
43✔
1079
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
43✔
1080
        c := database.Collection(CollectionDevices)
43✔
1081

43✔
1082
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
43✔
1083
                return err
×
1084
        }
×
1085

1086
        if incrementDeviceCount {
86✔
1087
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
43✔
1088
                if err != nil {
43✔
1089
                        return err
×
1090
                }
×
1091
        }
1092

1093
        return nil
43✔
1094
}
1095

1096
// InsertMany stores multiple device deployment objects.
1097
// TODO: Handle error cleanup, multi insert is not atomic, loop into two-phase commits
1098
func (db *DataStoreMongo) InsertMany(ctx context.Context,
1099
        deployments ...*model.DeviceDeployment) error {
78✔
1100

78✔
1101
        if len(deployments) == 0 {
102✔
1102
                return nil
24✔
1103
        }
24✔
1104

1105
        deviceCountIncrements := make(map[string]int)
54✔
1106

54✔
1107
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
54✔
1108
        var list []interface{}
54✔
1109
        for _, deployment := range deployments {
186✔
1110

132✔
1111
                if deployment == nil {
134✔
1112
                        return ErrStorageInvalidDeviceDeployment
2✔
1113
                }
2✔
1114

1115
                if err := deployment.Validate(); err != nil {
134✔
1116
                        return errors.Wrap(err, "Validating device deployment")
4✔
1117
                }
4✔
1118

1119
                list = append(list, deployment)
126✔
1120
                deviceCountIncrements[deployment.DeploymentId]++
126✔
1121
        }
1122

1123
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
48✔
1124
        collDevs := database.Collection(CollectionDevices)
48✔
1125

48✔
1126
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
48✔
1127
                return err
×
1128
        }
×
1129

1130
        for deploymentID := range deviceCountIncrements {
104✔
1131
                err := db.IncrementDeploymentDeviceCount(
56✔
1132
                        ctx,
56✔
1133
                        deploymentID,
56✔
1134
                        deviceCountIncrements[deploymentID],
56✔
1135
                )
56✔
1136
                if err != nil {
56✔
1137
                        return err
×
1138
                }
×
1139
        }
1140

1141
        return nil
48✔
1142
}
1143

1144
// ExistAssignedImageWithIDAndStatuses checks if image is used by deployment with specified status.
1145
func (db *DataStoreMongo) ExistAssignedImageWithIDAndStatuses(ctx context.Context,
1146
        imageID string, statuses ...model.DeviceDeploymentStatus) (bool, error) {
1✔
1147

1✔
1148
        // Verify ID formatting
1✔
1149
        if len(imageID) == 0 {
1✔
1150
                return false, ErrStorageInvalidID
×
1151
        }
×
1152

1153
        query := bson.M{StorageKeyDeviceDeploymentAssignedImageId: imageID}
1✔
1154

1✔
1155
        if len(statuses) > 0 {
2✔
1156
                query[StorageKeyDeviceDeploymentStatus] = bson.M{
1✔
1157
                        "$in": statuses,
1✔
1158
                }
1✔
1159
        }
1✔
1160

1161
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1162
        collDevs := database.Collection(CollectionDevices)
1✔
1163

1✔
1164
        // if found at least one then image in active deployment
1✔
1165
        var tmp interface{}
1✔
1166
        if err := collDevs.FindOne(ctx, query).Decode(&tmp); err != nil {
2✔
1167
                if err == mongo.ErrNoDocuments {
2✔
1168
                        return false, nil
1✔
1169
                }
1✔
1170
                return false, err
×
1171
        }
1172

1173
        return true, nil
×
1174
}
1175

1176
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1177
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1178
        ctx context.Context,
1179
        deviceID string,
1180
) (*model.DeviceDeployment, error) {
11✔
1181

11✔
1182
        // Verify ID formatting
11✔
1183
        if len(deviceID) == 0 {
13✔
1184
                return nil, ErrStorageInvalidID
2✔
1185
        }
2✔
1186

1187
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1188
        collDevs := database.Collection(CollectionDevices)
9✔
1189

9✔
1190
        // Device should know only about deployments that are not finished
9✔
1191
        query := bson.D{
9✔
1192
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
9✔
1193
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1194
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1195
                        {Key: "$exists", Value: false},
9✔
1196
                }},
9✔
1197
        }
9✔
1198

9✔
1199
        // Find the oldest one by sorting the creation timestamp
9✔
1200
        // in ascending order.
9✔
1201
        findOptions := mopts.FindOne()
9✔
1202
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
9✔
1203

9✔
1204
        // Select only the oldest one that have not been finished yet.
9✔
1205
        deployment := new(model.DeviceDeployment)
9✔
1206
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1207
                Decode(deployment); err != nil {
14✔
1208
                if err == mongo.ErrNoDocuments {
8✔
1209
                        return nil, nil
3✔
1210
                }
3✔
1211
                return nil, err
2✔
1212
        }
1213

1214
        return deployment, nil
5✔
1215
}
1216

1217
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1218
// matching device id that has not finished yet.
1219
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1220
        ctx context.Context,
1221
        deviceID string,
1222
) (*model.DeviceDeployment, error) {
11✔
1223

11✔
1224
        // Verify ID formatting
11✔
1225
        if len(deviceID) == 0 {
13✔
1226
                return nil, ErrStorageInvalidID
2✔
1227
        }
2✔
1228

1229
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1230
        collDevs := database.Collection(CollectionDevices)
9✔
1231

9✔
1232
        query := bson.D{
9✔
1233
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
9✔
1234
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1235
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1236
                        {Key: "$exists", Value: false},
9✔
1237
                }},
9✔
1238
        }
9✔
1239

9✔
1240
        // Find the latest one by sorting by the creation timestamp
9✔
1241
        // in ascending order.
9✔
1242
        findOptions := mopts.FindOne()
9✔
1243
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
9✔
1244

9✔
1245
        // Select only the latest one that have not been finished yet.
9✔
1246
        var deployment *model.DeviceDeployment
9✔
1247
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1248
                Decode(&deployment); err != nil {
14✔
1249
                if err == mongo.ErrNoDocuments {
8✔
1250
                        return nil, nil
3✔
1251
                }
3✔
1252
                return nil, err
2✔
1253
        }
1254

1255
        return deployment, nil
5✔
1256
}
1257

1258
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1259
        ctx context.Context,
1260
        deviceID string,
1261
        deploymentID string,
1262
        ddState model.DeviceDeploymentState,
1263
) (model.DeviceDeploymentStatus, error) {
19✔
1264

19✔
1265
        // Verify ID formatting
19✔
1266
        if len(deviceID) == 0 ||
19✔
1267
                len(deploymentID) == 0 {
23✔
1268
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
4✔
1269
        }
4✔
1270

1271
        if err := ddState.Validate(); err != nil {
17✔
1272
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
2✔
1273
        }
2✔
1274

1275
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1276
        collDevs := database.Collection(CollectionDevices)
13✔
1277

13✔
1278
        // Device should know only about deployments that are not finished
13✔
1279
        query := bson.D{
13✔
1280
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1281
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1282
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1283
                        {Key: "$exists", Value: false},
13✔
1284
                }},
13✔
1285
        }
13✔
1286

13✔
1287
        // update status field
13✔
1288
        set := bson.M{
13✔
1289
                StorageKeyDeviceDeploymentStatus: ddState.Status,
13✔
1290
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
13✔
1291
        }
13✔
1292
        // and finish time if provided
13✔
1293
        if ddState.FinishTime != nil {
16✔
1294
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
3✔
1295
        }
3✔
1296

1297
        if len(ddState.SubState) > 0 {
15✔
1298
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
2✔
1299
        }
2✔
1300

1301
        update := bson.D{
13✔
1302
                {Key: "$set", Value: set},
13✔
1303
        }
13✔
1304

13✔
1305
        var old model.DeviceDeployment
13✔
1306

13✔
1307
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
13✔
1308
                Decode(&old); err != nil {
17✔
1309
                if err == mongo.ErrNoDocuments {
8✔
1310
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
4✔
1311
                }
4✔
1312
                return model.DeviceDeploymentStatusNull, err
×
1313

1314
        }
1315

1316
        return old.Status, nil
9✔
1317
}
1318

1319
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1320
        deviceID string, deploymentID string, log bool) error {
13✔
1321

13✔
1322
        // Verify ID formatting
13✔
1323
        if len(deviceID) == 0 ||
13✔
1324
                len(deploymentID) == 0 {
17✔
1325
                return ErrStorageInvalidID
4✔
1326
        }
4✔
1327

1328
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1329
        collDevs := database.Collection(CollectionDevices)
9✔
1330

9✔
1331
        selector := bson.D{
9✔
1332
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1333
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1334
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1335
                        {Key: "$exists", Value: false},
9✔
1336
                }},
9✔
1337
        }
9✔
1338

9✔
1339
        update := bson.D{
9✔
1340
                {Key: "$set", Value: bson.M{
9✔
1341
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
9✔
1342
        }
9✔
1343

9✔
1344
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
9✔
1345
                return err
×
1346
        } else if res.MatchedCount == 0 {
13✔
1347
                return ErrStorageNotFound
4✔
1348
        }
4✔
1349

1350
        return nil
5✔
1351
}
1352

1353
// SaveDeviceDeploymentRequest saves device deployment request
1354
// with the device deployment object
1355
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1356
        ctx context.Context,
1357
        ID string,
1358
        request *model.DeploymentNextRequest,
1359
) error {
7✔
1360

7✔
1361
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1362
        collDevs := database.Collection(CollectionDevices)
7✔
1363

7✔
1364
        res, err := collDevs.UpdateOne(
7✔
1365
                ctx,
7✔
1366
                bson.D{{Key: StorageKeyId, Value: ID}},
7✔
1367
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
7✔
1368
        )
7✔
1369
        if err != nil {
7✔
1370
                return err
×
1371
        } else if res.MatchedCount == 0 {
9✔
1372
                return ErrStorageNotFound
2✔
1373
        }
2✔
1374
        return nil
5✔
1375
}
1376

1377
// AssignArtifact assigns artifact to the device deployment
1378
func (db *DataStoreMongo) AssignArtifact(
1379
        ctx context.Context,
1380
        deviceID string,
1381
        deploymentID string,
1382
        artifact *model.Image,
1383
) error {
1✔
1384

1✔
1385
        // Verify ID formatting
1✔
1386
        if len(deviceID) == 0 ||
1✔
1387
                len(deploymentID) == 0 {
1✔
1388
                return ErrStorageInvalidID
×
1389
        }
×
1390

1391
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1392
        collDevs := database.Collection(CollectionDevices)
1✔
1393

1✔
1394
        selector := bson.D{
1✔
1395
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1396
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1397
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1398
                        {Key: "$exists", Value: false},
1✔
1399
                }},
1✔
1400
        }
1✔
1401

1✔
1402
        update := bson.D{
1✔
1403
                {Key: "$set", Value: bson.M{
1✔
1404
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1405
                }},
1✔
1406
        }
1✔
1407

1✔
1408
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1409
                return err
×
1410
        } else if res.MatchedCount == 0 {
1✔
1411
                return ErrStorageNotFound
×
1412
        }
×
1413

1414
        return nil
1✔
1415
}
1416

1417
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1418
        id string) (model.Stats, error) {
11✔
1419

11✔
1420
        if len(id) == 0 {
11✔
1421
                return nil, ErrStorageInvalidID
×
1422
        }
×
1423

1424
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1425
        collDevs := database.Collection(CollectionDevices)
11✔
1426

11✔
1427
        match := bson.D{
11✔
1428
                {Key: "$match", Value: bson.M{
11✔
1429
                        StorageKeyDeviceDeploymentDeploymentID: id,
11✔
1430
                        StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1431
                                {Key: "$exists", Value: false},
11✔
1432
                        },
11✔
1433
                }},
11✔
1434
        }
11✔
1435
        group := bson.D{
11✔
1436
                {Key: "$group", Value: bson.D{
11✔
1437
                        {Key: "_id",
11✔
1438
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
11✔
1439
                        {Key: "count",
11✔
1440
                                Value: bson.M{"$sum": 1}}},
11✔
1441
                },
11✔
1442
        }
11✔
1443
        pipeline := []bson.D{
11✔
1444
                match,
11✔
1445
                group,
11✔
1446
        }
11✔
1447
        var results []struct {
11✔
1448
                Status model.DeviceDeploymentStatus `bson:"_id"`
11✔
1449
                Count  int
11✔
1450
        }
11✔
1451
        cursor, err := collDevs.Aggregate(ctx, pipeline)
11✔
1452
        if err != nil {
11✔
1453
                return nil, err
×
1454
        }
×
1455
        if err := cursor.All(ctx, &results); err != nil {
11✔
1456
                if err == mongo.ErrNoDocuments {
×
1457
                        return nil, nil
×
1458
                }
×
1459
                return nil, err
×
1460
        }
1461

1462
        raw := model.NewDeviceDeploymentStats()
11✔
1463
        for _, res := range results {
32✔
1464
                raw.Set(res.Status, res.Count)
21✔
1465
        }
21✔
1466
        return raw, nil
11✔
1467
}
1468

1469
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1470
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1471
        deploymentID string) ([]model.DeviceDeployment, error) {
11✔
1472

11✔
1473
        statuses := []model.DeviceDeployment{}
11✔
1474
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1475
        collDevs := database.Collection(CollectionDevices)
11✔
1476

11✔
1477
        query := bson.M{
11✔
1478
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1479
                StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1480
                        {Key: "$exists", Value: false},
11✔
1481
                },
11✔
1482
        }
11✔
1483

11✔
1484
        cursor, err := collDevs.Find(ctx, query)
11✔
1485
        if err != nil {
11✔
1486
                return nil, err
×
1487
        }
×
1488

1489
        if err = cursor.All(ctx, &statuses); err != nil {
11✔
1490
                if err == mongo.ErrNoDocuments {
×
1491
                        return nil, nil
×
1492
                }
×
1493
                return nil, err
×
1494
        }
1495

1496
        return statuses, nil
11✔
1497
}
1498

1499
func (db *DataStoreMongo) GetDevicesListForDeployment(ctx context.Context,
1500
        q store.ListQuery) ([]model.DeviceDeployment, int, error) {
27✔
1501

27✔
1502
        statuses := []model.DeviceDeployment{}
27✔
1503
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
27✔
1504
        collDevs := database.Collection(CollectionDevices)
27✔
1505

27✔
1506
        query := bson.D{
27✔
1507
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
27✔
1508
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
27✔
1509
                        {Key: "$exists", Value: false},
27✔
1510
                }},
27✔
1511
        }
27✔
1512
        if q.Status != nil {
33✔
1513
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
8✔
1514
                        query = append(query, bson.E{
2✔
1515
                                Key: "status", Value: bson.D{{
2✔
1516
                                        Key:   "$gte",
2✔
1517
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1518
                                }, {
2✔
1519
                                        Key:   "$lte",
2✔
1520
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1521
                                }},
2✔
1522
                        })
2✔
1523
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
6✔
1524
                        query = append(query, bson.E{
×
1525
                                Key: "status", Value: bson.D{{
×
1526
                                        Key:   "$gte",
×
1527
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1528
                                }, {
×
1529
                                        Key:   "$lte",
×
1530
                                        Value: model.DeviceDeploymentStatusPending,
×
1531
                                }},
×
1532
                        })
×
1533
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
4✔
NEW
1534
                        query = append(query, bson.E{
×
NEW
1535
                                Key: "status", Value: bson.D{{
×
NEW
1536
                                        Key: "$in",
×
NEW
1537
                                        Value: []model.DeviceDeploymentStatus{
×
NEW
1538
                                                model.DeviceDeploymentStatusFailure,
×
NEW
1539
                                                model.DeviceDeploymentStatusAborted,
×
NEW
1540
                                                model.DeviceDeploymentStatusSuccess,
×
NEW
1541
                                                model.DeviceDeploymentStatusNoArtifact,
×
NEW
1542
                                                model.DeviceDeploymentStatusAlreadyInst,
×
NEW
1543
                                                model.DeviceDeploymentStatusDecommissioned,
×
NEW
1544
                                        },
×
NEW
1545
                                }},
×
NEW
1546
                        })
×
1547
                } else {
4✔
1548
                        var status model.DeviceDeploymentStatus
4✔
1549
                        err := status.UnmarshalText([]byte(*q.Status))
4✔
1550
                        if err != nil {
6✔
1551
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1552
                        }
2✔
1553
                        query = append(query, bson.E{
2✔
1554
                                Key: "status", Value: status,
2✔
1555
                        })
2✔
1556
                }
1557
        }
1558

1559
        options := mopts.Find()
25✔
1560
        sortFieldQuery := bson.D{
25✔
1561
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
25✔
1562
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
25✔
1563
        }
25✔
1564
        options.SetSort(sortFieldQuery)
25✔
1565
        if q.Skip > 0 {
30✔
1566
                options.SetSkip(int64(q.Skip))
5✔
1567
        }
5✔
1568
        if q.Limit > 0 {
34✔
1569
                options.SetLimit(int64(q.Limit))
9✔
1570
        } else {
25✔
1571
                options.SetLimit(DefaultDocumentLimit)
16✔
1572
        }
16✔
1573

1574
        cursor, err := collDevs.Find(ctx, query, options)
25✔
1575
        if err != nil {
27✔
1576
                return nil, -1, err
2✔
1577
        }
2✔
1578

1579
        if err = cursor.All(ctx, &statuses); err != nil {
23✔
1580
                if err == mongo.ErrNoDocuments {
×
1581
                        return nil, -1, nil
×
1582
                }
×
1583
                return nil, -1, err
×
1584
        }
1585

1586
        count, err := collDevs.CountDocuments(ctx, query)
23✔
1587
        if err != nil {
23✔
1588
                return nil, -1, ErrDevicesCountFailed
×
1589
        }
×
1590

1591
        return statuses, int(count), nil
23✔
1592
}
1593

1594
func (db *DataStoreMongo) GetDeviceDeploymentsForDevice(ctx context.Context,
1595
        q store.ListQueryDeviceDeployments) ([]model.DeviceDeployment, int, error) {
18✔
1596

18✔
1597
        statuses := []model.DeviceDeployment{}
18✔
1598
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
18✔
1599
        collDevs := database.Collection(CollectionDevices)
18✔
1600

18✔
1601
        query := bson.D{{
18✔
1602
                Key:   StorageKeyDeviceDeploymentDeviceId,
18✔
1603
                Value: q.DeviceID,
18✔
1604
        }}
18✔
1605
        if q.Status != nil {
34✔
1606
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
18✔
1607
                        query = append(query, bson.E{
2✔
1608
                                Key: "status", Value: bson.D{{
2✔
1609
                                        Key:   "$gte",
2✔
1610
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1611
                                }, {
2✔
1612
                                        Key:   "$lte",
2✔
1613
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1614
                                }},
2✔
1615
                        })
2✔
1616
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
18✔
1617
                        query = append(query, bson.E{
2✔
1618
                                Key: "status", Value: bson.D{{
2✔
1619
                                        Key:   "$gte",
2✔
1620
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1621
                                }, {
2✔
1622
                                        Key:   "$lte",
2✔
1623
                                        Value: model.DeviceDeploymentStatusPending,
2✔
1624
                                }},
2✔
1625
                        })
2✔
1626
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
16✔
1627
                        query = append(query, bson.E{
2✔
1628
                                Key: "status", Value: bson.D{{
2✔
1629
                                        Key: "$in",
2✔
1630
                                        Value: []model.DeviceDeploymentStatus{
2✔
1631
                                                model.DeviceDeploymentStatusFailure,
2✔
1632
                                                model.DeviceDeploymentStatusAborted,
2✔
1633
                                                model.DeviceDeploymentStatusSuccess,
2✔
1634
                                                model.DeviceDeploymentStatusNoArtifact,
2✔
1635
                                                model.DeviceDeploymentStatusAlreadyInst,
2✔
1636
                                                model.DeviceDeploymentStatusDecommissioned,
2✔
1637
                                        },
2✔
1638
                                }},
2✔
1639
                        })
2✔
1640
                } else {
12✔
1641
                        var status model.DeviceDeploymentStatus
10✔
1642
                        err := status.UnmarshalText([]byte(*q.Status))
10✔
1643
                        if err != nil {
12✔
1644
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1645
                        }
2✔
1646
                        query = append(query, bson.E{
8✔
1647
                                Key: "status", Value: status,
8✔
1648
                        })
8✔
1649
                }
1650
        }
1651

1652
        options := mopts.Find()
16✔
1653
        sortFieldQuery := bson.D{
16✔
1654
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
16✔
1655
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
16✔
1656
        }
16✔
1657
        options.SetSort(sortFieldQuery)
16✔
1658
        if q.Skip > 0 {
18✔
1659
                options.SetSkip(int64(q.Skip))
2✔
1660
        }
2✔
1661
        if q.Limit > 0 {
32✔
1662
                options.SetLimit(int64(q.Limit))
16✔
1663
        } else {
16✔
1664
                options.SetLimit(DefaultDocumentLimit)
×
1665
        }
×
1666

1667
        cursor, err := collDevs.Find(ctx, query, options)
16✔
1668
        if err != nil {
16✔
1669
                return nil, -1, err
×
1670
        }
×
1671

1672
        if err = cursor.All(ctx, &statuses); err != nil {
16✔
1673
                if err == mongo.ErrNoDocuments {
×
1674
                        return nil, 0, nil
×
1675
                }
×
1676
                return nil, -1, err
×
1677
        }
1678

1679
        maxCount := maxCountDocuments
16✔
1680
        countOptions := &mopts.CountOptions{
16✔
1681
                Limit: &maxCount,
16✔
1682
        }
16✔
1683
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
16✔
1684
        if err != nil {
16✔
1685
                return nil, -1, ErrDevicesCountFailed
×
1686
        }
×
1687

1688
        return statuses, int(count), nil
16✔
1689
}
1690

1691
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1692
// `deviceID`, false otherwise. In case of errors returns false and an error
1693
// that occurred
1694
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1695
        deploymentID string, deviceID string) (bool, error) {
13✔
1696

13✔
1697
        var dep model.DeviceDeployment
13✔
1698
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1699
        collDevs := database.Collection(CollectionDevices)
13✔
1700

13✔
1701
        query := bson.D{
13✔
1702
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1703
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1704
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1705
                        {Key: "$exists", Value: false},
13✔
1706
                }},
13✔
1707
        }
13✔
1708

13✔
1709
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
19✔
1710
                if err == mongo.ErrNoDocuments {
12✔
1711
                        return false, nil
6✔
1712
                } else {
6✔
1713
                        return false, err
×
1714
                }
×
1715
        }
1716

1717
        return true, nil
7✔
1718
}
1719

1720
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
1721
        deploymentId string) error {
5✔
1722

5✔
1723
        if len(deploymentId) == 0 {
7✔
1724
                return ErrStorageInvalidID
2✔
1725
        }
2✔
1726

1727
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1728
        collDevs := database.Collection(CollectionDevices)
3✔
1729
        selector := bson.M{
3✔
1730
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
3✔
1731
                StorageKeyDeviceDeploymentActive:       true,
3✔
1732
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
1733
                        {Key: "$exists", Value: false},
3✔
1734
                },
3✔
1735
        }
3✔
1736

3✔
1737
        update := bson.M{
3✔
1738
                "$set": bson.M{
3✔
1739
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
3✔
1740
                        StorageKeyDeviceDeploymentActive: false,
3✔
1741
                },
3✔
1742
        }
3✔
1743

3✔
1744
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
3✔
1745
                return err
×
1746
        }
×
1747

1748
        return nil
3✔
1749
}
1750

1751
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
1752
        deviceID string) error {
4✔
1753
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
1754
        collDevs := database.Collection(CollectionDevices)
4✔
1755
        selector := bson.M{
4✔
1756
                StorageKeyDeviceDeploymentDeviceId: deviceID,
4✔
1757
                StorageKeyDeviceDeploymentActive:   false,
4✔
1758
                StorageKeyDeviceDeploymentDeleted: bson.M{
4✔
1759
                        "$exists": false,
4✔
1760
                },
4✔
1761
        }
4✔
1762

4✔
1763
        now := time.Now()
4✔
1764
        update := bson.M{
4✔
1765
                "$set": bson.M{
4✔
1766
                        StorageKeyDeviceDeploymentDeleted: &now,
4✔
1767
                },
4✔
1768
        }
4✔
1769

4✔
1770
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
4✔
1771
                return err
×
1772
        }
×
1773

1774
        return nil
4✔
1775
}
1776

1777
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
1778
        deviceId string) error {
4✔
1779

4✔
1780
        if len(deviceId) == 0 {
6✔
1781
                return ErrStorageInvalidID
2✔
1782
        }
2✔
1783

1784
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1785
        collDevs := database.Collection(CollectionDevices)
2✔
1786
        selector := bson.M{
2✔
1787
                StorageKeyDeviceDeploymentDeviceId: deviceId,
2✔
1788
                StorageKeyDeviceDeploymentActive:   true,
2✔
1789
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
1790
                        {Key: "$exists", Value: false},
2✔
1791
                },
2✔
1792
        }
2✔
1793

2✔
1794
        update := bson.M{
2✔
1795
                "$set": bson.M{
2✔
1796
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
2✔
1797
                        StorageKeyDeviceDeploymentActive: false,
2✔
1798
                },
2✔
1799
        }
2✔
1800

2✔
1801
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
1802
                return err
×
1803
        }
×
1804

1805
        return nil
2✔
1806
}
1807

1808
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
1809
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
1810

1✔
1811
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1812
        collDevs := database.Collection(CollectionDevices)
1✔
1813

1✔
1814
        filter := bson.M{
1✔
1815
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
1816
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
1817
        }
1✔
1818
        if !includeDeleted {
2✔
1819
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
1820
                        {Key: "$exists", Value: false},
1✔
1821
                }
1✔
1822
        }
1✔
1823

1824
        opts := &mopts.FindOneOptions{}
1✔
1825
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
1826

1✔
1827
        var dd model.DeviceDeployment
1✔
1828
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
1829
                if err == mongo.ErrNoDocuments {
2✔
1830
                        return nil, ErrStorageNotFound
1✔
1831
                }
1✔
1832
                return nil, err
×
1833
        }
1834

1835
        return &dd, nil
1✔
1836
}
1837

1838
// deployments
1839

1840
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
1841
        indexes ...mongo.IndexModel) error {
691✔
1842
        ctx := context.Background()
691✔
1843
        dataBase := db.client.Database(dbName)
691✔
1844

691✔
1845
        coll := dataBase.Collection(collName)
691✔
1846
        idxView := coll.Indexes()
691✔
1847
        _, err := idxView.CreateMany(ctx, indexes)
691✔
1848
        return err
691✔
1849
}
691✔
1850

1851
// return true if required indexing was set up
1852
func (db *DataStoreMongo) hasIndexing(ctx context.Context, client *mongo.Client) bool {
32✔
1853

32✔
1854
        var idx bson.M
32✔
1855
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
32✔
1856
        collDpl := database.Collection(CollectionDeployments)
32✔
1857
        idxView := collDpl.Indexes()
32✔
1858

32✔
1859
        cursor, err := idxView.List(ctx)
32✔
1860
        if err != nil {
32✔
1861
                // check failed, assume indexing is not there
×
1862
                return false
×
1863
        }
×
1864

1865
        has := map[string]bool{}
32✔
1866
        for cursor.Next(ctx) {
92✔
1867
                if err = cursor.Decode(&idx); err != nil {
60✔
1868
                        continue
×
1869
                }
1870
                if _, ok := idx["weights"]; ok {
90✔
1871
                        // text index
30✔
1872
                        for k := range idx["weights"].(bson.M) {
90✔
1873
                                has[k] = true
60✔
1874
                        }
60✔
1875
                } else {
30✔
1876
                        for i := range idx["key"].(bson.M) {
60✔
1877
                                has[i] = true
30✔
1878
                        }
30✔
1879

1880
                }
1881
        }
1882
        if err != nil {
32✔
1883
                return false
×
1884
        }
×
1885

1886
        for _, key := range StorageIndexes.Keys.(bson.D) {
94✔
1887
                _, ok := has[key.Key]
62✔
1888
                if !ok {
64✔
1889
                        return false
2✔
1890
                }
2✔
1891
        }
1892

1893
        return true
30✔
1894
}
1895

1896
// Insert persists object
1897
func (db *DataStoreMongo) InsertDeployment(
1898
        ctx context.Context,
1899
        deployment *model.Deployment,
1900
) error {
419✔
1901

419✔
1902
        if deployment == nil {
421✔
1903
                return ErrDeploymentStorageInvalidDeployment
2✔
1904
        }
2✔
1905

1906
        if err := deployment.Validate(); err != nil {
420✔
1907
                return err
3✔
1908
        }
3✔
1909

1910
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
415✔
1911
        collDpl := database.Collection(CollectionDeployments)
415✔
1912

415✔
1913
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
416✔
1914
                return err
1✔
1915
        }
1✔
1916
        return nil
415✔
1917
}
1918

1919
// Delete removed entry by ID
1920
// Noop on ID not found
1921
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
8✔
1922

8✔
1923
        if len(id) == 0 {
10✔
1924
                return ErrStorageInvalidID
2✔
1925
        }
2✔
1926

1927
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1928
        collDpl := database.Collection(CollectionDeployments)
6✔
1929

6✔
1930
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
6✔
1931
                return err
×
1932
        }
×
1933

1934
        return nil
6✔
1935
}
1936

1937
func (db *DataStoreMongo) FindDeploymentByID(
1938
        ctx context.Context,
1939
        id string,
1940
) (*model.Deployment, error) {
19✔
1941

19✔
1942
        if len(id) == 0 {
21✔
1943
                return nil, ErrStorageInvalidID
2✔
1944
        }
2✔
1945

1946
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
17✔
1947
        collDpl := database.Collection(CollectionDeployments)
17✔
1948

17✔
1949
        deployment := new(model.Deployment)
17✔
1950
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
17✔
1951
                Decode(deployment); err != nil {
23✔
1952
                if err == mongo.ErrNoDocuments {
12✔
1953
                        return nil, nil
6✔
1954
                }
6✔
1955
                return nil, err
×
1956
        }
1957

1958
        return deployment, nil
11✔
1959
}
1960

1961
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
1962
        ctx context.Context,
1963
        ids ...string,
1964
) (deploymentStats []*model.DeploymentStats, err error) {
4✔
1965

4✔
1966
        if len(ids) == 0 {
4✔
1967
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
1968
        }
×
1969

1970
        for _, id := range ids {
12✔
1971
                if len(id) == 0 {
8✔
1972
                        return nil, ErrStorageInvalidID
×
1973
                }
×
1974
        }
1975

1976
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
1977
        collDpl := database.Collection(CollectionDeployments)
4✔
1978

4✔
1979
        query := bson.M{
4✔
1980
                "_id": bson.M{
4✔
1981
                        "$in": ids,
4✔
1982
                },
4✔
1983
        }
4✔
1984
        statsProjection := &mopts.FindOptions{
4✔
1985
                Projection: bson.M{"stats": 1},
4✔
1986
        }
4✔
1987

4✔
1988
        results, err := collDpl.Find(
4✔
1989
                ctx,
4✔
1990
                query,
4✔
1991
                statsProjection,
4✔
1992
        )
4✔
1993
        if err != nil {
4✔
1994
                return nil, err
×
1995
        }
×
1996

1997
        for results.Next(context.Background()) {
12✔
1998
                depl := new(model.DeploymentStats)
8✔
1999
                if err = results.Decode(&depl); err != nil {
8✔
2000
                        if err == mongo.ErrNoDocuments {
×
2001
                                return nil, nil
×
2002
                        }
×
2003
                        return nil, err
×
2004
                }
2005
                deploymentStats = append(deploymentStats, depl)
8✔
2006
        }
2007

2008
        return deploymentStats, nil
4✔
2009
}
2010

2011
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2012
        id string) (*model.Deployment, error) {
15✔
2013

15✔
2014
        if len(id) == 0 {
17✔
2015
                return nil, ErrStorageInvalidID
2✔
2016
        }
2✔
2017

2018
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
2019
        collDpl := database.Collection(CollectionDeployments)
13✔
2020

13✔
2021
        var deployment *model.Deployment
13✔
2022
        filter := bson.D{
13✔
2023
                {Key: "_id", Value: id},
13✔
2024
                {Key: StorageKeyDeploymentFinished, Value: nil},
13✔
2025
        }
13✔
2026
        if err := collDpl.FindOne(ctx, filter).
13✔
2027
                Decode(&deployment); err != nil {
22✔
2028
                if err == mongo.ErrNoDocuments {
18✔
2029
                        return nil, nil
9✔
2030
                }
9✔
2031
                return nil, err
×
2032
        }
2033

2034
        return deployment, nil
5✔
2035
}
2036

2037
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2038
        ctx context.Context,
2039
        deploymentID string,
2040
        increment int,
2041
) error {
99✔
2042
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
99✔
2043
        collection := database.Collection(CollectionDeployments)
99✔
2044

99✔
2045
        filter := bson.M{
99✔
2046
                "_id": deploymentID,
99✔
2047
                StorageKeyDeploymentDeviceCount: bson.M{
99✔
2048
                        "$ne": nil,
99✔
2049
                },
99✔
2050
        }
99✔
2051

99✔
2052
        update := bson.M{
99✔
2053
                "$inc": bson.M{
99✔
2054
                        StorageKeyDeploymentDeviceCount: increment,
99✔
2055
                },
99✔
2056
        }
99✔
2057

99✔
2058
        _, err := collection.UpdateOne(ctx, filter, update)
99✔
2059
        return err
99✔
2060
}
99✔
2061

2062
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2063
        ctx context.Context,
2064
        deploymentID string,
2065
        count int,
2066
) error {
6✔
2067
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2068
        collection := database.Collection(CollectionDeployments)
6✔
2069

6✔
2070
        filter := bson.M{
6✔
2071
                "_id": deploymentID,
6✔
2072
                StorageKeyDeploymentDeviceCount: bson.M{
6✔
2073
                        "$eq": nil,
6✔
2074
                },
6✔
2075
        }
6✔
2076

6✔
2077
        update := bson.M{
6✔
2078
                "$set": bson.M{
6✔
2079
                        StorageKeyDeploymentDeviceCount: count,
6✔
2080
                },
6✔
2081
        }
6✔
2082

6✔
2083
        _, err := collection.UpdateOne(ctx, filter, update)
6✔
2084
        return err
6✔
2085
}
6✔
2086

2087
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2088
        id string) (int, error) {
6✔
2089

6✔
2090
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2091
        collDevs := database.Collection(CollectionDevices)
6✔
2092

6✔
2093
        filter := bson.M{
6✔
2094
                StorageKeyDeviceDeploymentDeploymentID: id,
6✔
2095
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
2096
                        {Key: "$exists", Value: false},
6✔
2097
                },
6✔
2098
        }
6✔
2099

6✔
2100
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
6✔
2101
        if err != nil {
6✔
2102
                return 0, err
×
2103
        }
×
2104

2105
        return int(deviceCount), nil
6✔
2106
}
2107

2108
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2109
        id string, stats model.Stats) error {
11✔
2110

11✔
2111
        if len(id) == 0 {
13✔
2112
                return ErrStorageInvalidID
2✔
2113
        }
2✔
2114

2115
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2116
        collDpl := database.Collection(CollectionDeployments)
9✔
2117

9✔
2118
        deployment, err := model.NewDeployment()
9✔
2119
        if err != nil {
9✔
2120
                return errors.Wrap(err, "failed to create deployment")
×
2121
        }
×
2122

2123
        deployment.Stats = stats
9✔
2124
        var update bson.M
9✔
2125
        if deployment.IsFinished() {
9✔
2126
                now := time.Now()
×
2127

×
2128
                update = bson.M{
×
2129
                        "$set": bson.M{
×
2130
                                StorageKeyDeploymentStats:    stats,
×
2131
                                StorageKeyDeploymentFinished: &now,
×
2132
                        },
×
2133
                }
×
2134
        } else {
9✔
2135
                update = bson.M{
9✔
2136
                        "$set": bson.M{
9✔
2137
                                StorageKeyDeploymentStats: stats,
9✔
2138
                        },
9✔
2139
                }
9✔
2140
        }
9✔
2141

2142
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
9✔
2143
        if res != nil && res.MatchedCount == 0 {
13✔
2144
                return ErrStorageInvalidID
4✔
2145
        }
4✔
2146
        return err
5✔
2147
}
2148

2149
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2150
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
15✔
2151

15✔
2152
        if len(id) == 0 {
17✔
2153
                return ErrStorageInvalidID
2✔
2154
        }
2✔
2155

2156
        if _, err := stateTo.MarshalText(); err != nil {
13✔
2157
                return ErrStorageInvalidInput
×
2158
        }
×
2159

2160
        // does not need any extra operations
2161
        // following query won't handle this case well and increase the state_to value
2162
        if stateFrom == stateTo {
15✔
2163
                return nil
2✔
2164
        }
2✔
2165

2166
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
2167
        collDpl := database.Collection(CollectionDeployments)
11✔
2168

11✔
2169
        var update bson.M
11✔
2170

11✔
2171
        if stateFrom == model.DeviceDeploymentStatusNull {
14✔
2172
                // note dot notation on embedded document
3✔
2173
                update = bson.M{
3✔
2174
                        "$inc": bson.M{
3✔
2175
                                "stats." + stateTo.String(): 1,
3✔
2176
                        },
3✔
2177
                }
3✔
2178
        } else {
12✔
2179
                // note dot notation on embedded document
9✔
2180
                update = bson.M{
9✔
2181
                        "$inc": bson.M{
9✔
2182
                                "stats." + stateFrom.String(): -1,
9✔
2183
                                "stats." + stateTo.String():   1,
9✔
2184
                        },
9✔
2185
                }
9✔
2186
        }
9✔
2187

2188
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
11✔
2189

11✔
2190
        if res != nil && res.MatchedCount == 0 {
13✔
2191
                return ErrStorageInvalidID
2✔
2192
        }
2✔
2193

2194
        return err
9✔
2195
}
2196

2197
func (db *DataStoreMongo) Find(ctx context.Context,
2198
        match model.Query) ([]*model.Deployment, int64, error) {
67✔
2199

67✔
2200
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
67✔
2201
        collDpl := database.Collection(CollectionDeployments)
67✔
2202

67✔
2203
        andq := []bson.M{}
67✔
2204

67✔
2205
        // filter by IDs
67✔
2206
        if len(match.IDs) > 0 {
67✔
2207
                tq := bson.M{
×
2208
                        "_id": bson.M{
×
2209
                                "$in": match.IDs,
×
2210
                        },
×
2211
                }
×
2212
                andq = append(andq, tq)
×
2213
        }
×
2214

2215
        // build deployment by name part of the query
2216
        if match.SearchText != "" {
99✔
2217
                // we must have indexing for text search
32✔
2218
                if !db.hasIndexing(ctx, db.client) {
34✔
2219
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
2✔
2220
                }
2✔
2221

2222
                tq := bson.M{
30✔
2223
                        "$text": bson.M{
30✔
2224
                                "$search": match.SearchText,
30✔
2225
                        },
30✔
2226
                }
30✔
2227

30✔
2228
                andq = append(andq, tq)
30✔
2229
        }
2230

2231
        // build deployment by status part of the query
2232
        if match.Status != model.StatusQueryAny {
85✔
2233
                var status model.DeploymentStatus
20✔
2234
                if match.Status == model.StatusQueryPending {
24✔
2235
                        status = model.DeploymentStatusPending
4✔
2236
                } else if match.Status == model.StatusQueryInProgress {
28✔
2237
                        status = model.DeploymentStatusInProgress
8✔
2238
                } else {
16✔
2239
                        status = model.DeploymentStatusFinished
8✔
2240
                }
8✔
2241
                stq := bson.M{StorageKeyDeploymentStatus: status}
20✔
2242
                andq = append(andq, stq)
20✔
2243
        }
2244

2245
        // build deployment by type part of the query
2246
        if match.Type != "" {
69✔
2247
                if match.Type == model.DeploymentTypeConfiguration {
8✔
2248
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
4✔
2249
                } else if match.Type == model.DeploymentTypeSoftware {
4✔
2250
                        andq = append(andq, bson.M{
×
2251
                                "$or": []bson.M{
×
2252
                                        {StorageKeyDeploymentType: match.Type},
×
2253
                                        {StorageKeyDeploymentType: ""},
×
2254
                                },
×
2255
                        })
×
2256
                }
×
2257
        }
2258

2259
        query := bson.M{}
65✔
2260
        if len(andq) != 0 {
111✔
2261
                // use search criteria if any
46✔
2262
                query = bson.M{
46✔
2263
                        "$and": andq,
46✔
2264
                }
46✔
2265
        }
46✔
2266

2267
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
65✔
2268
                query["created"] = bson.M{
×
2269
                        "$gte": match.CreatedAfter,
×
2270
                        "$lte": match.CreatedBefore,
×
2271
                }
×
2272
        } else if match.CreatedAfter != nil {
65✔
2273
                query["created"] = bson.M{
×
2274
                        "$gte": match.CreatedAfter,
×
2275
                }
×
2276
        } else if match.CreatedBefore != nil {
65✔
2277
                query["created"] = bson.M{
×
2278
                        "$lte": match.CreatedBefore,
×
2279
                }
×
2280
        }
×
2281

2282
        options := db.findOptions(match)
65✔
2283

65✔
2284
        var deployments []*model.Deployment
65✔
2285
        cursor, err := collDpl.Find(ctx, query, options)
65✔
2286
        if err != nil {
65✔
2287
                return nil, 0, err
×
2288
        }
×
2289
        if err := cursor.All(ctx, &deployments); err != nil {
65✔
2290
                return nil, 0, err
×
2291
        }
×
2292
        // Count documents if we didn't find all already.
2293
        count := int64(0)
65✔
2294
        if !match.DisableCount {
130✔
2295
                count = int64(len(deployments))
65✔
2296
                if count >= int64(match.Limit) {
129✔
2297
                        count, err = collDpl.CountDocuments(ctx, query)
64✔
2298
                        if err != nil {
64✔
2299
                                return nil, 0, err
×
2300
                        }
×
2301
                } else {
1✔
2302
                        // Don't forget to add the skipped documents
1✔
2303
                        count += int64(match.Skip)
1✔
2304
                }
1✔
2305
        }
2306

2307
        return deployments, count, nil
65✔
2308
}
2309

2310
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
65✔
2311
        options := &mopts.FindOptions{}
65✔
2312
        if match.Sort == model.SortDirectionAscending {
67✔
2313
                options.SetSort(bson.D{{Key: "created", Value: 1}})
2✔
2314
        } else {
65✔
2315
                options.SetSort(bson.D{{Key: "created", Value: -1}})
63✔
2316
        }
63✔
2317
        if match.Skip > 0 {
69✔
2318
                options.SetSkip(int64(match.Skip))
4✔
2319
        }
4✔
2320
        if match.Limit > 0 {
74✔
2321
                options.SetLimit(int64(match.Limit))
9✔
2322
        }
9✔
2323
        return options
65✔
2324
}
2325

2326
// FindNewerActiveDeployments finds active deployments which were created
2327
// after createdAfter
2328
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2329
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
9✔
2330

9✔
2331
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2332
        c := database.Collection(CollectionDeployments)
9✔
2333

9✔
2334
        queryFilters := make([]bson.M, 0)
9✔
2335
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
9✔
2336
        queryFilters = append(queryFilters,
9✔
2337
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
9✔
2338
        findQuery := bson.M{}
9✔
2339
        findQuery["$and"] = queryFilters
9✔
2340

9✔
2341
        findOptions := &mopts.FindOptions{}
9✔
2342
        findOptions.SetSkip(int64(skip))
9✔
2343
        findOptions.SetLimit(int64(limit))
9✔
2344

9✔
2345
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
9✔
2346
        cursor, err := c.Find(ctx, findQuery, findOptions)
9✔
2347
        if err != nil {
9✔
2348
                return nil, errors.Wrap(err, "failed to get deployments")
×
2349
        }
×
2350
        defer cursor.Close(ctx)
9✔
2351

9✔
2352
        var deployments []*model.Deployment
9✔
2353

9✔
2354
        if err = cursor.All(ctx, &deployments); err != nil {
9✔
2355
                return nil, errors.Wrap(err, "failed to get deployments")
×
2356
        }
×
2357

2358
        return deployments, nil
9✔
2359
}
2360

2361
// SetDeploymentStatus simply sets the status field
2362
// optionally sets 'finished time' if deployment is indeed finished
2363
func (db *DataStoreMongo) SetDeploymentStatus(
2364
        ctx context.Context,
2365
        id string,
2366
        status model.DeploymentStatus,
2367
        now time.Time,
2368
) error {
11✔
2369
        if len(id) == 0 {
11✔
2370
                return ErrStorageInvalidID
×
2371
        }
×
2372

2373
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
2374
        collDpl := database.Collection(CollectionDeployments)
11✔
2375

11✔
2376
        var update bson.M
11✔
2377
        if status == model.DeploymentStatusFinished {
14✔
2378
                update = bson.M{
3✔
2379
                        "$set": bson.M{
3✔
2380
                                StorageKeyDeploymentActive:   false,
3✔
2381
                                StorageKeyDeploymentStatus:   status,
3✔
2382
                                StorageKeyDeploymentFinished: &now,
3✔
2383
                        },
3✔
2384
                }
3✔
2385
        } else {
12✔
2386
                update = bson.M{
9✔
2387
                        "$set": bson.M{
9✔
2388
                                StorageKeyDeploymentActive: true,
9✔
2389
                                StorageKeyDeploymentStatus: status,
9✔
2390
                        },
9✔
2391
                }
9✔
2392
        }
9✔
2393

2394
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
11✔
2395

11✔
2396
        if res != nil && res.MatchedCount == 0 {
13✔
2397
                return ErrStorageInvalidID
2✔
2398
        }
2✔
2399

2400
        return err
9✔
2401
}
2402

2403
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2404
// given artifact
2405
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2406
        id string) (bool, error) {
7✔
2407

7✔
2408
        if len(id) == 0 {
7✔
2409
                return false, ErrStorageInvalidID
×
2410
        }
×
2411

2412
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2413
        collDpl := database.Collection(CollectionDeployments)
7✔
2414

7✔
2415
        var tmp interface{}
7✔
2416
        query := bson.D{
7✔
2417
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2418
                {Key: StorageKeyDeploymentArtifacts, Value: id},
7✔
2419
        }
7✔
2420
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
12✔
2421
                if err == mongo.ErrNoDocuments {
10✔
2422
                        return false, nil
5✔
2423
                }
5✔
2424
                return false, err
×
2425
        }
2426

2427
        return true, nil
3✔
2428
}
2429

2430
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2431
// given artifact
2432
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2433
        artifactName string) (bool, error) {
7✔
2434

7✔
2435
        if len(artifactName) == 0 {
7✔
2436
                return false, ErrImagesStorageInvalidArtifactName
×
2437
        }
×
2438

2439
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2440
        collDpl := database.Collection(CollectionDeployments)
7✔
2441

7✔
2442
        var tmp interface{}
7✔
2443
        query := bson.D{
7✔
2444
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2445
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
7✔
2446
        }
7✔
2447

7✔
2448
        projection := bson.M{
7✔
2449
                "_id": 1,
7✔
2450
        }
7✔
2451
        findOptions := mopts.FindOne()
7✔
2452
        findOptions.SetProjection(projection)
7✔
2453

7✔
2454
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
12✔
2455
                if err == mongo.ErrNoDocuments {
10✔
2456
                        return false, nil
5✔
2457
                }
5✔
2458
                return false, err
×
2459
        }
2460

2461
        return true, nil
2✔
2462
}
2463

2464
// ExistByArtifactId check if there is any deployment that uses give artifact
2465
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2466
        id string) (bool, error) {
×
2467

×
2468
        if len(id) == 0 {
×
2469
                return false, ErrStorageInvalidID
×
2470
        }
×
2471

2472
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2473
        collDpl := database.Collection(CollectionDeployments)
×
2474

×
2475
        var tmp interface{}
×
2476
        query := bson.D{
×
2477
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2478
        }
×
2479
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2480
                if err == mongo.ErrNoDocuments {
×
2481
                        return false, nil
×
2482
                }
×
2483
                return false, err
×
2484
        }
2485

2486
        return true, nil
×
2487
}
2488

2489
// Per-tenant storage settings
2490
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
3✔
2491
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2492
        collection := database.Collection(CollectionStorageSettings)
3✔
2493

3✔
2494
        settings := new(model.StorageSettings)
3✔
2495
        // supposed that it's only one document in the collection
3✔
2496
        query := bson.M{
3✔
2497
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2498
        }
3✔
2499
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
4✔
2500
                if err == mongo.ErrNoDocuments {
2✔
2501
                        return nil, nil
1✔
2502
                }
1✔
2503
                return nil, err
×
2504
        }
2505

2506
        return settings, nil
3✔
2507
}
2508

2509
func (db *DataStoreMongo) SetStorageSettings(
2510
        ctx context.Context,
2511
        storageSettings *model.StorageSettings,
2512
) error {
3✔
2513
        var err error
3✔
2514
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2515
        collection := database.Collection(CollectionStorageSettings)
3✔
2516

3✔
2517
        filter := bson.M{
3✔
2518
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2519
        }
3✔
2520
        if storageSettings != nil {
6✔
2521
                replaceOptions := mopts.Replace()
3✔
2522
                replaceOptions.SetUpsert(true)
3✔
2523
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
3✔
2524
        } else {
4✔
2525
                _, err = collection.DeleteOne(ctx, filter)
1✔
2526
        }
1✔
2527

2528
        return err
3✔
2529
}
2530

2531
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2532
        ctx context.Context,
2533
        artifactName string,
2534
        artifactIDs []string,
2535
) error {
2✔
2536
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2537
        collDpl := database.Collection(CollectionDeployments)
2✔
2538

2✔
2539
        query := bson.D{
2✔
2540
                {Key: StorageKeyDeploymentFinished, Value: nil},
2✔
2541
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
2✔
2542
        }
2✔
2543
        update := bson.M{
2✔
2544
                "$set": bson.M{
2✔
2545
                        StorageKeyDeploymentArtifacts: artifactIDs,
2✔
2546
                },
2✔
2547
        }
2✔
2548

2✔
2549
        _, err := collDpl.UpdateMany(ctx, query, update)
2✔
2550
        return err
2✔
2551
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc