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

mendersoftware / deployments / 1351052686

27 Jun 2024 03:52PM UTC coverage: 79.696% (-0.06%) from 79.752%
1351052686

Pull #1029

gitlab-ci

alfrunes
perf: Optimize database interactions when updating deployment stats

Changed the sequential find and double update ops with a findAndModify
followed by a conditional update if the status actually changed.
This also improves atomicity as there is an inherent race condition when
doing the deployment status aggregation locally.

I also noticed that most internal calls to UpdateDeviceDeploymentStatus
already has the DeviceDeployment already, so I added an internal
updateDeviceDeploymentStatus which skips fetching the device deployment
(again).

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #1029: perf: Optimize database interactions when updating deployment stats

56 of 76 new or added lines in 3 files covered. (73.68%)

349 existing lines in 2 files now uncovered.

8125 of 10195 relevant lines covered (79.7%)

34.6 hits per line

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

85.39
/store/mongo/datastore_mongo.go
1
// Copyright 2024 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
        "sort"
22
        "strings"
23
        "time"
24

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

31
        "github.com/mendersoftware/go-lib-micro/config"
32
        "github.com/mendersoftware/go-lib-micro/identity"
33
        "github.com/mendersoftware/go-lib-micro/log"
34
        "github.com/mendersoftware/go-lib-micro/mongo/migrate"
35
        mstore "github.com/mendersoftware/go-lib-micro/store"
36

37
        dconfig "github.com/mendersoftware/deployments/config"
38
        "github.com/mendersoftware/deployments/model"
39
        "github.com/mendersoftware/deployments/store"
40
)
41

42
const (
43
        DatabaseName                   = "deployment_service"
44
        CollectionLimits               = "limits"
45
        CollectionImages               = "images"
46
        CollectionDeployments          = "deployments"
47
        CollectionDeviceDeploymentLogs = "devices.logs"
48
        CollectionDevices              = "devices"
49
        CollectionDevicesLastStatus    = "devices_last_status"
50
        CollectionStorageSettings      = "settings"
51
        CollectionUploadIntents        = "uploads"
52
        CollectionReleases             = "releases"
53
        CollectionUpdateTypes          = "update_types"
54
)
55

56
const DefaultDocumentLimit = 20
57
const maxCountDocuments = int64(10000)
58

59
// Internal status codes from
60
// https://github.com/mongodb/mongo/blob/4.4/src/mongo/base/error_codes.yml
61
const (
62
        errorCodeNamespaceNotFound = 26
63
        errorCodeIndexNotFound     = 27
64
)
65

66
const (
67
        mongoOpSet = "$set"
68
)
69

70
var currentDbVersion map[string]*migrate.Version
71

72
var (
73
        // Indexes (version: 1.2.2)
74
        IndexUniqueNameAndDeviceTypeName          = "uniqueNameAndDeviceTypeIndex"
75
        IndexDeploymentArtifactName               = "deploymentArtifactNameIndex"
76
        IndexDeploymentDeviceStatusesName         = "deviceIdWithStatusByCreated"
77
        IndexDeploymentDeviceIdStatusName         = "devicesIdWithStatus"
78
        IndexDeploymentDeviceCreatedStatusName    = "devicesIdWithCreatedStatus"
79
        IndexDeploymentDeviceDeploymentIdName     = "devicesDeploymentId"
80
        IndexDeploymentStatusFinishedName         = "deploymentStatusFinished"
81
        IndexDeploymentStatusPendingName          = "deploymentStatusPending"
82
        IndexDeploymentCreatedName                = "deploymentCreated"
83
        IndexDeploymentDeviceStatusRebootingName  = "deploymentsDeviceStatusRebooting"
84
        IndexDeploymentDeviceStatusPendingName    = "deploymentsDeviceStatusPending"
85
        IndexDeploymentDeviceStatusInstallingName = "deploymentsDeviceStatusInstalling"
86
        IndexDeploymentDeviceStatusFinishedName   = "deploymentsFinished"
87

88
        // Indexes (version: 1.2.3)
89
        IndexArtifactNameDependsName = "artifactNameDepends"
90
        IndexNameAndDeviceTypeName   = "artifactNameAndDeviceTypeIndex"
91

92
        // Indexes (version: 1.2.4)
93
        IndexDeploymentStatus = "deploymentStatus"
94

95
        // Indexes 1.2.6
96
        IndexDeviceDeploymentStatusName = "deploymentid_status_deviceid"
97

98
        // Indexes 1.2.13
99
        IndexArtifactProvidesName = "artifact_provides"
100

101
        // Indexes 1.2.15
102
        IndexNameReleaseTags           = "release_tags"
103
        IndexNameReleaseUpdateTypes    = "release_update_types"
104
        IndexNameAggregatedUpdateTypes = "aggregated_release_update_types"
105
        IndexNameReleaseArtifactsCount = "release_artifacts_count"
106

107
        // Indexes 1.2.16
108
        IndexNameDeploymentConstructorChecksum = "deployment_deploymentconstructor_checksum"
109

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

280
        // 1.2.3
281
        IndexArtifactNameDepends = mongo.IndexModel{
282
                Keys: bson.D{
283
                        {Key: StorageKeyImageName,
284
                                Value: 1},
285
                        {Key: StorageKeyImageDependsIdx,
286
                                Value: 1},
287
                },
288
                Options: &mopts.IndexOptions{
289
                        Background: &_false,
290
                        Name:       &IndexArtifactNameDependsName,
291
                        Unique:     &_true,
292
                },
293
        }
294

295
        // Indexes 1.2.7
296
        IndexImageMetaDescription      = "image_meta_description"
297
        IndexImageMetaDescriptionModel = mongo.IndexModel{
298
                Keys: bson.D{
299
                        {Key: StorageKeyImageDescription, Value: 1},
300
                },
301
                Options: &mopts.IndexOptions{
302
                        Background: &_false,
303
                        Name:       &IndexImageMetaDescription,
304
                },
305
        }
306

307
        IndexImageMetaArtifactDeviceTypeCompatible      = "image_meta_artifact_device_type_compatible"
308
        IndexImageMetaArtifactDeviceTypeCompatibleModel = mongo.IndexModel{
309
                Keys: bson.D{
310
                        {Key: StorageKeyImageDeviceTypes, Value: 1},
311
                },
312
                Options: &mopts.IndexOptions{
313
                        Background: &_false,
314
                        Name:       &IndexImageMetaArtifactDeviceTypeCompatible,
315
                },
316
        }
317

318
        // Indexes 1.2.8
319
        IndexDeploymentsActiveCreated      = "active_created"
320
        IndexDeploymentsActiveCreatedModel = mongo.IndexModel{
321
                Keys: bson.D{
322
                        {Key: StorageKeyDeploymentCreated, Value: 1},
323
                },
324
                Options: &mopts.IndexOptions{
325
                        Background: &_false,
326
                        Name:       &IndexDeploymentsActiveCreated,
327
                        PartialFilterExpression: bson.M{
328
                                StorageKeyDeploymentActive: true,
329
                        },
330
                },
331
        }
332

333
        // Index 1.2.9
334
        IndexDeviceDeploymentsActiveCreated      = "active_deviceid_created"
335
        IndexDeviceDeploymentsActiveCreatedModel = mongo.IndexModel{
336
                Keys: bson.D{
337
                        {Key: StorageKeyDeviceDeploymentActive, Value: 1},
338
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
339
                        {Key: StorageKeyDeviceDeploymentCreated, Value: 1},
340
                },
341
                Options: mopts.Index().
342
                        SetName(IndexDeviceDeploymentsActiveCreated),
343
        }
344

345
        // Index 1.2.11
346
        IndexDeviceDeploymentsLogs      = "devices_logs"
347
        IndexDeviceDeploymentsLogsModel = mongo.IndexModel{
348
                Keys: bson.D{
349
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
350
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
351
                },
352
                Options: mopts.Index().
353
                        SetName(IndexDeviceDeploymentsLogs),
354
        }
355

356
        // 1.2.13
357
        IndexArtifactProvides = mongo.IndexModel{
358
                Keys: bson.D{
359
                        {Key: model.StorageKeyImageProvidesIdxKey,
360
                                Value: 1},
361
                        {Key: model.StorageKeyImageProvidesIdxValue,
362
                                Value: 1},
363
                },
364
                Options: &mopts.IndexOptions{
365
                        Background: &_false,
366
                        Sparse:     &_true,
367
                        Name:       &IndexArtifactProvidesName,
368
                },
369
        }
370
)
371

372
// Errors
373
var (
374
        ErrImagesStorageInvalidID           = errors.New("Invalid id")
375
        ErrImagesStorageInvalidArtifactName = errors.New("Invalid artifact name")
376
        ErrImagesStorageInvalidName         = errors.New("Invalid name")
377
        ErrImagesStorageInvalidDeviceType   = errors.New("Invalid device type")
378
        ErrImagesStorageInvalidImage        = errors.New("Invalid image")
379

380
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
381

382
        ErrDeploymentStorageInvalidDeployment = errors.New("Invalid deployment")
383
        ErrStorageInvalidID                   = errors.New("Invalid id")
384
        ErrStorageNotFound                    = errors.New("Not found")
385
        ErrDeploymentStorageInvalidQuery      = errors.New("Invalid query")
386
        ErrDeploymentStorageCannotExecQuery   = errors.New("Cannot execute query")
387
        ErrStorageInvalidInput                = errors.New("invalid input")
388

389
        ErrLimitNotFound      = errors.New("limit not found")
390
        ErrDevicesCountFailed = errors.New("failed to count devices")
391
        ErrConflictingDepends = errors.New(
392
                "an artifact with the same name and depends already exists",
393
        )
394
        ErrConflictingDeployment = errors.New(
395
                "an active deployment with the same parameter already exists",
396
        )
397
)
398

399
// Database keys
400
const (
401
        // Need to be kept in sync with structure filed names
402
        StorageKeyId       = "_id"
403
        StorageKeyTenantId = "tenant_id"
404

405
        StorageKeyImageProvides    = "meta_artifact.provides"
406
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
407
        StorageKeyImageDepends     = "meta_artifact.depends"
408
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
409
        StorageKeyImageSize        = "size"
410
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
411
        StorageKeyImageName        = "meta_artifact.name"
412
        StorageKeyUpdateType       = "meta_artifact.updates.typeinfo.type"
413
        StorageKeyImageDescription = "meta.description"
414
        StorageKeyImageModified    = "modified"
415

416
        // releases
417
        StorageKeyReleaseName                      = "_id"
418
        StorageKeyReleaseModified                  = "modified"
419
        StorageKeyReleaseTags                      = "tags"
420
        StorageKeyReleaseNotes                     = "notes"
421
        StorageKeyReleaseArtifacts                 = "artifacts"
422
        StorageKeyReleaseArtifactsCount            = "artifacts_count"
423
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
424
                StorageKeyImageDescription
425
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
426
                StorageKeyImageDescription
427
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
428
                StorageKeyImageDeviceTypes
429
        StorageKeyReleaseArtifactsUpdateTypes = StorageKeyReleaseArtifacts + "." +
430
                StorageKeyUpdateType
431
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
432
                StorageKeyImageModified
433
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
434
                StorageKeyId
435
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
436
                StorageKeyImageDependsIdx
437
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
438
                StorageKeyImageProvidesIdx
439

440
        StorageKeyDeviceDeploymentLogMessages = "messages"
441

442
        StorageKeyDeviceDeploymentAssignedImage   = "image"
443
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
444
                "." + StorageKeyId
445

446
        StorageKeyDeviceDeploymentActive         = "active"
447
        StorageKeyDeviceDeploymentCreated        = "created"
448
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
449
        StorageKeyDeviceDeploymentStatus         = "status"
450
        StorageKeyDeviceDeploymentStarted        = "started"
451
        StorageKeyDeviceDeploymentSubState       = "substate"
452
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
453
        StorageKeyDeviceDeploymentFinished       = "finished"
454
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
455
        StorageKeyDeviceDeploymentArtifact       = "image"
456
        StorageKeyDeviceDeploymentRequest        = "request"
457
        StorageKeyDeviceDeploymentDeleted        = "deleted"
458

459
        StorageKeyDeploymentName                = "deploymentconstructor.name"
460
        StorageKeyDeploymentArtifactName        = "deploymentconstructor.artifactname"
461
        StorageKeyDeploymentConstructorChecksum = "deploymentconstructor_checksum"
462
        StorageKeyDeploymentStats               = "stats"
463
        StorageKeyDeploymentActive              = "active"
464
        StorageKeyDeploymentStatus              = "status"
465
        StorageKeyDeploymentCreated             = "created"
466
        StorageKeyDeploymentDeviceList          = "device_list"
467
        StorageKeyDeploymentStatsCreated        = "created"
468
        StorageKeyDeploymentFinished            = "finished"
469
        StorageKeyDeploymentArtifacts           = "artifacts"
470
        StorageKeyDeploymentDeviceCount         = "device_count"
471
        StorageKeyDeploymentMaxDevices          = "max_devices"
472
        StorageKeyDeploymentType                = "type"
473
        StorageKeyDeploymentTotalSize           = "statistics.total_size"
474

475
        StorageKeyStorageSettingsDefaultID      = "settings"
476
        StorageKeyStorageSettingsBucket         = "bucket"
477
        StorageKeyStorageSettingsRegion         = "region"
478
        StorageKeyStorageSettingsKey            = "key"
479
        StorageKeyStorageSettingsSecret         = "secret"
480
        StorageKeyStorageSettingsURI            = "uri"
481
        StorageKeyStorageSettingsExternalURI    = "external_uri"
482
        StorageKeyStorageSettingsToken          = "token"
483
        StorageKeyStorageSettingsForcePathStyle = "force_path_style"
484
        StorageKeyStorageSettingsUseAccelerate  = "use_accelerate"
485

486
        StorageKeyStorageReleaseUpdateTypes = "update_types"
487

488
        ArtifactDependsDeviceType = "device_type"
489
)
490

