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

mendersoftware / mender-server / 1927764361

15 Jul 2025 11:39AM UTC coverage: 65.436% (-0.02%) from 65.454%
1927764361

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

139 of 232 new or added lines in 7 files covered. (59.91%)

77 existing lines in 3 files now uncovered.

32212 of 49227 relevant lines covered (65.44%)

1.39 hits per line

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

86.48
/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
                if len(filt.ExactNames) > 0 {
2✔
1321
                        filters[StorageKeyImageName] = bson.M{"$in": filt.ExactNames}
1✔
1322
                } else if filt.NamePrefix != "" {
3✔
1323
                        filters[StorageKeyImageName] = bson.M{
1✔
1324
                                "$regex": primitive.Regex{
1✔
1325
                                        Pattern: "^" + regexp.QuoteMeta(filt.NamePrefix) + ".*",
1✔
1326
                                        Options: "i",
1✔
1327
                                },
1✔
1328
                        }
1✔
1329
                }
1✔
1330

1331
                if filt.Description != "" {
2✔
1332
                        if strings.HasSuffix(filt.Description, `\*`) {
2✔
1333
                                description := strings.TrimSuffix(filt.Description, `\*`) + "*"
1✔
1334
                                filters[StorageKeyImageDescription] = description
1✔
1335

1✔
1336
                        } else if strings.HasSuffix(filt.Description, "*") {
3✔
1337
                                description := strings.TrimSuffix(filt.Description, "*")
1✔
1338
                                filters[StorageKeyImageDescription] = bson.M{
1✔
1339
                                        "$regex": primitive.Regex{
1✔
1340
                                                Pattern: "^" + regexp.QuoteMeta(description) + ".*",
1✔
1341
                                                Options: "i",
1✔
1342
                                        },
1✔
1343
                                }
1✔
1344
                        } else {
2✔
1345
                                filters[StorageKeyImageDescription] = filt.Description
1✔
1346
                        }
1✔
1347
                }
1348

1349
                if filt.DeviceType != "" {
2✔
1350
                        if strings.HasSuffix(filt.DeviceType, `\*`) {
1✔
NEW
1351
                                deviceType := strings.TrimSuffix(filt.DeviceType, `\*`) + "*"
×
NEW
1352
                                filters[StorageKeyImageDeviceTypes] = deviceType
×
NEW
1353

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

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

1✔
1397
        cursor, err := collImg.Find(ctx, filters, findOptions)
1✔
1398
        if err != nil {
1✔
NEW
1399
                return nil, err
×
NEW
1400
        }
×
1401

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

1411
        return images, nil
1✔
1412
}
1413

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

1426
// device deployment log
1427
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1428
        log model.DeploymentLog) error {
2✔
1429

2✔
1430
        if err := log.Validate(); err != nil {
3✔
1431
                return err
1✔
1432
        }
1✔
1433

1434
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1435
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
2✔
1436

2✔
1437
        query := bson.D{
2✔
1438
                {Key: StorageKeyDeviceDeploymentDeviceId,
2✔
1439
                        Value: log.DeviceID},
2✔
1440
                {Key: StorageKeyDeviceDeploymentDeploymentID,
2✔
1441
                        Value: log.DeploymentID},
2✔
1442
        }
2✔
1443

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

1458
        return nil
2✔
1459
}
1460

1461
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1462
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
2✔
1463

2✔
1464
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1465
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
2✔
1466

2✔
1467
        query := bson.M{
2✔
1468
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
2✔
1469
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
2✔
1470
        }
2✔
1471

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

1480
        return &depl, nil
2✔
1481
}
1482

1483
// device deployments
1484

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

3✔
1494
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
4✔
1495
                startedTime := time.Now().UTC()
1✔
1496
                deviceDeployment.Started = &startedTime
1✔
1497
        }
1✔
1498

1499
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
3✔
UNCOV
1500
                return err
×
1501
        }
×
1502

1503
        if incrementDeviceCount {
6✔
1504
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
3✔
1505
                if err != nil {
3✔
UNCOV
1506
                        return err
×
1507
                }
×
1508
        }
1509

1510
        return nil
3✔
1511
}
1512

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

1✔
1518
        if len(deployments) == 0 {
2✔
1519
                return nil
1✔
1520
        }
1✔
1521

1522
        deviceCountIncrements := make(map[string]int)
