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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

77.57
/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 {
1✔
453
        return &DataStoreMongo{
1✔
454
                client: client,
1✔
455
        }
1✔
456
}
1✔
457

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

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

1✔
468
        username := c.GetString(dconfig.SettingDbUsername)
1✔
469
        if username != "" {
1✔
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) {
1✔
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)
1✔
489
        defer cancel()
1✔
490
        client, err := mongo.Connect(ctx, clientOptions)
1✔
491
        if err != nil {
1✔
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 {
1✔
497
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
498
        }
×
499

500
        return client, nil
1✔
501
}
502

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

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

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

543
func (db *DataStoreMongo) GetReleases(
544
        ctx context.Context,
545
        filt *model.ReleaseOrImageFilter,
UNCOV
546
) ([]model.Release, int, error) {
×
UNCOV
547
        current, err := db.getCurrentDbVersion(ctx)
×
UNCOV
548
        if err != nil {
×
549
                return []model.Release{}, 0, err
×
UNCOV
550
        } else if current == nil {
×
551
                return []model.Release{}, 0, errors.New("couldn't get current database version")
×
552
        }
×
UNCOV
553
        if migrate.VersionIsLess(*current, migrate.Version{
×
UNCOV
554
                Major: 1, Minor: 2, Patch: 15,
×
UNCOV
555
        }) {
×
556
                return db.getReleases_1_2_14(ctx, filt)
×
UNCOV
557
        } else {
×
UNCOV
558
                return db.getReleases_1_2_15(ctx, filt)
×
UNCOV
559
        }
×
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) {
1✔
688
        l := log.FromContext(ctx)
1✔
689
        l.Infof("get releases method version 1.2.15")
1✔
690

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

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

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

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

1✔
725
        filter := bson.M{}
1✔
726
        if filt != nil {
2✔
727
                if filt.Name != "" {
2✔
728
                        filter[StorageKeyReleaseName] = bson.M{"$regex": primitive.Regex{
1✔
729
                                Pattern: regexp.QuoteMeta(filt.Name) + ".*",
1✔
730
                                Options: "i",
1✔
731
                        }}
1✔
732
                }
1✔
733
                if len(filt.Tags) > 0 {
2✔
734
                        filter[StorageKeyReleaseTags] = bson.M{"$in": filt.Tags}
1✔
735
                }
1✔
736
                if filt.Description != "" {
2✔
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 != "" {
2✔
743
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = filt.DeviceType
1✔
744
                }
1✔
745
                if filt.UpdateType != "" {
2✔
746
                        filter[StorageKeyReleaseArtifactsUpdateTypes] = filt.UpdateType
1✔
747
                }
1✔
748
        }
749
        releases := []model.Release{}
1✔
750
        cursor, err := collReleases.Find(ctx, filter, opts)
1✔
751
        if err != nil {
1✔
752
                return []model.Release{}, 0, err
×
753
        }
×
754
        if err := cursor.All(ctx, &releases); err != nil {
1✔
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)
1✔
761
        if err != nil {
1✔
762
                return []model.Release{}, 0, err
×
763
        }
×
764

765
        if count < 1 {
2✔
766
                return []model.Release{}, int(count), nil
1✔
767
        }
1✔
768
        return releases, int(count), nil
1✔
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 {
1✔
790

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

1✔
793
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
1✔
794
}
1✔
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,
UNCOV
888
        ids []string, deviceType string) (*model.Image, error) {
×
UNCOV
889

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

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

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

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

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

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

UNCOV
920
        return &image, nil
×
921
}
922

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

×
UNCOV
927
        var images []*model.Image
×
UNCOV
928

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

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

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

UNCOV
949
        return images, nil
×
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 {
1✔
974

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

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

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

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

1✔
989
        _, err := collImg.InsertOne(ctx, image)
1✔
990
        if err != nil {
2✔
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
1✔
1004
}
1005

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

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

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

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

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

UNCOV
1105
        return &image, nil
×
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) {
1✔
1115

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

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

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

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

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

1149
        for _, i := range images {
2✔
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
1✔
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 {
1✔
1167

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

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

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

1182
        return nil
1✔
1183
}
1184

1185
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
1✔
1186
        if filt != nil && filt.Sort != "" {
2✔
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
1✔
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) {
1✔
1209

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

1✔
1213
        filters := bson.M{}
1✔
1214
        if filt != nil {
2✔
1215
                if filt.Name != "" {
2✔
1216
                        filters[StorageKeyImageName] = bson.M{
1✔
1217
                                "$regex": primitive.Regex{
1✔
1218
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
1✔
1219
                                        Options: "i",
1✔
1220
                                },
1✔
1221
                        }
1✔
1222
                }
1✔
1223
                if filt.Description != "" {
2✔
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 != "" {
2✔
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{
1✔
1243
                StorageKeyImageDependsIdx:  0,
1✔
1244
                StorageKeyImageProvidesIdx: 0,
1✔
1245
        }
1✔
1246
        findOptions := &mopts.FindOptions{}
1✔
1247
        findOptions.SetProjection(projection)
1✔
1248
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
2✔
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)
1✔
1254
        if sortField == "" || sortField == "name" {
2✔
1255
                sortField = StorageKeyImageName
1✔
1256
        }
1✔
1257
        if sortOrder == 0 {
2✔
1258
                sortOrder = 1
1✔
1259
        }
1✔
1260
        findOptions.SetSort(bson.D{
1✔
1261
                {Key: sortField, Value: sortOrder},
1✔
1262
                {Key: "_id", Value: sortOrder},
1✔
1263
        })
1✔
1264

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

1270
        // NOTE: cursor.All closes the cursor before returning
1271
        var images []*model.Image
1✔
1272
        if err := cursor.All(ctx, &images); err != nil {
1✔
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)
1✔
1280
        if err != nil {
1✔
1281
                return nil, -1, ErrDevicesCountFailed
×
1282
        }
×
1283

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

1287
func (db *DataStoreMongo) DeleteImagesByNames(ctx context.Context, names []string) error {
1✔
1288
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1289
        collDevs := database.Collection(CollectionImages)
1✔
1290
        query := bson.M{
1✔
1291
                StorageKeyImageName: bson.M{
1✔
1292
                        "$in": names,
1✔
1293
                },
1✔
1294
        }
1✔
1295
        _, err := collDevs.DeleteMany(ctx, query)
1✔
1296
        return err
1✔
1297
}
1✔
1298

1299
// device deployment log
1300
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1301
        log model.DeploymentLog) error {
1✔
1302

1✔
1303
        if err := log.Validate(); err != nil {
2✔
1304
                return err
1✔
1305
        }
1✔
1306

1307
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1308
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
1✔
1309

1✔
1310
        query := bson.D{
1✔
1311
                {Key: StorageKeyDeviceDeploymentDeviceId,
1✔
1312
                        Value: log.DeviceID},
1✔
1313
                {Key: StorageKeyDeviceDeploymentDeploymentID,
1✔
1314
                        Value: log.DeploymentID},
1✔
1315
        }
1✔
1316

1✔
1317
        // update log messages
1✔
1318
        // if the deployment log is already present than messages will be overwritten
1✔
1319
        update := bson.D{
1✔
1320
                {Key: "$set", Value: bson.M{
1✔
1321
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
1✔
1322
                }},
1✔
1323
        }
1✔
1324
        updateOptions := mopts.Update()
1✔
1325
        updateOptions.SetUpsert(true)
1✔
1326
        if _, err := collLogs.UpdateOne(
1✔
1327
                ctx, query, update, updateOptions); err != nil {
1✔
1328
                return err
×
1329
        }
×
1330

1331
        return nil
1✔
1332
}
1333

1334
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1335
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
1✔
1336

1✔
1337
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1338
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
1✔
1339

1✔
1340
        query := bson.M{
1✔
1341
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
1342
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
1343
        }
1✔
1344

1✔
1345
        var depl model.DeploymentLog
1✔
1346
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
2✔
1347
                if err == mongo.ErrNoDocuments {
2✔
1348
                        return nil, nil
1✔
1349
                }
1✔
1350
                return nil, err
×
1351
        }
1352

1353
        return &depl, nil
1✔
1354
}
1355

1356
// device deployments
1357

1358
// Insert persists device deployment object
1359
func (db *DataStoreMongo) InsertDeviceDeployment(
1360
        ctx context.Context,
1361
        deviceDeployment *model.DeviceDeployment,
1362
        incrementDeviceCount bool,
1363
) error {
1✔
1364
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1365
        c := database.Collection(CollectionDevices)
1✔
1366

1✔
1367
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
2✔
1368
                startedTime := time.Now().UTC()
1✔
1369
                deviceDeployment.Started = &startedTime
1✔
1370
        }
1✔
1371

1372
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
1✔
1373
                return err
×
1374
        }
×
1375

1376
        if incrementDeviceCount {
2✔
1377
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
1✔
1378
                if err != nil {
1✔
1379
                        return err
×
1380
                }
×
1381
        }
1382

1383
        return nil
1✔
1384
}
1385

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

1✔
1391
        if len(deployments) == 0 {
2✔
1392
                return nil
1✔
1393
        }
1✔
1394

1395
        deviceCountIncrements := make(map[string]int)
1✔
1396

1✔
1397
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
1✔
1398
        var list []interface{}
1✔
1399
        for _, deployment := range deployments {
2✔
1400

1✔
1401
                if deployment == nil {
2✔
1402
                        return ErrStorageInvalidDeviceDeployment
1✔
1403
                }
1✔
1404

1405
                if err := deployment.Validate(); err != nil {
2✔
1406
                        return errors.Wrap(err, "Validating device deployment")
1✔
1407
                }
1✔
1408

1409
                list = append(list, deployment)
1✔
1410
                if deployment.Status != model.DeviceDeploymentStatusPending {
2✔
1411
                        startedTime := time.Now().UTC()
1✔
1412
                        deployment.Started = &startedTime
1✔
1413
                }
1✔
1414
                deviceCountIncrements[deployment.DeploymentId]++
1✔
1415
        }
1416

1417
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1418
        collDevs := database.Collection(CollectionDevices)
1✔
1419

1✔
1420
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
1✔
1421
                return err
×
1422
        }