491
type DataStoreMongo struct {
492
        client *mongo.Client
493
}
494

495
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
425✔
496
        return &DataStoreMongo{
425✔
497
                client: client,
425✔
498
        }
425✔
499
}
425✔
500

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

1✔
503
        clientOptions := mopts.Client()
1✔
504
        mongoURL := c.GetString(dconfig.SettingMongo)
1✔
505
        if !strings.Contains(mongoURL, "://") {
1✔
506
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
507
                        mongoURL)
×
508
        }
×
509
        clientOptions.ApplyURI(mongoURL)
1✔
510

1✔
511
        username := c.GetString(dconfig.SettingDbUsername)
1✔
512
        if username != "" {
1✔
513
                credentials := mopts.Credential{
×
514
                        Username: c.GetString(dconfig.SettingDbUsername),
×
515
                }
×
516
                password := c.GetString(dconfig.SettingDbPassword)
×
517
                if password != "" {
×
518
                        credentials.Password = password
×
519
                        credentials.PasswordSet = true
×
520
                }
×
521
                clientOptions.SetAuth(credentials)
×
522
        }
523

524
        if c.GetBool(dconfig.SettingDbSSL) {
1✔
525
                tlsConfig := &tls.Config{}
×
526
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
527
                clientOptions.SetTLSConfig(tlsConfig)
×
528
        }
×
529

530
        // Set 10s timeout
531
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
1✔
532
        defer cancel()
1✔
533
        client, err := mongo.Connect(ctx, clientOptions)
1✔
534
        if err != nil {
1✔
535
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
536
        }
×
537

538
        // Validate connection
539
        if err = client.Ping(ctx, nil); err != nil {
1✔
540
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
541
        }
×
542

543
        return client, nil
1✔
544
}
545

546
func (db *DataStoreMongo) Ping(ctx context.Context) error {
1✔
547
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
1✔
548
        return res.Err()
1✔
549
}
1✔
550

551
func (db *DataStoreMongo) setCurrentDbVersion(
552
        ctx context.Context,
553
) error {
1✔
554
        versions, err := migrate.GetMigrationInfo(
1✔
555
                ctx, db.client, mstore.DbFromContext(ctx, DatabaseName))
1✔
556
        if err != nil {
1✔
557
                return errors.Wrap(err, "failed to list applied migrations")
×
558
        }
×
559
        var current migrate.Version
1✔
560
        if len(versions) > 0 {
2✔
561
                // sort applied migrations wrt. version
1✔
562
                sort.Slice(versions, func(i int, j int) bool {
2✔
563
                        return migrate.VersionIsLess(versions[i].Version, versions[j].Version)
1✔
564
                })
1✔
565
                current = versions[len(versions)-1].Version
1✔
566
        }
567
        if currentDbVersion == nil {
2✔
568
                currentDbVersion = map[string]*migrate.Version{}
1✔
569
        }
1✔
570
        currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] = &current
1✔
571
        return nil
1✔
572
}
573

574
func (db *DataStoreMongo) getCurrentDbVersion(
575
        ctx context.Context,
576
) (*migrate.Version, error) {
1✔
577
        if currentDbVersion == nil ||
1✔
578
                currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] == nil {
2✔
579
                if err := db.setCurrentDbVersion(ctx); err != nil {
1✔
580
                        return nil, err
×
581
                }
×
582
        }
583
        return currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)], nil
1✔
584
}
585

586
func (db *DataStoreMongo) GetReleases(
587
        ctx context.Context,
588
        filt *model.ReleaseOrImageFilter,
589
) ([]model.Release, int, error) {
1✔
590
        current, err := db.getCurrentDbVersion(ctx)
1✔
591
        if err != nil {
1✔
592
                return []model.Release{}, 0, err
×
593
        } else if current == nil {
1✔
594
                return []model.Release{}, 0, errors.New("couldn't get current database version")
×
595
        }
×
596
        target, err := migrate.NewVersion(DbVersion)
1✔
597
        if err != nil {
1✔
598
                return []model.Release{}, 0, errors.Wrap(err, "failed to get latest DB version")
×
599
        }
×
600
        if migrate.VersionIsLess(*current, *target) {
1✔
601
                return db.getReleases_1_2_14(ctx, filt)
×
602
        } else {
1✔
603
                return db.getReleases_1_2_15(ctx, filt)
1✔
604
        }
1✔
605
}
606

607
func (db *DataStoreMongo) getReleases_1_2_14(
608
        ctx context.Context,
609
        filt *model.ReleaseOrImageFilter,
610
) ([]model.Release, int, error) {
8✔
611
        l := log.FromContext(ctx)
8✔
612
        l.Infof("get releases method version 1.2.14")
8✔
613
        var pipe []bson.D
8✔
614

8✔
615
        pipe = []bson.D{}
8✔
616
        if filt != nil && filt.Name != "" {
10✔
617
                pipe = append(pipe, bson.D{
2✔
618
                        {Key: "$match", Value: bson.M{
2✔
619
                                StorageKeyImageName: bson.M{
2✔
620
                                        "$regex": primitive.Regex{
2✔
621
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
2✔
622
                                                Options: "i",
2✔
623
                                        },
2✔
624
                                },
2✔
625
                        }},
2✔
626
                })
2✔
627
        }
2✔
628

629
        pipe = append(pipe, bson.D{
8✔
630
                // Remove (possibly expensive) sub-documents from pipeline
8✔
631
                {
8✔
632
                        Key: "$project",
8✔
633
                        Value: bson.M{
8✔
634
                                StorageKeyImageDependsIdx:  0,
8✔
635
                                StorageKeyImageProvidesIdx: 0,
8✔
636
                        },
8✔
637
                },
8✔
638
        })
8✔
639

8✔
640
        pipe = append(pipe, bson.D{
8✔
641
                {Key: "$group", Value: bson.D{
8✔
642
                        {Key: "_id", Value: "$" + StorageKeyImageName},
8✔
643
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
8✔
644
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
8✔
645
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
8✔
646
                }},
8✔
647
        })
8✔
648

8✔
649
        if filt != nil && filt.Description != "" {
10✔
650
                pipe = append(pipe, bson.D{
2✔
651
                        {Key: "$match", Value: bson.M{
2✔
652
                                "artifacts." + StorageKeyImageDescription: bson.M{
2✔
653
                                        "$regex": primitive.Regex{
2✔
654
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
655
                                                Options: "i",
2✔
656
                                        },
2✔
657
                                },
2✔
658
                        }},
2✔
659
                })
2✔
660
        }
2✔
661
        if filt != nil && filt.DeviceType != "" {
8✔
662
                pipe = append(pipe, bson.D{
×
663
                        {Key: "$match", Value: bson.M{
×
664
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
×
665
                                        "$regex": primitive.Regex{
×
666
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
×
667
                                                Options: "i",
×
668
                                        },
×
669
                                },
×
670
                        }},
×
671
                })
×
672
        }
×
673

674
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
8✔
675
        if sortField == "" {
13✔
676
                sortField = "name"
5✔
677
        }
5✔
678
        if sortOrder == 0 {
13✔
679
                sortOrder = 1
5✔
680
        }
5✔
681

682
        page := 1
8✔
683
        perPage := math.MaxInt64
8✔
684
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
9✔
685
                page = filt.Page
1✔
686
                perPage = filt.PerPage
1✔
687
        }
1✔
688
        pipe = append(pipe,
8✔
689
                bson.D{{Key: "$facet", Value: bson.D{
8✔
690
                        {Key: "results", Value: []bson.D{
8✔
691
                                {
8✔
692
                                        {Key: "$sort", Value: bson.D{
8✔
693
                                                {Key: sortField, Value: sortOrder},
8✔
694
                                                {Key: "_id", Value: 1},
8✔
695
                                        }},
8✔
696
                                },
8✔
697
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
8✔
698
                                {{Key: "$limit", Value: int64(perPage)}},
8✔
699
                        }},
8✔
700
                        {Key: "count", Value: []bson.D{
8✔
701
                                {{Key: "$count", Value: "count"}},
8✔
702
                        }},
8✔
703
                }}},
8✔
704
        )
8✔
705

8✔
706
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
707
        collImg := database.Collection(CollectionImages)
8✔
708

8✔
709
        cursor, err := collImg.Aggregate(ctx, pipe)
8✔
710
        if err != nil {
8✔
711
                return []model.Release{}, 0, err
×
712
        }
×
713
        defer cursor.Close(ctx)
8✔
714

8✔
715
        result := struct {
8✔
716
                Results []model.Release       `bson:"results"`
8✔
717
                Count   []struct{ Count int } `bson:"count"`
8✔
718
        }{}
8✔
719
        if !cursor.Next(ctx) {
8✔
720
                return []model.Release{}, 0, nil
×
721
        } else if err = cursor.Decode(&result); err != nil {
8✔
722
                return []model.Release{}, 0, err
×
723
        } else if len(result.Count) == 0 {
9✔
724
                return []model.Release{}, 0, err
1✔
725
        }
1✔
726
        return result.Results, result.Count[0].Count, nil
7✔
727
}
728

729
func (db *DataStoreMongo) getReleases_1_2_15(
730
        ctx context.Context,
731
        filt *model.ReleaseOrImageFilter,
732
) ([]model.Release, int, error) {
81✔
733
        l := log.FromContext(ctx)
81✔
734
        l.Infof("get releases method version 1.2.15")
81✔
735

81✔
736
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
81✔
737
        if sortField == "" {
148✔
738
                sortField = "_id"
67✔
739
        } else if sortField == "name" {
83✔
740
                sortField = StorageKeyReleaseName
2✔
741
        }
2✔
742
        if sortOrder == 0 {
148✔
743
                sortOrder = 1
67✔
744
        }
67✔
745

746
        page := 1
81✔
747
        perPage := DefaultDocumentLimit
81✔
748
        if filt != nil {
160✔
749
                if filt.Page > 0 {
82✔
750
                        page = filt.Page
3✔
751
                }
3✔
752
                if filt.PerPage > 0 {
82✔
753
                        perPage = filt.PerPage
3✔
754
                }
3✔
755
        }
756

757
        opts := &mopts.FindOptions{}
81✔
758
        opts.SetSort(bson.D{{Key: sortField, Value: sortOrder}})
81✔
759
        opts.SetSkip(int64((page - 1) * perPage))
81✔
760
        opts.SetLimit(int64(perPage))
81✔
761
        projection := bson.M{
81✔
762
                StorageKeyReleaseImageDependsIdx:  0,
81✔
763
                StorageKeyReleaseImageProvidesIdx: 0,
81✔
764
        }
81✔
765
        opts.SetProjection(projection)
81✔
766

81✔
767
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
81✔
768
        collReleases := database.Collection(CollectionReleases)
81✔
769

81✔
770
        filter := bson.M{}
81✔
771
        if filt != nil {
160✔
772
                if filt.Name != "" {
132✔
773
                        filter[StorageKeyReleaseName] = bson.M{"$regex": primitive.Regex{
53✔
774
                                Pattern: regexp.QuoteMeta(filt.Name) + ".*",
53✔
775
                                Options: "i",
53✔
776
                        }}
53✔
777
                }
53✔
778
                if len(filt.Tags) > 0 {
83✔
779
                        filter[StorageKeyReleaseTags] = bson.M{"$in": filt.Tags}
4✔
780
                }
4✔
781
                if filt.Description != "" {
83✔
782
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": primitive.Regex{
4✔
783
                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
784
                                Options: "i",
4✔
785
                        }}
4✔
786
                }
4✔
787
                if filt.DeviceType != "" {
81✔
788
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = filt.DeviceType
2✔
789
                }
2✔
790
                if filt.UpdateType != "" {
82✔
791
                        filter[StorageKeyReleaseArtifactsUpdateTypes] = filt.UpdateType
3✔
792
                }
3✔
793
        }
794
        releases := []model.Release{}