1✔
1523

1✔
1524
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
1✔
1525
        var list []interface{}
1✔
1526
        for _, deployment := range deployments {
2✔
1527

1✔
1528
                if deployment == nil {
2✔
1529
                        return ErrStorageInvalidDeviceDeployment
1✔
1530
                }
1✔
1531

1532
                if err := deployment.Validate(); err != nil {
2✔
1533
                        return errors.Wrap(err, "Validating device deployment")
1✔
1534
                }
1✔
1535

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

1544
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1545
        collDevs := database.Collection(CollectionDevices)
1✔
1546

1✔
1547
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
1✔
UNCOV
1548
                return err
×
1549
        }
×
1550

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

1562
        return nil
1✔
1563
}
1564

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

3✔
1571
        // Verify ID formatting
3✔
1572
        if len(deviceID) == 0 {
4✔
1573
                return nil, ErrStorageInvalidID
1✔
1574
        }
1✔
1575

1576
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1577
        collDevs := database.Collection(CollectionDevices)
3✔
1578

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

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

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

1603
        return deployment, nil
2✔
1604
}
1605

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

3✔
1613
        // Verify ID formatting
3✔
1614
        if len(deviceID) == 0 {
4✔
1615
                return nil, ErrStorageInvalidID
1✔
1616
        }
1✔
1617

1618
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1619
        collDevs := database.Collection(CollectionDevices)
3✔
1620

3✔
1621
        query := bson.D{
3✔
1622
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
3✔
1623
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
3✔
1624
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
3✔
1625
                        {Key: "$exists", Value: false},
3✔
1626
                }},
3✔
1627
        }
3✔
1628

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

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

1644
        return deployment, nil
3✔
1645
}
1646

1647
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1648
        ctx context.Context,
1649
        deviceID string,
1650
        deploymentID string,
1651
        ddState model.DeviceDeploymentState,
1652
        currentStatus model.DeviceDeploymentStatus,
1653
) (model.DeviceDeploymentStatus, error) {
3✔
1654

3✔
1655
        // Verify ID formatting
3✔
1656
        if len(deviceID) == 0 ||
3✔
1657
                len(deploymentID) == 0 {
4✔
1658
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
1✔
1659
        }
1✔
1660

1661
        if err := ddState.Validate(); err != nil {
4✔
1662
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1663
        }
1✔
1664

1665
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1666
        collDevs := database.Collection(CollectionDevices)
3✔
1667

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

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

1687
        if len(ddState.SubState) > 0 {
5✔
1688
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
2✔
1689
        }
2✔
1690

1691
        if currentStatus == model.DeviceDeploymentStatusPending &&
3✔
1692
                ddState.Status != currentStatus {
6✔
1693
                startedTime := time.Now().UTC()
3✔
1694
                set[StorageKeyDeviceDeploymentStarted] = startedTime
3✔
1695
        }
3✔
1696

1697
        update := bson.D{
3✔
1698
                {Key: "$set", Value: set},
3✔
1699
        }
3✔
1700

3✔
1701
        var old model.DeviceDeployment
3✔
1702

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

1710
        }
1711

1712
        return old.Status, nil
3✔
1713
}
1714

1715
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1716
        deviceID string, deploymentID string, log bool) error {
2✔
1717

2✔
1718
        // Verify ID formatting
2✔
1719
        if len(deviceID) == 0 ||
2✔
1720
                len(deploymentID) == 0 {
3✔
1721
                return ErrStorageInvalidID
1✔
1722
        }
1✔
1723

1724
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1725
        collDevs := database.Collection(CollectionDevices)
2✔
1726

2✔
1727
        selector := bson.D{
2✔
1728
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
2✔
1729
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
2✔
1730
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
2✔
1731
                        {Key: "$exists", Value: false},
2✔
1732
                }},
2✔
1733
        }
2✔
1734

2✔
1735
        update := bson.D{
2✔
1736
                {Key: "$set", Value: bson.M{
2✔
1737
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
2✔
1738
        }
2✔
1739

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

1746
        return nil
2✔
1747
}
1748

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

3✔
1757
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1758
        collDevs := database.Collection(CollectionDevices)
3✔
1759

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

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

2✔
1781
        // Verify ID formatting
2✔
1782
        if len(deviceID) == 0 ||
2✔
1783
                len(deploymentID) == 0 {
2✔
UNCOV
1784
                return ErrStorageInvalidID
×
1785
        }
×
1786

1787
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1788
        collDevs := database.Collection(CollectionDevices)
2✔
1789

2✔
1790
        selector := bson.D{
2✔
1791
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
2✔
1792
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
2✔
1793
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
2✔
1794
                        {Key: "$exists", Value: false},
2✔
1795
                }},