×
1423

1424
        for deploymentID := range deviceCountIncrements {
2✔
1425
                err := db.IncrementDeploymentDeviceCount(
1✔
1426
                        ctx,
1✔
1427
                        deploymentID,
1✔
1428
                        deviceCountIncrements[deploymentID],
1✔
1429
                )
1✔
1430
                if err != nil {
1✔
1431
                        return err
×
1432
                }
×
1433
        }
1434

1435
        return nil
1✔
1436
}
1437

1438
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1439
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1440
        ctx context.Context,
1441
        deviceID string,
1442
) (*model.DeviceDeployment, error) {
1✔
1443

1✔
1444
        // Verify ID formatting
1✔
1445
        if len(deviceID) == 0 {
2✔
1446
                return nil, ErrStorageInvalidID
1✔
1447
        }
1✔
1448

1449
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1450
        collDevs := database.Collection(CollectionDevices)
1✔
1451

1✔
1452
        // Device should know only about deployments that are not finished
1✔
1453
        query := bson.D{
1✔
1454
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
1✔
1455
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1456
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1457
                        {Key: "$exists", Value: false},
1✔
1458
                }},
1✔
1459
        }
1✔
1460

1✔
1461
        // Find the oldest one by sorting the creation timestamp
1✔
1462
        // in ascending order.
1✔
1463
        findOptions := mopts.FindOne()
1✔
1464
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
1465

1✔
1466
        // Select only the oldest one that have not been finished yet.
1✔
1467
        deployment := new(model.DeviceDeployment)
1✔
1468
        if err := collDevs.FindOne(ctx, query, findOptions).
1✔
1469
                Decode(deployment); err != nil {
2✔
1470
                if err == mongo.ErrNoDocuments {
2✔
1471
                        return nil, nil
1✔
1472
                }
1✔
1473
                return nil, err
1✔
1474
        }
1475

1476
        return deployment, nil
1✔
1477
}
1478

1479
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1480
// matching device id that has not finished yet.
1481
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1482
        ctx context.Context,
1483
        deviceID string,
1484
) (*model.DeviceDeployment, error) {
1✔
1485

1✔
1486
        // Verify ID formatting
1✔
1487
        if len(deviceID) == 0 {
2✔
1488
                return nil, ErrStorageInvalidID
1✔
1489
        }
1✔
1490

1491
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1492
        collDevs := database.Collection(CollectionDevices)
1✔
1493

1✔
1494
        query := bson.D{
1✔
1495
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
1✔
1496
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1497
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1498
                        {Key: "$exists", Value: false},
1✔
1499
                }},
1✔
1500
        }
1✔
1501

1✔
1502
        // Find the latest one by sorting by the creation timestamp
1✔
1503
        // in ascending order.
1✔
1504
        findOptions := mopts.FindOne()
1✔
1505
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
1506

1✔
1507
        // Select only the latest one that have not been finished yet.
1✔
1508
        var deployment *model.DeviceDeployment
1✔
1509
        if err := collDevs.FindOne(ctx, query, findOptions).
1✔
1510
                Decode(&deployment); err != nil {
2✔
1511
                if err == mongo.ErrNoDocuments {
2✔
1512
                        return nil, nil
1✔
1513
                }
1✔
1514
                return nil, err
1✔
1515
        }
1516

1517
        return deployment, nil
1✔
1518
}
1519

1520
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1521
        ctx context.Context,
1522
        deviceID string,
1523
        deploymentID string,
1524
        ddState model.DeviceDeploymentState,
1525
        currentStatus model.DeviceDeploymentStatus,
1526
) (model.DeviceDeploymentStatus, error) {
1✔
1527

1✔
1528
        // Verify ID formatting
1✔
1529
        if len(deviceID) == 0 ||
1✔
1530
                len(deploymentID) == 0 {
2✔
1531
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
1✔
1532
        }
1✔
1533

1534
        if err := ddState.Validate(); err != nil {
2✔
1535
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1536
        }
1✔
1537

1538
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1539
        collDevs := database.Collection(CollectionDevices)
1✔
1540

1✔
1541
        // Device should know only about deployments that are not finished
1✔
1542
        query := bson.D{
1✔
1543
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1544
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1545
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1546
                        {Key: "$exists", Value: false},
1✔
1547
                }},
1✔
1548
        }
1✔
1549

1✔
1550
        // update status field
1✔
1551
        set := bson.M{
1✔
1552
                StorageKeyDeviceDeploymentStatus: ddState.Status,
1✔
1553
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
1✔
1554
        }
1✔
1555
        // and finish time if provided
1✔
1556
        if ddState.FinishTime != nil {
2✔
1557
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
1✔
1558
        }
1✔
1559

1560
        if len(ddState.SubState) > 0 {
2✔
1561
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
1✔
1562
        }
1✔
1563

1564
        if currentStatus == model.DeviceDeploymentStatusPending &&
1✔
1565
                ddState.Status != currentStatus {
2✔
1566
                startedTime := time.Now().UTC()
1✔
1567
                set[StorageKeyDeviceDeploymentStarted] = startedTime
1✔
1568
        }
1✔
1569

1570
        update := bson.D{
1✔
1571
                {Key: "$set", Value: set},
1✔
1572
        }
1✔
1573

1✔
1574
        var old model.DeviceDeployment
1✔
1575

1✔
1576
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
1✔
1577
                Decode(&old); err != nil {
2✔
1578
                if err == mongo.ErrNoDocuments {
2✔
1579
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
1✔
1580
                }
1✔
1581
                return model.DeviceDeploymentStatusNull, err
×
1582

1583
        }
1584

1585
        return old.Status, nil
1✔
1586
}
1587

1588
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1589
        deviceID string, deploymentID string, log bool) error {
1✔
1590

1✔
1591
        // Verify ID formatting
1✔
1592
        if len(deviceID) == 0 ||
1✔
1593
                len(deploymentID) == 0 {
2✔
1594
                return ErrStorageInvalidID
1✔
1595
        }
1✔
1596

1597
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1598
        collDevs := database.Collection(CollectionDevices)
1✔
1599

1✔
1600
        selector := bson.D{
1✔
1601
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1602
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1603
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1604
                        {Key: "$exists", Value: false},
1✔
1605
                }},
1✔
1606
        }
1✔
1607

1✔
1608
        update := bson.D{
1✔
1609
                {Key: "$set", Value: bson.M{
1✔
1610
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
1✔
1611
        }
1✔
1612

1✔
1613
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1614
                return err
×
1615
        } else if res.MatchedCount == 0 {
2✔
1616
                return ErrStorageNotFound
1✔
1617
        }
1✔
1618

1619
        return nil
1✔
1620
}
1621