81✔
795
        cursor, err := collReleases.Find(ctx, filter, opts)
81✔
796
        if err != nil {
81✔
797
                return []model.Release{}, 0, err
×
798
        }
×
799
        if err := cursor.All(ctx, &releases); err != nil {
81✔
800
                return []model.Release{}, 0, err
×
801
        }
×
802

803
        // TODO: can we return number of all documents in the collection
804
        // using EstimatedDocumentCount?
805
        count, err := collReleases.CountDocuments(ctx, filter)
81✔
806
        if err != nil {
81✔
807
                return []model.Release{}, 0, err
×
808
        }
×
809

810
        if count < 1 {
99✔
811
                return []model.Release{}, int(count), nil
18✔
812
        }
18✔
813
        return releases, int(count), nil
64✔
814
}
815

816
// limits
817
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
4✔
818

4✔
819
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
820
        collLim := database.Collection(CollectionLimits)
4✔
821

4✔
822
        limit := new(model.Limit)
4✔
823
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
4✔
824
                Decode(limit); err != nil {
6✔
825
                if err == mongo.ErrNoDocuments {
4✔
826
                        return nil, ErrLimitNotFound
2✔
827
                }
2✔
828
                return nil, err
×
829
        }
830

831
        return limit, nil
2✔
832
}
833

834
func (db *DataStoreMongo) ProvisionTenant(ctx context.Context, tenantId string) error {
5✔
835

5✔
836
        dbname := mstore.DbNameForTenant(tenantId, DbName)
5✔
837

5✔
838
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
5✔
839
}
5✔
840

841
//images
842

843
// Exists checks if object with ID exists
844
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
845
        var result interface{}
×
846

×
847
        if len(id) == 0 {
×
848
                return false, ErrImagesStorageInvalidID
×
849
        }
×
850

851
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
852
        collImg := database.Collection(CollectionImages)
×
853

×
854
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
855
                Decode(&result); err != nil {
×
856
                if err == mongo.ErrNoDocuments {
×
857
                        return false, nil
×
858
                }
×
859
                return false, err
×
860
        }
861

862
        return true, nil
×
863
}
864

865
// Update provided Image
866
// Return false if not found
867
func (db *DataStoreMongo) Update(ctx context.Context,
868
        image *model.Image) (bool, error) {
1✔
869

1✔
870
        if err := image.Validate(); err != nil {
1✔
871
                return false, err
×
872
        }
×
873

874
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
875
        collImg := database.Collection(CollectionImages)
1✔
876

1✔
877
        // add special representation of artifact provides
1✔
878
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
879

1✔
880
        image.SetModified(time.Now())
1✔
881
        if res, err := collImg.ReplaceOne(
1✔
882
                ctx, bson.M{"_id": image.Id}, image,
1✔
883
        ); err != nil {
1✔
884
                return false, err
×
885
        } else if res.MatchedCount == 0 {
1✔
886
                return false, nil
×
887
        }
×
888

889
        return true, nil
1✔
890
}
891

892
// ImageByNameAndDeviceType finds image with specified application name and target device type
893
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
894
        name, deviceType string) (*model.Image, error) {
9✔
895

9✔
896
        if len(name) == 0 {
10✔
897
                return nil, ErrImagesStorageInvalidArtifactName
1✔
898
        }
1✔
899

900
        if len(deviceType) == 0 {
9✔
901
                return nil, ErrImagesStorageInvalidDeviceType
1✔
902
        }
1✔
903

904
        // equal to device type & software version (application name + version)
905
        query := bson.M{
7✔
906
                StorageKeyImageName:        name,
7✔
907
                StorageKeyImageDeviceTypes: deviceType,
7✔
908
        }
7✔
909

7✔
910
        // If multiple entries matches, pick the smallest one.
7✔
911
        findOpts := mopts.FindOne()
7✔
912
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
7✔
913

7✔
914
        dbName := mstore.DbFromContext(ctx, DatabaseName)
7✔
915
        database := db.client.Database(dbName)
7✔
916
        collImg := database.Collection(CollectionImages)
7✔
917

7✔
918
        // Both we lookup unique object, should be one or none.
7✔
919
        var image model.Image
7✔
920
        if err := collImg.FindOne(ctx, query, findOpts).
7✔
921
                Decode(&image); err != nil {
11✔
922
                if err == mongo.ErrNoDocuments {
8✔
923
                        return nil, nil
4✔
924
                }
4✔
925
                return nil, err
×
926
        }
927

928
        return &image, nil
3✔
929
}
930

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

1✔
935
        if len(deviceType) == 0 {
1✔
936
                return nil, ErrImagesStorageInvalidDeviceType
×
937
        }
×
938

939
        if len(ids) == 0 {
1✔
940
                return nil, ErrImagesStorageInvalidID
×
941
        }
×
942

943
        query := bson.D{
1✔
944
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
945
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
946
        }
1✔
947

1✔
948
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
949
        collImg := database.Collection(CollectionImages)
1✔
950

1✔
951
        // If multiple entries matches, pick the smallest one
1✔
952
        findOpts := mopts.FindOne()
1✔
953
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
954

1✔
955
        // Both we lookup unique object, should be one or none.
1✔
956
        var image model.Image
1✔
957
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
958
                Decode(&image); err != nil {
1✔
959
                if err == mongo.ErrNoDocuments {
×
960
                        return nil, nil
×
961
                }
×
962
                return nil, err
×
963
        }
964

965
        return &image, nil
1✔
966
}
967

968
// ImagesByName finds images with specified artifact name
969
func (db *DataStoreMongo) ImagesByName(
970
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
971

1✔
972
        var images []*model.Image
1✔
973

1✔
974
        if len(name) == 0 {
1✔
975
                return nil, ErrImagesStorageInvalidName
×
976
        }
×
977

978
        // equal to artifact name
979
        query := bson.M{
1✔
980
                StorageKeyImageName: name,
1✔
981
        }
1✔
982

1✔
983
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
984
        collImg := database.Collection(CollectionImages)
1✔
985
        cursor, err := collImg.Find(ctx, query)
1✔
986
        if err != nil {
1✔
987
                return nil, err
×
988
        }
×
989
        // Both we lookup unique object, should be one or none.
990
        if err = cursor.All(ctx, &images); err != nil {
1✔
991
                return nil, err
×
992
        }
×
993

994
        return images, nil
1✔
995
}
996

997
func newDependsConflictError(mgoErr mongo.WriteError) *model.ConflictError {
7✔
998
        var err error
7✔
999
        conflictErr := model.NewConflictError(ErrConflictingDepends)
7✔
1000
        // Try to lookup the document that caused the index violation:
7✔
1001
        if raw, ok := mgoErr.Raw.Lookup("keyValue").DocumentOK(); ok {
14✔
1002
                if raw, ok = raw.Lookup(StorageKeyImageDependsIdx).DocumentOK(); ok {
14✔
1003
                        var conflicts map[string]interface{}
7✔
1004
                        err = bson.Unmarshal([]byte(raw), &conflicts)
7✔
1005
                        if err == nil {
14✔
1006
                                _ = conflictErr.WithMetadata(
7✔
1007
                                        map[string]interface{}{
7✔
1008
                                                "conflict": conflicts,
7✔
1009
                                        },
7✔
1010
                                )
7✔
1011
                        }
7✔
1012
                }
1013
        }
1014
        return conflictErr
7✔
1015
}
1016

1017
// Insert persists object
1018
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
69✔
1019

69✔
1020
        if image == nil {
69✔
1021
                return ErrImagesStorageInvalidImage
×
1022
        }
×
1023

1024
        if err := image.Validate(); err != nil {
69✔
1025
                return err
×
1026
        }
×
1027

1028
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
69✔
1029
        collImg := database.Collection(CollectionImages)
69✔
1030

69✔
1031
        // add special representation of artifact provides
69✔
1032
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
69✔
1033

69✔
1034
        _, err := collImg.InsertOne(ctx, image)
69✔
1035
        if err != nil {
76✔
1036
                var wExc mongo.WriteException
7✔
1037
                if errors.As(err, &wExc) {
14✔
1038
                        for _, wErr := range wExc.WriteErrors {
14✔
1039
                                if !mongo.IsDuplicateKeyError(wErr) {
7✔
1040
                                        continue
×
1041
                                }
1042
                                return newDependsConflictError(wErr)
7✔
1043
                        }
1044
                }
1045
                return err
×
1046
        }
1047

1048
        return nil
62✔
1049
}
1050

1051
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
2✔
1052
        collUploads := db.client.
2✔
1053
                Database(DatabaseName).
2✔
1054
                Collection(CollectionUploadIntents)
2✔
1055
        if idty := identity.FromContext(ctx); idty != nil {
3✔
1056
                link.TenantID = idty.Tenant
1✔
1057
        }
1✔
1058
        _, err := collUploads.InsertOne(ctx, link)
2✔
1059
        return err
2✔
1060
}
1061

1062
func (db *DataStoreMongo) UpdateUploadIntentStatus(
1063
        ctx context.Context,
1064
        id string,
1065
        from, to model.LinkStatus,
1066
) error {
6✔
1067
        collUploads := db.client.
6✔
1068
                Database(DatabaseName).
6✔
1069
                Collection(CollectionUploadIntents)
6✔
1070
        q := bson.D{
6✔
1071
                {Key: "_id", Value: id},
6✔
1072
                {Key: "status", Value: from},
6✔
1073
        }
6✔
1074
        if idty := identity.FromContext(ctx); idty != nil {
11✔
1075
                q = append(q, bson.E{
5✔
1076
                        Key:   StorageKeyTenantId,
5✔
1077
                        Value: idty.Tenant,
5✔
1078
                })
5✔
1079
        }
5✔
1080
        update := bson.D{{
6✔
1081
                Key: "updated_ts", Value: time.Now(),
6✔
1082
        }}
6✔
1083
        if from != to {
12✔
1084
                update = append(update, bson.E{
6✔
1085
                        Key: "status", Value: to,
6✔
1086
                })
6✔
1087
        }
6✔
1088
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
6✔
1089
                {Key: "$set", Value: update},
6✔
1090
        })
6✔
1091
        if err != nil {
7✔
1092
                return err
1✔
1093
        } else if res.MatchedCount == 0 {
8✔
1094
                return store.ErrNotFound
2✔
1095
        }
2✔
1096
        return nil
3✔
1097
}
1098

1099
func (db *DataStoreMongo) FindUploadLinks(
1100
        ctx context.Context,
1101
        expiredAt time.Time,
1102
) (store.Iterator[model.UploadLink], error) {
1✔
1103
        collUploads := db.client.
1✔
1104
                Database(DatabaseName).
1✔
1105
                Collection(CollectionUploadIntents)
1✔
1106

1✔
1107
        q := bson.D{{
1✔
1108
                Key: "status",
1✔
1109
                Value: bson.D{{
1✔
1110
                        Key:   "$lt",
1✔
1111
                        Value: model.LinkStatusProcessedBit,
1✔
1112
                }},
1✔
1113
        }, {
1✔
1114
                Key: "expire",
1✔
1115
                Value: bson.D{{
1✔
1116
                        Key:   "$lt",
1✔
1117
                        Value: expiredAt,
1✔
1118
                }},
1✔
1119
        }}
1✔
1120
        cur, err := collUploads.Find(ctx, q)
1✔
1121
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1122
}
1✔
1123

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

1✔
1128
        if len(id) == 0 {
1✔
1129
                return nil, ErrImagesStorageInvalidID
×
1130
        }
×
1131

1132
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1133
        collImg := database.Collection(CollectionImages)
1✔
1134
        projection := bson.M{
1✔
1135
                StorageKeyImageDependsIdx:  0,
1✔
1136
                StorageKeyImageProvidesIdx: 0,
1✔
1137
        }
1✔
1138
        findOptions := mopts.FindOne()
1✔
1139
        findOptions.SetProjection(projection)
1✔
1140

1✔
1141
        var image model.Image
1✔
1142
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
1143
                Decode(&image); err != nil {
2✔
1144
                if err == mongo.ErrNoDocuments {
2✔
1145
                        return nil, nil
1✔
1146
                }
1✔
1147
                return nil, err
×
1148
        }
1149

1150
        return &image, nil
1✔
1151
}
1152