2✔
1796
        }
2✔
1797

2✔
1798
        update := bson.D{
2✔
1799
                {Key: "$set", Value: bson.M{
2✔
1800
                        StorageKeyDeviceDeploymentArtifact: artifact,
2✔
1801
                }},
2✔
1802
        }
2✔
1803

2✔
1804
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
2✔
UNCOV
1805
                return err
×
1806
        } else if res.MatchedCount == 0 {
2✔
UNCOV
1807
                return ErrStorageNotFound
×
1808
        }
×
1809

1810
        return nil
2✔
1811
}
1812

1813
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1814
        id string) (model.Stats, error) {
2✔
1815

2✔
1816
        if len(id) == 0 {
2✔
UNCOV
1817
                return nil, ErrStorageInvalidID
×
1818
        }
×
1819

1820
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1821
        collDevs := database.Collection(CollectionDevices)
2✔
1822

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

1858
        raw := model.NewDeviceDeploymentStats()
2✔
1859
        for _, res := range results {
4✔
1860
                raw.Set(res.Status, res.Count)
2✔
1861
        }
2✔
1862
        return raw, nil
2✔
1863
}
1864

1865
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1866
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1867
        deploymentID string) ([]model.DeviceDeployment, error) {
3✔
1868

3✔
1869
        statuses := []model.DeviceDeployment{}
3✔
1870
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1871
        collDevs := database.Collection(CollectionDevices)
3✔
1872

3✔
1873
        query := bson.M{
3✔
1874
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
3✔
1875
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
1876
                        {Key: "$exists", Value: false},
3✔
1877
                },
3✔
1878
        }
3✔
1879

3✔
1880
        cursor, err := collDevs.Find(ctx, query)
3✔
1881
        if err != nil {
3✔
UNCOV
1882
                return nil, err
×
1883
        }
×
1884

1885
        if err = cursor.All(ctx, &statuses); err != nil {
3✔
UNCOV
1886
                if err == mongo.ErrNoDocuments {
×
1887
                        return nil, nil
×
1888
                }
×
1889
                return nil, err
×
1890
        }
1891

1892
        return statuses, nil
3✔
1893
}
1894

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

2✔
1898
        statuses := []model.DeviceDeployment{}
2✔
1899
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1900
        collDevs := database.Collection(CollectionDevices)
2✔
1901

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

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

1970
        cursor, err := collDevs.Find(ctx, query, options)
2✔
1971
        if err != nil {
3✔
1972
                return nil, -1, err
1✔
1973
        }
1✔
1974

1975
        if err = cursor.All(ctx, &statuses); err != nil {
2✔
UNCOV
1976
                if err == mongo.ErrNoDocuments {
×
1977
                        return nil, -1, nil
×
1978
                }
×
1979
                return nil, -1, err
×
1980
        }
1981

1982
        count, err := collDevs.CountDocuments(ctx, query)
2✔
1983
        if err != nil {
2✔
UNCOV
1984
                return nil, -1, ErrDevicesCountFailed
×
1985
        }
×
1986

1987
        return statuses, int(count), nil
2✔
1988
}
1989

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

2✔
1993
        statuses := []model.DeviceDeployment{}
2✔
1994
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1995
        collDevs := database.Collection(CollectionDevices)
2✔
1996

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

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

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

2075
        cursor, err := collDevs.Find(ctx, query, options)
2✔
2076
        if err != nil {
2✔
UNCOV
2077
                return nil, -1, err
×
2078
        }
×
2079

2080
        if err = cursor.All(ctx, &statuses); err != nil {
2✔
UNCOV
2081
                if err == mongo.ErrNoDocuments {
×
2082
                        return nil, 0, nil
×
2083
                }
×
2084
                return nil, -1, err
×
2085
        }
2086

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

2096
        return statuses, int(count), nil
2✔
2097
}
2098

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

2✔
2105
        var dep model.DeviceDeployment
