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

mendersoftware / mender-server / 1925715791

14 Jul 2025 02:01PM UTC coverage: 65.487% (-0.02%) from 65.504%
1925715791

Pull #790

gitlab-ci

bahaa-ghazal
feat(deployments): Implement new v2 GET `/artifacts` endpoint

Ticket: MEN-8181
Changelog: Title
Signed-off-by: Bahaa Aldeen Ghazal <bahaa.ghazal@northern.tech>
Pull Request #790: feat(deployments): Implement new v2 GET `/artifacts` endpoint

145 of 237 new or added lines in 7 files covered. (61.18%)

129 existing lines in 3 files now uncovered.

32534 of 49680 relevant lines covered (65.49%)

1.38 hits per line

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

86.64
/backend/services/deployments/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/mender-server/pkg/config"
32
        "github.com/mendersoftware/mender-server/pkg/identity"
33
        "github.com/mendersoftware/mender-server/pkg/log"
34
        "github.com/mendersoftware/mender-server/pkg/mongo/migrate"
35
        mstore "github.com/mendersoftware/mender-server/pkg/store"
36

37
        dconfig "github.com/mendersoftware/mender-server/services/deployments/config"
38
        "github.com/mendersoftware/mender-server/services/deployments/model"
39
        "github.com/mendersoftware/mender-server/services/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
        // Indexes 1.2.17
111
        IndexNameDeploymentName = "deployment_name"
112

113
        _false         = false
114
        _true          = true
115
        StorageIndexes = mongo.IndexModel{
116
                // NOTE: Keys should be bson.D as element
117
                //       order matters!
118
                Keys: bson.D{
119
                        {Key: StorageKeyDeploymentName,
120
                                Value: "text"},
121
                        {Key: StorageKeyDeploymentArtifactName,
122
                                Value: "text"},
123
                },
124
                Options: &mopts.IndexOptions{
125
                        Background: &_false,
126
                        Name:       &IndexDeploymentArtifactName,
127
                },
128
        }
129
        StatusIndexes = mongo.IndexModel{
130
                Keys: bson.D{
131
                        {Key: StorageKeyDeviceDeploymentDeviceId,
132
                                Value: 1},
133
                        {Key: StorageKeyDeviceDeploymentStatus,
134
                                Value: 1},
135
                        {Key: StorageKeyDeploymentStatsCreated,
136
                                Value: 1},
137
                },
138
                Options: &mopts.IndexOptions{
139
                        Background: &_false,
140
                        Name:       &IndexDeploymentDeviceStatusesName,
141
                },
142
        }
143
        DeploymentStatusIndex = mongo.IndexModel{
144
                Keys: bson.D{
145
                        {Key: StorageKeyDeviceDeploymentStatus,
146
                                Value: 1},
147
                },
148
                Options: &mopts.IndexOptions{
149
                        Background: &_false,
150
                        Name:       &IndexDeploymentStatus,
151
                },
152
        }
153
        DeviceIDStatusIndexes = mongo.IndexModel{
154
                Keys: bson.D{
155
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
156
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
157
                },
158
                Options: &mopts.IndexOptions{
159
                        Background: &_false,
160
                        Name:       &IndexDeploymentDeviceIdStatusName,
161
                },
162
        }
163
        DeviceIDCreatedStatusIndex = mongo.IndexModel{
164
                Keys: bson.D{
165
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
166
                        {Key: StorageKeyDeploymentStatsCreated, Value: 1},
167
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
168
                },
169
                Options: &mopts.IndexOptions{
170
                        Background: &_false,
171
                        Name:       &IndexDeploymentDeviceCreatedStatusName,
172
                },
173
        }
174
        DeploymentIdIndexes = mongo.IndexModel{
175
                Keys: bson.D{
176
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
177
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
178
                },
179
                Options: &mopts.IndexOptions{
180
                        Background: &_false,
181
                        Name:       &IndexDeploymentDeviceDeploymentIdName,
182
                },
183
        }
184
        DeviceDeploymentIdStatus = mongo.IndexModel{
185
                Keys: bson.D{
186
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
187
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
188
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
189
                },
190
                Options: mopts.Index().
191
                        SetName(IndexDeviceDeploymentStatusName),
192
        }
193
        DeploymentCreatedIndex = mongo.IndexModel{
194
                Keys: bson.D{
195
                        {Key: "created", Value: -1},
196
                },
197
                Options: &mopts.IndexOptions{
198
                        Background: &_false,
199
                        Name:       &IndexDeploymentCreatedName,
200
                },
201
        }
202
        DeploymentDeviceStatusFinishedIndex = mongo.IndexModel{
203
                Keys: bson.D{
204
                        {Key: "finished", Value: 1},
205
                },
206
                Options: &mopts.IndexOptions{
207
                        Background: &_false,
208
                        Name:       &IndexDeploymentDeviceStatusFinishedName,
209
                },
210
        }
211
        UniqueNameVersionIndex = mongo.IndexModel{
212
                Keys: bson.D{
213
                        {Key: StorageKeyImageName,
214
                                Value: 1},
215
                        {Key: StorageKeyImageDeviceTypes,
216
                                Value: 1},
217
                },
218
                Options: &mopts.IndexOptions{
219
                        Background: &_false,
220
                        Name:       &IndexUniqueNameAndDeviceTypeName,
221
                        Unique:     &_true,
222
                },
223
        }
224

225
        // 1.2.3
226
        IndexArtifactNameDepends = mongo.IndexModel{
227
                Keys: bson.D{
228
                        {Key: StorageKeyImageName,
229
                                Value: 1},
230
                        {Key: StorageKeyImageDependsIdx,
231
                                Value: 1},
232
                },
233
                Options: &mopts.IndexOptions{
234
                        Background: &_false,
235
                        Name:       &IndexArtifactNameDependsName,
236
                        Unique:     &_true,
237
                },
238
        }
239

240
        // Indexes 1.2.7
241
        IndexImageMetaDescription      = "image_meta_description"
242
        IndexImageMetaDescriptionModel = mongo.IndexModel{
243
                Keys: bson.D{
244
                        {Key: StorageKeyImageDescription, Value: 1},
245
                },
246
                Options: &mopts.IndexOptions{
247
                        Background: &_false,
248
                        Name:       &IndexImageMetaDescription,
249
                },
250
        }
251

252
        IndexImageMetaArtifactDeviceTypeCompatible      = "image_meta_artifact_device_type_compatible"
253
        IndexImageMetaArtifactDeviceTypeCompatibleModel = mongo.IndexModel{
254
                Keys: bson.D{
255
                        {Key: StorageKeyImageDeviceTypes, Value: 1},
256
                },
257
                Options: &mopts.IndexOptions{
258
                        Background: &_false,
259
                        Name:       &IndexImageMetaArtifactDeviceTypeCompatible,
260
                },
261
        }
262

263
        // Indexes 1.2.8
264
        IndexDeploymentsActiveCreated      = "active_created"
265
        IndexDeploymentsActiveCreatedModel = mongo.IndexModel{
266
                Keys: bson.D{
267
                        {Key: StorageKeyDeploymentCreated, Value: 1},
268
                },
269
                Options: &mopts.IndexOptions{
270
                        Background: &_false,
271
                        Name:       &IndexDeploymentsActiveCreated,
272
                        PartialFilterExpression: bson.M{
273
                                StorageKeyDeploymentActive: true,
274
                        },
275
                },
276
        }
277

278
        // Index 1.2.9
279
        IndexDeviceDeploymentsActiveCreated      = "active_deviceid_created"
280
        IndexDeviceDeploymentsActiveCreatedModel = mongo.IndexModel{
281
                Keys: bson.D{
282
                        {Key: StorageKeyDeviceDeploymentActive, Value: 1},
283
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
284
                        {Key: StorageKeyDeviceDeploymentCreated, Value: 1},
285
                },
286
                Options: mopts.Index().
287
                        SetName(IndexDeviceDeploymentsActiveCreated),
288
        }
289

290
        // Index 1.2.11
291
        IndexDeviceDeploymentsLogs      = "devices_logs"
292
        IndexDeviceDeploymentsLogsModel = mongo.IndexModel{
293
                Keys: bson.D{
294
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
295
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
296
                },
297
                Options: mopts.Index().
298
                        SetName(IndexDeviceDeploymentsLogs),
299
        }
300

301
        // 1.2.13
302
        IndexArtifactProvides = mongo.IndexModel{
303
                Keys: bson.D{
304
                        {Key: model.StorageKeyImageProvidesIdxKey,
305
                                Value: 1},
306
                        {Key: model.StorageKeyImageProvidesIdxValue,
307
                                Value: 1},
308
                },
309
                Options: &mopts.IndexOptions{
310
                        Background: &_false,
311
                        Sparse:     &_true,
312
                        Name:       &IndexArtifactProvidesName,
313
                },
314
        }
315

316
        // 1.2.17
317
        IndexDeploymentName = mongo.IndexModel{
318
                Keys: bson.D{
319
                        {Key: StorageKeyDeploymentName, Value: 1},
320
                        {Key: StorageKeyDeploymentCreated, Value: 1},
321
                },
322
                Options: &mopts.IndexOptions{
323
                        Background: &_true,
324
                        Name:       &IndexNameDeploymentName,
325
                },
326
        }
327
)
328

329
// Errors
330
var (
331
        ErrImagesStorageInvalidID           = errors.New("Invalid id")
332
        ErrImagesStorageInvalidArtifactName = errors.New("Invalid artifact name")
333
        ErrImagesStorageInvalidName         = errors.New("Invalid name")
334
        ErrImagesStorageInvalidDeviceType   = errors.New("Invalid device type")
335
        ErrImagesStorageInvalidImage        = errors.New("Invalid image")
336

337
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
338

339
        ErrDeploymentStorageInvalidDeployment = errors.New("Invalid deployment")
340
        ErrStorageInvalidID                   = errors.New("Invalid id")
341
        ErrStorageNotFound                    = errors.New("Not found")
342
        ErrDeploymentStorageInvalidQuery      = errors.New("Invalid query")
343
        ErrDeploymentStorageCannotExecQuery   = errors.New("Cannot execute query")
344
        ErrStorageInvalidInput                = errors.New("invalid input")
345

346
        ErrLimitNotFound      = errors.New("limit not found")
347
        ErrDevicesCountFailed = errors.New("failed to count devices")
348
        ErrConflictingDepends = errors.New(
349
                "an artifact with the same name and depends already exists",
350
        )
351
        ErrConflictingDeployment = errors.New(
352
                "an active deployment with the same parameter already exists",
353
        )
354
)
355

356
// Database keys
357
const (
358
        // Need to be kept in sync with structure filed names
359
        StorageKeyId       = "_id"
360
        StorageKeyTenantId = "tenant_id"
361

362
        StorageKeyImageProvides    = "meta_artifact.provides"
363
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
364
        StorageKeyImageDepends     = "meta_artifact.depends"
365
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
366
        StorageKeyImageSize        = "size"
367
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
368
        StorageKeyImageName        = "meta_artifact.name"
369
        StorageKeyUpdateType       = "meta_artifact.updates.typeinfo.type"
370
        StorageKeyImageDescription = "meta.description"
371
        StorageKeyImageModified    = "modified"
372

373
        // releases
374
        StorageKeyReleaseName                      = "_id"
375
        StorageKeyReleaseModified                  = "modified"
376
        StorageKeyReleaseTags                      = "tags"
377
        StorageKeyReleaseNotes                     = "notes"
378
        StorageKeyReleaseArtifacts                 = "artifacts"
379
        StorageKeyReleaseArtifactsCount            = "artifacts_count"
380
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
381
                StorageKeyImageDescription
382
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
383
                StorageKeyImageDescription
384
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
385
                StorageKeyImageDeviceTypes
386
        StorageKeyReleaseArtifactsUpdateTypes = StorageKeyReleaseArtifacts + "." +
387
                StorageKeyUpdateType
388
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
389
                StorageKeyImageModified
390
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
391
                StorageKeyId
392
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
393
                StorageKeyImageDependsIdx
394
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
395
                StorageKeyImageProvidesIdx
396

397
        StorageKeyDeviceDeploymentLogMessages = "messages"
398

399
        StorageKeyDeviceDeploymentAssignedImage   = "image"
400
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
401
                "." + StorageKeyId
402

403
        StorageKeyDeviceDeploymentActive         = "active"
404
        StorageKeyDeviceDeploymentCreated        = "created"
405
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
406
        StorageKeyDeviceDeploymentStatus         = "status"
407
        StorageKeyDeviceDeploymentStarted        = "started"
408
        StorageKeyDeviceDeploymentSubState       = "substate"
409
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
410
        StorageKeyDeviceDeploymentFinished       = "finished"
411
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
412
        StorageKeyDeviceDeploymentArtifact       = "image"
413
        StorageKeyDeviceDeploymentRequest        = "request"
414
        StorageKeyDeviceDeploymentDeleted        = "deleted"
415

416
        StorageKeyDeploymentName                = "deploymentconstructor.name"
417
        StorageKeyDeploymentArtifactName        = "deploymentconstructor.artifactname"
418
        StorageKeyDeploymentConstructorChecksum = "deploymentconstructor_checksum"
419
        StorageKeyDeploymentStats               = "stats"
420
        StorageKeyDeploymentActive              = "active"
421
        StorageKeyDeploymentStatus              = "status"
422
        StorageKeyDeploymentCreated             = "created"
423
        StorageKeyDeploymentDeviceList          = "device_list"
424
        StorageKeyDeploymentStatsCreated        = "created"
425
        StorageKeyDeploymentFinished            = "finished"
426
        StorageKeyDeploymentArtifacts           = "artifacts"
427
        StorageKeyDeploymentDeviceCount         = "device_count"
428
        StorageKeyDeploymentMaxDevices          = "max_devices"
429
        StorageKeyDeploymentType                = "type"
430
        StorageKeyDeploymentTotalSize           = "statistics.total_size"
431

432
        StorageKeyStorageSettingsDefaultID      = "settings"
433
        StorageKeyStorageSettingsBucket         = "bucket"
434
        StorageKeyStorageSettingsRegion         = "region"
435
        StorageKeyStorageSettingsKey            = "key"
436
        StorageKeyStorageSettingsSecret         = "secret"
437
        StorageKeyStorageSettingsURI            = "uri"
438
        StorageKeyStorageSettingsExternalURI    = "external_uri"
439
        StorageKeyStorageSettingsToken          = "token"
440
        StorageKeyStorageSettingsForcePathStyle = "force_path_style"
441
        StorageKeyStorageSettingsUseAccelerate  = "use_accelerate"
442

443
        StorageKeyStorageReleaseUpdateTypes = "update_types"
444

445
        ArtifactDependsDeviceType = "device_type"
446
)
447