1153
// IsArtifactUnique checks if there is no artifact with the same artifactName
1154
// supporting one of the device types from deviceTypesCompatible list.
1155
// Returns true, nil if artifact is unique;
1156
// false, nil if artifact is not unique;
1157
// false, error in case of error.
1158
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1159
        artifactName string, deviceTypesCompatible []string) (bool, error) {
6✔
1160

6✔
1161
        if len(artifactName) == 0 {
7✔
1162
                return false, ErrImagesStorageInvalidArtifactName
1✔
1163
        }
1✔
1164

1165
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1166
        collImg := database.Collection(CollectionImages)
5✔
1167

5✔
1168
        query := bson.M{
5✔
1169
                "$and": []bson.M{
5✔
1170
                        {
5✔
1171
                                StorageKeyImageName: artifactName,
5✔
1172
                        },
5✔
1173
                        {
5✔
1174
                                StorageKeyImageDeviceTypes: bson.M{
5✔
1175
                                        "$in": deviceTypesCompatible},
5✔
1176
                        },
5✔
1177
                },
5✔
1178
        }
5✔
1179

5✔
1180
        // do part of the job manually
5✔
1181
        // if candidate images have any extra 'depends' - guaranteed non-overlap
5✔
1182
        // otherwise it's a match
5✔
1183
        cur, err := collImg.Find(ctx, query)
5✔
1184
        if err != nil {
5✔
1185
                return false, err
×
1186
        }
×
1187

1188
        var images []model.Image
5✔
1189
        err = cur.All(ctx, &images)
5✔
1190
        if err != nil {
5✔
1191
                return false, err
×
1192
        }
×
1193

1194
        for _, i := range images {
6✔
1195
                // the artifact already has same name and overlapping dev type
1✔
1196
                // if there are no more depends than dev type - it's not unique
1✔
1197
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1198
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1199
                                return false, nil
1✔
1200
                        }
1✔
1201
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1202
                        return false, nil
×
1203
                }
×
1204
        }
1205

1206
        return true, nil
4✔
1207
}
1208

1209
// Delete image specified by ID
1210
// Noop on if not found.
1211
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
3✔
1212

3✔
1213
        if len(id) == 0 {
3✔
1214
                return ErrImagesStorageInvalidID
×
1215
        }
×
1216

1217
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1218
        collImg := database.Collection(CollectionImages)
3✔
1219

3✔
1220
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
1221
                if res.DeletedCount == 0 {
×
1222
                        return nil
×
1223
                }
×
1224
                return err
×
1225
        }
1226

1227
        return nil
3✔
1228
}
1229

1230
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
103✔
1231
        if filt != nil && filt.Sort != "" {
123✔
1232
                sortParts := strings.SplitN(filt.Sort, ":", 2)
20✔
1233
                if len(sortParts) == 2 &&
20✔
1234
                        (sortParts[0] == "name" ||
20✔
1235
                                sortParts[0] == "modified" ||
20✔
1236
                                sortParts[0] == "artifacts_count" ||
20✔
1237
                                sortParts[0] == "tags") {
40✔
1238
                        sortField := sortParts[0]
20✔
1239
                        sortOrder := 1
20✔
1240
                        if sortParts[1] == model.SortDirectionDescending {
32✔
1241
                                sortOrder = -1
12✔
1242
                        }
12✔
1243
                        return sortField, sortOrder
20✔
1244
                }
1245
        }
1246
        return "", 0
83✔
1247
}
1248

1249
// ListImages lists all images
1250
func (db *DataStoreMongo) ListImages(
1251
        ctx context.Context,
1252
        filt *model.ReleaseOrImageFilter,
1253
) ([]*model.Image, int, error) {
15✔
1254

15✔
1255
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1256
        collImg := database.Collection(CollectionImages)
15✔
1257

15✔
1258
        filters := bson.M{}
15✔
1259
        if filt != nil {
25✔
1260
                if filt.Name != "" {
14✔
1261
                        filters[StorageKeyImageName] = bson.M{
4✔
1262
                                "$regex": primitive.Regex{
4✔
1263
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
4✔
1264
                                        Options: "i",
4✔
1265
                                },
4✔
1266
                        }
4✔
1267
                }
4✔
1268
                if filt.Description != "" {
12✔
1269
                        filters[StorageKeyImageDescription] = bson.M{
2✔
1270
                                "$regex": primitive.Regex{
2✔
1271
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
1272
                                        Options: "i",
2✔
1273
                                },
2✔
1274
                        }
2✔
1275
                }
2✔
1276
                if filt.DeviceType != "" {
11✔
1277
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1278
                                "$regex": primitive.Regex{
1✔
1279
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1280
                                        Options: "i",
1✔
1281
                                },
1✔
1282
                        }
1✔
1283
                }
1✔
1284

1285
        }
1286

1287
        projection := bson.M{
15✔
1288
                StorageKeyImageDependsIdx:  0,
15✔
1289
                StorageKeyImageProvidesIdx: 0,
15✔
1290
        }
15✔
1291
        findOptions := &mopts.FindOptions{}
15✔
1292
        findOptions.SetProjection(projection)
15✔
1293
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
16✔
1294
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1295
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1296
        }
1✔
1297

1298
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
15✔
1299
        if sortField == "" || sortField == "name" {
28✔
1300
                sortField = StorageKeyImageName
13✔
1301
        }
13✔
1302
        if sortOrder == 0 {
27✔
1303
                sortOrder = 1
12✔
1304
        }
12✔
1305
        findOptions.SetSort(bson.D{
15✔
1306
                {Key: sortField, Value: sortOrder},
15✔
1307
                {Key: "_id", Value: sortOrder},
15✔
1308
        })
15✔
1309

15✔
1310
        cursor, err := collImg.Find(ctx, filters, findOptions)
15✔
1311
        if err != nil {
15✔
1312
                return nil, 0, err
×
1313
        }
×
1314

1315
        // NOTE: cursor.All closes the cursor before returning
1316
        var images []*model.Image
15✔
1317
        if err := cursor.All(ctx, &images); err != nil {
15✔
1318
                if err == mongo.ErrNoDocuments {
×
1319
                        return nil, 0, nil
×
1320
                }
×
1321
                return nil, 0, err
×
1322
        }
1323

1324
        count, err := collImg.CountDocuments(ctx, filters)
15✔
1325
        if err != nil {
15✔
1326
                return nil, -1, ErrDevicesCountFailed
×
1327
        }
×
1328

1329
        return images, int(count), nil
15✔
1330
}
1331

1332
func (db *DataStoreMongo) DeleteImagesByNames(ctx context.Context, names []string) error {
1✔
1333
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1334
        collDevs := database.Collection(CollectionImages)
1✔
1335
        query := bson.M{
1✔
1336
                StorageKeyImageName: bson.M{
1✔
1337
                        "$in": names,
1✔
1338
                },
1✔
1339
        }
1✔
1340
        _, err := collDevs.DeleteMany(ctx, query)
1✔
1341
        return err
1✔
1342
}
1✔
1343

1344
// device deployment log
1345
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1346
        log model.DeploymentLog) error {
9✔
1347

9✔
1348
        if err := log.Validate(); err != nil {
12✔
1349
                return err
3✔
1350
        }
3✔
1351

1352
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1353
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1354

6✔
1355
        query := bson.D{
6✔
1356
                {Key: StorageKeyDeviceDeploymentDeviceId,
6✔
1357
                        Value: log.DeviceID},
6✔
1358
                {Key: StorageKeyDeviceDeploymentDeploymentID,
6✔
1359
                        Value: log.DeploymentID},
6✔
1360
        }
6✔
1361

6✔
1362
        // update log messages
6✔
1363
        // if the deployment log is already present than messages will be overwritten
6✔
1364
        update := bson.D{
6✔
1365
                {Key: "$set", Value: bson.M{
6✔
1366
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
6✔
1367
                }},
6✔
1368
        }
6✔
1369
        updateOptions := mopts.Update()
6✔
1370
        updateOptions.SetUpsert(true)
6✔
1371
        if _, err := collLogs.UpdateOne(
6✔
1372
                ctx, query, update, updateOptions); err != nil {
6✔
1373
                return err
×
1374
        }
×
1375

1376
        return nil
6✔
1377
}
1378

1379
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1380
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
6✔
1381

6✔
1382
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1383
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1384

6✔
1385
        query := bson.M{
6✔
1386
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
6✔
1387
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1388
        }
6✔
1389

6✔
1390
        var depl model.DeploymentLog
6✔
1391
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
8✔
1392
                if err == mongo.ErrNoDocuments {
4✔
1393
                        return nil, nil
2✔
1394
                }
2✔
1395
                return nil, err
×
1396
        }
1397

1398
        return &depl, nil
4✔
1399
}
1400

1401
// device deployments
1402

1403
// Insert persists device deployment object
1404
func (db *DataStoreMongo) InsertDeviceDeployment(
1405
        ctx context.Context,
1406
        deviceDeployment *model.DeviceDeployment,
1407
        incrementDeviceCount bool,
1408
) error {
29✔
1409
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1410
        c := database.Collection(CollectionDevices)
29✔
1411

29✔
1412
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
49✔
1413
                startedTime := time.Now().UTC()
20✔
1414
                deviceDeployment.Started = &startedTime
20✔
1415
        }
20✔
1416

1417
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
29✔
1418
                return err
×
1419
        }
×
1420

1421
        if incrementDeviceCount {
58✔
1422
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
29✔
1423
                if err != nil {
29✔
1424
                        return err
×
1425
                }
×
1426
        }
1427

1428
        return nil
29✔
1429
}
1430

1431
// InsertMany stores multiple device deployment objects.
1432
// TODO: Handle error cleanup, multi insert is not atomic, loop into two-phase commits
1433
func (db *DataStoreMongo) InsertMany(ctx context.Context,
1434
        deployments ...*model.DeviceDeployment) error {
43✔
1435

43✔
1436
        if len(deployments) == 0 {
55✔
1437
                return nil
12✔
1438
        }
12✔
1439

1440
        deviceCountIncrements := make(map[string]int)
31✔
1441

31✔
1442
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
31✔
1443
        var list []interface{}
31✔
1444
        for _, deployment := range deployments {
104✔
1445

73✔
1446
                if deployment == nil {
74✔
1447
                        return ErrStorageInvalidDeviceDeployment
1✔
1448
                }
1✔
1449

1450
                if err := deployment.Validate(); err != nil {
74✔
1451
                        return errors.Wrap(err, "Validating device deployment")
2✔
1452
                }
2✔
1453

1454
                list = append(list, deployment)
70✔
1455
                if deployment.Status != model.DeviceDeploymentStatusPending {
84✔
1456
                        startedTime := time.Now().UTC()
14✔
1457
                        deployment.Started = &startedTime
14✔
1458
                }
14✔
1459
                deviceCountIncrements[deployment.DeploymentId]++
70✔
1460
        }
1461

1462
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
28✔
1463
        collDevs := database.Collection(CollectionDevices)
28✔
1464

28✔
1465
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
28✔
1466
                return err
×
1467
        }
×
1468

1469
        for deploymentID := range deviceCountIncrements {
63✔
1470
                err := db.IncrementDeploymentDeviceCount(
35✔
1471
                        ctx,
35✔
1472
                        deploymentID,
35✔
1473
                        deviceCountIncrements[deploymentID],
35✔
1474
                )
35✔
1475
                if err != nil {
35✔
1476
                        return err
×
1477
                }
×
1478
        }
1479

1480
        return nil
28✔
1481
}
1482

1483
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1484
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1485
        ctx context.Context,
1486
        deviceID string,
1487
) (*model.DeviceDeployment, error) {
6✔
1488

6✔
1489
        // Verify ID formatting
6✔
1490
        if len(deviceID) == 0 {
7✔
1491
                return nil, ErrStorageInvalidID
1✔
1492
        }
1✔
1493

1494
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1495
        collDevs := database.Collection(CollectionDevices)
5✔
1496

5✔
1497
        // Device should know only about deployments that are not finished
5✔
1498
        query := bson.D{
5✔
1499
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
5✔
1500
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1501
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1502
                        {Key: "$exists", Value: false},
5✔
1503
                }},
5✔
1504
        }
5✔
1505

5✔
1506
        // Find the oldest one by sorting the creation timestamp
5✔
1507
        // in ascending order.
5✔
1508
        findOptions := mopts.FindOne()
5✔
1509
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
5✔
1510

5✔
1511
        // Select only the oldest one that have not been finished yet.
5✔
1512
        deployment := new(model.DeviceDeployment)
5✔
1513
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1514
                Decode(deployment); err != nil {
8✔
1515
                if err == mongo.ErrNoDocuments {
5✔
1516
                        return nil, nil
2✔
1517
                }
2✔
1518
                return nil, err
1✔
1519
        }
1520

1521
        return deployment, nil
3✔
1522
}
1523

1524
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1525
// matching device id that has not finished yet.
1526
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1527
        ctx context.Context,
1528
        deviceID string,
1529
) (*model.DeviceDeployment, error) {
6✔
1530

6✔
1531
        // Verify ID formatting
6✔
1532
        if len(deviceID) == 0 {
7✔
1533
                return nil, ErrStorageInvalidID
1✔
1534
        }
1✔
1535

1536
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1537
        collDevs := database.Collection(CollectionDevices)
5✔
1538

5✔
1539
        query := bson.D{
5✔
1540
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
5✔
1541
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1542
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1543
                        {Key: "$exists", Value: false},
5✔
1544
                }},
5✔
1545
        }
5✔
1546

5✔
1547
        // Find the latest one by sorting by the creation timestamp
5✔
1548
        // in ascending order.