1622
// SaveDeviceDeploymentRequest saves device deployment request
1623
// with the device deployment object
1624
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1625
        ctx context.Context,
1626
        ID string,
1627
        request *model.DeploymentNextRequest,
1628
) error {
1✔
1629

1✔
1630
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1631
        collDevs := database.Collection(CollectionDevices)
1✔
1632

1✔
1633
        res, err := collDevs.UpdateOne(
1✔
1634
                ctx,
1✔
1635
                bson.D{{Key: StorageKeyId, Value: ID}},
1✔
1636
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
1✔
1637
        )
1✔
1638
        if err != nil {
1✔
1639
                return err
×
1640
        } else if res.MatchedCount == 0 {
2✔
1641
                return ErrStorageNotFound
1✔
1642
        }
1✔
1643
        return nil
1✔
1644
}
1645

1646
// AssignArtifact assigns artifact to the device deployment
1647
func (db *DataStoreMongo) AssignArtifact(
1648
        ctx context.Context,
1649
        deviceID string,
1650
        deploymentID string,
1651
        artifact *model.Image,
UNCOV
1652
) error {
×
UNCOV
1653

×
UNCOV
1654
        // Verify ID formatting
×
UNCOV
1655
        if len(deviceID) == 0 ||
×
UNCOV
1656
                len(deploymentID) == 0 {
×
1657
                return ErrStorageInvalidID
×
1658
        }
×
1659

UNCOV
1660
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
UNCOV
1661
        collDevs := database.Collection(CollectionDevices)
×
UNCOV
1662

×
UNCOV
1663
        selector := bson.D{
×
UNCOV
1664
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
×
UNCOV
1665
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
×
UNCOV
1666
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
×
UNCOV
1667
                        {Key: "$exists", Value: false},
×
UNCOV
1668
                }},
×
UNCOV
1669
        }
×
UNCOV
1670

×
UNCOV
1671
        update := bson.D{
×
UNCOV
1672
                {Key: "$set", Value: bson.M{
×
UNCOV
1673
                        StorageKeyDeviceDeploymentArtifact: artifact,
×
UNCOV
1674
                }},
×
UNCOV
1675
        }
×
UNCOV
1676

×
UNCOV
1677
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
×
1678
                return err
×
UNCOV
1679
        } else if res.MatchedCount == 0 {
×
1680
                return ErrStorageNotFound
×
1681
        }
×
1682

UNCOV
1683
        return nil
×
1684
}
1685

1686
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1687
        id string) (model.Stats, error) {
1✔
1688

1✔
1689
        if len(id) == 0 {
1✔
1690
                return nil, ErrStorageInvalidID
×
1691
        }
×
1692

1693
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1694
        collDevs := database.Collection(CollectionDevices)
1✔
1695

1✔
1696
        match := bson.D{
1✔
1697
                {Key: "$match", Value: bson.M{
1✔
1698
                        StorageKeyDeviceDeploymentDeploymentID: id,
1✔
1699
                        StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
1700
                                {Key: "$exists", Value: false},
1✔
1701
                        },
1✔
1702
                }},
1✔
1703
        }
1✔
1704
        group := bson.D{
1✔
1705
                {Key: "$group", Value: bson.D{
1✔
1706
                        {Key: "_id",
1✔
1707
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
1✔
1708
                        {Key: "count",
1✔
1709
                                Value: bson.M{"$sum": 1}}},
1✔
1710
                },
1✔
1711
        }
1✔
1712
        pipeline := []bson.D{
1✔
1713
                match,
1✔
1714
                group,
1✔
1715
        }
1✔
1716
        var results []struct {
1✔
1717
                Status model.DeviceDeploymentStatus `bson:"_id"`
1✔
1718
                Count  int
1✔
1719
        }
1✔
1720
        cursor, err := collDevs.Aggregate(ctx, pipeline)
1✔
1721
        if err != nil {
1✔
1722
                return nil, err
×
1723
        }
×
1724
        if err := cursor.All(ctx, &results); err != nil {
1✔
1725
                if err == mongo.ErrNoDocuments {
×
1726
                        return nil, nil
×
1727
                }
×
1728
                return nil, err
×
1729
        }
1730

1731
        raw := model.NewDeviceDeploymentStats()
1✔
1732
        for _, res := range results {
2✔
1733
                raw.Set(res.Status, res.Count)
1✔
1734
        }
1✔
1735
        return raw, nil
1✔
1736
}
1737

1738
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1739
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1740
        deploymentID string) ([]model.DeviceDeployment, error) {
1✔
1741

1✔
1742
        statuses := []model.DeviceDeployment{}
1✔
1743
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1744
        collDevs := database.Collection(CollectionDevices)
1✔
1745

1✔
1746
        query := bson.M{
1✔
1747
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
1748
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
1749
                        {Key: "$exists", Value: false},
1✔
1750
                },
1✔
1751
        }
1✔
1752

1✔
1753
        cursor, err := collDevs.Find(ctx, query)
1✔
1754
        if err != nil {
1✔
1755
                return nil, err
×
1756
        }
×
1757

1758
        if err = cursor.All(ctx, &statuses); err != nil {
1✔
1759
                if err == mongo.ErrNoDocuments {
×
1760
                        return nil, nil
×
1761
                }
×
1762
                return nil, err
×
1763
        }
1764

1765
        return statuses, nil
1✔
1766
}
1767

1768
func (db *DataStoreMongo) GetDevicesListForDeployment(ctx context.Context,
1769
        q store.ListQuery) ([]model.DeviceDeployment, int, error) {
1✔
1770

1✔
1771
        statuses := []model.DeviceDeployment{}
1✔
1772
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1773
        collDevs := database.Collection(CollectionDevices)
1✔
1774

1✔
1775
        query := bson.D{
1✔
1776
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
1✔
1777
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1778
                        {Key: "$exists", Value: false},
1✔
1779
                }},
1✔
1780
        }
1✔
1781
        if q.Status != nil {
2✔
1782
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
2✔
1783
                        query = append(query, bson.E{
1✔
1784
                                Key: "status", Value: bson.D{{
1✔
1785
                                        Key:   "$gte",
1✔
1786
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1787
                                }, {
1✔
1788
                                        Key:   "$lte",
1✔
1789
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1790
                                }},
1✔
1791
                        })
1✔
1792
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
2✔
1793
                        query = append(query, bson.E{
×
1794
                                Key: "status", Value: bson.D{{
×
1795
                                        Key:   "$gte",
×
1796
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1797
                                }, {
×
1798
                                        Key:   "$lte",
×
1799
                                        Value: model.DeviceDeploymentStatusPending,
×
1800
                                }},
×
1801
                        })
×
1802
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
2✔
1803
                        query = append(query, bson.E{
1✔
1804
                                Key: "status", Value: bson.D{{
1✔
1805
                                        Key: "$in",
1✔
1806
                                        Value: []model.DeviceDeploymentStatus{
1✔
1807
                                                model.DeviceDeploymentStatusFailure,
1✔
1808
                                                model.DeviceDeploymentStatusAborted,
1✔
1809
                                                model.DeviceDeploymentStatusSuccess,
1✔
1810
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1811
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1812
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1813
                                        },
1✔
1814
                                }},
1✔
1815
                        })
1✔
1816
                } else {
2✔
1817
                        var status model.DeviceDeploymentStatus
1✔
1818
                        err := status.UnmarshalText([]byte(*q.Status))
1✔
1819
                        if err != nil {
2✔
1820
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1821
                        }
1✔
1822
                        query = append(query, bson.E{
1✔
1823
                                Key: "status", Value: status,
1✔
1824
                        })
1✔
1825
                }
1826
        }
1827

1828
        options := mopts.Find()
1✔
1829
        sortFieldQuery := bson.D{
1✔
1830
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
1✔
1831
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
1✔
1832
        }
1✔
1833
        options.SetSort(sortFieldQuery)
1✔
1834
        if q.Skip > 0 {
2✔
1835
                options.SetSkip(int64(q.Skip))
1✔
1836
        }
1✔
1837
        if q.Limit > 0 {
2✔
1838
                options.SetLimit(int64(q.Limit))
1✔
1839
        } else {
2✔
1840
                options.SetLimit(DefaultDocumentLimit)
1✔
1841
        }