448
type DataStoreMongo struct {
449
        client *mongo.Client
450
}
451

452
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
3✔
453
        return &DataStoreMongo{
3✔
454
                client: client,
3✔
455
        }
3✔
456
}
3✔
457

458
func NewMongoClient(ctx context.Context, c config.Reader) (*mongo.Client, error) {
2✔
459

2✔
460
        clientOptions := mopts.Client()
2✔
461
        mongoURL := c.GetString(dconfig.SettingMongo)
2✔
462
        if !strings.Contains(mongoURL, "://") {
2✔
463
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
464
                        mongoURL)
×
465
        }
×
466
        clientOptions.ApplyURI(mongoURL)
2✔
467

2✔
468
        username := c.GetString(dconfig.SettingDbUsername)
2✔
469
        if username != "" {
2✔
470
                credentials := mopts.Credential{
×
471
                        Username: c.GetString(dconfig.SettingDbUsername),
×
472
                }
×
473
                password := c.GetString(dconfig.SettingDbPassword)
×
474
                if password != "" {
×
475
                        credentials.Password = password
×
476
                        credentials.PasswordSet = true
×
477
                }
×
478
                clientOptions.SetAuth(credentials)
×
479
        }
480

481
        if c.GetBool(dconfig.SettingDbSSL) {
2✔
482
                tlsConfig := &tls.Config{}
×
483
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
484
                clientOptions.SetTLSConfig(tlsConfig)
×
485
        }
×
486

487
        // Set 10s timeout
488
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
2✔
489
        defer cancel()
2✔
490
        client, err := mongo.Connect(ctx, clientOptions)
2✔
491
        if err != nil {
2✔
492
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
493
        }
×
494

495
        // Validate connection
496
        if err = client.Ping(ctx, nil); err != nil {
2✔
497
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
498
        }
×
499

500
        return client, nil
2✔
501
}
502

503
func (db *DataStoreMongo) Ping(ctx context.Context) error {
2✔
504
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
2✔
505
        return res.Err()
2✔
506
}
2✔
507

508
func (db *DataStoreMongo) setCurrentDbVersion(
509
        ctx context.Context,
510
) error {
1✔
511
        versions, err := migrate.GetMigrationInfo(
1✔
512
                ctx, db.client, mstore.DbFromContext(ctx, DatabaseName))
1✔
513
        if err != nil {
1✔
514
                return errors.Wrap(err, "failed to list applied migrations")
×
515
        }
×
516
        var current migrate.Version
1✔
517
        if len(versions) > 0 {
2✔
518
                // sort applied migrations wrt. version
1✔
519
                sort.Slice(versions, func(i int, j int) bool {
2✔
520
                        return migrate.VersionIsLess(versions[i].Version, versions[j].Version)
1✔
521
                })
1✔
522
                current = versions[len(versions)-1].Version
1✔
523
        }
524
        if currentDbVersion == nil {
2✔
525
                currentDbVersion = map[string]*migrate.Version{}
1✔
526
        }
1✔
527
        currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] = &current
1✔
528
        return nil
1✔
529
}
530

531
func (db *DataStoreMongo) getCurrentDbVersion(
532
        ctx context.Context,
533
) (*migrate.Version, error) {
1✔
534
        if currentDbVersion == nil ||
1✔
535
                currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] == nil {
2✔
536
                if err := db.setCurrentDbVersion(ctx); err != nil {
1✔
537
                        return nil, err
×
538
                }
×
539
        }
540
        return currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)], nil
1✔
541
}
542

543
func (db *DataStoreMongo) GetReleases(
544
        ctx context.Context,
545
        filt *model.ReleaseOrImageFilter,
546
) ([]model.Release, int, error) {
1✔
547
        current, err := db.getCurrentDbVersion(ctx)
1✔
548
        if err != nil {
1✔
549
                return []model.Release{}, 0, err
×
550
        } else if current == nil {
1✔
551
                return []model.Release{}, 0, errors.New("couldn't get current database version")
×
552
        }
×
553
        if migrate.VersionIsLess(*current, migrate.Version{
1✔
554
                Major: 1, Minor: 2, Patch: 15,
1✔
555
        }) {
1✔
556
                return db.getReleases_1_2_14(ctx, filt)
×
557
        } else {
1✔
558
                return db.getReleases_1_2_15(ctx, filt)
1✔
559
        }
1✔
560
}
561

562
func (db *DataStoreMongo) getReleases_1_2_14(
563
        ctx context.Context,
564
        filt *model.ReleaseOrImageFilter,
565
) ([]model.Release, int, error) {
1✔
566
        l := log.FromContext(ctx)
1✔
567
        l.Infof("get releases method version 1.2.14")
1✔
568
        var pipe []bson.D
1✔
569

1✔
570
        pipe = []bson.D{}
1✔
571
        if filt != nil && filt.Name != "" {
2✔
572
                pipe = append(pipe, bson.D{
1✔
573
                        {Key: "$match", Value: bson.M{
1✔
574
                                StorageKeyImageName: bson.M{
1✔
575
                                        "$regex": primitive.Regex{
1✔
576
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
1✔
577
                                                Options: "i",
1✔
578
                                        },
1✔
579
                                },
1✔
580
                        }},
1✔
581
                })
1✔
582
        }
1✔
583

584
        pipe = append(pipe, bson.D{
1✔
585
                // Remove (possibly expensive) sub-documents from pipeline
1✔
586
                {
1✔
587
                        Key: "$project",
1✔
588
                        Value: bson.M{
1✔
589
                                StorageKeyImageDependsIdx:  0,
1✔
590
                                StorageKeyImageProvidesIdx: 0,
1✔
591
                        },
1✔
592
                },
1✔
593
        })
1✔
594

1✔
595
        pipe = append(pipe, bson.D{
1✔
596
                {Key: "$group", Value: bson.D{
1✔
597
                        {Key: "_id", Value: "$" + StorageKeyImageName},
1✔
598
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
1✔
599
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
1✔
600
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
1✔
601
                }},
1✔
602
        })
1✔
603

1✔
604
        if filt != nil && filt.Description != "" {
2✔
605
                pipe = append(pipe, bson.D{
1✔
606
                        {Key: "$match", Value: bson.M{
1✔
607
                                "artifacts." + StorageKeyImageDescription: bson.M{
1✔
608
                                        "$regex": primitive.Regex{
1✔
609
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
1✔
610
                                                Options: "i",
1✔
611
                                        },
1✔
612
                                },
1✔
613
                        }},
1✔
614
                })
1✔
615
        }
1✔
616
        if filt != nil && filt.DeviceType != "" {
1✔
617
                pipe = append(pipe, bson.D{
×
618
                        {Key: "$match", Value: bson.M{
×
619
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
×
620
                                        "$regex": primitive.Regex{
×
621
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
×
622
                                                Options: "i",
×
623
                                        },
×
624
                                },
×
625
                        }},
×
626
                })
×
627
        }
×
628

629
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
1✔
630
        if sortField == "" {
2✔
631
                sortField = "name"
1✔
632
        }
1✔
633
        if sortOrder == 0 {
2✔
634
                sortOrder = 1
1✔
635
        }
1✔
636

637
        page := 1
1✔
638
        perPage := math.MaxInt64
1✔
639
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
2✔
640
                page = filt.Page
1✔
641
                perPage = filt.PerPage
1✔
642
        }
1✔
643
        pipe = append(pipe,
1✔
644
                bson.D{{Key: "$facet", Value: bson.D{
1✔
645
                        {Key: "results", Value: []bson.D{
1✔
646
                                {
1✔
647
                                        {Key: "$sort", Value: bson.D{
1✔
648
                                                {Key: sortField, Value: sortOrder},
1✔
649
                                                {Key: "_id", Value: 1},
1✔
650
                                        }},
1✔
651
                                },
1✔
652
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
1✔
653
                                {{Key: "$limit", Value: int64(perPage)}},
1✔
654
                        }},
1✔
655
                        {Key: "count", Value: []bson.D{
1✔
656
                                {{Key: "$count", Value: "count"}},
1✔
657
                        }},
1✔
658
                }}},
1✔
659
        )
1✔
660

1✔
661
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
662
        collImg := database.Collection(CollectionImages)
1✔
663

1✔
664
        cursor, err := collImg.Aggregate(ctx, pipe)
1✔
665
        if err != nil {
1✔
666
                return []model.Release{}, 0, err
×
667
        }
×
668
        defer cursor.Close(ctx)
1✔
669

1✔
670
        result := struct {
1✔
671
                Results []model.Release       `bson:"results"`
1✔
672
                Count   []struct{ Count int } `bson:"count"`
1✔
673
        }{}
1✔
674
        if !cursor.Next(ctx) {
1✔
675
                return []model.Release{}, 0, nil
×
676
        } else if err = cursor.Decode(&result); err != nil {
1✔
677
                return []model.Release{}, 0, err
×
678
        } else if len(result.Count) == 0 {
2✔
679
                return []model.Release{}, 0, err
1✔
680
        }
1✔
681
        return result.Results, result.Count[0].Count, nil
1✔
682
}
683

684
func (db *DataStoreMongo) getReleases_1_2_15(
685
        ctx context.Context,
686
        filt *model.ReleaseOrImageFilter,
687
) ([]model.Release, int, error) {
2✔
688
        l := log.FromContext(ctx)
2✔
689
        l.Infof("get releases method version 1.2.15")
2✔
690

2✔
691
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
2✔
692
        if sortField == "" {
4✔
693
                sortField = "_id"
2✔
694
        } else if sortField == "name" {
4✔
695
                sortField = StorageKeyReleaseName
1✔
696
        }
1✔
697
        if sortOrder == 0 {
4✔
698
                sortOrder = 1
2✔
699
        }
2✔
700

701
        page := 1
2✔
702
        perPage := DefaultDocumentLimit
2✔
703
        if filt != nil {
4✔
704
                if filt.Page > 0 {
4✔
705
                        page = filt.Page
2✔
706
                }
2✔
707
                if filt.PerPage > 0 {
4✔
708
                        perPage = filt.PerPage
2✔
709
                }
2✔
710
        }
711

712
        opts := &mopts.FindOptions{}
2✔
713
        opts.SetSort(bson.D{{Key: sortField, Value: sortOrder}})
2✔
714
        opts.SetSkip(int64((page - 1) * perPage))
2✔
715
        opts.SetLimit(int64(perPage))
2✔
716
        projection := bson.M{
2✔
717
                StorageKeyReleaseImageDependsIdx:  0,
2✔
718
                StorageKeyReleaseImageProvidesIdx: 0,
2✔
719
        }
2✔
720
        opts.SetProjection(projection)
2✔
721

2✔
722
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
723
        collReleases := database.Collection(CollectionReleases)
2✔
724

2✔
725
        filter := bson.M{}
2✔
726
        if filt != nil {
4✔
727
                if filt.Name != "" {
4✔
728
                        filter[StorageKeyReleaseName] = bson.M{"$regex": primitive.Regex{
2✔
729
                                Pattern: regexp.QuoteMeta(filt.Name) + ".*",
2✔
730
                                Options: "i",
2✔
731
                        }}
2✔
732
                }
2✔
733
                if len(filt.Tags) > 0 {
3✔
734
                        filter[StorageKeyReleaseTags] = bson.M{"$in": filt.Tags}
1✔
735
                }
1✔
736
                if filt.Description != "" {
3✔
737
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": primitive.Regex{
1✔
738
                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
1✔
739
                                Options: "i",
1✔
740
                        }}
1✔
741
                }
1✔
742
                if filt.DeviceType != "" {
3✔
743
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = filt.DeviceType
1✔
744
                }
1✔
745
                if filt.UpdateType != "" {
4✔
746
                        filter[StorageKeyReleaseArtifactsUpdateTypes] = filt.UpdateType
2✔
747
                }
2✔
748
        }
749
        releases := []model.Release{}
2✔
750
        cursor, err := collReleases.Find(ctx, filter, opts)
2✔
751
        if err != nil {
2✔
752
                return []model.Release{}, 0, err
×
753
        }
×
754
        if err := cursor.All(ctx, &releases); err != nil {
2✔
755
                return []model.Release{}, 0, err
×
756
        }
×
757

758
        // TODO: can we return number of all documents in the collection
759
        // using EstimatedDocumentCount?
760
        count, err := collReleases.CountDocuments(ctx, filter)
2✔
761
        if err != nil {
2✔
762
                return []model.Release{}, 0, err
×
763
        }
×
764

765
        if count < 1 {
4✔
766
                return []model.Release{}, int(count), nil
2✔
767
        }
2✔
768
        return releases, int(count), nil
2✔
769
}
770

771
// limits
772
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
1✔
773

1✔
774
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
775
        collLim := database.Collection(CollectionLimits)
1✔
776

1✔
777
        limit := new(model.Limit)
1✔
778
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
1✔
779
                Decode(limit); err != nil {
2✔
780
                if err == mongo.ErrNoDocuments {
2✔
781
                        return nil, ErrLimitNotFound
1✔
782
                }
1✔
783
                return nil, err
×
784
        }
785

786
        return limit, nil
1✔
787
}
788

789
func (db *DataStoreMongo) ProvisionTenant(ctx context.Context, tenantId string) error {
2✔
790

2✔
791
        dbname := mstore.DbNameForTenant(tenantId, DbName)
2✔
792

2✔
793
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
2✔
794
}
2✔
795

796
//images
797

798
// Exists checks if object with ID exists
799
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
800
        var result interface{}