5✔
1549
        findOptions := mopts.FindOne()
5✔
1550
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
5✔
1551

5✔
1552
        // Select only the latest one that have not been finished yet.
5✔
1553
        var deployment *model.DeviceDeployment
5✔
1554
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1555
                Decode(&deployment); err != nil {
8✔
1556
                if err == mongo.ErrNoDocuments {
5✔
1557
                        return nil, nil
2✔
1558
                }
2✔
1559
                return nil, err
1✔
1560
        }
1561

1562
        return deployment, nil
3✔
1563
}
1564

1565
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1566
        ctx context.Context,
1567
        deviceID string,
1568
        deploymentID string,
1569
        ddState model.DeviceDeploymentState,
1570
        currentStatus model.DeviceDeploymentStatus,
1571
) (model.DeviceDeploymentStatus, error) {
12✔
1572

12✔
1573
        // Verify ID formatting
12✔
1574
        if len(deviceID) == 0 ||
12✔
1575
                len(deploymentID) == 0 {
14✔
1576
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
2✔
1577
        }
2✔
1578

1579
        if err := ddState.Validate(); err != nil {
11✔
1580
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1581
        }
1✔
1582

1583
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1584
        collDevs := database.Collection(CollectionDevices)
9✔
1585

9✔
1586
        // Device should know only about deployments that are not finished
9✔
1587
        query := bson.D{
9✔
1588
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1589
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1590
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1591
                        {Key: "$exists", Value: false},
9✔
1592
                }},
9✔
1593
        }
9✔
1594

9✔
1595
        // update status field
9✔
1596
        set := bson.M{
9✔
1597
                StorageKeyDeviceDeploymentStatus: ddState.Status,
9✔
1598
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
9✔
1599
        }
9✔
1600
        // and finish time if provided
9✔
1601
        if ddState.FinishTime != nil {
11✔
1602
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
2✔
1603
        }
2✔
1604

1605
        if len(ddState.SubState) > 0 {
12✔
1606
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
3✔
1607
        }
3✔
1608

1609
        if currentStatus == model.DeviceDeploymentStatusPending &&
9✔
1610
                ddState.Status != currentStatus {
11✔
1611
                startedTime := time.Now().UTC()
2✔
1612
                set[StorageKeyDeviceDeploymentStarted] = startedTime
2✔
1613
        }
2✔
1614

1615
        update := bson.D{
9✔
1616
                {Key: "$set", Value: set},
9✔
1617
        }
9✔
1618

9✔
1619
        var old model.DeviceDeployment
9✔
1620

9✔
1621
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
9✔
1622
                Decode(&old); err != nil {
11✔
1623
                if err == mongo.ErrNoDocuments {
4✔
1624
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
2✔
1625
                }
2✔
1626
                return model.DeviceDeploymentStatusNull, err
×
1627

1628
        }
1629

1630
        return old.Status, nil
7✔
1631
}
1632

1633
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1634
        deviceID string, deploymentID string, log bool) error {
7✔
1635

7✔
1636
        // Verify ID formatting
7✔
1637
        if len(deviceID) == 0 ||
7✔
1638
                len(deploymentID) == 0 {
9✔
1639
                return ErrStorageInvalidID
2✔
1640
        }
2✔
1641

1642
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1643
        collDevs := database.Collection(CollectionDevices)
5✔
1644

5✔
1645
        selector := bson.D{
5✔
1646
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1647
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
5✔
1648
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1649
                        {Key: "$exists", Value: false},
5✔
1650
                }},
5✔
1651
        }
5✔
1652

5✔
1653
        update := bson.D{
5✔
1654
                {Key: "$set", Value: bson.M{
5✔
1655
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
5✔
1656
        }
5✔
1657

5✔
1658
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
5✔
1659
                return err
×
1660
        } else if res.MatchedCount == 0 {
7✔
1661
                return ErrStorageNotFound
2✔
1662
        }
2✔
1663

1664
        return nil
3✔
1665
}
1666

1667
// SaveDeviceDeploymentRequest saves device deployment request
1668
// with the device deployment object
1669
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1670
        ctx context.Context,
1671
        ID string,
1672
        request *model.DeploymentNextRequest,
1673
) error {
4✔
1674

4✔
1675
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
1676
        collDevs := database.Collection(CollectionDevices)
4✔
1677

4✔
1678
        res, err := collDevs.UpdateOne(
4✔
1679
                ctx,
4✔
1680
                bson.D{{Key: StorageKeyId, Value: ID}},
4✔
1681
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
4✔
1682
        )
4✔
1683
        if err != nil {
4✔
1684
                return err
×
1685
        } else if res.MatchedCount == 0 {
5✔
1686
                return ErrStorageNotFound
1✔
1687
        }
1✔
1688
        return nil
3✔
1689
}
1690

1691
// AssignArtifact assigns artifact to the device deployment
1692
func (db *DataStoreMongo) AssignArtifact(
1693
        ctx context.Context,
1694
        deviceID string,
1695
        deploymentID string,
1696
        artifact *model.Image,
1697
) error {
1✔
1698

1✔
1699
        // Verify ID formatting
1✔
1700
        if len(deviceID) == 0 ||
1✔
1701
                len(deploymentID) == 0 {
1✔
1702
                return ErrStorageInvalidID
×
1703
        }
×
1704

1705
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1706
        collDevs := database.Collection(CollectionDevices)
1✔
1707

1✔
1708
        selector := bson.D{
1✔
1709
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1710
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1711
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1712
                        {Key: "$exists", Value: false},
1✔
1713
                }},
1✔
1714
        }
1✔
1715

1✔
1716
        update := bson.D{
1✔
1717
                {Key: "$set", Value: bson.M{
1✔
1718
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1719
                }},
1✔
1720
        }
1✔
1721

1✔
1722
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1723
                return err
×
1724
        } else if res.MatchedCount == 0 {
1✔
1725
                return ErrStorageNotFound
×
1726
        }
×
1727

1728
        return nil
1✔
1729
}
1730

1731
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1732
        id string) (model.Stats, error) {
6✔
1733

6✔
1734
        if len(id) == 0 {
6✔
1735
                return nil, ErrStorageInvalidID
×
1736
        }
×
1737

1738
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1739
        collDevs := database.Collection(CollectionDevices)
6✔
1740

6✔
1741
        match := bson.D{
6✔
1742
                {Key: "$match", Value: bson.M{
6✔
1743
                        StorageKeyDeviceDeploymentDeploymentID: id,
6✔
1744
                        StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1745
                                {Key: "$exists", Value: false},
6✔
1746
                        },
6✔
1747
                }},
6✔
1748
        }
6✔
1749
        group := bson.D{
6✔
1750
                {Key: "$group", Value: bson.D{
6✔
1751
                        {Key: "_id",
6✔
1752
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
6✔
1753
                        {Key: "count",
6✔
1754
                                Value: bson.M{"$sum": 1}}},
6✔
1755
                },
6✔
1756
        }
6✔
1757
        pipeline := []bson.D{
6✔
1758
                match,
6✔
1759
                group,
6✔
1760
        }
6✔
1761
        var results []struct {
6✔
1762
                Status model.DeviceDeploymentStatus `bson:"_id"`
6✔
1763
                Count  int
6✔
1764
        }
6✔
1765
        cursor, err := collDevs.Aggregate(ctx, pipeline)
6✔
1766
        if err != nil {
6✔
1767
                return nil, err
×
1768
        }
×
1769
        if err := cursor.All(ctx, &results); err != nil {
6✔
1770
                if err == mongo.ErrNoDocuments {
×
1771
                        return nil, nil
×
1772
                }
×
1773
                return nil, err
×
1774
        }
1775

1776
        raw := model.NewDeviceDeploymentStats()
6✔
1777
        for _, res := range results {
17✔
1778
                raw.Set(res.Status, res.Count)
11✔
1779
        }
11✔
1780
        return raw, nil
6✔
1781
}
1782

1783
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1784
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1785
        deploymentID string) ([]model.DeviceDeployment, error) {
6✔
1786

6✔
1787
        statuses := []model.DeviceDeployment{}
6✔
1788
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1789
        collDevs := database.Collection(CollectionDevices)
6✔
1790

6✔
1791
        query := bson.M{
6✔
1792
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1793
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1794
                        {Key: "$exists", Value: false},
6✔
1795
                },
6✔
1796
        }
6✔
1797

6✔
1798
        cursor, err := collDevs.Find(ctx, query)
6✔
1799
        if err != nil {
6✔
1800
                return nil, err
×
1801
        }
×
1802

1803
        if err = cursor.All(ctx, &statuses); err != nil {
6✔
1804
                if err == mongo.ErrNoDocuments {
×
1805
                        return nil, nil
×
1806
                }
×
1807
                return nil, err
×
1808
        }
1809

1810
        return statuses, nil
6✔
1811
}
1812

1813
func (db *DataStoreMongo) GetDevicesListForDeployment(ctx context.Context,
1814
        q store.ListQuery) ([]model.DeviceDeployment, int, error) {
15✔
1815

15✔
1816
        statuses := []model.DeviceDeployment{}
15✔
1817
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1818
        collDevs := database.Collection(CollectionDevices)
15✔
1819

15✔
1820
        query := bson.D{
15✔
1821
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
15✔
1822
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
15✔
1823
                        {Key: "$exists", Value: false},
15✔
1824
                }},
15✔
1825
        }
15✔
1826
        if q.Status != nil {
19✔
1827
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
5✔
1828
                        query = append(query, bson.E{
1✔
1829
                                Key: "status", Value: bson.D{{
1✔
1830
                                        Key:   "$gte",
1✔
1831
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1832
                                }, {
1✔
1833
                                        Key:   "$lte",
1✔
1834
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1835
                                }},
1✔
1836
                        })
1✔
1837
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
4✔
1838
                        query = append(query, bson.E{
×
1839
                                Key: "status", Value: bson.D{{
×
1840
                                        Key:   "$gte",
×
1841
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1842
                                }, {
×
1843
                                        Key:   "$lte",
×
1844
                                        Value: model.DeviceDeploymentStatusPending,
×
1845
                                }},
×
1846
                        })
×
1847
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
4✔
1848
                        query = append(query, bson.E{
1✔
1849
                                Key: "status", Value: bson.D{{
1✔
1850
                                        Key: "$in",
1✔
1851
                                        Value: []model.DeviceDeploymentStatus{
1✔
1852
                                                model.DeviceDeploymentStatusFailure,
1✔
1853
                                                model.DeviceDeploymentStatusAborted,
1✔
1854
                                                model.DeviceDeploymentStatusSuccess,
1✔
1855
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1856
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1857
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1858
                                        },
1✔
1859
                                }},
1✔
1860
                        })
1✔
1861
                } else {
3✔
1862
                        var status model.DeviceDeploymentStatus
2✔
1863
                        err := status.UnmarshalText([]byte(*q.Status))
2✔
1864
                        if err != nil {
3✔
1865
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1866
                        }
1✔
1867
                        query = append(query, bson.E{
1✔
1868
                                Key: "status", Value: status,
1✔
1869
                        })
1✔
1870
                }
1871
        }
1872

1873
        options := mopts.Find()
14✔
1874
        sortFieldQuery := bson.D{
14✔
1875
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
14✔
1876
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
14✔
1877
        }
14✔
1878
        options.SetSort(sortFieldQuery)
14✔
1879
        if q.Skip > 0 {
17✔
1880
                options.SetSkip(int64(q.Skip))
3✔
1881
        }
3✔
1882
        if q.Limit > 0 {
19✔
1883
                options.SetLimit(int64(q.Limit))
5✔
1884
        } else {
14✔
1885
                options.SetLimit(DefaultDocumentLimit)
9✔
1886
        }
9✔
1887

1888
        cursor, err := collDevs.Find(ctx, query, options)
14✔
1889
        if err != nil {
15✔
1890
                return nil, -1, err
1✔
1891
        }
1✔
1892

1893
        if err = cursor.All(ctx, &statuses); err != nil {
13✔
1894
                if err == mongo.ErrNoDocuments {
×
1895
                        return nil, -1, nil
×
1896
                }
×
1897
                return nil, -1, err
×
1898
        }
1899

1900
        count, err := collDevs.CountDocuments(ctx, query)
13✔
1901
        if err != nil {
13✔
1902
                return nil, -1, ErrDevicesCountFailed
×
1903
        }
×
1904

1905
        return statuses, int(count), nil
13✔
1906
}
1907