1✔
1842

1843
        cursor, err := collDevs.Find(ctx, query, options)
1✔
1844
        if err != nil {
2✔
1845
                return nil, -1, err
1✔
1846
        }
1✔
1847

1848
        if err = cursor.All(ctx, &statuses); err != nil {
1✔
1849
                if err == mongo.ErrNoDocuments {
×
1850
                        return nil, -1, nil
×
1851
                }
×
1852
                return nil, -1, err
×
1853
        }
1854

1855
        count, err := collDevs.CountDocuments(ctx, query)
1✔
1856
        if err != nil {
1✔
1857
                return nil, -1, ErrDevicesCountFailed
×
1858
        }
×
1859

1860
        return statuses, int(count), nil
1✔
1861
}
1862

1863
func (db *DataStoreMongo) GetDeviceDeploymentsForDevice(ctx context.Context,
1864
        q store.ListQueryDeviceDeployments) ([]model.DeviceDeployment, int, error) {
1✔
1865

1✔
1866
        statuses := []model.DeviceDeployment{}
1✔
1867
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1868
        collDevs := database.Collection(CollectionDevices)
1✔
1869

1✔
1870
        query := bson.D{}
1✔
1871
        if q.DeviceID != "" {
2✔
1872
                query = append(query, bson.E{
1✔
1873
                        Key:   StorageKeyDeviceDeploymentDeviceId,
1✔
1874
                        Value: q.DeviceID,
1✔
1875
                })
1✔
1876
        } else if len(q.IDs) > 0 {
3✔
1877
                query = append(query, bson.E{
1✔
1878
                        Key: StorageKeyId,
1✔
1879
                        Value: bson.D{{
1✔
1880
                                Key:   "$in",
1✔
1881
                                Value: q.IDs,
1✔
1882
                        }},
1✔
1883
                })
1✔
1884
        }
1✔
1885

1886
        if q.Status != nil {
2✔
1887
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
2✔
1888
                        query = append(query, bson.E{
1✔
1889
                                Key: "status", Value: bson.D{{
1✔
1890
                                        Key:   "$gte",
1✔
1891
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1892
                                }, {
1✔
1893
                                        Key:   "$lte",
1✔
1894
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1895
                                }},
1✔
1896
                        })
1✔
1897
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
3✔
1898
                        query = append(query, bson.E{
1✔
1899
                                Key: "status", Value: bson.D{{
1✔
1900
                                        Key:   "$gte",
1✔
1901
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1902
                                }, {
1✔
1903
                                        Key:   "$lte",
1✔
1904
                                        Value: model.DeviceDeploymentStatusPending,
1✔
1905
                                }},
1✔
1906
                        })
1✔
1907
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
3✔
1908
                        query = append(query, bson.E{
1✔
1909
                                Key: "status", Value: bson.D{{
1✔
1910
                                        Key: "$in",
1✔
1911
                                        Value: []model.DeviceDeploymentStatus{
1✔
1912
                                                model.DeviceDeploymentStatusFailure,
1✔
1913
                                                model.DeviceDeploymentStatusAborted,
1✔
1914
                                                model.DeviceDeploymentStatusSuccess,
1✔
1915
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1916
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1917
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1918
                                        },
1✔
1919
                                }},
1✔
1920
                        })
1✔
1921
                } else {
2✔
1922
                        var status model.DeviceDeploymentStatus
1✔
1923
                        err := status.UnmarshalText([]byte(*q.Status))
1✔
1924
                        if err != nil {
2✔
1925
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1926
                        }
1✔
1927
                        query = append(query, bson.E{
1✔
1928
                                Key: "status", Value: status,
1✔
1929
                        })
1✔
1930
                }
1931
        }
1932

1933
        options := mopts.Find()
1✔
1934
        sortFieldQuery := bson.D{
1✔
1935
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
1✔
1936
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
1✔
1937
        }
1✔
1938
        options.SetSort(sortFieldQuery)
1✔
1939
        if q.Skip > 0 {
2✔
1940
                options.SetSkip(int64(q.Skip))
1✔
1941
        }
1✔
1942
        if q.Limit > 0 {
2✔
1943
                options.SetLimit(int64(q.Limit))
1✔
1944
        } else {
1✔
1945
                options.SetLimit(DefaultDocumentLimit)
×
1946
        }
×
1947

1948
        cursor, err := collDevs.Find(ctx, query, options)
1✔
1949
        if err != nil {
1✔
1950
                return nil, -1, err
×
1951
        }
×
1952

1953
        if err = cursor.All(ctx, &statuses); err != nil {
1✔
1954
                if err == mongo.ErrNoDocuments {
×
1955
                        return nil, 0, nil
×
1956
                }
×
1957
                return nil, -1, err
×
1958
        }
1959

1960
        maxCount := maxCountDocuments
1✔
1961
        countOptions := &mopts.CountOptions{
1✔
1962
                Limit: &maxCount,
1✔
1963
        }
1✔
1964
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
1✔
1965
        if err != nil {
1✔
1966
                return nil, -1, ErrDevicesCountFailed
×
1967
        }
×
1968

1969
        return statuses, int(count), nil
1✔
1970
}
1971

1972
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1973
// `deviceID`, false otherwise. In case of errors returns false and an error
1974
// that occurred
1975
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1976
        deploymentID string, deviceID string) (bool, error) {
1✔
1977

1✔
1978
        var dep model.DeviceDeployment
1✔
1979
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1980
        collDevs := database.Collection(CollectionDevices)
1✔
1981

1✔
1982
        query := bson.D{
1✔
1983
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1984
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1985
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1986
                        {Key: "$exists", Value: false},
1✔
1987
                }},
1✔
1988
        }
1✔
1989

1✔
1990
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
2✔
1991
                if err == mongo.ErrNoDocuments {
2✔
1992
                        return false, nil
1✔
1993
                } else {
1✔
1994
                        return false, err
×
1995
                }
×
1996
        }
1997

1998
        return true, nil
1✔
1999
}
2000

2001
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2002
        deploymentId string) error {
1✔
2003

1✔
2004
        if len(deploymentId) == 0 {
2✔
2005
                return ErrStorageInvalidID
1✔
2006
        }
1✔
2007

2008
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2009
        collDevs := database.Collection(CollectionDevices)
1✔
2010
        selector := bson.M{
1✔
2011
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
1✔
2012
                StorageKeyDeviceDeploymentActive:       true,
1✔
2013
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2014
                        {Key: "$exists", Value: false},
1✔
2015
                },
1✔
2016
        }
1✔
2017

1✔
2018
        update := bson.M{
1✔
2019
                "$set": bson.M{
1✔
2020
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
1✔
2021
                        StorageKeyDeviceDeploymentActive: false,
1✔
2022
                },
1✔
2023
        }
1✔
2024

1✔
2025
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2026
                return err
×
2027
        }
×
2028

2029
        return nil
1✔
2030
}
2031

2032
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2033
        deviceID string) error {
1✔
2034
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2035
        collDevs := database.Collection(CollectionDevices)
1✔
2036
        selector := bson.M{
1✔
2037
                StorageKeyDeviceDeploymentDeviceId: deviceID,
1✔
2038
                StorageKeyDeviceDeploymentActive:   false,
1✔
2039
                StorageKeyDeviceDeploymentDeleted: bson.M{
1✔
2040
                        "$exists": false,
1✔
2041
                },
1✔
2042
        }
1✔
2043

1✔
2044
        now := time.Now()
1✔
2045
        update := bson.M{
1✔
2046
                "$set": bson.M{
1✔
2047
                        StorageKeyDeviceDeploymentDeleted: &now,
1✔
2048
                },
1✔
2049
        }
1✔
2050

1✔
2051
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2052
                return err
×
2053
        }
×
2054

2055
        database = db.client.Database(DatabaseName)
1✔
2056
        collDevs = database.Collection(CollectionDevicesLastStatus)
1✔
2057
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
1✔
2058

1✔
2059
        return err
1✔
2060
}
2061

2062
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2063
        deviceId string) error {
1✔
2064

1✔
2065
        if len(deviceId) == 0 {
2✔
2066
                return ErrStorageInvalidID
1✔
2067
        }
1✔
2068

2069
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2070
        collDevs := database.Collection(CollectionDevices)
1✔
2071
        selector := bson.M{
1✔
2072
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2073
                StorageKeyDeviceDeploymentActive:   true,
1✔
2074
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2075
                        {Key: "$exists", Value: false},
1✔
2076
                },
1✔
2077
        }