×
801

×
802
        if len(id) == 0 {
×
803
                return false, ErrImagesStorageInvalidID
×
804
        }
×
805

806
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
807
        collImg := database.Collection(CollectionImages)
×
808

×
809
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
810
                Decode(&result); err != nil {
×
811
                if err == mongo.ErrNoDocuments {
×
812
                        return false, nil
×
813
                }
×
814
                return false, err
×
815
        }
816

817
        return true, nil
×
818
}
819

820
// Update provided Image
821
// Return false if not found
822
func (db *DataStoreMongo) Update(ctx context.Context,
823
        image *model.Image) (bool, error) {
1✔
824

1✔
825
        if err := image.Validate(); err != nil {
1✔
826
                return false, err
×
827
        }
×
828

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

1✔
832
        // add special representation of artifact provides
1✔
833
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
834

1✔
835
        image.SetModified(time.Now())
1✔
836
        if res, err := collImg.ReplaceOne(
1✔
837
                ctx, bson.M{"_id": image.Id}, image,
1✔
838
        ); err != nil {
1✔
839
                return false, err
×
840
        } else if res.MatchedCount == 0 {
1✔
841
                return false, nil
×
842
        }
×
843

844
        return true, nil
1✔
845
}
846

847
// ImageByNameAndDeviceType finds image with specified application name and target device type
848
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
849
        name, deviceType string) (*model.Image, error) {
1✔
850

1✔
851
        if len(name) == 0 {
2✔
852
                return nil, ErrImagesStorageInvalidArtifactName
1✔
853
        }
1✔
854

855
        if len(deviceType) == 0 {
2✔
856
                return nil, ErrImagesStorageInvalidDeviceType
1✔
857
        }
1✔
858

859
        // equal to device type & software version (application name + version)
860
        query := bson.M{
1✔
861
                StorageKeyImageName:        name,
1✔
862
                StorageKeyImageDeviceTypes: deviceType,
1✔
863
        }
1✔
864

1✔
865
        // If multiple entries matches, pick the smallest one.
1✔
866
        findOpts := mopts.FindOne()
1✔
867
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
868

1✔
869
        dbName := mstore.DbFromContext(ctx, DatabaseName)
1✔
870
        database := db.client.Database(dbName)
1✔
871
        collImg := database.Collection(CollectionImages)
1✔
872

1✔
873
        // Both we lookup unique object, should be one or none.
1✔
874
        var image model.Image
1✔
875
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
876
                Decode(&image); err != nil {
2✔
877
                if err == mongo.ErrNoDocuments {
2✔
878
                        return nil, nil
1✔
879
                }
1✔
880
                return nil, err
×
881
        }
882

883
        return &image, nil
1✔
884
}
885

886
// ImageByIdsAndDeviceType finds image with id from ids and target device type
887
func (db *DataStoreMongo) ImageByIdsAndDeviceType(ctx context.Context,
888
        ids []string, deviceType string) (*model.Image, error) {
2✔
889

2✔
890
        if len(deviceType) == 0 {
2✔
891
                return nil, ErrImagesStorageInvalidDeviceType
×
892
        }
×
893

894
        if len(ids) == 0 {
2✔
895
                return nil, ErrImagesStorageInvalidID
×
896
        }
×
897

898
        query := bson.D{
2✔
899
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
2✔
900
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
2✔
901
        }
2✔
902

2✔
903
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
904
        collImg := database.Collection(CollectionImages)
2✔
905

2✔
906
        // If multiple entries matches, pick the smallest one
2✔
907
        findOpts := mopts.FindOne()
2✔
908
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
2✔
909

2✔
910
        // Both we lookup unique object, should be one or none.
2✔
911
        var image model.Image
2✔
912
        if err := collImg.FindOne(ctx, query, findOpts).
2✔
913
                Decode(&image); err != nil {
3✔
914
                if err == mongo.ErrNoDocuments {
2✔
915
                        return nil, nil
1✔
916
                }
1✔
917
                return nil, err
×
918
        }
919

920
        return &image, nil
2✔
921
}
922

923
// ImagesByName finds images with specified artifact name
924
func (db *DataStoreMongo) ImagesByName(
925
        ctx context.Context, name string) ([]*model.Image, error) {
2✔
926

2✔
927
        var images []*model.Image
2✔
928

2✔
929
        if len(name) == 0 {
2✔
930
                return nil, ErrImagesStorageInvalidName
×
931
        }
×
932

933
        // equal to artifact name
934
        query := bson.M{
2✔
935
                StorageKeyImageName: name,
2✔
936
        }
2✔
937

2✔
938
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
939
        collImg := database.Collection(CollectionImages)
2✔
940
        cursor, err := collImg.Find(ctx, query)
2✔
941
        if err != nil {
2✔
942
                return nil, err
×
943
        }
×
944
        // Both we lookup unique object, should be one or none.
945
        if err = cursor.All(ctx, &images); err != nil {
2✔
946
                return nil, err
×
947
        }
×
948

949
        return images, nil
2✔
950
}
951

952
func newDependsConflictError(mgoErr mongo.WriteError) *model.ConflictError {
1✔
953
        var err error
1✔
954
        conflictErr := model.NewConflictError(ErrConflictingDepends)
1✔
955
        // Try to lookup the document that caused the index violation:
1✔
956
        if raw, ok := mgoErr.Raw.Lookup("keyValue").DocumentOK(); ok {
2✔
957
                if raw, ok = raw.Lookup(StorageKeyImageDependsIdx).DocumentOK(); ok {
2✔
958
                        var conflicts map[string]interface{}
1✔
959
                        err = bson.Unmarshal([]byte(raw), &conflicts)
1✔
960
                        if err == nil {
2✔
961
                                _ = conflictErr.WithMetadata(
1✔
962
                                        map[string]interface{}{
1✔
963
                                                "conflict": conflicts,
1✔
964
                                        },
1✔
965
                                )
1✔
966
                        }
1✔
967
                }
968
        }
969
        return conflictErr
1✔
970
}
971

972
// Insert persists object
973
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
3✔
974

3✔
975
        if image == nil {
3✔
976
                return ErrImagesStorageInvalidImage
×
977
        }
×
978

979
        if err := image.Validate(); err != nil {
3✔
980
                return err
×
981
        }
×
982

983
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
984
        collImg := database.Collection(CollectionImages)
3✔
985

3✔
986
        // add special representation of artifact provides
3✔
987
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
3✔
988

3✔
989
        _, err := collImg.InsertOne(ctx, image)
3✔
990
        if err != nil {
4✔
991
                var wExc mongo.WriteException
1✔
992
                if errors.As(err, &wExc) {
2✔
993
                        for _, wErr := range wExc.WriteErrors {
2✔
994
                                if !mongo.IsDuplicateKeyError(wErr) {
1✔
995
                                        continue
×
996
                                }
997
                                return newDependsConflictError(wErr)
1✔
998
                        }
999
                }
1000
                return err
×
1001
        }
1002

1003
        return nil
3✔
1004
}
1005

1006
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
2✔
1007
        collUploads := db.client.
2✔
1008
                Database(DatabaseName).
2✔
1009
                Collection(CollectionUploadIntents)
2✔
1010
        if idty := identity.FromContext(ctx); idty != nil {
3✔
1011
                link.TenantID = idty.Tenant
1✔
1012
        }
1✔
1013
        _, err := collUploads.InsertOne(ctx, link)
2✔
1014
        return err
2✔
1015
}
1016

1017
func (db *DataStoreMongo) UpdateUploadIntentStatus(
1018
        ctx context.Context,
1019
        id string,
1020
        from, to model.LinkStatus,
1021
) error {
2✔
1022
        collUploads := db.client.
2✔
1023
                Database(DatabaseName).
2✔
1024
                Collection(CollectionUploadIntents)
2✔
1025
        q := bson.D{
2✔
1026
                {Key: "_id", Value: id},
2✔
1027
                {Key: "status", Value: from},
2✔
1028
        }
2✔
1029
        if idty := identity.FromContext(ctx); idty != nil {
4✔
1030
                q = append(q, bson.E{
2✔
1031
                        Key:   StorageKeyTenantId,
2✔
1032
                        Value: idty.Tenant,
2✔
1033
                })
2✔
1034
        }
2✔
1035
        update := bson.D{{
2✔
1036
                Key: "updated_ts", Value: time.Now(),
2✔
1037
        }}
2✔
1038
        if from != to {
4✔
1039
                update = append(update, bson.E{
2✔
1040
                        Key: "status", Value: to,
2✔
1041
                })
2✔
1042
        }
2✔
1043
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
2✔
1044
                {Key: "$set", Value: update},
2✔
1045
        })
2✔
1046
        if err != nil {
3✔
1047
                return err
1✔
1048
        } else if res.MatchedCount == 0 {
4✔
1049
                return store.ErrNotFound
1✔
1050
        }
1✔
1051
        return nil
2✔
1052
}
1053

1054
func (db *DataStoreMongo) FindUploadLinks(
1055
        ctx context.Context,
1056
        expiredAt time.Time,
1057
) (store.Iterator[model.UploadLink], error) {
1✔
1058
        collUploads := db.client.
1✔
1059
                Database(DatabaseName).
1✔
1060
                Collection(CollectionUploadIntents)
1✔
1061

1✔
1062
        q := bson.D{{
1✔
1063
                Key: "status",
1✔
1064
                Value: bson.D{{
1✔
1065
                        Key:   "$lt",
1✔
1066
                        Value: model.LinkStatusProcessedBit,
1✔
1067
                }},
1✔
1068
        }, {
1✔
1069
                Key: "expire",
1✔
1070
                Value: bson.D{{
1✔
1071
                        Key:   "$lt",
1✔
1072
                        Value: expiredAt,
1✔
1073
                }},
1✔
1074
        }}
1✔
1075
        cur, err := collUploads.Find(ctx, q)
1✔
1076
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1077
}
1✔
1078

1079
// FindImageByID search storage for image with ID, returns nil if not found
1080
func (db *DataStoreMongo) FindImageByID(ctx context.Context,
1081
        id string) (*model.Image, error) {
2✔
1082

2✔
1083
        if len(id) == 0 {
2✔
1084
                return nil, ErrImagesStorageInvalidID
×
1085
        }
×
1086

1087
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1088
        collImg := database.Collection(CollectionImages)
2✔
1089
        projection := bson.M{
2✔
1090
                StorageKeyImageDependsIdx:  0,
2✔
1091
                StorageKeyImageProvidesIdx: 0,
2✔
1092
        }
2✔
1093
        findOptions := mopts.FindOne()
2✔
1094
        findOptions.SetProjection(projection)
2✔
1095

2✔
1096
        var image model.Image
2✔
1097
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
2✔
1098
                Decode(&image); err != nil {
3✔
1099
                if err == mongo.ErrNoDocuments {
2✔
1100
                        return nil, nil
1✔
1101
                }
1✔
1102
                return nil, err
×
1103
        }
1104

1105
        return &image, nil
2✔
1106
}
1107

1108
// IsArtifactUnique checks if there is no artifact with the same artifactName
1109
// supporting one of the device types from deviceTypesCompatible list.
1110
// Returns true, nil if artifact is unique;
1111
// false, nil if artifact is not unique;
1112
// false, error in case of error.
1113
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1114
        artifactName string, deviceTypesCompatible []string) (bool, error) {
3✔
1115

3✔
1116
        if len(artifactName) == 0 {
4✔
1117
                return false, ErrImagesStorageInvalidArtifactName
1✔
1118
        }
1✔
1119

1120
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1121
        collImg := database.Collection(CollectionImages)
3✔
1122

3✔
1123
        query := bson.M{
3✔
1124
                "$and": []bson.M{
3✔
1125
                        {
3✔
1126
                                StorageKeyImageName: artifactName,
3✔
1127
                        },
3✔
1128
                        {
3✔
1129
                                StorageKeyImageDeviceTypes: bson.M{
3✔
1130
                                        "$in": deviceTypesCompatible},
3✔
1131
                        },
3✔
1132
                },
3✔
1133
        }
3✔
1134

3✔
1135
        // do part of the job manually
3✔
1136
        // if candidate images have any extra 'depends' - guaranteed non-overlap
3✔
1137
        // otherwise it's a match
3✔
1138
        cur, err := collImg.Find(ctx, query)
3✔
1139
        if err != nil {
3✔
1140
                return false, err
×
1141
        }
×
1142

1143
        var images []model.Image
3✔
1144
        err = cur.All(ctx, &images)
3✔
1145
        if err != nil {
3✔
1146
                return false, err
×
1147
        }
×
1148

1149
        for _, i := range images {
4✔
1150
                // the artifact already has same name and overlapping dev type
1✔
1151
                // if there are no more depends than dev type - it's not unique
1✔
1152
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1153
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1154
                                return false, nil
1✔
1155
                        }
1✔
1156
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1157
                        return false, nil
×
1158
                }
×
1159
        }
1160

1161
        return true, nil
3✔
1162
}
1163

1164
// Delete image specified by ID
1165
// Noop on if not found.
1166
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
2✔
1167

2✔
1168
        if len(id) == 0 {
2✔
1169
                return ErrImagesStorageInvalidID
×
1170
        }
×
1171

1172
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1173
        collImg := database.Collection(CollectionImages)
2✔
1174

2✔
1175
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
2✔
1176
                if res.DeletedCount == 0 {
×
1177
                        return nil
×
1178
                }
×
1179
                return err
×
1180
        }
1181

1182
        return nil
2✔
1183
}
1184

1185
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
3✔
1186
        if filt != nil && filt.Sort != "" {
4✔
1187
                sortParts := strings.SplitN(filt.Sort, ":", 2)
1✔
1188
                if len(sortParts) == 2 &&
1✔
1189
                        (sortParts[0] == "name" ||
1✔
1190
                                sortParts[0] == "modified" ||
1✔
1191
                                sortParts[0] == "artifacts_count" ||
1✔
1192
                                sortParts[0] == "tags") {
2✔
1193
                        sortField := sortParts[0]
1✔
1194
                        sortOrder := 1
1✔
1195
                        if sortParts[1] == model.SortDirectionDescending {
2✔
1196
                                sortOrder = -1
1✔
1197
                        }
1✔
1198
                        return sortField, sortOrder
1✔
1199
                }
1200
        }