2✔
2106
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2107
        collDevs := database.Collection(CollectionDevices)
2✔
2108

2✔
2109
        query := bson.D{
2✔
2110
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
2✔
2111
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
2✔
2112
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
2✔
2113
                        {Key: "$exists", Value: false},
2✔
2114
                }},
2✔
2115
        }
2✔
2116

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

2125
        return true, nil
2✔
2126
}
2127

2128
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2129
        deploymentId string) error {
2✔
2130

2✔
2131
        if len(deploymentId) == 0 {
3✔
2132
                return ErrStorageInvalidID
1✔
2133
        }
1✔
2134

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

2✔
2145
        update := bson.M{
2✔
2146
                "$set": bson.M{
2✔
2147
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2148
                        StorageKeyDeviceDeploymentActive: false,
2✔
2149
                },
2✔
2150
        }
2✔
2151

2✔
2152
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
UNCOV
2153
                return err
×
2154
        }
×
2155

2156
        return nil
2✔
2157
}
2158

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

1✔
2171
        now := time.Now()
1✔
2172
        update := bson.M{
1✔
2173
                "$set": bson.M{
1✔
2174
                        StorageKeyDeviceDeploymentDeleted: &now,
1✔
2175
                },
1✔
2176
        }
1✔
2177

1✔
2178
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
UNCOV
2179
                return err
×
2180
        }
×
2181

2182
        database = db.client.Database(DatabaseName)
1✔
2183
        collDevs = database.Collection(CollectionDevicesLastStatus)
1✔
2184
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
1✔
2185

1✔
2186
        return err
1✔
2187
}
2188

2189
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2190
        deviceId string) error {
1✔
2191

1✔
2192
        if len(deviceId) == 0 {
2✔
2193
                return ErrStorageInvalidID
1✔
2194
        }
1✔
2195

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

1✔
2206
        update := bson.M{
1✔
2207
                "$set": bson.M{
1✔
2208
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2209
                        StorageKeyDeviceDeploymentActive: false,
1✔
2210
                },
1✔
2211
        }
1✔
2212

1✔
2213
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
UNCOV
2214
                return err
×
2215
        }
×
2216

2217
        return nil
1✔
2218
}
2219

2220
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2221
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
2✔
2222

2✔
2223
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2224
        collDevs := database.Collection(CollectionDevices)
2✔
2225

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

2236
        opts := &mopts.FindOneOptions{}
2✔
2237
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
2✔
2238

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

2247
        return &dd, nil
2✔
2248
}
2249

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

1✔
2259
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2260
        collDevs := database.Collection(CollectionDevices)
1✔
2261

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

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

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

2293
        return deviceDeployments, nil
1✔
2294
}
2295

2296
// deployments
2297

2298
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2299
        indexes ...mongo.IndexModel) error {
3✔
2300
        ctx := context.Background()
3✔
2301
        dataBase := db.client.Database(dbName)
3✔
2302

3✔
2303
        coll := dataBase.Collection(collName)
3✔
2304
        idxView := coll.Indexes()
3✔
2305
        _, err := idxView.CreateMany(ctx, indexes)
3✔
2306
        return err
3✔
2307
}
3✔
2308

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

1✔
2312
        var idx bson.M
1✔
2313
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2314
        collDpl := database.Collection(CollectionDeployments)
1✔
2315
        idxView := collDpl.Indexes()
1✔
2316

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

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

2338
                }
2339
        }
2340
        if err != nil {
1✔
UNCOV
2341
                return false
×
2342
        }
×
2343

2344
        for _, key := range StorageIndexes.Keys.(bson.D) {
2✔
2345
                _, ok := has[key.Key]
1✔
2346
                if !ok {
2✔
2347
                        return false
1✔
2348
                }
1✔
2349
        }
2350

2351
        return true
1✔
2352
}
2353

2354
// Insert persists object
2355
func (db *DataStoreMongo) InsertDeployment(
2356
        ctx context.Context,
2357
        deployment *model.Deployment,
2358
) error {
3✔
2359

3✔
2360
        if deployment == nil {
4✔
2361
                return ErrDeploymentStorageInvalidDeployment
1✔
2362
        }
1✔
2363

2364
        if err := deployment.Validate(); err != nil {
5✔
2365
                return err
2✔
2366
        }
2✔
2367

2368
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2369
        collDpl := database.Collection(CollectionDeployments)
3✔
2370

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

2380
// Delete removed entry by ID
2381
// Noop on ID not found
2382
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
1✔
2383

1✔
2384
        if len(id) == 0 {
2✔
2385
                return ErrStorageInvalidID
1✔
2386
        }
1✔
2387

2388
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2389
        collDpl := database.Collection(CollectionDeployments)
1✔
2390

1✔
2391
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
UNCOV
2392
                return err
×
2393
        }