1908
func (db *DataStoreMongo) GetDeviceDeploymentsForDevice(ctx context.Context,
1909
        q store.ListQueryDeviceDeployments) ([]model.DeviceDeployment, int, error) {
10✔
1910

10✔
1911
        statuses := []model.DeviceDeployment{}
10✔
1912
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
10✔
1913
        collDevs := database.Collection(CollectionDevices)
10✔
1914

10✔
1915
        query := bson.D{}
10✔
1916
        if q.DeviceID != "" {
19✔
1917
                query = append(query, bson.E{
9✔
1918
                        Key:   StorageKeyDeviceDeploymentDeviceId,
9✔
1919
                        Value: q.DeviceID,
9✔
1920
                })
9✔
1921
        } else if len(q.IDs) > 0 {
11✔
1922
                query = append(query, bson.E{
1✔
1923
                        Key: StorageKeyId,
1✔
1924
                        Value: bson.D{{
1✔
1925
                                Key:   "$in",
1✔
1926
                                Value: q.IDs,
1✔
1927
                        }},
1✔
1928
                })
1✔
1929
        }
1✔
1930

1931
        if q.Status != nil {
18✔
1932
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
9✔
1933
                        query = append(query, bson.E{
1✔
1934
                                Key: "status", Value: bson.D{{
1✔
1935
                                        Key:   "$gte",
1✔
1936
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1937
                                }, {
1✔
1938
                                        Key:   "$lte",
1✔
1939
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1940
                                }},
1✔
1941
                        })
1✔
1942
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
9✔
1943
                        query = append(query, bson.E{
1✔
1944
                                Key: "status", Value: bson.D{{
1✔
1945
                                        Key:   "$gte",
1✔
1946
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1947
                                }, {
1✔
1948
                                        Key:   "$lte",
1✔
1949
                                        Value: model.DeviceDeploymentStatusPending,
1✔
1950
                                }},
1✔
1951
                        })
1✔
1952
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
8✔
1953
                        query = append(query, bson.E{
1✔
1954
                                Key: "status", Value: bson.D{{
1✔
1955
                                        Key: "$in",
1✔
1956
                                        Value: []model.DeviceDeploymentStatus{
1✔
1957
                                                model.DeviceDeploymentStatusFailure,
1✔
1958
                                                model.DeviceDeploymentStatusAborted,
1✔
1959
                                                model.DeviceDeploymentStatusSuccess,
1✔
1960
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1961
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1962
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1963
                                        },
1✔
1964
                                }},
1✔
1965
                        })
1✔
1966
                } else {
6✔
1967
                        var status model.DeviceDeploymentStatus
5✔
1968
                        err := status.UnmarshalText([]byte(*q.Status))
5✔
1969
                        if err != nil {
6✔
1970
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1971
                        }
1✔
1972
                        query = append(query, bson.E{
4✔
1973
                                Key: "status", Value: status,
4✔
1974
                        })
4✔
1975
                }
1976
        }
1977

1978
        options := mopts.Find()
9✔
1979
        sortFieldQuery := bson.D{
9✔
1980
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
9✔
1981
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
9✔
1982
        }
9✔
1983
        options.SetSort(sortFieldQuery)
9✔
1984
        if q.Skip > 0 {
10✔
1985
                options.SetSkip(int64(q.Skip))
1✔
1986
        }
1✔
1987
        if q.Limit > 0 {
18✔
1988
                options.SetLimit(int64(q.Limit))
9✔
1989
        } else {
9✔
1990
                options.SetLimit(DefaultDocumentLimit)
×
1991
        }
×
1992

1993
        cursor, err := collDevs.Find(ctx, query, options)
9✔
1994
        if err != nil {
9✔
1995
                return nil, -1, err
×
1996
        }
×
1997

1998
        if err = cursor.All(ctx, &statuses); err != nil {
9✔
1999
                if err == mongo.ErrNoDocuments {
×
2000
                        return nil, 0, nil
×
2001
                }
×
2002
                return nil, -1, err
×
2003
        }
2004

2005
        maxCount := maxCountDocuments
9✔
2006
        countOptions := &mopts.CountOptions{
9✔
2007
                Limit: &maxCount,
9✔
2008
        }
9✔
2009
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
9✔
2010
        if err != nil {
9✔
2011
                return nil, -1, ErrDevicesCountFailed
×
2012
        }
×
2013

2014
        return statuses, int(count), nil
9✔
2015
}
2016

2017
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
2018
// `deviceID`, false otherwise. In case of errors returns false and an error
2019
// that occurred
2020
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
2021
        deploymentID string, deviceID string) (bool, error) {
7✔
2022

7✔
2023
        var dep model.DeviceDeployment
7✔
2024
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2025
        collDevs := database.Collection(CollectionDevices)
7✔
2026

7✔
2027
        query := bson.D{
7✔
2028
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
2029
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
2030
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
2031
                        {Key: "$exists", Value: false},
7✔
2032
                }},
7✔
2033
        }
7✔
2034

7✔
2035
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
10✔
2036
                if err == mongo.ErrNoDocuments {
6✔
2037
                        return false, nil
3✔
2038
                } else {
3✔
2039
                        return false, err
×
2040
                }
×
2041
        }
2042

2043
        return true, nil
4✔
2044
}
2045

2046
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2047
        deploymentId string) error {
3✔
2048

3✔
2049
        if len(deploymentId) == 0 {
4✔
2050
                return ErrStorageInvalidID
1✔
2051
        }
1✔
2052

2053
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2054
        collDevs := database.Collection(CollectionDevices)
2✔
2055
        selector := bson.M{
2✔
2056
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
2✔
2057
                StorageKeyDeviceDeploymentActive:       true,
2✔
2058
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2059
                        {Key: "$exists", Value: false},
2✔
2060
                },
2✔
2061
        }
2✔
2062

2✔
2063
        update := bson.M{
2✔
2064
                "$set": bson.M{
2✔
2065
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2066
                        StorageKeyDeviceDeploymentActive: false,
2✔
2067
                },
2✔
2068
        }
2✔
2069

2✔
2070
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2071
                return err
×
2072
        }
×
2073

2074
        return nil
2✔
2075
}
2076

2077
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2078
        deviceID string) error {
2✔
2079
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2080
        collDevs := database.Collection(CollectionDevices)
2✔
2081
        selector := bson.M{
2✔
2082
                StorageKeyDeviceDeploymentDeviceId: deviceID,
2✔
2083
                StorageKeyDeviceDeploymentActive:   false,
2✔
2084
                StorageKeyDeviceDeploymentDeleted: bson.M{
2✔
2085
                        "$exists": false,
2✔
2086
                },
2✔
2087
        }
2✔
2088

2✔
2089
        now := time.Now()
2✔
2090
        update := bson.M{
2✔
2091
                "$set": bson.M{
2✔
2092
                        StorageKeyDeviceDeploymentDeleted: &now,
2✔
2093
                },
2✔
2094
        }
2✔
2095

2✔
2096
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2097
                return err
×
2098
        }
×
2099

2100
        database = db.client.Database(DatabaseName)
2✔
2101
        collDevs = database.Collection(CollectionDevicesLastStatus)
2✔
2102
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
2✔
2103

2✔
2104
        return err
2✔
2105
}
2106

2107
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2108
        deviceId string) error {
2✔
2109

2✔
2110
        if len(deviceId) == 0 {
3✔
2111
                return ErrStorageInvalidID
1✔
2112
        }
1✔
2113

2114
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2115
        collDevs := database.Collection(CollectionDevices)
1✔
2116
        selector := bson.M{
1✔
2117
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2118
                StorageKeyDeviceDeploymentActive:   true,
1✔
2119
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2120
                        {Key: "$exists", Value: false},
1✔
2121
                },
1✔
2122
        }
1✔
2123

1✔
2124
        update := bson.M{
1✔
2125
                "$set": bson.M{
1✔
2126
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2127
                        StorageKeyDeviceDeploymentActive: false,
1✔
2128
                },
1✔
2129
        }
1✔
2130

1✔
2131
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2132
                return err
×
2133
        }
×
2134

2135
        return nil
1✔
2136
}
2137

2138
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2139
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2140

1✔
2141
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2142
        collDevs := database.Collection(CollectionDevices)
1✔
2143

1✔
2144
        filter := bson.M{
1✔
2145
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2146
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2147
        }
1✔
2148
        if !includeDeleted {
2✔
2149
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2150
                        {Key: "$exists", Value: false},
1✔
2151
                }
1✔
2152
        }
1✔
2153

2154
        opts := &mopts.FindOneOptions{}
1✔
2155
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2156

1✔
2157
        var dd model.DeviceDeployment
1✔
2158
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2159
                if err == mongo.ErrNoDocuments {
2✔
2160
                        return nil, ErrStorageNotFound
1✔
2161
                }
1✔
2162
                return nil, err
×
2163
        }
2164

2165
        return &dd, nil
1✔
2166
}
2167

2168
func (db *DataStoreMongo) GetDeviceDeployments(
2169
        ctx context.Context,
2170
        skip int,
2171
        limit int,
2172
        deviceID string,
2173
        active *bool,
2174
        includeDeleted bool,
2175
) ([]model.DeviceDeployment, error) {
4✔
2176

4✔
2177
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2178
        collDevs := database.Collection(CollectionDevices)
4✔
2179

4✔
2180
        filter := bson.M{}
4✔
2181
        if !includeDeleted {
6✔
2182
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2183
                        {Key: "$exists", Value: false},
2✔
2184
                }
2✔
2185
        }
2✔
2186
        if deviceID != "" {
5✔
2187
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2188
        }
1✔
2189
        if active != nil {
5✔
2190
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2191
        }
1✔
2192

2193
        opts := &mopts.FindOptions{}
4✔
2194
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
4✔
2195
        if skip > 0 {
5✔
2196
                opts.SetSkip(int64(skip))
1✔
2197
        }
1✔
2198
        if limit > 0 {
5✔
2199
                opts.SetLimit(int64(limit))
1✔
2200
        }
1✔
2201

2202
        var deviceDeployments []model.DeviceDeployment
4✔
2203
        cursor, err := collDevs.Find(ctx, filter, opts)
4✔
2204
        if err != nil {
4✔
2205
                return nil, err
×
2206
        }
×
2207
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
4✔
2208
                return nil, err
×
2209
        }
×
2210

2211
        return deviceDeployments, nil
4✔
2212
}
2213

2214
// deployments
2215

2216
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2217
        indexes ...mongo.IndexModel) error {
466✔
2218
        ctx := context.Background()
466✔
2219
        dataBase := db.client.Database(dbName)
466✔
2220

466✔
2221
        coll := dataBase.Collection(collName)
466✔
2222
        idxView := coll.Indexes()
466✔
2223
        _, err := idxView.CreateMany(ctx, indexes)
466✔
2224
        return err
466✔
2225
}
466✔
2226

2227
// return true if required indexing was set up
2228
func (db *DataStoreMongo) hasIndexing(ctx context.Context, client *mongo.Client) bool {
16✔
2229

16✔
2230
        var idx bson.M
16✔
2231
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
16✔
2232
        collDpl := database.Collection(CollectionDeployments)
16✔
2233
        idxView := collDpl.Indexes()
16✔
2234

16✔
2235
        cursor, err := idxView.List(ctx)
16✔
2236
        if err != nil {
16✔
2237
                // check failed, assume indexing is not there
×
2238
                return false
×
2239
        }
×
2240

2241
        has := map[string]bool{}
16✔
2242
        for cursor.Next(ctx) {
46✔
2243
                if err = cursor.Decode(&idx); err != nil {
30✔
2244
                        continue
×
2245
                }
2246
                if _, ok := idx["weights"]; ok {
45✔
2247
                        // text index
15✔
2248
                        for k := range idx["weights"].(bson.M) {
45✔
2249
                                has[k] = true
30✔
2250
                        }
30✔
2251
                } else {
15✔
2252
                        for i := range idx["key"].(bson.M) {
30✔
2253
                                has[i] = true
15✔
2254
                        }
15✔
2255

2256
                }
2257
        }
2258
        if err != nil {
16✔
2259
                return false
×
2260
        }
×
2261

2262
        for _, key := range StorageIndexes.Keys.(bson.D) {
47✔
2263
                _, ok := has[key.Key]
31✔
2264
                if !ok {
32✔
2265
                        return false
1✔
2266
                }
1✔
2267
        }
2268

2269
        return true
15✔
2270
}
2271

2272
// Insert persists object
2273
func (db *DataStoreMongo) InsertDeployment(
2274
        ctx context.Context,
2275
        deployment *model.Deployment,
2276
) error {
216✔
2277

216✔
2278
        if deployment == nil {
217✔
2279
                return ErrDeploymentStorageInvalidDeployment
1✔
2280
        }
1✔
2281

2282
        if err := deployment.Validate(); err != nil {
217✔
2283
                return err
2✔
2284
        }
2✔
2285

2286
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
214✔
2287
        collDpl := database.Collection(CollectionDeployments)
214✔
2288

214✔
2289
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
216✔
2290
                if mongo.IsDuplicateKeyError(err) {
4✔
2291
                        return ErrConflictingDeployment
2✔
2292
                }
2✔
2293
                return err
×
2294
        }
2295
        return nil
213✔
2296
}
2297

2298
// Delete removed entry by ID
2299
// Noop on ID not found
2300
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
4✔
2301

4✔
2302
        if len(id) == 0 {
5✔
2303
                return ErrStorageInvalidID
1✔
2304
        }