1201
        return "", 0
3✔
1202
}
1203

1204
// ListImages lists all images
1205
func (db *DataStoreMongo) ListImages(
1206
        ctx context.Context,
1207
        filt *model.ReleaseOrImageFilter,
1208
) ([]*model.Image, int, error) {
3✔
1209

3✔
1210
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1211
        collImg := database.Collection(CollectionImages)
3✔
1212

3✔
1213
        filters := bson.M{}
3✔
1214
        if filt != nil {
6✔
1215
                if filt.Name != "" {
5✔
1216
                        filters[StorageKeyImageName] = bson.M{
2✔
1217
                                "$regex": primitive.Regex{
2✔
1218
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
2✔
1219
                                        Options: "i",
2✔
1220
                                },
2✔
1221
                        }
2✔
1222
                }
2✔
1223
                if filt.Description != "" {
4✔
1224
                        filters[StorageKeyImageDescription] = bson.M{
1✔
1225
                                "$regex": primitive.Regex{
1✔
1226
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
1✔
1227
                                        Options: "i",
1✔
1228
                                },
1✔
1229
                        }
1✔
1230
                }
1✔
1231
                if filt.DeviceType != "" {
4✔
1232
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1233
                                "$regex": primitive.Regex{
1✔
1234
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1235
                                        Options: "i",
1✔
1236
                                },
1✔
1237
                        }
1✔
1238
                }
1✔
1239

1240
        }
1241

1242
        projection := bson.M{
3✔
1243
                StorageKeyImageDependsIdx:  0,
3✔
1244
                StorageKeyImageProvidesIdx: 0,
3✔
1245
        }
3✔
1246
        findOptions := &mopts.FindOptions{}
3✔
1247
        findOptions.SetProjection(projection)
3✔
1248
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
4✔
1249
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1250
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1251
        }
1✔
1252

1253
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
3✔
1254
        if sortField == "" || sortField == "name" {
6✔
1255
                sortField = StorageKeyImageName
3✔
1256
        }
3✔
1257
        if sortOrder == 0 {
6✔
1258
                sortOrder = 1
3✔
1259
        }
3✔
1260
        findOptions.SetSort(bson.D{
3✔
1261
                {Key: sortField, Value: sortOrder},
3✔
1262
                {Key: "_id", Value: sortOrder},
3✔
1263
        })
3✔
1264

3✔
1265
        cursor, err := collImg.Find(ctx, filters, findOptions)
3✔
1266
        if err != nil {
3✔
1267
                return nil, 0, err
×
1268
        }
×
1269

1270
        // NOTE: cursor.All closes the cursor before returning
1271
        var images []*model.Image
3✔
1272
        if err := cursor.All(ctx, &images); err != nil {
3✔
1273
                if err == mongo.ErrNoDocuments {
×
1274
                        return nil, 0, nil
×
1275
                }
×
1276
                return nil, 0, err
×
1277
        }
1278

1279
        count, err := collImg.CountDocuments(ctx, filters)
3✔
1280
        if err != nil {
3✔
1281
                return nil, -1, ErrDevicesCountFailed
×
1282
        }
×
1283

1284
        return images, int(count), nil
3✔
1285
}
1286

1287
/*
1288
// This could be used in the future to support sorting
1289

1290
        func getImagesSortFieldAndOrder(filt *model.ImageFilter) (string, int) {
1291
                if filt != nil && filt.Sort != "" {
1292
                        sortParts := strings.SplitN(filt.Sort, ":", 2)
1293
                        if len(sortParts) == 2 &&
1294
                                (sortParts[0] == "name" ||
1295
                                        sortParts[0] == "modified" ||
1296
                                        sortParts[0] == "artifacts_count" ||
1297
                                        sortParts[0] == "tags") {
1298
                                sortField := sortParts[0]
1299
                                sortOrder := 1
1300
                                if sortParts[1] == model.SortDirectionDescending {
1301
                                        sortOrder = -1
1302
                                }
1303
                                return sortField, sortOrder
1304
                        }
1305
                }
1306
                return "", 0
1307
        }
1308
*/
1309
func (db *DataStoreMongo) ListImagesV2(
1310
        ctx context.Context,
1311
        filt *model.ImageFilter,
1312
) ([]*model.Image, error) {
1✔
1313
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1314
        collImg := database.Collection(CollectionImages)
1✔
1315

1✔
1316
        filters := bson.M{}
1✔
1317

1✔
1318
        if filt != nil {
2✔
1319

1✔
1320
                var nameFilter []interface{}
1✔
1321
                // Check for exact names
1✔
1322
                for _, name := range filt.ExactNames {
2✔
1323
                        nameFilter = append(nameFilter, bson.M{
1✔
1324
                                StorageKeyImageName: name,
1✔
1325
                        })
1✔
1326
                }
1✔
1327
                // Check for prefixes
1328
                for _, name := range filt.NamePrefixes {
2✔
1329
                        nameFilter = append(nameFilter, bson.M{
1✔
1330
                                StorageKeyImageName: bson.M{
1✔
1331
                                        "$regex": primitive.Regex{
1✔
1332
                                                Pattern: "^" + regexp.QuoteMeta(name) + ".*",
1✔
1333
                                                Options: "i",
1✔
1334
                                        },
1✔
1335
                                },
1✔
1336
                        })
1✔
1337
                }
1✔
1338
                if len(nameFilter) > 0 {
2✔
1339
                        filters["$or"] = nameFilter
1✔
1340
                }
1✔
1341

1342
                if filt.Description != "" {
2✔
1343
                        if strings.HasSuffix(filt.Description, "*") {
2✔
1344
                                description := strings.TrimSuffix(filt.Description, "*")
1✔
1345
                                filters[StorageKeyImageDescription] = bson.M{
1✔
1346
                                        "$regex": primitive.Regex{
1✔
1347
                                                Pattern: "^" + regexp.QuoteMeta(description) + ".*",
1✔
1348
                                                Options: "i",
1✔
1349
                                        },
1✔
1350
                                }
1✔
1351
                        } else {
2✔
1352
                                filters[StorageKeyImageDescription] = filt.Description
1✔
1353
                        }
1✔
1354
                }
1355

1356
                if filt.DeviceType != "" {
2✔
1357
                        if strings.HasSuffix(filt.DeviceType, "*") {
2✔
1358
                                deviceType := strings.TrimSuffix(filt.DeviceType, "*")
1✔
1359
                                filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1360
                                        "$regex": primitive.Regex{
1✔
1361
                                                Pattern: "^" + regexp.QuoteMeta(deviceType) + ".*",
1✔
1362
                                                Options: "i",
1✔
1363
                                        },
1✔
1364
                                }
1✔
1365
                        } else {
2✔
1366
                                filters[StorageKeyImageDeviceTypes] = filt.DeviceType
1✔
1367
                        }
1✔
1368
                }
1369
        }
1370

1371
        projection := bson.M{
1✔
1372
                StorageKeyImageDependsIdx:  0,
1✔
1373
                StorageKeyImageProvidesIdx: 0,
1✔
1374
        }
1✔
1375
        findOptions := &mopts.FindOptions{}
1✔
1376
        findOptions.SetProjection(projection)
1✔
1377
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
2✔
1378
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1379
                if filt.Limit > 0 {
1✔
NEW
1380
                        findOptions.SetLimit(int64(filt.Limit))
×
1381
                } else {
1✔
1382
                        findOptions.SetLimit(int64(filt.PerPage))
1✔
1383
                }
1✔
1384
        }
1385
        /*
1386
                sortField, sortOrder := getImagesSortFieldAndOrder(filt)
1387
                if sortField == "" || sortField == "name" {
1388
                        sortField = StorageKeyImageName
1389
                }
1390
                if sortOrder == 0 {
1391
                        sortOrder = 1
1392
                }
1393
        */
1394
        sortField := StorageKeyImageName
1✔
1395
        sortOrder := 1
1✔
1396
        findOptions.SetSort(bson.D{
1✔
1397
                {Key: sortField, Value: sortOrder},
1✔
1398
                {Key: "_id", Value: sortOrder},
1✔
1399
        })
1✔
1400

1✔
1401
        cursor, err := collImg.Find(ctx, filters, findOptions)
1✔
1402
        if err != nil {
1✔
NEW
1403
                return nil, err
×
NEW
1404
        }
×
1405

1406
        // NOTE: cursor.All closes the cursor before returning
1407
        var images []*model.Image
1✔
1408
        if err := cursor.All(ctx, &images); err != nil {
1✔
NEW
1409
                if err == mongo.ErrNoDocuments {
×
NEW
1410
                        return nil, nil
×
NEW
1411
                }
×
NEW
1412
                return nil, err
×
1413
        }
1414

1415
        return images, nil
1✔
1416
}
1417

1418
func (db *DataStoreMongo) DeleteImagesByNames(ctx context.Context, names []string) error {
1✔
1419
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1420
        collDevs := database.Collection(CollectionImages)
1✔
1421
        query := bson.M{
1✔
1422
                StorageKeyImageName: bson.M{
1✔
1423
                        "$in": names,
1✔
1424
                },
1✔
1425
        }
1✔
1426
        _, err := collDevs.DeleteMany(ctx, query)
1✔
1427
        return err
1✔
1428
}
1✔
1429

1430
// device deployment log
1431
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1432
        log model.DeploymentLog) error {
2✔
1433

2✔
1434
        if err := log.Validate(); err != nil {
3✔
1435
                return err
1✔
1436
        }
1✔
1437

1438
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1439
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
2✔
1440

2✔
1441
        query := bson.D{
2✔
1442
                {Key: StorageKeyDeviceDeploymentDeviceId,
2✔
1443
                        Value: log.DeviceID},
2✔
1444
                {Key: StorageKeyDeviceDeploymentDeploymentID,
2✔
1445
                        Value: log.DeploymentID},
2✔
1446
        }
2✔
1447

2✔
1448
        // update log messages
2✔
1449
        // if the deployment log is already present than messages will be overwritten
2✔
1450
        update := bson.D{
2✔
1451
                {Key: "$set", Value: bson.M{
2✔
1452
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
2✔
1453
                }},
2✔
1454
        }
2✔
1455
        updateOptions := mopts.Update()
2✔
1456
        updateOptions.SetUpsert(true)
2✔
1457
        if _, err := collLogs.UpdateOne(
2✔
1458
                ctx, query, update, updateOptions); err != nil {
2✔
UNCOV
1459
                return err
×
UNCOV
1460
        }
×
1461

1462
        return nil
2✔
1463
}
1464

1465
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1466
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
2✔
1467

2✔
1468
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1469
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
2✔
1470

2✔
1471
        query := bson.M{
2✔
1472
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
2✔
1473
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
2✔
1474
        }
2✔
1475

2✔
1476
        var depl model.DeploymentLog
2✔
1477
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
3✔
1478
                if err == mongo.ErrNoDocuments {
2✔
1479
                        return nil, nil
1✔
1480
                }
1✔
UNCOV
1481
                return nil, err
×
1482
        }
1483

1484
        return &depl, nil
2✔
1485
}
1486

1487
// device deployments
1488

1489
// Insert persists device deployment object
1490
func (db *DataStoreMongo) InsertDeviceDeployment(
1491
        ctx context.Context,
1492
        deviceDeployment *model.DeviceDeployment,
1493
        incrementDeviceCount bool,
1494
) error {
3✔
1495
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1496
        c := database.Collection(CollectionDevices)
3✔
1497

3✔
1498
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
4✔
1499
                startedTime := time.Now().UTC()
1✔
1500
                deviceDeployment.Started = &startedTime
1✔
1501
        }
1✔
1502

1503
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
3✔
UNCOV
1504
                return err
×
UNCOV
1505
        }
×
1506

1507
        if incrementDeviceCount {
6✔
1508
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
3✔
1509
                if err != nil {
3✔
UNCOV
1510
                        return err
×
UNCOV
1511
                }
×
1512
        }
1513

1514
        return nil
3✔
1515
}
1516

1517
// InsertMany stores multiple device deployment objects.
1518
// TODO: Handle error cleanup, multi insert is not atomic, loop into two-phase commits
1519
func (db *DataStoreMongo) InsertMany(ctx context.Context,
1520
        deployments ...*model.DeviceDeployment) error {
1✔
1521

1✔
1522
        if len(deployments) == 0 {
2✔
1523
                return nil
1✔
1524
        }
1✔
1525

1526
        deviceCountIncrements := make(map[string]int)
1✔
1527

1✔
1528
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
1✔
1529
        var list []interface{}
1✔
1530
        for _, deployment := range deployments {
2✔
1531

1✔
1532
                if deployment == nil {
2✔
1533
                        return ErrStorageInvalidDeviceDeployment
1✔
1534
                }
1✔
1535

1536
                if err := deployment.Validate(); err != nil {
2✔
1537
                        return errors.Wrap(err, "Validating device deployment")
1✔
1538
                }
1✔
1539

1540
                list = append(list, deployment)
1✔
1541
                if deployment.Status != model.DeviceDeploymentStatusPending {
2✔
1542
                        startedTime := time.Now().UTC()
1✔
1543
                        deployment.Started = &startedTime
1✔
1544
                }
1✔
1545
                deviceCountIncrements[deployment.DeploymentId]++
1✔
1546
        }
1547

1548
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1549
        collDevs := database.Collection(CollectionDevices)
1✔
1550

1✔
1551
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
1✔
UNCOV
1552
                return err
×
UNCOV
1553
        }
×
1554

1555
        for deploymentID := range deviceCountIncrements {
2✔
1556
                err := db.IncrementDeploymentDeviceCount(
1✔
1557
                        ctx,
1✔
1558
                        deploymentID,
1✔
1559
                        deviceCountIncrements[deploymentID],
1✔
1560
                )
1✔
1561
                if err != nil {
1✔
UNCOV
1562
                        return err
×
UNCOV
1563
                }
×
1564
        }
1565

1566
        return nil
1✔
1567
}
1568