1✔
2078

1✔
2079
        update := bson.M{
1✔
2080
                "$set": bson.M{
1✔
2081
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2082
                        StorageKeyDeviceDeploymentActive: false,
1✔
2083
                },
1✔
2084
        }
1✔
2085

1✔
2086
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2087
                return err
×
2088
        }
×
2089

2090
        return nil
1✔
2091
}
2092

2093
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
UNCOV
2094
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
×
UNCOV
2095

×
UNCOV
2096
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
UNCOV
2097
        collDevs := database.Collection(CollectionDevices)
×
UNCOV
2098

×
UNCOV
2099
        filter := bson.M{
×
UNCOV
2100
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
×
UNCOV
2101
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
×
UNCOV
2102
        }
×
UNCOV
2103
        if !includeDeleted {
×
UNCOV
2104
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
×
UNCOV
2105
                        {Key: "$exists", Value: false},
×
UNCOV
2106
                }
×
UNCOV
2107
        }
×
2108

UNCOV
2109
        opts := &mopts.FindOneOptions{}
×
UNCOV
2110
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
×
UNCOV
2111

×
UNCOV
2112
        var dd model.DeviceDeployment
×
UNCOV
2113
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
×
UNCOV
2114
                if err == mongo.ErrNoDocuments {
×
UNCOV
2115
                        return nil, ErrStorageNotFound
×
UNCOV
2116
                }
×
2117
                return nil, err
×
2118
        }
2119

UNCOV
2120
        return &dd, nil
×
2121
}
2122

2123
func (db *DataStoreMongo) GetDeviceDeployments(
2124
        ctx context.Context,
2125
        skip int,
2126
        limit int,
2127
        deviceID string,
2128
        active *bool,
2129
        includeDeleted bool,
2130
) ([]model.DeviceDeployment, error) {
1✔
2131

1✔
2132
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2133
        collDevs := database.Collection(CollectionDevices)
1✔
2134

1✔
2135
        filter := bson.M{}
1✔
2136
        if !includeDeleted {
2✔
2137
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2138
                        {Key: "$exists", Value: false},
1✔
2139
                }
1✔
2140
        }
1✔
2141
        if deviceID != "" {
2✔
2142
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2143
        }
1✔
2144
        if active != nil {
2✔
2145
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2146
        }
1✔
2147

2148
        opts := &mopts.FindOptions{}
1✔
2149
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2150
        if skip > 0 {
2✔
2151
                opts.SetSkip(int64(skip))
1✔
2152
        }
1✔
2153
        if limit > 0 {
2✔
2154
                opts.SetLimit(int64(limit))
1✔
2155
        }
1✔
2156

2157
        var deviceDeployments []model.DeviceDeployment
1✔
2158
        cursor, err := collDevs.Find(ctx, filter, opts)
1✔
2159
        if err != nil {
1✔
2160
                return nil, err
×
2161
        }
×
2162
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
1✔
2163
                return nil, err
×
2164
        }
×
2165

2166
        return deviceDeployments, nil
1✔
2167
}
2168

2169
// deployments
2170

2171
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2172
        indexes ...mongo.IndexModel) error {
1✔
2173
        ctx := context.Background()
1✔
2174
        dataBase := db.client.Database(dbName)
1✔
2175

1✔
2176
        coll := dataBase.Collection(collName)
1✔
2177
        idxView := coll.Indexes()
1✔
2178
        _, err := idxView.CreateMany(ctx, indexes)
1✔
2179
        return err
1✔
2180
}
1✔
2181

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

1✔
2185
        var idx bson.M
1✔
2186
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2187
        collDpl := database.Collection(CollectionDeployments)
1✔
2188
        idxView := collDpl.Indexes()
1✔
2189

1✔
2190
        cursor, err := idxView.List(ctx)
1✔
2191
        if err != nil {
1✔
2192
                // check failed, assume indexing is not there
×
2193
                return false
×
2194
        }
×
2195

2196
        has := map[string]bool{}
1✔
2197
        for cursor.Next(ctx) {
2✔
2198
                if err = cursor.Decode(&idx); err != nil {
1✔
2199
                        continue
×
2200
                }
2201
                if _, ok := idx["weights"]; ok {
2✔
2202
                        // text index
1✔
2203
                        for k := range idx["weights"].(bson.M) {
2✔
2204
                                has[k] = true
1✔
2205
                        }
1✔
2206
                } else {
1✔
2207
                        for i := range idx["key"].(bson.M) {
2✔
2208
                                has[i] = true
1✔
2209
                        }
1✔
2210

2211
                }
2212
        }
2213
        if err != nil {
1✔
2214
                return false
×
2215
        }
×
2216

2217
        for _, key := range StorageIndexes.Keys.(bson.D) {
2✔
2218
                _, ok := has[key.Key]
1✔
2219
                if !ok {
2✔
2220
                        return false
1✔
2221
                }
1✔
2222
        }
2223

2224
        return true
1✔
2225
}
2226

2227
// Insert persists object
2228
func (db *DataStoreMongo) InsertDeployment(
2229
        ctx context.Context,
2230
        deployment *model.Deployment,
2231
) error {
1✔
2232

1✔
2233
        if deployment == nil {
2✔
2234
                return ErrDeploymentStorageInvalidDeployment
1✔
2235
        }
1✔
2236

2237
        if err := deployment.Validate(); err != nil {
2✔
2238
                return err
1✔
2239
        }
1✔
2240

2241
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2242
        collDpl := database.Collection(CollectionDeployments)
1✔
2243

1✔
2244
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
2✔
2245
                if mongo.IsDuplicateKeyError(err) {
2✔
2246
                        return ErrConflictingDeployment
1✔
2247
                }
1✔
2248
                return err
×
2249
        }
2250
        return nil
1✔
2251
}
2252

2253
// Delete removed entry by ID
2254
// Noop on ID not found
2255
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
1✔
2256

1✔
2257
        if len(id) == 0 {
2✔
2258
                return ErrStorageInvalidID
1✔
2259
        }
1✔
2260

2261
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2262
        collDpl := database.Collection(CollectionDeployments)
1✔
2263

1✔
2264
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
2265
                return err
×
2266
        }
×
2267

2268
        return nil
1✔
2269
}
2270

2271
func (db *DataStoreMongo) FindDeploymentByID(
2272
        ctx context.Context,
2273
        id string,
2274
) (*model.Deployment, error) {
1✔
2275

1✔
2276
        if len(id) == 0 {
2✔
2277
                return nil, ErrStorageInvalidID
1✔
2278
        }
1✔
2279

2280
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2281
        collDpl := database.Collection(CollectionDeployments)
1✔
2282

1✔
2283
        deployment := new(model.Deployment)
1✔
2284
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
1✔
2285
                Decode(deployment); err != nil {
2✔
2286
                if err == mongo.ErrNoDocuments {
2✔
2287
                        return nil, nil
1✔
2288
                }
1✔
2289
                return nil, err
×
2290
        }
2291

2292
        return deployment, nil
1✔
2293
}
2294

2295
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2296
        ctx context.Context,
2297
        ids ...string,
2298
) (deploymentStats []*model.DeploymentStats, err error) {
1✔
2299

1✔
2300
        if len(ids) == 0 {
1✔
2301
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2302
        }
×
2303

2304
        for _, id := range ids {
2✔
2305
                if len(id) == 0 {
1✔
2306
                        return nil, ErrStorageInvalidID
×
2307
                }
×
2308
        }
2309

2310
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2311
        collDpl := database.Collection(CollectionDeployments)
1✔
2312

1✔
2313
        query := bson.M{
1✔
2314
                "_id": bson.M{
1✔
2315
                        "$in": ids,
1✔
2316
                },
1✔
2317
        }
1✔
2318
        statsProjection := &mopts.FindOptions{
1✔
2319
                Projection: bson.M{"stats": 1},
1✔
2320
        }
1✔
2321

1✔
2322
        results, err := collDpl.Find(
1✔
2323
                ctx,
1✔
2324
                query,
1✔
2325
                statsProjection,
1✔
2326
        )
1✔
2327
        if err != nil {
1✔
2328
                return nil, err
×
2329
        }
×
2330

2331
        for results.Next(context.Background()) {
2✔
2332
                depl := new(model.DeploymentStats)
1✔
2333
                if err = results.Decode(&depl); err != nil {
1✔
2334
                        if err == mongo.ErrNoDocuments {
×
2335
                                return nil, nil
×
2336
                        }
×
2337
                        return nil, err
×
2338
                }
2339
                deploymentStats = append(deploymentStats, depl)
1✔
2340
        }
2341

2342
        return deploymentStats, nil
1✔
2343
}
2344