×
2394

2395
        return nil
1✔
2396
}
2397

2398
func (db *DataStoreMongo) FindDeploymentByID(
2399
        ctx context.Context,
2400
        id string,
2401
) (*model.Deployment, error) {
3✔
2402

3✔
2403
        if len(id) == 0 {
4✔
2404
                return nil, ErrStorageInvalidID
1✔
2405
        }
1✔
2406

2407
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2408
        collDpl := database.Collection(CollectionDeployments)
3✔
2409

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

2419
        return deployment, nil
3✔
2420
}
2421

2422
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2423
        ctx context.Context,
2424
        ids ...string,
2425
) (deploymentStats []*model.DeploymentStats, err error) {
1✔
2426

1✔
2427
        if len(ids) == 0 {
1✔
UNCOV
2428
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2429
        }
×
2430

2431
        for _, id := range ids {
2✔
2432
                if len(id) == 0 {
1✔
UNCOV
2433
                        return nil, ErrStorageInvalidID
×
2434
                }
×
2435
        }
2436

2437
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2438
        collDpl := database.Collection(CollectionDeployments)
1✔
2439

1✔
2440
        query := bson.M{
1✔
2441
                "_id": bson.M{
1✔
2442
                        "$in": ids,
1✔
2443
                },
1✔
2444
        }
1✔
2445
        statsProjection := &mopts.FindOptions{
1✔
2446
                Projection: bson.M{"stats": 1},
1✔
2447
        }
1✔
2448

1✔
2449
        results, err := collDpl.Find(
1✔
2450
                ctx,
1✔
2451
                query,
1✔
2452
                statsProjection,
1✔
2453
        )
1✔
2454
        if err != nil {
1✔
UNCOV
2455
                return nil, err
×
2456
        }
×
2457

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

2469
        return deploymentStats, nil
1✔
2470
}
2471

2472
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2473
        id string) (*model.Deployment, error) {
2✔
2474

2✔
2475
        if len(id) == 0 {
3✔
2476
                return nil, ErrStorageInvalidID
1✔
2477
        }
1✔
2478

2479
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2480
        collDpl := database.Collection(CollectionDeployments)
2✔
2481

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

2495
        return deployment, nil
2✔
2496
}
2497

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

3✔
2506
        filter := bson.M{
3✔
2507
                "_id": deploymentID,
3✔
2508
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2509
                        "$ne": nil,
3✔
2510
                },
3✔
2511
        }
3✔
2512

3✔
2513
        update := bson.M{
3✔
2514
                "$inc": bson.M{
3✔
2515
                        StorageKeyDeploymentDeviceCount: increment,
3✔
2516
                },
3✔
2517
        }
3✔
2518

3✔
2519
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2520
        return err
3✔
2521
}
3✔
2522

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

1✔
2531
        filter := bson.M{
1✔
2532
                "_id": deploymentID,
1✔
2533
                StorageKeyDeploymentDeviceCount: bson.M{
1✔
2534
                        "$eq": nil,
1✔
2535
                },
1✔
2536
        }
1✔
2537

1✔
2538
        update := bson.M{
1✔
2539
                "$set": bson.M{
1✔
2540
                        StorageKeyDeploymentDeviceCount: count,
1✔
2541
                },
1✔
2542
        }
1✔
2543

1✔
2544
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2545
        return err
1✔
2546
}
1✔
2547

2548
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2549
        id string) (int, error) {
1✔
2550

1✔
2551
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2552
        collDevs := database.Collection(CollectionDevices)
1✔
2553

1✔
2554
        filter := bson.M{
1✔
2555
                StorageKeyDeviceDeploymentDeploymentID: id,
1✔
2556
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2557
                        {Key: "$exists", Value: false},
1✔
2558
                },
1✔
2559
        }
1✔
2560

1✔
2561
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
1✔
2562
        if err != nil {
1✔
UNCOV
2563
                return 0, err
×
2564
        }