1569
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1570
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1571
        ctx context.Context,
1572
        deviceID string,
1573
) (*model.DeviceDeployment, error) {
3✔
1574

3✔
1575
        // Verify ID formatting
3✔
1576
        if len(deviceID) == 0 {
4✔
1577
                return nil, ErrStorageInvalidID
1✔
1578
        }
1✔
1579

1580
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1581
        collDevs := database.Collection(CollectionDevices)
3✔
1582

3✔
1583
        // Device should know only about deployments that are not finished
3✔
1584
        query := bson.D{
3✔
1585
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
3✔
1586
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
3✔
1587
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
3✔
1588
                        {Key: "$exists", Value: false},
3✔
1589
                }},
3✔
1590
        }
3✔
1591

3✔
1592
        // Find the oldest one by sorting the creation timestamp
3✔
1593
        // in ascending order.
3✔
1594
        findOptions := mopts.FindOne()
3✔
1595
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
3✔
1596

3✔
1597
        // Select only the oldest one that have not been finished yet.
3✔
1598
        deployment := new(model.DeviceDeployment)
3✔
1599
        if err := collDevs.FindOne(ctx, query, findOptions).
3✔
1600
                Decode(deployment); err != nil {
6✔
1601
                if err == mongo.ErrNoDocuments {
6✔
1602
                        return nil, nil
3✔
1603
                }
3✔
1604
                return nil, err
1✔
1605
        }
1606

1607
        return deployment, nil
2✔
1608
}
1609

1610
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1611
// matching device id that has not finished yet.
1612
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1613
        ctx context.Context,
1614
        deviceID string,
1615
) (*model.DeviceDeployment, error) {
3✔
1616

3✔
1617
        // Verify ID formatting
3✔
1618
        if len(deviceID) == 0 {
4✔
1619
                return nil, ErrStorageInvalidID
1✔
1620
        }
1✔
1621

1622
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1623
        collDevs := database.Collection(CollectionDevices)
3✔
1624

3✔
1625
        query := bson.D{
3✔
1626
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
3✔
1627
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
3✔
1628
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
3✔
1629
                        {Key: "$exists", Value: false},
3✔
1630
                }},
3✔
1631
        }
3✔
1632

3✔
1633
        // Find the latest one by sorting by the creation timestamp
3✔
1634
        // in ascending order.
3✔
1635
        findOptions := mopts.FindOne()
3✔
1636
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
3✔
1637

3✔
1638
        // Select only the latest one that have not been finished yet.
3✔
1639
        var deployment *model.DeviceDeployment
3✔
1640
        if err := collDevs.FindOne(ctx, query, findOptions).
3✔
1641
                Decode(&deployment); err != nil {
6✔
1642
                if err == mongo.ErrNoDocuments {
6✔
1643
                        return nil, nil
3✔
1644
                }
3✔
1645
                return nil, err
1✔
1646
        }
1647

1648
        return deployment, nil
3✔
1649
}
1650

1651
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1652
        ctx context.Context,
1653
        deviceID string,
1654
        deploymentID string,
1655
        ddState model.DeviceDeploymentState,
1656
        currentStatus model.DeviceDeploymentStatus,
1657
) (model.DeviceDeploymentStatus, error) {
3✔
1658

3✔
1659
        // Verify ID formatting
3✔
1660
        if len(deviceID) == 0 ||
3✔
1661
                len(deploymentID) == 0 {
4✔
1662
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
1✔
1663
        }
1✔
1664

1665
        if err := ddState.Validate(); err != nil {
4✔
1666
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1667
        }
1✔
1668

1669
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1670
        collDevs := database.Collection(CollectionDevices)
3✔
1671

3✔
1672
        // Device should know only about deployments that are not finished
3✔
1673
        query := bson.D{
3✔
1674
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
3✔
1675
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
3✔
1676
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
3✔
1677
                        {Key: "$exists", Value: false},
3✔
1678
                }},
3✔
1679
        }
3✔
1680

3✔
1681
        // update status field
3✔
1682
        set := bson.M{
3✔
1683
                StorageKeyDeviceDeploymentStatus: ddState.Status,
3✔
1684
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
3✔
1685
        }
3✔
1686
        // and finish time if provided
3✔
1687
        if ddState.FinishTime != nil {
6✔
1688
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
3✔
1689
        }
3✔
1690

1691
        if len(ddState.SubState) > 0 {
5✔
1692
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
2✔
1693
        }
2✔
1694

1695
        if currentStatus == model.DeviceDeploymentStatusPending &&
3✔
1696
                ddState.Status != currentStatus {
6✔
1697
                startedTime := time.Now().UTC()
3✔
1698
                set[StorageKeyDeviceDeploymentStarted] = startedTime
3✔
1699
        }
3✔
1700

1701
        update := bson.D{
3✔
1702
                {Key: "$set", Value: set},
3✔
1703
        }
3✔
1704

3✔
1705
        var old model.DeviceDeployment
3✔
1706

3✔
1707
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
3✔
1708
                Decode(&old); err != nil {
4✔
1709
                if err == mongo.ErrNoDocuments {
2✔
1710
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
1✔
1711
                }
1✔
UNCOV
1712
                return model.DeviceDeploymentStatusNull, err
×
1713

1714
        }
1715

1716
        return old.Status, nil
3✔
1717
}
1718

1719
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1720
        deviceID string, deploymentID string, log bool) error {
2✔
1721

2✔
1722
        // Verify ID formatting
2✔
1723
        if len(deviceID) == 0 ||
2✔
1724
                len(deploymentID) == 0 {
3✔
1725
                return ErrStorageInvalidID
1✔
1726
        }
1✔
1727

1728
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1729
        collDevs := database.Collection(CollectionDevices)
2✔
1730

2✔
1731
        selector := bson.D{
2✔
1732
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
2✔
1733
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
2✔
1734
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
2✔
1735
                        {Key: "$exists", Value: false},
2✔
1736
                }},
2✔
1737
        }
2✔
1738

2✔
1739
        update := bson.D{
2✔
1740
                {Key: "$set", Value: bson.M{
2✔
1741
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
2✔
1742
        }
2✔
1743

2✔
1744
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
2✔
UNCOV
1745
                return err
×
1746
        } else if res.MatchedCount == 0 {
3✔
1747
                return ErrStorageNotFound
1✔
1748
        }
1✔
1749

1750
        return nil
2✔
1751
}
1752

1753
// SaveDeviceDeploymentRequest saves device deployment request
1754
// with the device deployment object
1755
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1756
        ctx context.Context,
1757
        ID string,
1758
        request *model.DeploymentNextRequest,
1759
) error {
3✔
1760

3✔
1761
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1762
        collDevs := database.Collection(CollectionDevices)
3✔
1763

3✔
1764
        res, err := collDevs.UpdateOne(
3✔
1765
                ctx,
3✔
1766
                bson.D{{Key: StorageKeyId, Value: ID}},
3✔
1767
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
3✔
1768
        )
3✔
1769
        if err != nil {
3✔
UNCOV
1770
                return err
×
1771
        } else if res.MatchedCount == 0 {
4✔
1772
                return ErrStorageNotFound
1✔
1773
        }
1✔
1774
        return nil
3✔
1775
}
1776

1777
// AssignArtifact assigns artifact to the device deployment
1778
func (db *DataStoreMongo) AssignArtifact(
1779
        ctx context.Context,
1780
        deviceID string,
1781
        deploymentID string,
1782
        artifact *model.Image,
1783
) error {
2✔
1784

2✔
1785
        // Verify ID formatting
2✔
1786
        if len(deviceID) == 0 ||
2✔
1787
                len(deploymentID) == 0 {
2✔
UNCOV
1788
                return ErrStorageInvalidID
×
UNCOV
1789
        }
×
1790

1791
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1792
        collDevs := database.Collection(CollectionDevices)
2✔
1793

2✔
1794
        selector := bson.D{
2✔
1795
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
2✔
1796
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
2✔
1797
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
2✔
1798
                        {Key: "$exists", Value: false},
2✔
1799
                }},
2✔
1800
        }
2✔
1801

2✔
1802
        update := bson.D{
2✔
1803
                {Key: "$set", Value: bson.M{
2✔
1804
                        StorageKeyDeviceDeploymentArtifact: artifact,
2✔
1805
                }},
2✔
1806
        }
2✔
1807

2✔
1808
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
2✔
1809
                return err
×
1810
        } else if res.MatchedCount == 0 {
2✔
UNCOV
1811
                return ErrStorageNotFound
×
UNCOV
1812
        }
×
1813

1814
        return nil
2✔
1815
}
1816

1817
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1818
        id string) (model.Stats, error) {
2✔
1819

2✔
1820
        if len(id) == 0 {
2✔
UNCOV
1821
                return nil, ErrStorageInvalidID
×
UNCOV
1822
        }
×
1823

1824
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1825
        collDevs := database.Collection(CollectionDevices)
2✔
1826

2✔
1827
        match := bson.D{
2✔
1828
                {Key: "$match", Value: bson.M{
2✔
1829
                        StorageKeyDeviceDeploymentDeploymentID: id,
2✔
1830
                        StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
1831
                                {Key: "$exists", Value: false},
2✔
1832
                        },
2✔
1833
                }},
2✔
1834
        }
2✔
1835
        group := bson.D{
2✔
1836
                {Key: "$group", Value: bson.D{
2✔
1837
                        {Key: "_id",
2✔
1838
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
2✔
1839
                        {Key: "count",
2✔
1840
                                Value: bson.M{"$sum": 1}}},
2✔
1841
                },
2✔
1842
        }
2✔
1843
        pipeline := []bson.D{
2✔
1844
                match,
2✔
1845
                group,
2✔
1846
        }
2✔
1847
        var results []struct {
2✔
1848
                Status model.DeviceDeploymentStatus `bson:"_id"`
2✔
1849
                Count  int
2✔
1850
        }
2✔
1851
        cursor, err := collDevs.Aggregate(ctx, pipeline)
2✔
1852
        if err != nil {
2✔
1853
                return nil, err
×
1854
        }
×
1855
        if err := cursor.All(ctx, &results); err != nil {
2✔
1856
                if err == mongo.ErrNoDocuments {
×
UNCOV
1857
                        return nil, nil
×
UNCOV
1858
                }
×
UNCOV
1859
                return nil, err
×
1860
        }
1861

1862
        raw := model.NewDeviceDeploymentStats()
2✔
1863
        for _, res := range results {
4✔
1864
                raw.Set(res.Status, res.Count)
2✔
1865
        }
2✔
1866
        return raw, nil
2✔
1867
}
1868

1869
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1870
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1871
        deploymentID string) ([]model.DeviceDeployment, error) {
3✔
1872

3✔
1873
        statuses := []model.DeviceDeployment{}
3✔
1874
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1875
        collDevs := database.Collection(CollectionDevices)
3✔
1876

3✔
1877
        query := bson.M{
3✔
1878
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
3✔
1879
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
1880
                        {Key: "$exists", Value: false},
3✔
1881
                },
3✔
1882
        }
3✔
1883

3✔
1884
        cursor, err := collDevs.Find(ctx, query)
3✔
1885
        if err != nil {
3✔
UNCOV
1886
                return nil, err
×
1887
        }
×
1888

1889
        if err = cursor.All(ctx, &statuses); err != nil {
3✔
1890
                if err == mongo.ErrNoDocuments {
×
UNCOV
1891
                        return nil, nil
×
UNCOV
1892
                }
×
UNCOV
1893
                return nil, err
×
1894
        }
1895

1896
        return statuses, nil
3✔
1897
}
1898

1899
func (db *DataStoreMongo) GetDevicesListForDeployment(ctx context.Context,
1900
        q store.ListQuery) ([]model.DeviceDeployment, int, error) {
2✔
1901

2✔
1902
        statuses := []model.DeviceDeployment{}
2✔
1903
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1904
        collDevs := database.Collection(CollectionDevices)
2✔
1905

2✔
1906
        query := bson.D{
2✔
1907
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
2✔
1908
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
2✔
1909
                        {Key: "$exists", Value: false},
2✔
1910
                }},
2✔
1911
        }
2✔
1912
        if q.Status != nil {
3✔
1913
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
2✔
1914
                        query = append(query, bson.E{
1✔
1915
                                Key: "status", Value: bson.D{{
1✔
1916
                                        Key:   "$gte",
1✔
1917
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1918
                                }, {
1✔
1919
                                        Key:   "$lte",
1✔
1920
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1921
                                }},
1✔
1922
                        })
1✔
1923
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
2✔
1924
                        query = append(query, bson.E{
×
1925
                                Key: "status", Value: bson.D{{
×
1926
                                        Key:   "$gte",
×
1927
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1928
                                }, {
×
1929
                                        Key:   "$lte",
×
UNCOV
1930
                                        Value: model.DeviceDeploymentStatusPending,
×
UNCOV
1931
                                }},
×
UNCOV
1932
                        })
×
1933
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
2✔
1934
                        query = append(query, bson.E{
1✔
1935
                                Key: "status", Value: bson.D{{
1✔
1936
                                        Key: "$in",
1✔
1937
                                        Value: []model.DeviceDeploymentStatus{
1✔
1938
                                                model.DeviceDeploymentStatusFailure,
1✔
1939
                                                model.DeviceDeploymentStatusAborted,
1✔
1940
                                                model.DeviceDeploymentStatusSuccess,
1✔
1941
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1942
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1943
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1944
                                        },
1✔
1945
                                }},
1✔
1946
                        })
1✔
1947
                } else {
2✔
1948
                        var status model.DeviceDeploymentStatus
1✔
1949
                        err := status.UnmarshalText([]byte(*q.Status))
1✔
1950
                        if err != nil {
2✔
1951
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1952
                        }
1✔
1953
                        query = append(query, bson.E{
1✔
1954
                                Key: "status", Value: status,
1✔
1955
                        })
1✔
1956
                }
1957
        }
1958

1959
        options := mopts.Find()
2✔
1960
        sortFieldQuery := bson.D{
2✔
1961
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
2✔
1962
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
2✔
1963
        }
2✔
1964
        options.SetSort(sortFieldQuery)