2345
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2346
        id string) (*model.Deployment, error) {
1✔
2347

1✔
2348
        if len(id) == 0 {
2✔
2349
                return nil, ErrStorageInvalidID
1✔
2350
        }
1✔
2351

2352
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2353
        collDpl := database.Collection(CollectionDeployments)
1✔
2354

1✔
2355
        var deployment *model.Deployment
1✔
2356
        filter := bson.D{
1✔
2357
                {Key: "_id", Value: id},
1✔
2358
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2359
        }
1✔
2360
        if err := collDpl.FindOne(ctx, filter).
1✔
2361
                Decode(&deployment); err != nil {
2✔
2362
                if err == mongo.ErrNoDocuments {
2✔
2363
                        return nil, nil
1✔
2364
                }
1✔
2365
                return nil, err
×
2366
        }
2367

2368
        return deployment, nil
1✔
2369
}
2370

2371
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2372
        ctx context.Context,
2373
        deploymentID string,
2374
        increment int,
2375
) error {
1✔
2376
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2377
        collection := database.Collection(CollectionDeployments)
1✔
2378

1✔
2379
        filter := bson.M{
1✔
2380
                "_id": deploymentID,
1✔
2381
                StorageKeyDeploymentDeviceCount: bson.M{
1✔
2382
                        "$ne": nil,
1✔
2383
                },
1✔
2384
        }
1✔
2385

1✔
2386
        update := bson.M{
1✔
2387
                "$inc": bson.M{
1✔
2388
                        StorageKeyDeploymentDeviceCount: increment,
1✔
2389
                },
1✔
2390
        }
1✔
2391

1✔
2392
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2393
        return err
1✔
2394
}
1✔
2395

2396
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2397
        ctx context.Context,
2398
        deploymentID string,
2399
        count int,
2400
) error {
1✔
2401
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2402
        collection := database.Collection(CollectionDeployments)
1✔
2403

1✔
2404
        filter := bson.M{
1✔
2405
                "_id": deploymentID,
1✔
2406
                StorageKeyDeploymentDeviceCount: bson.M{
1✔
2407
                        "$eq": nil,
1✔
2408
                },
1✔
2409
        }
1✔
2410

1✔
2411
        update := bson.M{
1✔
2412
                "$set": bson.M{
1✔
2413
                        StorageKeyDeploymentDeviceCount: count,
1✔
2414
                },
1✔
2415
        }
1✔
2416

1✔
2417
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2418
        return err
1✔
2419
}
1✔
2420

2421
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2422
        id string) (int, error) {
1✔
2423

1✔
2424
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2425
        collDevs := database.Collection(CollectionDevices)
1✔
2426

1✔
2427
        filter := bson.M{
1✔
2428
                StorageKeyDeviceDeploymentDeploymentID: id,
1✔
2429
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2430
                        {Key: "$exists", Value: false},
1✔
2431
                },
1✔
2432
        }
1✔
2433

1✔
2434
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
1✔
2435
        if err != nil {
1✔
2436
                return 0, err
×
2437
        }
×
2438

2439
        return int(deviceCount), nil
1✔
2440
}
2441

2442
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2443
        id string, stats model.Stats) error {
1✔
2444

1✔
2445
        if len(id) == 0 {
2✔
2446
                return ErrStorageInvalidID
1✔
2447
        }
1✔
2448

2449
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2450
        collDpl := database.Collection(CollectionDeployments)
1✔
2451

1✔
2452
        deployment, err := model.NewDeployment()
1✔
2453
        if err != nil {
1✔
2454
                return errors.Wrap(err, "failed to create deployment")
×
2455
        }
×
2456

2457
        deployment.Stats = stats
1✔
2458
        var update bson.M
1✔
2459
        if deployment.IsFinished() {
1✔
2460
                now := time.Now()
×
2461

×
2462
                update = bson.M{
×
2463
                        "$set": bson.M{
×
2464
                                StorageKeyDeploymentStats:    stats,
×
2465
                                StorageKeyDeploymentFinished: &now,
×
2466
                        },
×
2467
                }
×
2468
        } else {
1✔
2469
                update = bson.M{
1✔
2470
                        "$set": bson.M{
1✔
2471
                                StorageKeyDeploymentStats: stats,
1✔
2472
                        },
1✔
2473
                }
1✔
2474
        }
1✔
2475

2476
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
1✔
2477
        if res != nil && res.MatchedCount == 0 {
2✔
2478
                return ErrStorageInvalidID
1✔
2479
        }
1✔
2480
        return err
1✔
2481
}
2482

2483
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2484
        stateFrom, stateTo model.DeviceDeploymentStatus) (model.Stats, error) {
1✔
2485

1✔
2486
        if len(id) == 0 {
2✔
2487
                return nil, ErrStorageInvalidID
1✔
2488
        }
1✔
2489

2490
        if _, err := stateTo.MarshalText(); err != nil {
1✔
2491
                return nil, ErrStorageInvalidInput
×
2492
        }
×
2493

2494
        // does not need any extra operations
2495
        // following query won't handle this case well and increase the state_to value
2496
        if stateFrom == stateTo {
2✔
2497
                return nil, nil
1✔
2498
        }
1✔
2499

2500
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2501
        collDpl := database.Collection(CollectionDeployments)
1✔
2502

1✔
2503
        var update bson.M
1✔
2504

1✔
2505
        if stateFrom == model.DeviceDeploymentStatusNull {
2✔
2506
                // note dot notation on embedded document
1✔
2507
                update = bson.M{
1✔
2508
                        "$inc": bson.M{
1✔
2509
                                "stats." + stateTo.String(): 1,
1✔
2510
                        },
1✔
2511
                }
1✔
2512
        } else {
2✔
2513
                // note dot notation on embedded document
1✔
2514
                update = bson.M{
1✔
2515
                        "$inc": bson.M{
1✔
2516
                                "stats." + stateFrom.String(): -1,
1✔
2517
                                "stats." + stateTo.String():   1,
1✔
2518
                        },
1✔
2519
                }
1✔
2520
        }
1✔
2521

2522
        var res struct {
1✔
2523
                Stats model.Stats `bson:"stats"`
1✔
2524
        }
1✔
2525
        err := collDpl.FindOneAndUpdate(ctx,
1✔
2526
                bson.M{StorageKeyId: id},
1✔
2527
                update,
1✔
2528
                mopts.FindOneAndUpdate().
1✔
2529
                        SetReturnDocument(mopts.After).
1✔
2530
                        SetProjection(bson.M{
1✔
2531
                                StorageKeyDeploymentStats: 1,
1✔
2532
                        }),
1✔
2533
        ).Decode(&res)
1✔
2534

1✔
2535
        if errors.Is(err, mongo.ErrNoDocuments) {
2✔
2536
                return nil, ErrStorageInvalidID
1✔
2537
        }
1✔
2538

2539
        return res.Stats, err
1✔
2540
}
2541

2542
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2543
        ctx context.Context,
2544
        deploymentID string,
2545
        increment int64,
2546
) error {
1✔
2547
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2548
        collection := database.Collection(CollectionDeployments)
1✔
2549

1✔
2550
        filter := bson.M{
1✔
2551
                "_id": deploymentID,
1✔
2552
        }
1✔
2553

1✔
2554
        update := bson.M{
1✔
2555
                "$inc": bson.M{
1✔
2556
                        StorageKeyDeploymentTotalSize: increment,
1✔
2557
                },
1✔
2558
        }
1✔
2559

1✔
2560
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2561
        return err
1✔
2562
}
1✔
2563