×
2565

2566
        return int(deviceCount), nil
1✔
2567
}
2568

2569
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2570
        id string, stats model.Stats) error {
2✔
2571

2✔
2572
        if len(id) == 0 {
3✔
2573
                return ErrStorageInvalidID
1✔
2574
        }
1✔
2575

2576
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2577
        collDpl := database.Collection(CollectionDeployments)
2✔
2578

2✔
2579
        deployment, err := model.NewDeployment()
2✔
2580
        if err != nil {
2✔
UNCOV
2581
                return errors.Wrap(err, "failed to create deployment")
×
2582
        }
×
2583

2584
        deployment.Stats = stats
2✔
2585
        var update bson.M
2✔
2586
        if deployment.IsFinished() {
2✔
UNCOV
2587
                now := time.Now()
×
2588

×
2589
                update = bson.M{
×
2590
                        "$set": bson.M{
×
2591
                                StorageKeyDeploymentStats:    stats,
×
2592
                                StorageKeyDeploymentFinished: &now,
×
2593
                        },
×
2594
                }
×
2595
        } else {
2✔
2596
                update = bson.M{
2✔
2597
                        "$set": bson.M{
2✔
2598
                                StorageKeyDeploymentStats: stats,
2✔
2599
                        },
2✔
2600
                }
2✔
2601
        }
2✔
2602

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

2610
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2611
        stateFrom, stateTo model.DeviceDeploymentStatus) (model.Stats, error) {
3✔
2612

3✔
2613
        if len(id) == 0 {
4✔
2614
                return nil, ErrStorageInvalidID
1✔
2615
        }
1✔
2616

2617
        if _, err := stateTo.MarshalText(); err != nil {
3✔
UNCOV
2618
                return nil, ErrStorageInvalidInput
×
2619
        }
×
2620

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

2627
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2628
        collDpl := database.Collection(CollectionDeployments)
3✔
2629

3✔
2630
        var update bson.M
3✔
2631

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

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

3✔
2662
        if errors.Is(err, mongo.ErrNoDocuments) {
4✔
2663
                return nil, ErrStorageInvalidID
1✔
2664
        }
1✔
2665

2666
        return res.Stats, err
3✔
2667
}
2668

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

3✔
2677
        filter := bson.M{
3✔
2678
                "_id": deploymentID,
3✔
2679
        }
3✔
2680

3✔
2681
        update := bson.M{
3✔
2682
                "$inc": bson.M{
3✔
2683
                        StorageKeyDeploymentTotalSize: increment,
3✔
2684
                },
3✔
2685
        }
3✔
2686

3✔
2687
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2688
        return err
3✔
2689
}
3✔
2690

2691
func (db *DataStoreMongo) FindDeployments(ctx context.Context,
2692
        match model.Query) ([]*model.Deployment, int64, error) {
3✔
2693

3✔
2694
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2695
        collDpl := database.Collection(CollectionDeployments)
3✔
2696

3✔
2697
        query, err := db.buildDeploymentsQuery(ctx, match)
3✔
2698
        if err != nil {
4✔
2699
                return nil, 0, err
1✔
2700
        }
1✔
2701

2702
        options := db.findOptions(match)
3✔
2703

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

2727
        return deployments, count, nil
3✔
2728
}
2729

2730
func (db *DataStoreMongo) buildDeploymentsQuery(
2731
        ctx context.Context,
2732
        match model.Query,
2733
) (bson.M, error) {
3✔
2734
        andq := []bson.M{}
3✔
2735

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

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

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

2763
                tq := bson.M{
1✔
2764
                        "$text": bson.M{
1✔
2765
                                "$search": "\"" + match.SearchText + "\"",
1✔
2766
                        },
1✔
2767
                }
1✔
2768

1✔
2769
                andq = append(andq, tq)
1✔
2770
        }
2771

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

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

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

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

2823
        return query, nil
3✔
2824
}
2825

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

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

1✔
2850
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2851
        c := database.Collection(CollectionDeployments)
1✔
2852

1✔
2853
        queryFilters := make([]bson.M, 0)