2✔
1965
        if q.Skip > 0 {
4✔
1966
                options.SetSkip(int64(q.Skip))
2✔
1967
        }
2✔
1968
        if q.Limit > 0 {
4✔
1969
                options.SetLimit(int64(q.Limit))
2✔
1970
        } else {
3✔
1971
                options.SetLimit(DefaultDocumentLimit)
1✔
1972
        }
1✔
1973

1974
        cursor, err := collDevs.Find(ctx, query, options)
2✔
1975
        if err != nil {
3✔
1976
                return nil, -1, err
1✔
1977
        }
1✔
1978

1979
        if err = cursor.All(ctx, &statuses); err != nil {
2✔
1980
                if err == mongo.ErrNoDocuments {
×
UNCOV
1981
                        return nil, -1, nil
×
UNCOV
1982
                }
×
UNCOV
1983
                return nil, -1, err
×
1984
        }
1985

1986
        count, err := collDevs.CountDocuments(ctx, query)
2✔
1987
        if err != nil {
2✔
UNCOV
1988
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
1989
        }
×
1990

1991
        return statuses, int(count), nil
2✔
1992
}
1993

1994
func (db *DataStoreMongo) GetDeviceDeploymentsForDevice(ctx context.Context,
1995
        q store.ListQueryDeviceDeployments) ([]model.DeviceDeployment, int, error) {
2✔
1996

2✔
1997
        statuses := []model.DeviceDeployment{}
2✔
1998
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1999
        collDevs := database.Collection(CollectionDevices)
2✔
2000

2✔
2001
        query := bson.D{}
2✔
2002
        if q.DeviceID != "" {
4✔
2003
                query = append(query, bson.E{
2✔
2004
                        Key:   StorageKeyDeviceDeploymentDeviceId,
2✔
2005
                        Value: q.DeviceID,
2✔
2006
                })
2✔
2007
        } else if len(q.IDs) > 0 {
4✔
2008
                query = append(query, bson.E{
1✔
2009
                        Key: StorageKeyId,
1✔
2010
                        Value: bson.D{{
1✔
2011
                                Key:   "$in",
1✔
2012
                                Value: q.IDs,
1✔
2013
                        }},
1✔
2014
                })
1✔
2015
        }
1✔
2016

2017
        if q.Status != nil {
3✔
2018
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
2✔
2019
                        query = append(query, bson.E{
1✔
2020
                                Key: "status", Value: bson.D{{
1✔
2021
                                        Key:   "$gte",
1✔
2022
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
2023
                                }, {
1✔
2024
                                        Key:   "$lte",
1✔
2025
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
2026
                                }},
1✔
2027
                        })
1✔
2028
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
3✔
2029
                        query = append(query, bson.E{
1✔
2030
                                Key: "status", Value: bson.D{{
1✔
2031
                                        Key:   "$gte",
1✔
2032
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
2033
                                }, {
1✔
2034
                                        Key:   "$lte",
1✔
2035
                                        Value: model.DeviceDeploymentStatusPending,
1✔
2036
                                }},
1✔
2037
                        })
1✔
2038
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
3✔
2039
                        query = append(query, bson.E{
1✔
2040
                                Key: "status", Value: bson.D{{
1✔
2041
                                        Key: "$in",
1✔
2042
                                        Value: []model.DeviceDeploymentStatus{
1✔
2043
                                                model.DeviceDeploymentStatusFailure,
1✔
2044
                                                model.DeviceDeploymentStatusAborted,
1✔
2045
                                                model.DeviceDeploymentStatusSuccess,
1✔
2046
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
2047
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
2048
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
2049
                                        },
1✔
2050
                                }},
1✔
2051
                        })
1✔
2052
                } else {
2✔
2053
                        var status model.DeviceDeploymentStatus
1✔
2054
                        err := status.UnmarshalText([]byte(*q.Status))
1✔
2055
                        if err != nil {
2✔
2056
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
2057
                        }
1✔
2058
                        query = append(query, bson.E{
1✔
2059
                                Key: "status", Value: status,
1✔
2060
                        })
1✔
2061
                }
2062
        }
2063

2064
        options := mopts.Find()
2✔
2065
        sortFieldQuery := bson.D{
2✔
2066
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
2✔
2067
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
2✔
2068
        }
2✔
2069
        options.SetSort(sortFieldQuery)
2✔
2070
        if q.Skip > 0 {
3✔
2071
                options.SetSkip(int64(q.Skip))
1✔
2072
        }
1✔
2073
        if q.Limit > 0 {
4✔
2074
                options.SetLimit(int64(q.Limit))
2✔
2075
        } else {
2✔
UNCOV
2076
                options.SetLimit(DefaultDocumentLimit)
×
UNCOV
2077
        }
×
2078

2079
        cursor, err := collDevs.Find(ctx, query, options)
2✔
2080
        if err != nil {
2✔
UNCOV
2081
                return nil, -1, err
×
2082
        }
×
2083

2084
        if err = cursor.All(ctx, &statuses); err != nil {
2✔
2085
                if err == mongo.ErrNoDocuments {
×
UNCOV
2086
                        return nil, 0, nil
×
UNCOV
2087
                }
×
UNCOV
2088
                return nil, -1, err
×
2089
        }
2090

2091
        maxCount := maxCountDocuments
2✔
2092
        countOptions := &mopts.CountOptions{
2✔
2093
                Limit: &maxCount,
2✔
2094
        }
2✔
2095
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
2✔
2096
        if err != nil {
2✔
UNCOV
2097
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
2098
        }
×
2099

2100
        return statuses, int(count), nil
2✔
2101
}
2102

2103
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
2104
// `deviceID`, false otherwise. In case of errors returns false and an error
2105
// that occurred
2106
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
2107
        deploymentID string, deviceID string) (bool, error) {
2✔
2108

2✔
2109
        var dep model.DeviceDeployment
2✔
2110
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2111
        collDevs := database.Collection(CollectionDevices)
2✔
2112

2✔
2113
        query := bson.D{
2✔
2114
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
2✔
2115
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
2✔
2116
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
2✔
2117
                        {Key: "$exists", Value: false},
2✔
2118
                }},
2✔
2119
        }
2✔
2120

2✔
2121
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
3✔
2122
                if err == mongo.ErrNoDocuments {
2✔
2123
                        return false, nil
1✔
2124
                } else {
1✔
UNCOV
2125
                        return false, err
×
UNCOV
2126
                }
×
2127
        }
2128

2129
        return true, nil
2✔
2130
}
2131

2132
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2133
        deploymentId string) error {
2✔
2134

2✔
2135
        if len(deploymentId) == 0 {
3✔
2136
                return ErrStorageInvalidID
1✔
2137
        }
1✔
2138

2139
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2140
        collDevs := database.Collection(CollectionDevices)
2✔
2141
        selector := bson.M{
2✔
2142
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
2✔
2143
                StorageKeyDeviceDeploymentActive:       true,
2✔
2144
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2145
                        {Key: "$exists", Value: false},
2✔
2146
                },
2✔
2147
        }
2✔
2148

2✔
2149
        update := bson.M{
2✔
2150
                "$set": bson.M{
2✔
2151
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2152
                        StorageKeyDeviceDeploymentActive: false,
2✔
2153
                },
2✔
2154
        }
2✔
2155

2✔
2156
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
UNCOV
2157
                return err
×
UNCOV
2158
        }
×
2159

2160
        return nil
2✔
2161
}
2162

2163
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2164
        deviceID string) error {
1✔
2165
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2166
        collDevs := database.Collection(CollectionDevices)
1✔
2167
        selector := bson.M{
1✔
2168
                StorageKeyDeviceDeploymentDeviceId: deviceID,
1✔
2169
                StorageKeyDeviceDeploymentActive:   false,
1✔
2170
                StorageKeyDeviceDeploymentDeleted: bson.M{
1✔
2171
                        "$exists": false,
1✔
2172
                },
1✔
2173
        }
1✔
2174

1✔
2175
        now := time.Now()
1✔
2176
        update := bson.M{
1✔
2177
                "$set": bson.M{
1✔
2178
                        StorageKeyDeviceDeploymentDeleted: &now,
1✔
2179
                },
1✔
2180
        }
1✔
2181

1✔
2182
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
UNCOV
2183
                return err
×
UNCOV
2184
        }
×
2185

2186
        database = db.client.Database(DatabaseName)
1✔
2187
        collDevs = database.Collection(CollectionDevicesLastStatus)
1✔
2188
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
1✔
2189

1✔
2190
        return err
1✔
2191
}
2192

2193
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2194
        deviceId string) error {
1✔
2195

1✔
2196
        if len(deviceId) == 0 {
2✔
2197
                return ErrStorageInvalidID
1✔
2198
        }
1✔
2199

2200
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2201
        collDevs := database.Collection(CollectionDevices)
1✔
2202
        selector := bson.M{
1✔
2203
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2204
                StorageKeyDeviceDeploymentActive:   true,
1✔
2205
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2206
                        {Key: "$exists", Value: false},
1✔
2207
                },
1✔
2208
        }
1✔
2209

1✔
2210
        update := bson.M{
1✔
2211
                "$set": bson.M{
1✔
2212
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2213
                        StorageKeyDeviceDeploymentActive: false,
1✔
2214
                },
1✔
2215
        }
1✔
2216

1✔
2217
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
UNCOV
2218
                return err
×
UNCOV
2219
        }
×
2220

2221
        return nil
1✔
2222
}
2223

2224
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2225
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
2✔
2226

2✔
2227
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2228
        collDevs := database.Collection(CollectionDevices)
2✔
2229

2✔
2230
        filter := bson.M{
2✔
2231
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
2✔
2232
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
2✔
2233
        }
2✔
2234
        if !includeDeleted {
4✔
2235
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2236
                        {Key: "$exists", Value: false},
2✔
2237
                }
2✔
2238
        }
2✔
2239

2240
        opts := &mopts.FindOneOptions{}
2✔
2241
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
2✔
2242

2✔
2243
        var dd model.DeviceDeployment
2✔
2244
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
4✔
2245
                if err == mongo.ErrNoDocuments {
4✔
2246
                        return nil, ErrStorageNotFound
2✔
2247
                }
2✔
UNCOV
2248
                return nil, err
×
2249
        }
2250

2251
        return &dd, nil
2✔
2252
}
2253

2254
func (db *DataStoreMongo) GetDeviceDeployments(
2255
        ctx context.Context,
2256
        skip int,
2257
        limit int,
2258
        deviceID string,
2259
        active *bool,
2260
        includeDeleted bool,
2261
) ([]model.DeviceDeployment, error) {
1✔
2262

1✔
2263
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2264
        collDevs := database.Collection(CollectionDevices)
1✔
2265

1✔
2266
        filter := bson.M{}
1✔
2267
        if !includeDeleted {
2✔
2268
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2269
                        {Key: "$exists", Value: false},
1✔
2270
                }
1✔
2271
        }
1✔
2272
        if deviceID != "" {
2✔
2273
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2274
        }
1✔
2275
        if active != nil {
2✔
2276
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2277
        }
1✔
2278

2279
        opts := &mopts.FindOptions{}
1✔
2280
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2281
        if skip > 0 {
2✔
2282
                opts.SetSkip(int64(skip))
1✔
2283
        }
1✔
2284
        if limit > 0 {
2✔
2285
                opts.SetLimit(int64(limit))
1✔
2286
        }
1✔
2287

2288
        var deviceDeployments []model.DeviceDeployment
1✔
2289
        cursor, err := collDevs.Find(ctx, filter, opts)
1✔
2290
        if err != nil {
1✔
2291
                return nil, err
×
2292
        }
×
2293
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
1✔
UNCOV
2294
                return nil, err
×
UNCOV
2295
        }
×
2296

2297
        return deviceDeployments, nil
1✔
2298
}
2299

2300
// deployments
2301

2302
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2303
        indexes ...mongo.IndexModel) error {
3✔
2304
        ctx := context.Background()
3✔
2305
        dataBase := db.client.Database(dbName)
3✔
2306

3✔
2307
        coll := dataBase.Collection(collName)
3✔
2308
        idxView := coll.Indexes()
3✔
2309
        _, err := idxView.CreateMany(ctx, indexes)
3✔
2310
        return err
3✔
2311
}
3✔
2312

2313
// return true if required indexing was set up
2314
func (db *DataStoreMongo) hasIndexing(ctx context.Context, client *mongo.Client) bool {
1✔
2315

1✔
2316
        var idx bson.M
1✔
2317
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2318
        collDpl := database.Collection(CollectionDeployments)
1✔
2319
        idxView := collDpl.Indexes()
1✔
2320

1✔
2321
        cursor, err := idxView.List(ctx)
1✔
2322
        if err != nil {
1✔
UNCOV
2323
                // check failed, assume indexing is not there
×
UNCOV
2324
                return false
×
UNCOV
2325
        }
×
2326

2327
        has := map[string]bool{}
1✔
2328
        for cursor.Next(ctx) {
2✔
2329
                if err = cursor.Decode(&idx); err != nil {
1✔
UNCOV
2330
                        continue
×
2331
                }
2332
                if _, ok := idx["weights"]; ok {
2✔
2333
                        // text index
1✔
2334
                        for k := range idx["weights"].(bson.M) {
2✔
2335
                                has[k] = true
1✔
2336
                        }
1✔
2337
                } else {
1✔
2338
                        for i := range idx["key"].(bson.M) {
2✔
2339
                                has[i] = true
1✔
2340
                        }
1✔
2341

2342
                }
2343
        }
2344
        if err != nil {
1✔
UNCOV
2345
                return false
×
UNCOV
2346
        }
×
2347

2348
        for _, key := range StorageIndexes.Keys.(bson.D) {
2✔
2349
                _, ok := has[key.Key]
1✔
2350
                if !ok {
2✔
2351
                        return false
1✔
2352
                }
1✔
2353
        }
2354

2355
        return true
1✔
2356
}
2357