2564
func (db *DataStoreMongo) FindDeployments(ctx context.Context,
2565
        match model.Query) ([]*model.Deployment, int64, error) {
1✔
2566

1✔
2567
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2568
        collDpl := database.Collection(CollectionDeployments)
1✔
2569

1✔
2570
        query, err := db.buildDeploymentsQuery(ctx, match)
1✔
2571
        if err != nil {
2✔
2572
                return nil, 0, err
1✔
2573
        }
1✔
2574

2575
        options := db.findOptions(match)
1✔
2576

1✔
2577
        var deployments []*model.Deployment
1✔
2578
        cursor, err := collDpl.Find(ctx, query, options)
1✔
2579
        if err != nil {
1✔
2580
                return nil, 0, err
×
2581
        }
×
2582
        if err := cursor.All(ctx, &deployments); err != nil {
1✔
2583
                return nil, 0, err
×
2584
        }
×
2585
        // Count documents if we didn't find all already.
2586
        count := int64(0)
1✔
2587
        if !match.DisableCount {
2✔
2588
                count = int64(len(deployments))
1✔
2589
                if count >= int64(match.Limit) {
2✔
2590
                        count, err = collDpl.CountDocuments(ctx, query)
1✔
2591
                        if err != nil {
1✔
2592
                                return nil, 0, err
×
2593
                        }
×
UNCOV
2594
                } else {
×
UNCOV
2595
                        // Don't forget to add the skipped documents
×
UNCOV
2596
                        count += int64(match.Skip)
×
UNCOV
2597
                }
×
2598
        }
2599

2600
        return deployments, count, nil
1✔
2601
}
2602

2603
func (db *DataStoreMongo) buildDeploymentsQuery(
2604
        ctx context.Context,
2605
        match model.Query,
2606
) (bson.M, error) {
1✔
2607
        andq := []bson.M{}
1✔
2608

1✔
2609
        // filter by IDs
1✔
2610
        if match.IDs != nil {
2✔
2611
                tq := bson.M{
1✔
2612
                        "_id": bson.M{
1✔
2613
                                "$in": match.IDs,
1✔
2614
                        },
1✔
2615
                }
1✔
2616
                andq = append(andq, tq)
1✔
2617
        }
1✔
2618

2619
        // filter by Names
2620
        if match.Names != nil {
2✔
2621
                tq := bson.M{
1✔
2622
                        StorageKeyDeploymentName: bson.M{
1✔
2623
                                "$in": match.Names,
1✔
2624
                        },
1✔
2625
                }
1✔
2626
                andq = append(andq, tq)
1✔
2627
        }
1✔
2628

2629
        // build deployment by name part of the query
2630
        if match.SearchText != "" {
2✔
2631
                // we must have indexing for text search
1✔
2632
                if !db.hasIndexing(ctx, db.client) {
2✔
2633
                        return nil, ErrDeploymentStorageCannotExecQuery
1✔
2634
                }
1✔
2635

2636
                tq := bson.M{
1✔
2637
                        "$text": bson.M{
1✔
2638
                                "$search": "\"" + match.SearchText + "\"",
1✔
2639
                        },
1✔
2640
                }
1✔
2641

1✔
2642
                andq = append(andq, tq)
1✔
2643
        }
2644

2645
        // build deployment by status part of the query
2646
        if match.Status != model.StatusQueryAny {
2✔
2647
                var status model.DeploymentStatus
1✔
2648
                if match.Status == model.StatusQueryPending {
2✔
2649
                        status = model.DeploymentStatusPending
1✔
2650
                } else if match.Status == model.StatusQueryInProgress {
3✔
2651
                        status = model.DeploymentStatusInProgress
1✔
2652
                } else {
2✔
2653
                        status = model.DeploymentStatusFinished
1✔
2654
                }
1✔
2655
                stq := bson.M{StorageKeyDeploymentStatus: status}
1✔
2656
                andq = append(andq, stq)
1✔
2657
        }
2658

2659
        // build deployment by type part of the query
2660
        if match.Type != "" {
2✔
2661
                if match.Type == model.DeploymentTypeConfiguration {
2✔
2662
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
1✔
2663
                } else if match.Type == model.DeploymentTypeSoftware {
1✔
2664
                        andq = append(andq, bson.M{
×
2665
                                "$or": []bson.M{
×
2666
                                        {StorageKeyDeploymentType: match.Type},
×
2667
                                        {StorageKeyDeploymentType: ""},
×
2668
                                },
×
2669
                        })
×
2670
                }
×
2671
        }
2672

2673
        query := bson.M{}
1✔
2674
        if len(andq) != 0 {
2✔
2675
                // use search criteria if any
1✔
2676
                query = bson.M{
1✔
2677
                        "$and": andq,
1✔
2678
                }
1✔
2679
        }
1✔
2680

2681
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
1✔
2682
                query["created"] = bson.M{
×
2683
                        "$gte": match.CreatedAfter,
×
2684
                        "$lte": match.CreatedBefore,
×
2685
                }
×
2686
        } else if match.CreatedAfter != nil {
1✔
UNCOV
2687
                query["created"] = bson.M{
×
UNCOV
2688
                        "$gte": match.CreatedAfter,
×
UNCOV
2689
                }
×
2690
        } else if match.CreatedBefore != nil {
1✔
UNCOV
2691
                query["created"] = bson.M{
×
UNCOV
2692
                        "$lte": match.CreatedBefore,
×
UNCOV
2693
                }
×
UNCOV
2694
        }
×
2695

2696
        return query, nil
1✔
2697
}
2698

2699
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
1✔
2700
        options := &mopts.FindOptions{}
1✔
2701
        if match.Sort == model.SortDirectionAscending {
2✔
2702
                options.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
2703
        } else {
2✔
2704
                options.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2705
        }
1✔
2706
        if match.Skip > 0 {
2✔
2707
                options.SetSkip(int64(match.Skip))
1✔
2708
        }
1✔
2709
        if match.Limit > 0 {
2✔
2710
                options.SetLimit(int64(match.Limit))
1✔
2711
        } else {
2✔
2712
                options.SetLimit(DefaultDocumentLimit)
1✔
2713
        }
1✔
2714
        return options
1✔
2715
}
2716

2717
// FindNewerActiveDeployments finds active deployments which were created
2718
// after createdAfter
2719
// Deprecated: No longer in use
2720
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2721
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
1✔
2722

1✔
2723
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2724
        c := database.Collection(CollectionDeployments)
1✔
2725

1✔
2726
        queryFilters := make([]bson.M, 0)
1✔
2727
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
1✔
2728
        queryFilters = append(queryFilters,
1✔
2729
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
1✔
2730
        findQuery := bson.M{}
1✔
2731
        findQuery["$and"] = queryFilters
1✔
2732

1✔
2733
        findOptions := &mopts.FindOptions{}
1✔
2734
        findOptions.SetSkip(int64(skip))
1✔
2735
        findOptions.SetLimit(int64(limit))
1✔
2736

1✔
2737
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
1✔
2738
        cursor, err := c.Find(ctx, findQuery, findOptions)
1✔
2739
        if err != nil {
1✔
2740
                return nil, errors.Wrap(err, "failed to get deployments")
×
2741
        }
×
2742
        defer cursor.Close(ctx)
1✔
2743

1✔
2744
        var deployments []*model.Deployment
1✔
2745

1✔
2746
        if err = cursor.All(ctx, &deployments); err != nil {
1✔
2747
                return nil, errors.Wrap(err, "failed to get deployments")
×
2748
        }
×
2749

2750
        return deployments, nil
1✔
2751
}
2752

2753
// FindNewerActiveDeployment finds active deployments which were created
2754
// after createdAfter where deviceID is part of the device list.
2755
func (db *DataStoreMongo) FindNewerActiveDeployment(ctx context.Context,
UNCOV
2756
        createdAfter *time.Time, deviceID string) (*model.Deployment, error) {
×
UNCOV
2757

×
UNCOV
2758
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
UNCOV
2759
        c := database.Collection(CollectionDeployments)
×
UNCOV
2760

×
UNCOV
2761
        findQuery := bson.D{
×
UNCOV
2762
                {Key: StorageKeyDeploymentActive, Value: true},
×
UNCOV
2763
                {Key: StorageKeyDeploymentCreated, Value: bson.M{"$gt": createdAfter}},
×
UNCOV
2764
                {Key: StorageKeyDeploymentDeviceList, Value: deviceID},
×
UNCOV
2765
        }
×
UNCOV
2766
        findOptions := mopts.FindOne().
×
UNCOV
2767
                SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}}).
×
UNCOV
2768
                SetProjection(bson.M{
×
UNCOV
2769
                        // Discard information we don't need
×
UNCOV
2770
                        StorageKeyDeploymentConstructorChecksum: 0,
×
UNCOV
2771
                        StorageKeyDeploymentDeviceList:          0,
×
UNCOV
2772
                })