1✔
2854
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
1✔
2855
        queryFilters = append(queryFilters,
1✔
2856
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
1✔
2857
        findQuery := bson.M{}
1✔
2858
        findQuery["$and"] = queryFilters
1✔
2859

1✔
2860
        findOptions := &mopts.FindOptions{}
1✔
2861
        findOptions.SetSkip(int64(skip))
1✔
2862
        findOptions.SetLimit(int64(limit))
1✔
2863

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

1✔
2871
        var deployments []*model.Deployment
1✔
2872

1✔
2873
        if err = cursor.All(ctx, &deployments); err != nil {
1✔
UNCOV
2874
                return nil, errors.Wrap(err, "failed to get deployments")
×
2875
        }
×
2876

2877
        return deployments, nil
1✔
2878
}
2879

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

2✔
2885
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2886
        c := database.Collection(CollectionDeployments)
2✔
2887

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

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

2911
        return deployment, nil
2✔
2912
}
2913

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

2926
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2927
        collDpl := database.Collection(CollectionDeployments)
3✔
2928

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

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

3✔
2949
        if res != nil && res.MatchedCount == 0 {
4✔
2950
                return ErrStorageInvalidID
1✔
2951
        }
1✔
2952

2953
        return err
3✔
2954
}
2955

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

2✔
2961
        if len(id) == 0 {
2✔
UNCOV
2962
                return false, ErrStorageInvalidID
×
2963
        }
×
2964

2965
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2966
        collDpl := database.Collection(CollectionDeployments)
2✔
2967

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

2980
        return true, nil
2✔
2981
}
2982

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

3✔
2988
        if len(artifactName) == 0 {
3✔
UNCOV
2989
                return false, ErrImagesStorageInvalidArtifactName
×
2990
        }
×
2991

2992
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2993
        collDpl := database.Collection(CollectionDeployments)
3✔
2994

3✔
2995
        var tmp interface{}
3✔
2996
        query := bson.D{
3✔
2997
                {Key: StorageKeyDeploymentFinished, Value: nil},
3✔
2998
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
3✔
2999
        }
3✔
3000

3✔
3001
        projection := bson.M{
3✔
3002
                "_id": 1,
3✔
3003
        }
3✔
3004
        findOptions := mopts.FindOne()
3✔
3005
        findOptions.SetProjection(projection)
3✔
3006

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

3014
        return true, nil
1✔
3015
}
3016

3017
// ExistByArtifactId check if there is any deployment that uses give artifact
3018
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
UNCOV
3019
        id string) (bool, error) {
×
3020

×
3021
        if len(id) == 0 {
×
3022
                return false, ErrStorageInvalidID
×
3023
        }
×
3024

UNCOV
3025
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
3026
        collDpl := database.Collection(CollectionDeployments)
×
3027

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

UNCOV
3039
        return true, nil
×
3040
}
3041

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

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

3059
        return settings, nil
2✔
3060
}
3061

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

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

3081
        return err
2✔
3082
}
3083

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

1✔
3092
        query := bson.D{
1✔
3093
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
3094
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
3095
        }
1✔
3096
        update := bson.M{
1✔
3097
                "$set": bson.M{
1✔
3098
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
3099
                },
1✔
3100
        }
1✔
3101

1✔
3102
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
3103
        return err
1✔
3104
}
1✔
3105

3106
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
3107
        ctx context.Context,
3108
        artifactNames []string,
3109
) ([]string, error) {
1✔
3110

1✔
3111
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
3112
        collDpl := database.Collection(CollectionDeployments)
1✔
3113

1✔
3114
        query := bson.M{
1✔
3115
                StorageKeyDeploymentArtifactName: bson.M{
1✔
3116
                        "$in": artifactNames,
1✔
3117
                },
1✔
3118
        }
1✔
3119

1✔
3120
        projection := bson.M{
1✔
3121
                "_id": 1,
1✔
3122
        }
1✔
3123
        findOptions := mopts.Find()
1✔
3124
        findOptions.SetProjection(projection)
1✔
3125

1✔
3126
        cursor, err := collDpl.Find(ctx, query, findOptions)
1✔
3127
        if err != nil {
1✔
UNCOV
3128
                return []string{}, err
×
3129
        }
×
3130
        defer cursor.Close(ctx)
1✔
3131

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

3140
        ids := make([]string, len(deployments))
1✔
3141
        for i, d := range deployments {
2✔
3142
                ids[i] = d.Id
1✔
3143
        }
1✔
3144

3145
        return ids, nil
1✔
3146
}
3147

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