1✔
2305

2306
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2307
        collDpl := database.Collection(CollectionDeployments)
3✔
2308

3✔
2309
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
2310
                return err
×
2311
        }
×
2312

2313
        return nil
3✔
2314
}
2315

2316
func (db *DataStoreMongo) FindDeploymentByID(
2317
        ctx context.Context,
2318
        id string,
2319
) (*model.Deployment, error) {
10✔
2320

10✔
2321
        if len(id) == 0 {
11✔
2322
                return nil, ErrStorageInvalidID
1✔
2323
        }
1✔
2324

2325
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2326
        collDpl := database.Collection(CollectionDeployments)
9✔
2327

9✔
2328
        deployment := new(model.Deployment)
9✔
2329
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
9✔
2330
                Decode(deployment); err != nil {
12✔
2331
                if err == mongo.ErrNoDocuments {
6✔
2332
                        return nil, nil
3✔
2333
                }
3✔
2334
                return nil, err
×
2335
        }
2336

2337
        return deployment, nil
6✔
2338
}
2339

2340
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2341
        ctx context.Context,
2342
        ids ...string,
2343
) (deploymentStats []*model.DeploymentStats, err error) {
2✔
2344

2✔
2345
        if len(ids) == 0 {
2✔
2346
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2347
        }
×
2348

2349
        for _, id := range ids {
6✔
2350
                if len(id) == 0 {
4✔
2351
                        return nil, ErrStorageInvalidID
×
2352
                }
×
2353
        }
2354

2355
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2356
        collDpl := database.Collection(CollectionDeployments)
2✔
2357

2✔
2358
        query := bson.M{
2✔
2359
                "_id": bson.M{
2✔
2360
                        "$in": ids,
2✔
2361
                },
2✔
2362
        }
2✔
2363
        statsProjection := &mopts.FindOptions{
2✔
2364
                Projection: bson.M{"stats": 1},
2✔
2365
        }
2✔
2366

2✔
2367
        results, err := collDpl.Find(
2✔
2368
                ctx,
2✔
2369
                query,
2✔
2370
                statsProjection,
2✔
2371
        )
2✔
2372
        if err != nil {
2✔
2373
                return nil, err
×
2374
        }
×
2375

2376
        for results.Next(context.Background()) {
6✔
2377
                depl := new(model.DeploymentStats)
4✔
2378
                if err = results.Decode(&depl); err != nil {
4✔
2379
                        if err == mongo.ErrNoDocuments {
×
2380
                                return nil, nil
×
2381
                        }
×
2382
                        return nil, err
×
2383
                }
2384
                deploymentStats = append(deploymentStats, depl)
4✔
2385
        }
2386

2387
        return deploymentStats, nil
2✔
2388
}
2389

2390
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2391
        id string) (*model.Deployment, error) {
8✔
2392

8✔
2393
        if len(id) == 0 {
9✔
2394
                return nil, ErrStorageInvalidID
1✔
2395
        }
1✔
2396

2397
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2398
        collDpl := database.Collection(CollectionDeployments)
7✔
2399

7✔
2400
        var deployment *model.Deployment
7✔
2401
        filter := bson.D{
7✔
2402
                {Key: "_id", Value: id},
7✔
2403
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2404
        }
7✔
2405
        if err := collDpl.FindOne(ctx, filter).
7✔
2406
                Decode(&deployment); err != nil {
12✔
2407
                if err == mongo.ErrNoDocuments {
10✔
2408
                        return nil, nil
5✔
2409
                }
5✔
2410
                return nil, err
×
2411
        }
2412

2413
        return deployment, nil
3✔
2414
}
2415

2416
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2417
        ctx context.Context,
2418
        deploymentID string,
2419
        increment int,
2420
) error {
64✔
2421
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
64✔
2422
        collection := database.Collection(CollectionDeployments)
64✔
2423

64✔
2424
        filter := bson.M{
64✔
2425
                "_id": deploymentID,
64✔
2426
                StorageKeyDeploymentDeviceCount: bson.M{
64✔
2427
                        "$ne": nil,
64✔
2428
                },
64✔
2429
        }
64✔
2430

64✔
2431
        update := bson.M{
64✔
2432
                "$inc": bson.M{
64✔
2433
                        StorageKeyDeploymentDeviceCount: increment,
64✔
2434
                },
64✔
2435
        }
64✔
2436

64✔
2437
        _, err := collection.UpdateOne(ctx, filter, update)
64✔
2438
        return err
64✔
2439
}
64✔
2440

2441
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2442
        ctx context.Context,
2443
        deploymentID string,
2444
        count int,
2445
) error {
3✔
2446
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2447
        collection := database.Collection(CollectionDeployments)
3✔
2448

3✔
2449
        filter := bson.M{
3✔
2450
                "_id": deploymentID,
3✔
2451
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2452
                        "$eq": nil,
3✔
2453
                },
3✔
2454
        }
3✔
2455

3✔
2456
        update := bson.M{
3✔
2457
                "$set": bson.M{
3✔
2458
                        StorageKeyDeploymentDeviceCount: count,
3✔
2459
                },
3✔
2460
        }
3✔
2461

3✔
2462
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2463
        return err
3✔
2464
}
3✔
2465

2466
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2467
        id string) (int, error) {
3✔
2468

3✔
2469
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2470
        collDevs := database.Collection(CollectionDevices)
3✔
2471

3✔
2472
        filter := bson.M{
3✔
2473
                StorageKeyDeviceDeploymentDeploymentID: id,
3✔
2474
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
2475
                        {Key: "$exists", Value: false},
3✔
2476
                },
3✔
2477
        }
3✔
2478

3✔
2479
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
3✔
2480
        if err != nil {
3✔
2481
                return 0, err
×
2482
        }
×
2483

2484
        return int(deviceCount), nil
3✔
2485
}
2486

2487
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2488
        id string, stats model.Stats) error {
6✔
2489

6✔
2490
        if len(id) == 0 {
7✔
2491
                return ErrStorageInvalidID
1✔
2492
        }
1✔
2493

2494
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2495
        collDpl := database.Collection(CollectionDeployments)
5✔
2496

5✔
2497
        deployment, err := model.NewDeployment()
5✔
2498
        if err != nil {
5✔
2499
                return errors.Wrap(err, "failed to create deployment")
×
2500
        }
×
2501

2502
        deployment.Stats = stats
5✔
2503
        var update bson.M
5✔
2504
        if deployment.IsFinished() {
5✔
2505
                now := time.Now()
×
2506

×
2507
                update = bson.M{
×
2508
                        "$set": bson.M{
×
2509
                                StorageKeyDeploymentStats:    stats,
×
2510
                                StorageKeyDeploymentFinished: &now,
×
2511
                        },
×
2512
                }
×
2513
        } else {
5✔
2514
                update = bson.M{
5✔
2515
                        "$set": bson.M{
5✔
2516
                                StorageKeyDeploymentStats: stats,
5✔
2517
                        },
5✔
2518
                }
5✔
2519
        }
5✔
2520

2521
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
5✔
2522
        if res != nil && res.MatchedCount == 0 {
7✔
2523
                return ErrStorageInvalidID
2✔
2524
        }
2✔
2525
        return err
3✔
2526
}
2527

2528
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2529
        stateFrom, stateTo model.DeviceDeploymentStatus) (model.Stats, error) {
8✔
2530

8✔
2531
        if len(id) == 0 {
9✔
2532
                return nil, ErrStorageInvalidID
1✔
2533
        }
1✔
2534

2535
        if _, err := stateTo.MarshalText(); err != nil {
7✔
NEW
2536
                return nil, ErrStorageInvalidInput
×
2537
        }
×
2538

2539
        // does not need any extra operations
2540
        // following query won't handle this case well and increase the state_to value
2541
        if stateFrom == stateTo {
8✔
2542
                return nil, nil
1✔
2543
        }
1✔
2544

2545
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2546
        collDpl := database.Collection(CollectionDeployments)
6✔
2547

6✔
2548
        var update bson.M
6✔
2549

6✔
2550
        if stateFrom == model.DeviceDeploymentStatusNull {
8✔
2551
                // note dot notation on embedded document
2✔
2552
                update = bson.M{
2✔
2553
                        "$inc": bson.M{
2✔
2554
                                "stats." + stateTo.String(): 1,
2✔
2555
                        },
2✔
2556
                }
2✔
2557
        } else {
7✔
2558
                // note dot notation on embedded document
5✔
2559
                update = bson.M{
5✔
2560
                        "$inc": bson.M{
5✔
2561
                                "stats." + stateFrom.String(): -1,
5✔
2562
                                "stats." + stateTo.String():   1,
5✔
2563
                        },
5✔
2564
                }
5✔
2565
        }
5✔
2566

2567
        var res struct {
6✔
2568
                Stats model.Stats `bson:"stats"`
6✔
2569
        }
6✔
2570
        err := collDpl.FindOneAndUpdate(ctx,
6✔
2571
                bson.M{"_id": id},
6✔
2572
                update,
6✔
2573
                mopts.FindOneAndUpdate().SetReturnDocument(mopts.After),
6✔
2574
        ).Decode(&res)
6✔
2575

6✔
2576
        if errors.Is(err, mongo.ErrNoDocuments) {
7✔
2577
                return nil, ErrStorageInvalidID
1✔
2578
        }
1✔
2579

2580
        return res.Stats, err
5✔
2581
}
2582

2583
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2584
        ctx context.Context,
2585
        deploymentID string,
2586
        increment int64,
2587
) error {
3✔
2588
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2589
        collection := database.Collection(CollectionDeployments)
3✔
2590

3✔
2591
        filter := bson.M{
3✔
2592
                "_id": deploymentID,
3✔
2593
        }
3✔
2594

3✔
2595
        update := bson.M{
3✔
2596
                "$inc": bson.M{
3✔
2597
                        StorageKeyDeploymentTotalSize: increment,
3✔
2598
                },
3✔
2599
        }
3✔
2600

3✔
2601
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2602
        return err
3✔
2603
}
3✔
2604

2605
func (db *DataStoreMongo) Find(ctx context.Context,
2606
        match model.Query) ([]*model.Deployment, int64, error) {
36✔
2607

36✔
2608
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
36✔
2609
        collDpl := database.Collection(CollectionDeployments)
36✔
2610

36✔
2611
        andq := []bson.M{}
36✔
2612

36✔
2613
        // filter by IDs
36✔
2614
        if match.IDs != nil {
36✔
2615
                tq := bson.M{
×
2616
                        "_id": bson.M{
×
2617
                                "$in": match.IDs,
×
2618
                        },
×
2619
                }
×
2620
                andq = append(andq, tq)
×
2621
        }
×
2622

2623
        // build deployment by name part of the query
2624
        if match.SearchText != "" {
52✔
2625
                // we must have indexing for text search
16✔
2626
                if !db.hasIndexing(ctx, db.client) {
17✔
2627
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
1✔
2628
                }
1✔
2629

2630
                tq := bson.M{
15✔
2631
                        "$text": bson.M{
15✔
2632
                                "$search": "\"" + match.SearchText + "\"",
15✔
2633
                        },
15✔
2634
                }
15✔
2635

15✔
2636
                andq = append(andq, tq)
15✔
2637
        }
2638

2639
        // build deployment by status part of the query
2640
        if match.Status != model.StatusQueryAny {
45✔
2641
                var status model.DeploymentStatus
10✔
2642
                if match.Status == model.StatusQueryPending {
12✔
2643
                        status = model.DeploymentStatusPending
2✔
2644
                } else if match.Status == model.StatusQueryInProgress {
14✔
2645
                        status = model.DeploymentStatusInProgress
4✔
2646
                } else {
8✔
2647
                        status = model.DeploymentStatusFinished
4✔
2648
                }
4✔
2649
                stq := bson.M{StorageKeyDeploymentStatus: status}
10✔
2650
                andq = append(andq, stq)
10✔
2651
        }
2652

2653
        // build deployment by type part of the query
2654
        if match.Type != "" {
37✔
2655
                if match.Type == model.DeploymentTypeConfiguration {
4✔
2656
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
2✔
2657
                } else if match.Type == model.DeploymentTypeSoftware {
2✔
2658
                        andq = append(andq, bson.M{
×
2659
                                "$or": []bson.M{
×
2660
                                        {StorageKeyDeploymentType: match.Type},
×
2661
                                        {StorageKeyDeploymentType: ""},
×
2662
                                },
×
2663
                        })
×
2664
                }
×
2665
        }
2666

2667
        query := bson.M{}
35✔
2668
        if len(andq) != 0 {
58✔
2669
                // use search criteria if any
23✔
2670
                query = bson.M{
23✔
2671
                        "$and": andq,
23✔
2672
                }
23✔
2673
        }
23✔
2674

2675
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
35✔
2676
                query["created"] = bson.M{
×
2677
                        "$gte": match.CreatedAfter,
×
2678
                        "$lte": match.CreatedBefore,
×
2679
                }
×
2680
        } else if match.CreatedAfter != nil {
35✔
2681
                query["created"] = bson.M{
×
2682
                        "$gte": match.CreatedAfter,
×
2683
                }
×
2684
        } else if match.CreatedBefore != nil {
35✔
2685
                query["created"] = bson.M{
×
2686
                        "$lte": match.CreatedBefore,
×
2687
                }
×
2688
        }