2358
// Insert persists object
2359
func (db *DataStoreMongo) InsertDeployment(
2360
        ctx context.Context,
2361
        deployment *model.Deployment,
2362
) error {
3✔
2363

3✔
2364
        if deployment == nil {
4✔
2365
                return ErrDeploymentStorageInvalidDeployment
1✔
2366
        }
1✔
2367

2368
        if err := deployment.Validate(); err != nil {
5✔
2369
                return err
2✔
2370
        }
2✔
2371

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

3✔
2375
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
5✔
2376
                if mongo.IsDuplicateKeyError(err) {
4✔
2377
                        return ErrConflictingDeployment
2✔
2378
                }
2✔
UNCOV
2379
                return err
×
2380
        }
2381
        return nil
3✔
2382
}
2383

2384
// Delete removed entry by ID
2385
// Noop on ID not found
2386
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
1✔
2387

1✔
2388
        if len(id) == 0 {
2✔
2389
                return ErrStorageInvalidID
1✔
2390
        }
1✔
2391

2392
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2393
        collDpl := database.Collection(CollectionDeployments)
1✔
2394

1✔
2395
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
UNCOV
2396
                return err
×
UNCOV
2397
        }
×
2398

2399
        return nil
1✔
2400
}
2401

2402
func (db *DataStoreMongo) FindDeploymentByID(
2403
        ctx context.Context,
2404
        id string,
2405
) (*model.Deployment, error) {
3✔
2406

3✔
2407
        if len(id) == 0 {
4✔
2408
                return nil, ErrStorageInvalidID
1✔
2409
        }
1✔
2410

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

3✔
2414
        deployment := new(model.Deployment)
3✔
2415
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
3✔
2416
                Decode(deployment); err != nil {
4✔
2417
                if err == mongo.ErrNoDocuments {
2✔
2418
                        return nil, nil
1✔
2419
                }
1✔
UNCOV
2420
                return nil, err
×
2421
        }
2422

2423
        return deployment, nil
3✔
2424
}
2425

2426
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2427
        ctx context.Context,
2428
        ids ...string,
2429
) (deploymentStats []*model.DeploymentStats, err error) {
1✔
2430

1✔
2431
        if len(ids) == 0 {
1✔
UNCOV
2432
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
UNCOV
2433
        }
×
2434

2435
        for _, id := range ids {
2✔
2436
                if len(id) == 0 {
1✔
UNCOV
2437
                        return nil, ErrStorageInvalidID
×
UNCOV
2438
                }
×
2439
        }
2440

2441
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2442
        collDpl := database.Collection(CollectionDeployments)
1✔
2443

1✔
2444
        query := bson.M{
1✔
2445
                "_id": bson.M{
1✔
2446
                        "$in": ids,
1✔
2447
                },
1✔
2448
        }
1✔
2449
        statsProjection := &mopts.FindOptions{
1✔
2450
                Projection: bson.M{"stats": 1},
1✔
2451
        }
1✔
2452

1✔
2453
        results, err := collDpl.Find(
1✔
2454
                ctx,
1✔
2455
                query,
1✔
2456
                statsProjection,
1✔
2457
        )
1✔
2458
        if err != nil {
1✔
UNCOV
2459
                return nil, err
×
UNCOV
2460
        }
×
2461

2462
        for results.Next(context.Background()) {
2✔
2463
                depl := new(model.DeploymentStats)
1✔
2464
                if err = results.Decode(&depl); err != nil {
1✔
2465
                        if err == mongo.ErrNoDocuments {
×
UNCOV
2466
                                return nil, nil
×
UNCOV
2467
                        }
×
UNCOV
2468
                        return nil, err
×
2469
                }
2470
                deploymentStats = append(deploymentStats, depl)
1✔
2471
        }
2472

2473
        return deploymentStats, nil
1✔
2474
}
2475

2476
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2477
        id string) (*model.Deployment, error) {
2✔
2478

2✔
2479
        if len(id) == 0 {
3✔
2480
                return nil, ErrStorageInvalidID
1✔
2481
        }
1✔
2482

2483
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2484
        collDpl := database.Collection(CollectionDeployments)
2✔
2485

2✔
2486
        var deployment *model.Deployment
2✔
2487
        filter := bson.D{
2✔
2488
                {Key: "_id", Value: id},
2✔
2489
                {Key: StorageKeyDeploymentFinished, Value: nil},
2✔
2490
        }
2✔
2491
        if err := collDpl.FindOne(ctx, filter).
2✔
2492
                Decode(&deployment); err != nil {
4✔
2493
                if err == mongo.ErrNoDocuments {
4✔
2494
                        return nil, nil
2✔
2495
                }
2✔
UNCOV
2496
                return nil, err
×
2497
        }
2498

2499
        return deployment, nil
2✔
2500
}
2501

2502
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2503
        ctx context.Context,
2504
        deploymentID string,
2505
        increment int,
2506
) error {
3✔
2507
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2508
        collection := database.Collection(CollectionDeployments)
3✔
2509

3✔
2510
        filter := bson.M{
3✔
2511
                "_id": deploymentID,
3✔
2512
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2513
                        "$ne": nil,
3✔
2514
                },
3✔
2515
        }
3✔
2516

3✔
2517
        update := bson.M{
3✔
2518
                "$inc": bson.M{
3✔
2519
                        StorageKeyDeploymentDeviceCount: increment,
3✔
2520
                },
3✔
2521
        }
3✔
2522

3✔
2523
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2524
        return err
3✔
2525
}
3✔
2526

2527
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2528
        ctx context.Context,
2529
        deploymentID string,
2530
        count int,
2531
) error {
1✔
2532
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2533
        collection := database.Collection(CollectionDeployments)
1✔
2534

1✔
2535
        filter := bson.M{
1✔
2536
                "_id": deploymentID,
1✔
2537
                StorageKeyDeploymentDeviceCount: bson.M{
1✔
2538
                        "$eq": nil,
1✔
2539
                },
1✔
2540
        }
1✔
2541

1✔
2542
        update := bson.M{
1✔
2543
                "$set": bson.M{
1✔
2544
                        StorageKeyDeploymentDeviceCount: count,
1✔
2545
                },
1✔
2546
        }
1✔
2547

1✔
2548
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2549
        return err
1✔
2550
}
1✔
2551

2552
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2553
        id string) (int, error) {
1✔
2554

1✔
2555
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2556
        collDevs := database.Collection(CollectionDevices)
1✔
2557

1✔
2558
        filter := bson.M{
1✔
2559
                StorageKeyDeviceDeploymentDeploymentID: id,
1✔
2560
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2561
                        {Key: "$exists", Value: false},
1✔
2562
                },
1✔
2563
        }
1✔
2564

1✔
2565
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
1✔
2566
        if err != nil {
1✔
UNCOV
2567
                return 0, err
×
UNCOV
2568
        }
×
2569

2570
        return int(deviceCount), nil
1✔
2571
}
2572

2573
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2574
        id string, stats model.Stats) error {
2✔
2575

2✔
2576
        if len(id) == 0 {
3✔
2577
                return ErrStorageInvalidID
1✔
2578
        }
1✔
2579

2580
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2581
        collDpl := database.Collection(CollectionDeployments)
2✔
2582

2✔
2583
        deployment, err := model.NewDeployment()
2✔
2584
        if err != nil {
2✔
UNCOV
2585
                return errors.Wrap(err, "failed to create deployment")
×
UNCOV
2586
        }
×
2587

2588
        deployment.Stats = stats
2✔
2589
        var update bson.M
2✔
2590
        if deployment.IsFinished() {
2✔
2591
                now := time.Now()
×
2592

×
2593
                update = bson.M{
×
2594
                        "$set": bson.M{
×
2595
                                StorageKeyDeploymentStats:    stats,
×
UNCOV
2596
                                StorageKeyDeploymentFinished: &now,
×
UNCOV
2597
                        },
×
UNCOV
2598
                }
×
2599
        } else {
2✔
2600
                update = bson.M{
2✔
2601
                        "$set": bson.M{
2✔
2602
                                StorageKeyDeploymentStats: stats,
2✔
2603
                        },
2✔
2604
                }
2✔
2605
        }
2✔
2606

2607
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
2✔
2608
        if res != nil && res.MatchedCount == 0 {
3✔
2609
                return ErrStorageInvalidID
1✔
2610
        }
1✔
2611
        return err
2✔
2612
}
2613

2614
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2615
        stateFrom, stateTo model.DeviceDeploymentStatus) (model.Stats, error) {
3✔
2616

3✔
2617
        if len(id) == 0 {
4✔
2618
                return nil, ErrStorageInvalidID
1✔
2619
        }
1✔
2620

2621
        if _, err := stateTo.MarshalText(); err != nil {
3✔
UNCOV
2622
                return nil, ErrStorageInvalidInput
×
UNCOV
2623
        }
×
2624

2625
        // does not need any extra operations
2626
        // following query won't handle this case well and increase the state_to value
2627
        if stateFrom == stateTo {
4✔
2628
                return nil, nil
1✔
2629
        }
1✔
2630

2631
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2632
        collDpl := database.Collection(CollectionDeployments)
3✔
2633

3✔
2634
        var update bson.M
3✔
2635

3✔
2636
        if stateFrom == model.DeviceDeploymentStatusNull {
6✔
2637
                // note dot notation on embedded document
3✔
2638
                update = bson.M{
3✔
2639
                        "$inc": bson.M{
3✔
2640
                                "stats." + stateTo.String(): 1,
3✔
2641
                        },
3✔
2642
                }
3✔
2643
        } else {
6✔
2644
                // note dot notation on embedded document
3✔
2645
                update = bson.M{
3✔
2646
                        "$inc": bson.M{
3✔
2647
                                "stats." + stateFrom.String(): -1,
3✔
2648
                                "stats." + stateTo.String():   1,
3✔
2649
                        },
3✔
2650
                }
3✔
2651
        }
3✔
2652

2653
        var res struct {
3✔
2654
                Stats model.Stats `bson:"stats"`
3✔
2655
        }
3✔
2656
        err := collDpl.FindOneAndUpdate(ctx,
3✔
2657
                bson.M{StorageKeyId: id},
3✔
2658
                update,
3✔
2659
                mopts.FindOneAndUpdate().
3✔
2660
                        SetReturnDocument(mopts.After).
3✔
2661
                        SetProjection(bson.M{
3✔
2662
                                StorageKeyDeploymentStats: 1,
3✔
2663
                        }),
3✔
2664
        ).Decode(&res)
3✔
2665

3✔
2666
        if errors.Is(err, mongo.ErrNoDocuments) {
4✔
2667
                return nil, ErrStorageInvalidID
1✔
2668
        }
1✔
2669

2670
        return res.Stats, err
3✔
2671
}
2672

2673
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2674
        ctx context.Context,
2675
        deploymentID string,
2676
        increment int64,
2677
) error {
3✔
2678
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2679
        collection := database.Collection(CollectionDeployments)
3✔
2680

3✔
2681
        filter := bson.M{
3✔
2682
                "_id": deploymentID,
3✔
2683
        }
3✔
2684

3✔
2685
        update := bson.M{
3✔
2686
                "$inc": bson.M{
3✔
2687
                        StorageKeyDeploymentTotalSize: increment,
3✔
2688
                },
3✔
2689
        }
3✔
2690

3✔
2691
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2692
        return err
3✔
2693
}
3✔
2694

2695
func (db *DataStoreMongo) FindDeployments(ctx context.Context,
2696
        match model.Query) ([]*model.Deployment, int64, error) {
3✔
2697

3✔
2698
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2699
        collDpl := database.Collection(CollectionDeployments)
3✔
2700

3✔
2701
        query, err := db.buildDeploymentsQuery(ctx, match)
3✔
2702
        if err != nil {
4✔
2703
                return nil, 0, err
1✔
2704
        }
1✔
2705

2706
        options := db.findOptions(match)
3✔
2707

3✔
2708
        var deployments []*model.Deployment
3✔
2709
        cursor, err := collDpl.Find(ctx, query, options)
3✔
2710
        if err != nil {
3✔
2711
                return nil, 0, err
×
2712
        }
×
2713
        if err := cursor.All(ctx, &deployments); err != nil {
3✔
UNCOV
2714
                return nil, 0, err
×
UNCOV
2715
        }
×
2716
        // Count documents if we didn't find all already.
2717
        count := int64(0)
3✔
2718
        if !match.DisableCount {
6✔
2719
                count = int64(len(deployments))
3✔
2720
                if count >= int64(match.Limit) {
5✔
2721
                        count, err = collDpl.CountDocuments(ctx, query)
2✔
2722
                        if err != nil {
2✔
UNCOV
2723
                                return nil, 0, err
×
UNCOV
2724
                        }
×
2725
                } else {
2✔
2726
                        // Don't forget to add the skipped documents
2✔
2727
                        count += int64(match.Skip)
2✔
2728
                }
2✔
2729
        }
2730

2731
        return deployments, count, nil
3✔
2732
}
2733