×
UNCOV
2773

×
UNCOV
2774
        var deployment = new(model.Deployment)
×
UNCOV
2775
        err := c.FindOne(ctx, findQuery, findOptions).
×
UNCOV
2776
                Decode(deployment)
×
UNCOV
2777
        if err != nil {
×
UNCOV
2778
                if errors.Is(err, mongo.ErrNoDocuments) {
×
UNCOV
2779
                        return nil, nil
×
UNCOV
2780
                }
×
2781
                return nil, errors.Wrap(err, "failed to get deployments")
×
2782
        }
2783

UNCOV
2784
        return deployment, nil
×
2785
}
2786

2787
// SetDeploymentStatus simply sets the status field
2788
// optionally sets 'finished time' if deployment is indeed finished
2789
func (db *DataStoreMongo) SetDeploymentStatus(
2790
        ctx context.Context,
2791
        id string,
2792
        status model.DeploymentStatus,
2793
        now time.Time,
2794
) error {
1✔
2795
        if len(id) == 0 {
1✔
2796
                return ErrStorageInvalidID
×
2797
        }
×
2798

2799
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2800
        collDpl := database.Collection(CollectionDeployments)
1✔
2801

1✔
2802
        var update bson.M
1✔
2803
        if status == model.DeploymentStatusFinished {
2✔
2804
                update = bson.M{
1✔
2805
                        "$set": bson.M{
1✔
2806
                                StorageKeyDeploymentActive:   false,
1✔
2807
                                StorageKeyDeploymentStatus:   status,
1✔
2808
                                StorageKeyDeploymentFinished: &now,
1✔
2809
                        },
1✔
2810
                }
1✔
2811
        } else {
2✔
2812
                update = bson.M{
1✔
2813
                        "$set": bson.M{
1✔
2814
                                StorageKeyDeploymentActive: true,
1✔
2815
                                StorageKeyDeploymentStatus: status,
1✔
2816
                        },
1✔
2817
                }
1✔
2818
        }
1✔
2819

2820
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
1✔
2821

1✔
2822
        if res != nil && res.MatchedCount == 0 {
2✔
2823
                return ErrStorageInvalidID
1✔
2824
        }
1✔
2825

2826
        return err
1✔
2827
}
2828

2829
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2830
// given artifact
2831
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2832
        id string) (bool, error) {
1✔
2833

1✔
2834
        if len(id) == 0 {
1✔
2835
                return false, ErrStorageInvalidID
×
2836
        }
×
2837

2838
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2839
        collDpl := database.Collection(CollectionDeployments)
1✔
2840

1✔
2841
        var tmp interface{}
1✔
2842
        query := bson.D{
1✔
2843
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2844
                {Key: StorageKeyDeploymentArtifacts, Value: id},
1✔
2845
        }
1✔
2846
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
2✔
2847
                if err == mongo.ErrNoDocuments {
2✔
2848
                        return false, nil
1✔
2849
                }
1✔
2850
                return false, err
×
2851
        }
2852

2853
        return true, nil
1✔
2854
}
2855

2856
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2857
// given artifact
2858
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2859
        artifactName string) (bool, error) {
1✔
2860

1✔
2861
        if len(artifactName) == 0 {
1✔
2862
                return false, ErrImagesStorageInvalidArtifactName
×
2863
        }
×
2864

2865
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2866
        collDpl := database.Collection(CollectionDeployments)
1✔
2867

1✔
2868
        var tmp interface{}
1✔
2869
        query := bson.D{
1✔
2870
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2871
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2872
        }
1✔
2873

1✔
2874
        projection := bson.M{
1✔
2875
                "_id": 1,
1✔
2876
        }
1✔
2877
        findOptions := mopts.FindOne()
1✔
2878
        findOptions.SetProjection(projection)
1✔
2879

1✔
2880
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
2✔
2881
                if err == mongo.ErrNoDocuments {
2✔
2882
                        return false, nil
1✔
2883
                }
1✔
2884
                return false, err
×
2885
        }
2886

2887
        return true, nil
1✔
2888
}
2889

2890
// ExistByArtifactId check if there is any deployment that uses give artifact
2891
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2892
        id string) (bool, error) {
×
2893

×
2894
        if len(id) == 0 {
×
2895
                return false, ErrStorageInvalidID
×
2896
        }
×
2897

2898
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2899
        collDpl := database.Collection(CollectionDeployments)
×
2900

×
2901
        var tmp interface{}
×
2902
        query := bson.D{
×
2903
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2904
        }
×
2905
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2906
                if err == mongo.ErrNoDocuments {
×
2907
                        return false, nil
×
2908
                }
×
2909
                return false, err
×
2910
        }
2911

2912
        return true, nil
×
2913
}
2914

2915
// Per-tenant storage settings
2916
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
1✔
2917
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2918
        collection := database.Collection(CollectionStorageSettings)
1✔
2919

1✔
2920
        settings := new(model.StorageSettings)
1✔
2921
        // supposed that it's only one document in the collection
1✔
2922
        query := bson.M{
1✔
2923
                "_id": StorageKeyStorageSettingsDefaultID,
1✔
2924
        }
1✔
2925
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
1✔
UNCOV
2926
                if err == mongo.ErrNoDocuments {
×
UNCOV
2927
                        return nil, nil
×
UNCOV
2928
                }
×
2929
                return nil, err
×
2930
        }
2931

2932
        return settings, nil
1✔
2933
}
2934

2935
func (db *DataStoreMongo) SetStorageSettings(
2936
        ctx context.Context,
2937
        storageSettings *model.StorageSettings,
2938
) error {
1✔
2939
        var err error
1✔
2940
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2941
        collection := database.Collection(CollectionStorageSettings)
1✔
2942

1✔
2943
        filter := bson.M{
1✔
2944
                "_id": StorageKeyStorageSettingsDefaultID,
1✔
2945
        }
1✔
2946
        if storageSettings != nil {
2✔
2947
                replaceOptions := mopts.Replace()
1✔
2948
                replaceOptions.SetUpsert(true)
1✔
2949
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
1✔
2950
        } else {
1✔
UNCOV
2951
                _, err = collection.DeleteOne(ctx, filter)
×
UNCOV
2952
        }
×
2953

2954
        return err
1✔
2955
}
2956

2957
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2958
        ctx context.Context,
2959
        artifactName string,
2960
        artifactIDs []string,
2961
) error {
1✔
2962
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2963
        collDpl := database.Collection(CollectionDeployments)
1✔
2964

1✔
2965
        query := bson.D{
1✔
2966
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2967
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2968
        }
1✔
2969
        update := bson.M{
1✔
2970
                "$set": bson.M{
1✔
2971
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
2972
                },
1✔
2973
        }
1✔
2974

1✔
2975
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
2976
        return err
1✔
2977
}
1✔
2978

2979
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
2980
        ctx context.Context,
2981
        artifactNames []string,
2982
) ([]string, error) {
1✔
2983

1✔
2984
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2985
        collDpl := database.Collection(CollectionDeployments)
1✔
2986

1✔
2987
        query := bson.M{
1✔
2988
                StorageKeyDeploymentArtifactName: bson.M{
1✔
2989
                        "$in": artifactNames,
1✔
2990
                },
1✔
2991
        }
1✔
2992

1✔
2993
        projection := bson.M{
1✔
2994
                "_id": 1,
1✔
2995
        }
1✔
2996
        findOptions := mopts.Find()
1✔
2997
        findOptions.SetProjection(projection)
1✔
2998

1✔
2999
        cursor, err := collDpl.Find(ctx, query, findOptions)
1✔
3000
        if err != nil {
1✔
3001
                return []string{}, err
×
3002
        }
×
3003
        defer cursor.Close(ctx)
1✔
3004

1✔
3005
        var deployments []*model.Deployment
1✔
3006
        if err = cursor.All(ctx, &deployments); err != nil {
1✔
3007
                if err == mongo.ErrNoDocuments {
×
3008
                        err = nil
×
3009
                }
×
3010
                return []string{}, err
×
3011
        }
3012

3013
        ids := make([]string, len(deployments))
1✔
3014
        for i, d := range deployments {
2✔
3015
                ids[i] = d.Id
1✔
3016
        }
1✔
3017

3018
        return ids, nil
1✔
3019
}
3020

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