×
2689

2690
        options := db.findOptions(match)
35✔
2691

35✔
2692
        var deployments []*model.Deployment
35✔
2693
        cursor, err := collDpl.Find(ctx, query, options)
35✔
2694
        if err != nil {
35✔
2695
                return nil, 0, err
×
2696
        }
×
2697
        if err := cursor.All(ctx, &deployments); err != nil {
35✔
2698
                return nil, 0, err
×
2699
        }
×
2700
        // Count documents if we didn't find all already.
2701
        count := int64(0)
35✔
2702
        if !match.DisableCount {
70✔
2703
                count = int64(len(deployments))
35✔
2704
                if count >= int64(match.Limit) {
69✔
2705
                        count, err = collDpl.CountDocuments(ctx, query)
34✔
2706
                        if err != nil {
34✔
2707
                                return nil, 0, err
×
2708
                        }
×
2709
                } else {
1✔
2710
                        // Don't forget to add the skipped documents
1✔
2711
                        count += int64(match.Skip)
1✔
2712
                }
1✔
2713
        }
2714

2715
        return deployments, count, nil
35✔
2716
}
2717

2718
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
35✔
2719
        options := &mopts.FindOptions{}
35✔
2720
        if match.Sort == model.SortDirectionAscending {
36✔
2721
                options.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
2722
        } else {
35✔
2723
                options.SetSort(bson.D{{Key: "created", Value: -1}})
34✔
2724
        }
34✔
2725
        if match.Skip > 0 {
37✔
2726
                options.SetSkip(int64(match.Skip))
2✔
2727
        }
2✔
2728
        if match.Limit > 0 {
40✔
2729
                options.SetLimit(int64(match.Limit))
5✔
2730
        } else {
35✔
2731
                options.SetLimit(DefaultDocumentLimit)
30✔
2732
        }
30✔
2733
        return options
35✔
2734
}
2735

2736
// FindNewerActiveDeployments finds active deployments which were created
2737
// after createdAfter
2738
// Deprecated: No longer in use
2739
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2740
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
4✔
2741

4✔
2742
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2743
        c := database.Collection(CollectionDeployments)
4✔
2744

4✔
2745
        queryFilters := make([]bson.M, 0)
4✔
2746
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
4✔
2747
        queryFilters = append(queryFilters,
4✔
2748
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
4✔
2749
        findQuery := bson.M{}
4✔
2750
        findQuery["$and"] = queryFilters
4✔
2751

4✔
2752
        findOptions := &mopts.FindOptions{}
4✔
2753
        findOptions.SetSkip(int64(skip))
4✔
2754
        findOptions.SetLimit(int64(limit))
4✔
2755

4✔
2756
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
4✔
2757
        cursor, err := c.Find(ctx, findQuery, findOptions)
4✔
2758
        if err != nil {
4✔
2759
                return nil, errors.Wrap(err, "failed to get deployments")
×
2760
        }
×
2761
        defer cursor.Close(ctx)
4✔
2762

4✔
2763
        var deployments []*model.Deployment
4✔
2764

4✔
2765
        if err = cursor.All(ctx, &deployments); err != nil {
4✔
2766
                return nil, errors.Wrap(err, "failed to get deployments")
×
2767
        }
×
2768

2769
        return deployments, nil
4✔
2770
}
2771

2772
// FindNewerActiveDeployment finds active deployments which were created
2773
// after createdAfter where deviceID is part of the device list.
2774
func (db *DataStoreMongo) FindNewerActiveDeployment(ctx context.Context,
2775
        createdAfter *time.Time, deviceID string) (*model.Deployment, error) {
1✔
2776

1✔
2777
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2778
        c := database.Collection(CollectionDeployments)
1✔
2779

1✔
2780
        findQuery := bson.D{
1✔
2781
                {Key: StorageKeyDeploymentActive, Value: true},
1✔
2782
                {Key: StorageKeyDeploymentCreated, Value: bson.M{"$gt": createdAfter}},
1✔
2783
                {Key: StorageKeyDeploymentDeviceList, Value: deviceID},
1✔
2784
        }
1✔
2785
        findOptions := mopts.FindOne().
1✔
2786
                SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}}).
1✔
2787
                SetProjection(bson.M{
1✔
2788
                        // Discard information we don't need
1✔
2789
                        StorageKeyDeploymentConstructorChecksum: 0,
1✔
2790
                        StorageKeyDeploymentDeviceList:          0,
1✔
2791
                })
1✔
2792

1✔
2793
        var deployment = new(model.Deployment)
1✔
2794
        err := c.FindOne(ctx, findQuery, findOptions).
1✔
2795
                Decode(deployment)
1✔
2796
        if err != nil {
2✔
2797
                if errors.Is(err, mongo.ErrNoDocuments) {
2✔
2798
                        return nil, nil
1✔
2799
                }
1✔
2800
                return nil, errors.Wrap(err, "failed to get deployments")
×
2801
        }
2802

2803
        return deployment, nil
1✔
2804
}
2805

2806
// SetDeploymentStatus simply sets the status field
2807
// optionally sets 'finished time' if deployment is indeed finished
2808
func (db *DataStoreMongo) SetDeploymentStatus(
2809
        ctx context.Context,
2810
        id string,
2811
        status model.DeploymentStatus,
2812
        now time.Time,
2813
) error {
6✔
2814
        if len(id) == 0 {
6✔
2815
                return ErrStorageInvalidID
×
2816
        }
×
2817

2818
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2819
        collDpl := database.Collection(CollectionDeployments)
6✔
2820

6✔
2821
        var update bson.M
6✔
2822
        if status == model.DeploymentStatusFinished {
8✔
2823
                update = bson.M{
2✔
2824
                        "$set": bson.M{
2✔
2825
                                StorageKeyDeploymentActive:   false,
2✔
2826
                                StorageKeyDeploymentStatus:   status,
2✔
2827
                                StorageKeyDeploymentFinished: &now,
2✔
2828
                        },
2✔
2829
                }
2✔
2830
        } else {
7✔
2831
                update = bson.M{
5✔
2832
                        "$set": bson.M{
5✔
2833
                                StorageKeyDeploymentActive: true,
5✔
2834
                                StorageKeyDeploymentStatus: status,
5✔
2835
                        },
5✔
2836
                }
5✔
2837
        }
5✔
2838

2839
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
6✔
2840

6✔
2841
        if res != nil && res.MatchedCount == 0 {
7✔
2842
                return ErrStorageInvalidID
1✔
2843
        }
1✔
2844

2845
        return err
5✔
2846
}
2847

2848
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2849
// given artifact
2850
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2851
        id string) (bool, error) {
4✔
2852

4✔
2853
        if len(id) == 0 {
4✔
2854
                return false, ErrStorageInvalidID
×
2855
        }
×
2856

2857
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2858
        collDpl := database.Collection(CollectionDeployments)
4✔
2859

4✔
2860
        var tmp interface{}
4✔
2861
        query := bson.D{
4✔
2862
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2863
                {Key: StorageKeyDeploymentArtifacts, Value: id},
4✔
2864
        }
4✔
2865
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
7✔
2866
                if err == mongo.ErrNoDocuments {
6✔
2867
                        return false, nil
3✔
2868
                }
3✔
2869
                return false, err
×
2870
        }
2871

2872
        return true, nil
2✔
2873
}
2874

2875
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2876
// given artifact
2877
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2878
        artifactName string) (bool, error) {
4✔
2879

4✔
2880
        if len(artifactName) == 0 {
4✔
2881
                return false, ErrImagesStorageInvalidArtifactName
×
2882
        }
×
2883

2884
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2885
        collDpl := database.Collection(CollectionDeployments)
4✔
2886

4✔
2887
        var tmp interface{}
4✔
2888
        query := bson.D{
4✔
2889
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2890
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
4✔
2891
        }
4✔
2892

4✔
2893
        projection := bson.M{
4✔
2894
                "_id": 1,
4✔
2895
        }
4✔
2896
        findOptions := mopts.FindOne()
4✔
2897
        findOptions.SetProjection(projection)
4✔
2898

4✔
2899
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
7✔
2900
                if err == mongo.ErrNoDocuments {
6✔
2901
                        return false, nil
3✔
2902
                }
3✔
2903
                return false, err
×
2904
        }
2905

2906
        return true, nil
1✔
2907
}
2908

2909
// ExistByArtifactId check if there is any deployment that uses give artifact
2910
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2911
        id string) (bool, error) {
×
2912

×
2913
        if len(id) == 0 {
×
2914
                return false, ErrStorageInvalidID
×
2915
        }
×
2916

2917
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2918
        collDpl := database.Collection(CollectionDeployments)
×
2919

×
2920
        var tmp interface{}
×
2921
        query := bson.D{
×
2922
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2923
        }
×
2924
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2925
                if err == mongo.ErrNoDocuments {
×
2926
                        return false, nil
×
2927
                }
×
2928
                return false, err
×
2929
        }
2930

2931
        return true, nil
×
2932
}
2933

2934
// Per-tenant storage settings
2935
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
2✔
2936
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2937
        collection := database.Collection(CollectionStorageSettings)
2✔
2938

2✔
2939
        settings := new(model.StorageSettings)
2✔
2940
        // supposed that it's only one document in the collection
2✔
2941
        query := bson.M{
2✔
2942
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2943
        }
2✔
2944
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
3✔
2945
                if err == mongo.ErrNoDocuments {
2✔
2946
                        return nil, nil
1✔
2947
                }
1✔
2948
                return nil, err
×
2949
        }
2950

2951
        return settings, nil
2✔
2952
}
2953

2954
func (db *DataStoreMongo) SetStorageSettings(
2955
        ctx context.Context,
2956
        storageSettings *model.StorageSettings,
2957
) error {
2✔
2958
        var err error
2✔
2959
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2960
        collection := database.Collection(CollectionStorageSettings)
2✔
2961

2✔
2962
        filter := bson.M{
2✔
2963
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2964
        }
2✔
2965
        if storageSettings != nil {
4✔
2966
                replaceOptions := mopts.Replace()
2✔
2967
                replaceOptions.SetUpsert(true)
2✔
2968
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
2✔
2969
        } else {
3✔
2970
                _, err = collection.DeleteOne(ctx, filter)
1✔
2971
        }
1✔
2972

2973
        return err
2✔
2974
}
2975

2976
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2977
        ctx context.Context,
2978
        artifactName string,
2979
        artifactIDs []string,
2980
) error {
1✔
2981
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2982
        collDpl := database.Collection(CollectionDeployments)
1✔
2983

1✔
2984
        query := bson.D{
1✔
2985
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2986
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2987
        }
1✔
2988
        update := bson.M{
1✔
2989
                "$set": bson.M{
1✔
2990
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
2991
                },
1✔
2992
        }
1✔
2993

1✔
2994
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
2995
        return err
1✔
2996
}
1✔
2997

2998
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
2999
        ctx context.Context,
3000
        artifactNames []string,
3001
) ([]string, error) {
2✔
3002

2✔
3003
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
3004
        collDpl := database.Collection(CollectionDeployments)
2✔
3005

2✔
3006
        query := bson.M{
2✔
3007
                StorageKeyDeploymentArtifactName: bson.M{
2✔
3008
                        "$in": artifactNames,
2✔
3009
                },
2✔
3010
        }
2✔
3011

2✔
3012
        projection := bson.M{
2✔
3013
                "_id": 1,
2✔
3014
        }
2✔
3015
        findOptions := mopts.Find()
2✔
3016
        findOptions.SetProjection(projection)
2✔
3017

2✔
3018
        cursor, err := collDpl.Find(ctx, query, findOptions)
2✔
3019
        if err != nil {
2✔
3020
                return []string{}, err
×
3021
        }
×
3022
        defer cursor.Close(ctx)
2✔
3023

2✔
3024
        var deployments []*model.Deployment
2✔
3025
        if err = cursor.All(ctx, &deployments); err != nil {
2✔
3026
                if err == mongo.ErrNoDocuments {
×
3027
                        err = nil
×
3028
                }
×
3029
                return []string{}, err
×
3030
        }
3031

3032
        ids := make([]string, len(deployments))
2✔
3033
        for i, d := range deployments {
4✔
3034
                ids[i] = d.Id
2✔
3035
        }
2✔
3036

3037
        return ids, nil
2✔
3038
}
3039

3040
func (db *DataStoreMongo) GetTenantDbs() ([]string, error) {
×
3041
        return migrate.GetTenantDbs(context.Background(), db.client, mstore.IsTenantDb(DbName))
×
3042
}
×
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