2734
func (db *DataStoreMongo) buildDeploymentsQuery(
2735
        ctx context.Context,
2736
        match model.Query,
2737
) (bson.M, error) {
3✔
2738
        andq := []bson.M{}
3✔
2739

3✔
2740
        // filter by IDs
3✔
2741
        if match.IDs != nil {
5✔
2742
                tq := bson.M{
2✔
2743
                        "_id": bson.M{
2✔
2744
                                "$in": match.IDs,
2✔
2745
                        },
2✔
2746
                }
2✔
2747
                andq = append(andq, tq)
2✔
2748
        }
2✔
2749

2750
        // filter by Names
2751
        if match.Names != nil {
5✔
2752
                tq := bson.M{
2✔
2753
                        StorageKeyDeploymentName: bson.M{
2✔
2754
                                "$in": match.Names,
2✔
2755
                        },
2✔
2756
                }
2✔
2757
                andq = append(andq, tq)
2✔
2758
        }
2✔
2759

2760
        // build deployment by name part of the query
2761
        if match.SearchText != "" {
4✔
2762
                // we must have indexing for text search
1✔
2763
                if !db.hasIndexing(ctx, db.client) {
2✔
2764
                        return nil, ErrDeploymentStorageCannotExecQuery
1✔
2765
                }
1✔
2766

2767
                tq := bson.M{
1✔
2768
                        "$text": bson.M{
1✔
2769
                                "$search": "\"" + match.SearchText + "\"",
1✔
2770
                        },
1✔
2771
                }
1✔
2772

1✔
2773
                andq = append(andq, tq)
1✔
2774
        }
2775

2776
        // build deployment by status part of the query
2777
        if match.Status != model.StatusQueryAny {
4✔
2778
                var status model.DeploymentStatus
1✔
2779
                if match.Status == model.StatusQueryPending {
2✔
2780
                        status = model.DeploymentStatusPending
1✔
2781
                } else if match.Status == model.StatusQueryInProgress {
3✔
2782
                        status = model.DeploymentStatusInProgress
1✔
2783
                } else {
2✔
2784
                        status = model.DeploymentStatusFinished
1✔
2785
                }
1✔
2786
                stq := bson.M{StorageKeyDeploymentStatus: status}
1✔
2787
                andq = append(andq, stq)
1✔
2788
        }
2789

2790
        // build deployment by type part of the query
2791
        if match.Type != "" {
4✔
2792
                if match.Type == model.DeploymentTypeConfiguration {
2✔
2793
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
1✔
2794
                } else if match.Type == model.DeploymentTypeSoftware {
1✔
2795
                        andq = append(andq, bson.M{
×
2796
                                "$or": []bson.M{
×
2797
                                        {StorageKeyDeploymentType: match.Type},
×
2798
                                        {StorageKeyDeploymentType: ""},
×
UNCOV
2799
                                },
×
UNCOV
2800
                        })
×
UNCOV
2801
                }
×
2802
        }
2803

2804
        query := bson.M{}
3✔
2805
        if len(andq) != 0 {
5✔
2806
                // use search criteria if any
2✔
2807
                query = bson.M{
2✔
2808
                        "$and": andq,
2✔
2809
                }
2✔
2810
        }
2✔
2811

2812
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
3✔
2813
                query["created"] = bson.M{
×
UNCOV
2814
                        "$gte": match.CreatedAfter,
×
UNCOV
2815
                        "$lte": match.CreatedBefore,
×
UNCOV
2816
                }
×
2817
        } else if match.CreatedAfter != nil {
4✔
2818
                query["created"] = bson.M{
1✔
2819
                        "$gte": match.CreatedAfter,
1✔
2820
                }
1✔
2821
        } else if match.CreatedBefore != nil {
5✔
2822
                query["created"] = bson.M{
1✔
2823
                        "$lte": match.CreatedBefore,
1✔
2824
                }
1✔
2825
        }
1✔
2826

2827
        return query, nil
3✔
2828
}
2829

2830
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
3✔
2831
        options := &mopts.FindOptions{}
3✔
2832
        if match.Sort == model.SortDirectionAscending {
4✔
2833
                options.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
2834
        } else {
4✔
2835
                options.SetSort(bson.D{{Key: "created", Value: -1}})
3✔
2836
        }
3✔
2837
        if match.Skip > 0 {
5✔
2838
                options.SetSkip(int64(match.Skip))
2✔
2839
        }
2✔
2840
        if match.Limit > 0 {
6✔
2841
                options.SetLimit(int64(match.Limit))
3✔
2842
        } else {
4✔
2843
                options.SetLimit(DefaultDocumentLimit)
1✔
2844
        }
1✔
2845
        return options
3✔
2846
}
2847

2848
// FindNewerActiveDeployments finds active deployments which were created
2849
// after createdAfter
2850
// Deprecated: No longer in use
2851
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2852
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
1✔
2853

1✔
2854
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2855
        c := database.Collection(CollectionDeployments)
1✔
2856

1✔
2857
        queryFilters := make([]bson.M, 0)
1✔
2858
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
1✔
2859
        queryFilters = append(queryFilters,
1✔
2860
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
1✔
2861
        findQuery := bson.M{}
1✔
2862
        findQuery["$and"] = queryFilters
1✔
2863

1✔
2864
        findOptions := &mopts.FindOptions{}
1✔
2865
        findOptions.SetSkip(int64(skip))
1✔
2866
        findOptions.SetLimit(int64(limit))
1✔
2867

1✔
2868
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
1✔
2869
        cursor, err := c.Find(ctx, findQuery, findOptions)
1✔
2870
        if err != nil {
1✔
UNCOV
2871
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2872
        }
×
2873
        defer cursor.Close(ctx)
1✔
2874

1✔
2875
        var deployments []*model.Deployment
1✔
2876

1✔
2877
        if err = cursor.All(ctx, &deployments); err != nil {
1✔
UNCOV
2878
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2879
        }
×
2880

2881
        return deployments, nil
1✔
2882
}
2883

2884
// FindNewerActiveDeployment finds active deployments which were created
2885
// after createdAfter where deviceID is part of the device list.
2886
func (db *DataStoreMongo) FindNewerActiveDeployment(ctx context.Context,
2887
        createdAfter *time.Time, deviceID string) (*model.Deployment, error) {
2✔
2888

2✔
2889
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2890
        c := database.Collection(CollectionDeployments)
2✔
2891

2✔
2892
        findQuery := bson.D{
2✔
2893
                {Key: StorageKeyDeploymentActive, Value: true},
2✔
2894
                {Key: StorageKeyDeploymentCreated, Value: bson.M{"$gt": createdAfter}},
2✔
2895
                {Key: StorageKeyDeploymentDeviceList, Value: deviceID},
2✔
2896
        }
2✔
2897
        findOptions := mopts.FindOne().
2✔
2898
                SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}}).
2✔
2899
                SetProjection(bson.M{
2✔
2900
                        // Discard information we don't need
2✔
2901
                        StorageKeyDeploymentConstructorChecksum: 0,
2✔
2902
                        StorageKeyDeploymentDeviceList:          0,
2✔
2903
                })
2✔
2904

2✔
2905
        var deployment = new(model.Deployment)
2✔
2906
        err := c.FindOne(ctx, findQuery, findOptions).
2✔
2907
                Decode(deployment)
2✔
2908
        if err != nil {
4✔
2909
                if errors.Is(err, mongo.ErrNoDocuments) {
4✔
2910
                        return nil, nil
2✔
2911
                }
2✔
UNCOV
2912
                return nil, errors.Wrap(err, "failed to get deployments")
×
2913
        }
2914

2915
        return deployment, nil
2✔
2916
}
2917

2918
// SetDeploymentStatus simply sets the status field
2919
// optionally sets 'finished time' if deployment is indeed finished
2920
func (db *DataStoreMongo) SetDeploymentStatus(
2921
        ctx context.Context,
2922
        id string,
2923
        status model.DeploymentStatus,
2924
        now time.Time,
2925
) error {
3✔
2926
        if len(id) == 0 {
3✔
UNCOV
2927
                return ErrStorageInvalidID
×
UNCOV
2928
        }
×
2929

2930
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2931
        collDpl := database.Collection(CollectionDeployments)
3✔
2932

3✔
2933
        var update bson.M
3✔
2934
        if status == model.DeploymentStatusFinished {
6✔
2935
                update = bson.M{
3✔
2936
                        "$set": bson.M{
3✔
2937
                                StorageKeyDeploymentActive:   false,
3✔
2938
                                StorageKeyDeploymentStatus:   status,
3✔
2939
                                StorageKeyDeploymentFinished: &now,
3✔
2940
                        },
3✔
2941
                }
3✔
2942
        } else {
6✔
2943
                update = bson.M{
3✔
2944
                        "$set": bson.M{
3✔
2945
                                StorageKeyDeploymentActive: true,
3✔
2946
                                StorageKeyDeploymentStatus: status,
3✔
2947
                        },
3✔
2948
                }
3✔
2949
        }
3✔
2950

2951
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
3✔
2952

3✔
2953
        if res != nil && res.MatchedCount == 0 {
4✔
2954
                return ErrStorageInvalidID
1✔
2955
        }
1✔
2956

2957
        return err
3✔
2958
}
2959

2960
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2961
// given artifact
2962
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2963
        id string) (bool, error) {
2✔
2964

2✔
2965
        if len(id) == 0 {
2✔
UNCOV
2966
                return false, ErrStorageInvalidID
×
UNCOV
2967
        }
×
2968

2969
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2970
        collDpl := database.Collection(CollectionDeployments)
2✔
2971

2✔
2972
        var tmp interface{}
2✔
2973
        query := bson.D{
2✔
2974
                {Key: StorageKeyDeploymentFinished, Value: nil},
2✔
2975
                {Key: StorageKeyDeploymentArtifacts, Value: id},
2✔
2976
        }
2✔
2977
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
4✔
2978
                if err == mongo.ErrNoDocuments {
4✔
2979
                        return false, nil
2✔
2980
                }
2✔
UNCOV
2981
                return false, err
×
2982
        }
2983

2984
        return true, nil
2✔
2985
}
2986

2987
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2988
// given artifact
2989
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2990
        artifactName string) (bool, error) {
3✔
2991

3✔
2992
        if len(artifactName) == 0 {
3✔
UNCOV
2993
                return false, ErrImagesStorageInvalidArtifactName
×
UNCOV
2994
        }
×
2995

2996
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2997
        collDpl := database.Collection(CollectionDeployments)
3✔
2998

3✔
2999
        var tmp interface{}
3✔
3000
        query := bson.D{
3✔
3001
                {Key: StorageKeyDeploymentFinished, Value: nil},
3✔
3002
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
3✔
3003
        }
3✔
3004

3✔
3005
        projection := bson.M{
3✔
3006
                "_id": 1,
3✔
3007
        }
3✔
3008
        findOptions := mopts.FindOne()
3✔
3009
        findOptions.SetProjection(projection)
3✔
3010

3✔
3011
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
6✔
3012
                if err == mongo.ErrNoDocuments {
6✔
3013
                        return false, nil
3✔
3014
                }
3✔
UNCOV
3015
                return false, err
×
3016
        }
3017

3018
        return true, nil
1✔
3019
}
3020

3021
// ExistByArtifactId check if there is any deployment that uses give artifact
3022
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
3023
        id string) (bool, error) {
×
3024

×
UNCOV
3025
        if len(id) == 0 {
×
3026
                return false, ErrStorageInvalidID
×
3027
        }
×
3028

3029
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
3030
        collDpl := database.Collection(CollectionDeployments)
×
3031

×
3032
        var tmp interface{}
×
3033
        query := bson.D{
×
3034
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
3035
        }
×
3036
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
3037
                if err == mongo.ErrNoDocuments {
×
UNCOV
3038
                        return false, nil
×
UNCOV
3039
                }
×
3040
                return false, err
×
3041
        }
3042

UNCOV
3043
        return true, nil
×
3044
}
3045

3046
// Per-tenant storage settings
3047
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
3✔
3048
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
3049
        collection := database.Collection(CollectionStorageSettings)
3✔
3050

3✔
3051
        settings := new(model.StorageSettings)
3✔
3052
        // supposed that it's only one document in the collection
3✔
3053
        query := bson.M{
3✔
3054
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
3055
        }
3✔
3056
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
5✔
3057
                if err == mongo.ErrNoDocuments {
4✔
3058
                        return nil, nil
2✔
3059
                }
2✔
UNCOV
3060
                return nil, err
×
3061
        }
3062

3063
        return settings, nil
2✔
3064
}
3065

3066
func (db *DataStoreMongo) SetStorageSettings(
3067
        ctx context.Context,
3068
        storageSettings *model.StorageSettings,
3069
) error {
2✔
3070
        var err error
2✔
3071
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
3072
        collection := database.Collection(CollectionStorageSettings)
2✔
3073

2✔
3074
        filter := bson.M{
2✔
3075
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
3076
        }
2✔
3077
        if storageSettings != nil {
4✔
3078
                replaceOptions := mopts.Replace()
2✔
3079
                replaceOptions.SetUpsert(true)
2✔
3080
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
2✔
3081
        } else {
3✔
3082
                _, err = collection.DeleteOne(ctx, filter)
1✔
3083
        }
1✔
3084

3085
        return err
2✔
3086
}
3087

3088
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
3089
        ctx context.Context,
3090
        artifactName string,
3091
        artifactIDs []string,
3092
) error {
1✔
3093
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
3094
        collDpl := database.Collection(CollectionDeployments)
1✔
3095

1✔
3096
        query := bson.D{
1✔
3097
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
3098
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
3099
        }
1✔
3100
        update := bson.M{
1✔
3101
                "$set": bson.M{
1✔
3102
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
3103
                },
1✔
3104
        }
1✔
3105

1✔
3106
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
3107
        return err
1✔
3108
}
1✔
3109

3110
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
3111
        ctx context.Context,
3112
        artifactNames []string,
3113
) ([]string, error) {
1✔
3114

1✔
3115
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
3116
        collDpl := database.Collection(CollectionDeployments)
1✔
3117

1✔
3118
        query := bson.M{
1✔
3119
                StorageKeyDeploymentArtifactName: bson.M{
1✔
3120
                        "$in": artifactNames,
1✔
3121
                },
1✔
3122
        }
1✔
3123

1✔
3124
        projection := bson.M{
1✔
3125
                "_id": 1,
1✔
3126
        }
1✔
3127
        findOptions := mopts.Find()
1✔
3128
        findOptions.SetProjection(projection)
1✔
3129

1✔
3130
        cursor, err := collDpl.Find(ctx, query, findOptions)
1✔
3131
        if err != nil {
1✔
UNCOV
3132
                return []string{}, err
×
UNCOV
3133
        }
×
3134
        defer cursor.Close(ctx)
1✔
3135

1✔
3136
        var deployments []*model.Deployment
1✔
3137
        if err = cursor.All(ctx, &deployments); err != nil {
1✔
3138
                if err == mongo.ErrNoDocuments {
×
UNCOV
3139
                        err = nil
×
UNCOV
3140
                }
×
UNCOV
3141
                return []string{}, err
×
3142
        }
3143

3144
        ids := make([]string, len(deployments))
1✔
3145
        for i, d := range deployments {
2✔
3146
                ids[i] = d.Id
1✔
3147
        }
1✔
3148

3149
        return ids, nil
1✔
3150
}
3151

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