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

mendersoftware / deployments / 1436275438

02 Sep 2024 10:51AM UTC coverage: 79.694%. First build
1436275438

push

gitlab-ci

web-flow
Merge pull request #1041 from merlin-northern/reverting_prefix_release_search

Revert "fix: excessive patterns in regexp for image and releases sear…

7 of 8 new or added lines in 1 file covered. (87.5%)

8124 of 10194 relevant lines covered (79.69%)

34.59 hits per line

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

85.42
/store/mongo/datastore_mongo.go
1
// Copyright 2024 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package mongo
15

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

314
// Errors
315
var (
316
        ErrImagesStorageInvalidID           = errors.New("Invalid id")
317
        ErrImagesStorageInvalidArtifactName = errors.New("Invalid artifact name")
318
        ErrImagesStorageInvalidName         = errors.New("Invalid name")
319
        ErrImagesStorageInvalidDeviceType   = errors.New("Invalid device type")
320
        ErrImagesStorageInvalidImage        = errors.New("Invalid image")
321

322
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
323

324
        ErrDeploymentStorageInvalidDeployment = errors.New("Invalid deployment")
325
        ErrStorageInvalidID                   = errors.New("Invalid id")
326
        ErrStorageNotFound                    = errors.New("Not found")
327
        ErrDeploymentStorageInvalidQuery      = errors.New("Invalid query")
328
        ErrDeploymentStorageCannotExecQuery   = errors.New("Cannot execute query")
329
        ErrStorageInvalidInput                = errors.New("invalid input")
330

331
        ErrLimitNotFound      = errors.New("limit not found")
332
        ErrDevicesCountFailed = errors.New("failed to count devices")
333
        ErrConflictingDepends = errors.New(
334
                "an artifact with the same name and depends already exists",
335
        )
336
        ErrConflictingDeployment = errors.New(
337
                "an active deployment with the same parameter already exists",
338
        )
339
)
340

341
// Database keys
342
const (
343
        // Need to be kept in sync with structure filed names
344
        StorageKeyId       = "_id"
345
        StorageKeyTenantId = "tenant_id"
346

347
        StorageKeyImageProvides    = "meta_artifact.provides"
348
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
349
        StorageKeyImageDepends     = "meta_artifact.depends"
350
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
351
        StorageKeyImageSize        = "size"
352
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
353
        StorageKeyImageName        = "meta_artifact.name"
354
        StorageKeyUpdateType       = "meta_artifact.updates.typeinfo.type"
355
        StorageKeyImageDescription = "meta.description"
356
        StorageKeyImageModified    = "modified"
357

358
        // releases
359
        StorageKeyReleaseName                      = "_id"
360
        StorageKeyReleaseModified                  = "modified"
361
        StorageKeyReleaseTags                      = "tags"
362
        StorageKeyReleaseNotes                     = "notes"
363
        StorageKeyReleaseArtifacts                 = "artifacts"
364
        StorageKeyReleaseArtifactsCount            = "artifacts_count"
365
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
366
                StorageKeyImageDescription
367
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
368
                StorageKeyImageDescription
369
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
370
                StorageKeyImageDeviceTypes
371
        StorageKeyReleaseArtifactsUpdateTypes = StorageKeyReleaseArtifacts + "." +
372
                StorageKeyUpdateType
373
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
374
                StorageKeyImageModified
375
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
376
                StorageKeyId
377
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
378
                StorageKeyImageDependsIdx
379
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
380
                StorageKeyImageProvidesIdx
381

382
        StorageKeyDeviceDeploymentLogMessages = "messages"
383

384
        StorageKeyDeviceDeploymentAssignedImage   = "image"
385
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
386
                "." + StorageKeyId
387

388
        StorageKeyDeviceDeploymentActive         = "active"
389
        StorageKeyDeviceDeploymentCreated        = "created"
390
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
391
        StorageKeyDeviceDeploymentStatus         = "status"
392
        StorageKeyDeviceDeploymentStarted        = "started"
393
        StorageKeyDeviceDeploymentSubState       = "substate"
394
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
395
        StorageKeyDeviceDeploymentFinished       = "finished"
396
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
397
        StorageKeyDeviceDeploymentArtifact       = "image"
398
        StorageKeyDeviceDeploymentRequest        = "request"
399
        StorageKeyDeviceDeploymentDeleted        = "deleted"
400

401
        StorageKeyDeploymentName                = "deploymentconstructor.name"
402
        StorageKeyDeploymentArtifactName        = "deploymentconstructor.artifactname"
403
        StorageKeyDeploymentConstructorChecksum = "deploymentconstructor_checksum"
404
        StorageKeyDeploymentStats               = "stats"
405
        StorageKeyDeploymentActive              = "active"
406
        StorageKeyDeploymentStatus              = "status"
407
        StorageKeyDeploymentCreated             = "created"
408
        StorageKeyDeploymentDeviceList          = "device_list"
409
        StorageKeyDeploymentStatsCreated        = "created"
410
        StorageKeyDeploymentFinished            = "finished"
411
        StorageKeyDeploymentArtifacts           = "artifacts"
412
        StorageKeyDeploymentDeviceCount         = "device_count"
413
        StorageKeyDeploymentMaxDevices          = "max_devices"
414
        StorageKeyDeploymentType                = "type"
415
        StorageKeyDeploymentTotalSize           = "statistics.total_size"
416

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

428
        StorageKeyStorageReleaseUpdateTypes = "update_types"
429

430
        ArtifactDependsDeviceType = "device_type"
431
)
432

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

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

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

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

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

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

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

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

485
        return client, nil
1✔
486
}
487

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

493
func (db *DataStoreMongo) setCurrentDbVersion(
494
        ctx context.Context,
495
) error {
1✔
496
        versions, err := migrate.GetMigrationInfo(
1✔
497
                ctx, db.client, mstore.DbFromContext(ctx, DatabaseName))
1✔
498
        if err != nil {
1✔
499
                return errors.Wrap(err, "failed to list applied migrations")
×
500
        }
×
501
        var current migrate.Version
1✔
502
        if len(versions) > 0 {
2✔
503
                // sort applied migrations wrt. version
1✔
504
                sort.Slice(versions, func(i int, j int) bool {
2✔
505
                        return migrate.VersionIsLess(versions[i].Version, versions[j].Version)
1✔
506
                })
1✔
507
                current = versions[len(versions)-1].Version
1✔
508
        }
509
        if currentDbVersion == nil {
2✔
510
                currentDbVersion = map[string]*migrate.Version{}
1✔
511
        }
1✔
512
        currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] = &current
1✔
513
        return nil
1✔
514
}
515

516
func (db *DataStoreMongo) getCurrentDbVersion(
517
        ctx context.Context,
518
) (*migrate.Version, error) {
1✔
519
        if currentDbVersion == nil ||
1✔
520
                currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] == nil {
2✔
521
                if err := db.setCurrentDbVersion(ctx); err != nil {
1✔
522
                        return nil, err
×
523
                }
×
524
        }
525
        return currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)], nil
1✔
526
}
527

528
func (db *DataStoreMongo) GetReleases(
529
        ctx context.Context,
530
        filt *model.ReleaseOrImageFilter,
531
) ([]model.Release, int, error) {
1✔
532
        current, err := db.getCurrentDbVersion(ctx)
1✔
533
        if err != nil {
1✔
534
                return []model.Release{}, 0, err
×
535
        } else if current == nil {
1✔
536
                return []model.Release{}, 0, errors.New("couldn't get current database version")
×
537
        }
×
538
        target, err := migrate.NewVersion(DbVersion)
1✔
539
        if err != nil {
1✔
540
                return []model.Release{}, 0, errors.Wrap(err, "failed to get latest DB version")
×
541
        }
×
542
        if migrate.VersionIsLess(*current, *target) {
1✔
543
                return db.getReleases_1_2_14(ctx, filt)
×
544
        } else {
1✔
545
                return db.getReleases_1_2_15(ctx, filt)
1✔
546
        }
1✔
547
}
548

549
func (db *DataStoreMongo) getReleases_1_2_14(
550
        ctx context.Context,
551
        filt *model.ReleaseOrImageFilter,
552
) ([]model.Release, int, error) {
8✔
553
        l := log.FromContext(ctx)
8✔
554
        l.Infof("get releases method version 1.2.14")
8✔
555
        var pipe []bson.D
8✔
556

8✔
557
        pipe = []bson.D{}
8✔
558
        if filt != nil && filt.Name != "" {
10✔
559
                pipe = append(pipe, bson.D{
2✔
560
                        {Key: "$match", Value: bson.M{
2✔
561
                                StorageKeyImageName: bson.M{
2✔
562
                                        "$regex": primitive.Regex{
2✔
563
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
2✔
564
                                                Options: "i",
2✔
565
                                        },
2✔
566
                                },
2✔
567
                        }},
2✔
568
                })
2✔
569
        }
2✔
570

571
        pipe = append(pipe, bson.D{
8✔
572
                // Remove (possibly expensive) sub-documents from pipeline
8✔
573
                {
8✔
574
                        Key: "$project",
8✔
575
                        Value: bson.M{
8✔
576
                                StorageKeyImageDependsIdx:  0,
8✔
577
                                StorageKeyImageProvidesIdx: 0,
8✔
578
                        },
8✔
579
                },
8✔
580
        })
8✔
581

8✔
582
        pipe = append(pipe, bson.D{
8✔
583
                {Key: "$group", Value: bson.D{
8✔
584
                        {Key: "_id", Value: "$" + StorageKeyImageName},
8✔
585
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
8✔
586
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
8✔
587
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
8✔
588
                }},
8✔
589
        })
8✔
590

8✔
591
        if filt != nil && filt.Description != "" {
10✔
592
                pipe = append(pipe, bson.D{
2✔
593
                        {Key: "$match", Value: bson.M{
2✔
594
                                "artifacts." + StorageKeyImageDescription: bson.M{
2✔
595
                                        "$regex": primitive.Regex{
2✔
596
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
597
                                                Options: "i",
2✔
598
                                        },
2✔
599
                                },
2✔
600
                        }},
2✔
601
                })
2✔
602
        }
2✔
603
        if filt != nil && filt.DeviceType != "" {
8✔
604
                pipe = append(pipe, bson.D{
×
605
                        {Key: "$match", Value: bson.M{
×
606
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
×
607
                                        "$regex": primitive.Regex{
×
NEW
608
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
×
609
                                                Options: "i",
×
610
                                        },
×
611
                                },
×
612
                        }},
×
613
                })
×
614
        }
×
615

616
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
8✔
617
        if sortField == "" {
13✔
618
                sortField = "name"
5✔
619
        }
5✔
620
        if sortOrder == 0 {
13✔
621
                sortOrder = 1
5✔
622
        }
5✔
623

624
        page := 1
8✔
625
        perPage := math.MaxInt64
8✔
626
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
9✔
627
                page = filt.Page
1✔
628
                perPage = filt.PerPage
1✔
629
        }
1✔
630
        pipe = append(pipe,
8✔
631
                bson.D{{Key: "$facet", Value: bson.D{
8✔
632
                        {Key: "results", Value: []bson.D{
8✔
633
                                {
8✔
634
                                        {Key: "$sort", Value: bson.D{
8✔
635
                                                {Key: sortField, Value: sortOrder},
8✔
636
                                                {Key: "_id", Value: 1},
8✔
637
                                        }},
8✔
638
                                },
8✔
639
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
8✔
640
                                {{Key: "$limit", Value: int64(perPage)}},
8✔
641
                        }},
8✔
642
                        {Key: "count", Value: []bson.D{
8✔
643
                                {{Key: "$count", Value: "count"}},
8✔
644
                        }},
8✔
645
                }}},
8✔
646
        )
8✔
647

8✔
648
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
649
        collImg := database.Collection(CollectionImages)
8✔
650

8✔
651
        cursor, err := collImg.Aggregate(ctx, pipe)
8✔
652
        if err != nil {
8✔
653
                return []model.Release{}, 0, err
×
654
        }
×
655
        defer cursor.Close(ctx)
8✔
656

8✔
657
        result := struct {
8✔
658
                Results []model.Release       `bson:"results"`
8✔
659
                Count   []struct{ Count int } `bson:"count"`
8✔
660
        }{}
8✔
661
        if !cursor.Next(ctx) {
8✔
662
                return []model.Release{}, 0, nil
×
663
        } else if err = cursor.Decode(&result); err != nil {
8✔
664
                return []model.Release{}, 0, err
×
665
        } else if len(result.Count) == 0 {
9✔
666
                return []model.Release{}, 0, err
1✔
667
        }
1✔
668
        return result.Results, result.Count[0].Count, nil
7✔
669
}
670

671
func (db *DataStoreMongo) getReleases_1_2_15(
672
        ctx context.Context,
673
        filt *model.ReleaseOrImageFilter,
674
) ([]model.Release, int, error) {
81✔
675
        l := log.FromContext(ctx)
81✔
676
        l.Infof("get releases method version 1.2.15")
81✔
677

81✔
678
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
81✔
679
        if sortField == "" {
148✔
680
                sortField = "_id"
67✔
681
        } else if sortField == "name" {
83✔
682
                sortField = StorageKeyReleaseName
2✔
683
        }
2✔
684
        if sortOrder == 0 {
148✔
685
                sortOrder = 1
67✔
686
        }
67✔
687

688
        page := 1
81✔
689
        perPage := DefaultDocumentLimit
81✔
690
        if filt != nil {
160✔
691
                if filt.Page > 0 {
82✔
692
                        page = filt.Page
3✔
693
                }
3✔
694
                if filt.PerPage > 0 {
82✔
695
                        perPage = filt.PerPage
3✔
696
                }
3✔
697
        }
698

699
        opts := &mopts.FindOptions{}
81✔
700
        opts.SetSort(bson.D{{Key: sortField, Value: sortOrder}})
81✔
701
        opts.SetSkip(int64((page - 1) * perPage))
81✔
702
        opts.SetLimit(int64(perPage))
81✔
703
        projection := bson.M{
81✔
704
                StorageKeyReleaseImageDependsIdx:  0,
81✔
705
                StorageKeyReleaseImageProvidesIdx: 0,
81✔
706
        }
81✔
707
        opts.SetProjection(projection)
81✔
708

81✔
709
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
81✔
710
        collReleases := database.Collection(CollectionReleases)
81✔
711

81✔
712
        filter := bson.M{}
81✔
713
        if filt != nil {
160✔
714
                if filt.Name != "" {
132✔
715
                        filter[StorageKeyReleaseName] = bson.M{"$regex": primitive.Regex{
53✔
716
                                Pattern: regexp.QuoteMeta(filt.Name) + ".*",
53✔
717
                                Options: "i",
53✔
718
                        }}
53✔
719
                }
53✔
720
                if len(filt.Tags) > 0 {
83✔
721
                        filter[StorageKeyReleaseTags] = bson.M{"$in": filt.Tags}
4✔
722
                }
4✔
723
                if filt.Description != "" {
83✔
724
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": primitive.Regex{
4✔
725
                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
726
                                Options: "i",
4✔
727
                        }}
4✔
728
                }
4✔
729
                if filt.DeviceType != "" {
81✔
730
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = filt.DeviceType
2✔
731
                }
2✔
732
                if filt.UpdateType != "" {
82✔
733
                        filter[StorageKeyReleaseArtifactsUpdateTypes] = filt.UpdateType
3✔
734
                }
3✔
735
        }
736
        releases := []model.Release{}
81✔
737
        cursor, err := collReleases.Find(ctx, filter, opts)
81✔
738
        if err != nil {
81✔
739
                return []model.Release{}, 0, err
×
740
        }
×
741
        if err := cursor.All(ctx, &releases); err != nil {
81✔
742
                return []model.Release{}, 0, err
×
743
        }
×
744

745
        // TODO: can we return number of all documents in the collection
746
        // using EstimatedDocumentCount?
747
        count, err := collReleases.CountDocuments(ctx, filter)
81✔
748
        if err != nil {
81✔
749
                return []model.Release{}, 0, err
×
750
        }
×
751

752
        if count < 1 {
99✔
753
                return []model.Release{}, int(count), nil
18✔
754
        }
18✔
755
        return releases, int(count), nil
64✔
756
}
757

758
// limits
759
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
4✔
760

4✔
761
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
762
        collLim := database.Collection(CollectionLimits)
4✔
763

4✔
764
        limit := new(model.Limit)
4✔
765
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
4✔
766
                Decode(limit); err != nil {
6✔
767
                if err == mongo.ErrNoDocuments {
4✔
768
                        return nil, ErrLimitNotFound
2✔
769
                }
2✔
770
                return nil, err
×
771
        }
772

773
        return limit, nil
2✔
774
}
775

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

5✔
778
        dbname := mstore.DbNameForTenant(tenantId, DbName)
5✔
779

5✔
780
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
5✔
781
}
5✔
782

783
//images
784

785
// Exists checks if object with ID exists
786
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
787
        var result interface{}
×
788

×
789
        if len(id) == 0 {
×
790
                return false, ErrImagesStorageInvalidID
×
791
        }
×
792

793
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
794
        collImg := database.Collection(CollectionImages)
×
795

×
796
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
797
                Decode(&result); err != nil {
×
798
                if err == mongo.ErrNoDocuments {
×
799
                        return false, nil
×
800
                }
×
801
                return false, err
×
802
        }
803

804
        return true, nil
×
805
}
806

807
// Update provided Image
808
// Return false if not found
809
func (db *DataStoreMongo) Update(ctx context.Context,
810
        image *model.Image) (bool, error) {
1✔
811

1✔
812
        if err := image.Validate(); err != nil {
1✔
813
                return false, err
×
814
        }
×
815

816
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
817
        collImg := database.Collection(CollectionImages)
1✔
818

1✔
819
        // add special representation of artifact provides
1✔
820
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
821

1✔
822
        image.SetModified(time.Now())
1✔
823
        if res, err := collImg.ReplaceOne(
1✔
824
                ctx, bson.M{"_id": image.Id}, image,
1✔
825
        ); err != nil {
1✔
826
                return false, err
×
827
        } else if res.MatchedCount == 0 {
1✔
828
                return false, nil
×
829
        }
×
830

831
        return true, nil
1✔
832
}
833

834
// ImageByNameAndDeviceType finds image with specified application name and target device type
835
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
836
        name, deviceType string) (*model.Image, error) {
9✔
837

9✔
838
        if len(name) == 0 {
10✔
839
                return nil, ErrImagesStorageInvalidArtifactName
1✔
840
        }
1✔
841

842
        if len(deviceType) == 0 {
9✔
843
                return nil, ErrImagesStorageInvalidDeviceType
1✔
844
        }
1✔
845

846
        // equal to device type & software version (application name + version)
847
        query := bson.M{
7✔
848
                StorageKeyImageName:        name,
7✔
849
                StorageKeyImageDeviceTypes: deviceType,
7✔
850
        }
7✔
851

7✔
852
        // If multiple entries matches, pick the smallest one.
7✔
853
        findOpts := mopts.FindOne()
7✔
854
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
7✔
855

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

7✔
860
        // Both we lookup unique object, should be one or none.
7✔
861
        var image model.Image
7✔
862
        if err := collImg.FindOne(ctx, query, findOpts).
7✔
863
                Decode(&image); err != nil {
11✔
864
                if err == mongo.ErrNoDocuments {
8✔
865
                        return nil, nil
4✔
866
                }
4✔
867
                return nil, err
×
868
        }
869

870
        return &image, nil
3✔
871
}
872

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

1✔
877
        if len(deviceType) == 0 {
1✔
878
                return nil, ErrImagesStorageInvalidDeviceType
×
879
        }
×
880

881
        if len(ids) == 0 {
1✔
882
                return nil, ErrImagesStorageInvalidID
×
883
        }
×
884

885
        query := bson.D{
1✔
886
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
887
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
888
        }
1✔
889

1✔
890
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
891
        collImg := database.Collection(CollectionImages)
1✔
892

1✔
893
        // If multiple entries matches, pick the smallest one
1✔
894
        findOpts := mopts.FindOne()
1✔
895
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
896

1✔
897
        // Both we lookup unique object, should be one or none.
1✔
898
        var image model.Image
1✔
899
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
900
                Decode(&image); err != nil {
1✔
901
                if err == mongo.ErrNoDocuments {
×
902
                        return nil, nil
×
903
                }
×
904
                return nil, err
×
905
        }
906

907
        return &image, nil
1✔
908
}
909

910
// ImagesByName finds images with specified artifact name
911
func (db *DataStoreMongo) ImagesByName(
912
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
913

1✔
914
        var images []*model.Image
1✔
915

1✔
916
        if len(name) == 0 {
1✔
917
                return nil, ErrImagesStorageInvalidName
×
918
        }
×
919

920
        // equal to artifact name
921
        query := bson.M{
1✔
922
                StorageKeyImageName: name,
1✔
923
        }
1✔
924

1✔
925
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
926
        collImg := database.Collection(CollectionImages)
1✔
927
        cursor, err := collImg.Find(ctx, query)
1✔
928
        if err != nil {
1✔
929
                return nil, err
×
930
        }
×
931
        // Both we lookup unique object, should be one or none.
932
        if err = cursor.All(ctx, &images); err != nil {
1✔
933
                return nil, err
×
934
        }
×
935

936
        return images, nil
1✔
937
}
938

939
func newDependsConflictError(mgoErr mongo.WriteError) *model.ConflictError {
7✔
940
        var err error
7✔
941
        conflictErr := model.NewConflictError(ErrConflictingDepends)
7✔
942
        // Try to lookup the document that caused the index violation:
7✔
943
        if raw, ok := mgoErr.Raw.Lookup("keyValue").DocumentOK(); ok {
14✔
944
                if raw, ok = raw.Lookup(StorageKeyImageDependsIdx).DocumentOK(); ok {
14✔
945
                        var conflicts map[string]interface{}
7✔
946
                        err = bson.Unmarshal([]byte(raw), &conflicts)
7✔
947
                        if err == nil {
14✔
948
                                _ = conflictErr.WithMetadata(
7✔
949
                                        map[string]interface{}{
7✔
950
                                                "conflict": conflicts,
7✔
951
                                        },
7✔
952
                                )
7✔
953
                        }
7✔
954
                }
955
        }
956
        return conflictErr
7✔
957
}
958

959
// Insert persists object
960
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
69✔
961

69✔
962
        if image == nil {
69✔
963
                return ErrImagesStorageInvalidImage
×
964
        }
×
965

966
        if err := image.Validate(); err != nil {
69✔
967
                return err
×
968
        }
×
969

970
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
69✔
971
        collImg := database.Collection(CollectionImages)
69✔
972

69✔
973
        // add special representation of artifact provides
69✔
974
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
69✔
975

69✔
976
        _, err := collImg.InsertOne(ctx, image)
69✔
977
        if err != nil {
76✔
978
                var wExc mongo.WriteException
7✔
979
                if errors.As(err, &wExc) {
14✔
980
                        for _, wErr := range wExc.WriteErrors {
14✔
981
                                if !mongo.IsDuplicateKeyError(wErr) {
7✔
982
                                        continue
×
983
                                }
984
                                return newDependsConflictError(wErr)
7✔
985
                        }
986
                }
987
                return err
×
988
        }
989

990
        return nil
62✔
991
}
992

993
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
2✔
994
        collUploads := db.client.
2✔
995
                Database(DatabaseName).
2✔
996
                Collection(CollectionUploadIntents)
2✔
997
        if idty := identity.FromContext(ctx); idty != nil {
3✔
998
                link.TenantID = idty.Tenant
1✔
999
        }
1✔
1000
        _, err := collUploads.InsertOne(ctx, link)
2✔
1001
        return err
2✔
1002
}
1003

1004
func (db *DataStoreMongo) UpdateUploadIntentStatus(
1005
        ctx context.Context,
1006
        id string,
1007
        from, to model.LinkStatus,
1008
) error {
6✔
1009
        collUploads := db.client.
6✔
1010
                Database(DatabaseName).
6✔
1011
                Collection(CollectionUploadIntents)
6✔
1012
        q := bson.D{
6✔
1013
                {Key: "_id", Value: id},
6✔
1014
                {Key: "status", Value: from},
6✔
1015
        }
6✔
1016
        if idty := identity.FromContext(ctx); idty != nil {
11✔
1017
                q = append(q, bson.E{
5✔
1018
                        Key:   StorageKeyTenantId,
5✔
1019
                        Value: idty.Tenant,
5✔
1020
                })
5✔
1021
        }
5✔
1022
        update := bson.D{{
6✔
1023
                Key: "updated_ts", Value: time.Now(),
6✔
1024
        }}
6✔
1025
        if from != to {
12✔
1026
                update = append(update, bson.E{
6✔
1027
                        Key: "status", Value: to,
6✔
1028
                })
6✔
1029
        }
6✔
1030
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
6✔
1031
                {Key: "$set", Value: update},
6✔
1032
        })
6✔
1033
        if err != nil {
7✔
1034
                return err
1✔
1035
        } else if res.MatchedCount == 0 {
8✔
1036
                return store.ErrNotFound
2✔
1037
        }
2✔
1038
        return nil
3✔
1039
}
1040

1041
func (db *DataStoreMongo) FindUploadLinks(
1042
        ctx context.Context,
1043
        expiredAt time.Time,
1044
) (store.Iterator[model.UploadLink], error) {
1✔
1045
        collUploads := db.client.
1✔
1046
                Database(DatabaseName).
1✔
1047
                Collection(CollectionUploadIntents)
1✔
1048

1✔
1049
        q := bson.D{{
1✔
1050
                Key: "status",
1✔
1051
                Value: bson.D{{
1✔
1052
                        Key:   "$lt",
1✔
1053
                        Value: model.LinkStatusProcessedBit,
1✔
1054
                }},
1✔
1055
        }, {
1✔
1056
                Key: "expire",
1✔
1057
                Value: bson.D{{
1✔
1058
                        Key:   "$lt",
1✔
1059
                        Value: expiredAt,
1✔
1060
                }},
1✔
1061
        }}
1✔
1062
        cur, err := collUploads.Find(ctx, q)
1✔
1063
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1064
}
1✔
1065

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

1✔
1070
        if len(id) == 0 {
1✔
1071
                return nil, ErrImagesStorageInvalidID
×
1072
        }
×
1073

1074
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1075
        collImg := database.Collection(CollectionImages)
1✔
1076
        projection := bson.M{
1✔
1077
                StorageKeyImageDependsIdx:  0,
1✔
1078
                StorageKeyImageProvidesIdx: 0,
1✔
1079
        }
1✔
1080
        findOptions := mopts.FindOne()
1✔
1081
        findOptions.SetProjection(projection)
1✔
1082

1✔
1083
        var image model.Image
1✔
1084
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
1085
                Decode(&image); err != nil {
2✔
1086
                if err == mongo.ErrNoDocuments {
2✔
1087
                        return nil, nil
1✔
1088
                }
1✔
1089
                return nil, err
×
1090
        }
1091

1092
        return &image, nil
1✔
1093
}
1094

1095
// IsArtifactUnique checks if there is no artifact with the same artifactName
1096
// supporting one of the device types from deviceTypesCompatible list.
1097
// Returns true, nil if artifact is unique;
1098
// false, nil if artifact is not unique;
1099
// false, error in case of error.
1100
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1101
        artifactName string, deviceTypesCompatible []string) (bool, error) {
6✔
1102

6✔
1103
        if len(artifactName) == 0 {
7✔
1104
                return false, ErrImagesStorageInvalidArtifactName
1✔
1105
        }
1✔
1106

1107
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1108
        collImg := database.Collection(CollectionImages)
5✔
1109

5✔
1110
        query := bson.M{
5✔
1111
                "$and": []bson.M{
5✔
1112
                        {
5✔
1113
                                StorageKeyImageName: artifactName,
5✔
1114
                        },
5✔
1115
                        {
5✔
1116
                                StorageKeyImageDeviceTypes: bson.M{
5✔
1117
                                        "$in": deviceTypesCompatible},
5✔
1118
                        },
5✔
1119
                },
5✔
1120
        }
5✔
1121

5✔
1122
        // do part of the job manually
5✔
1123
        // if candidate images have any extra 'depends' - guaranteed non-overlap
5✔
1124
        // otherwise it's a match
5✔
1125
        cur, err := collImg.Find(ctx, query)
5✔
1126
        if err != nil {
5✔
1127
                return false, err
×
1128
        }
×
1129

1130
        var images []model.Image
5✔
1131
        err = cur.All(ctx, &images)
5✔
1132
        if err != nil {
5✔
1133
                return false, err
×
1134
        }
×
1135

1136
        for _, i := range images {
6✔
1137
                // the artifact already has same name and overlapping dev type
1✔
1138
                // if there are no more depends than dev type - it's not unique
1✔
1139
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1140
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1141
                                return false, nil
1✔
1142
                        }
1✔
1143
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1144
                        return false, nil
×
1145
                }
×
1146
        }
1147

1148
        return true, nil
4✔
1149
}
1150

1151
// Delete image specified by ID
1152
// Noop on if not found.
1153
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
3✔
1154

3✔
1155
        if len(id) == 0 {
3✔
1156
                return ErrImagesStorageInvalidID
×
1157
        }
×
1158

1159
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1160
        collImg := database.Collection(CollectionImages)
3✔
1161

3✔
1162
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
1163
                if res.DeletedCount == 0 {
×
1164
                        return nil
×
1165
                }
×
1166
                return err
×
1167
        }
1168

1169
        return nil
3✔
1170
}
1171

1172
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
103✔
1173
        if filt != nil && filt.Sort != "" {
123✔
1174
                sortParts := strings.SplitN(filt.Sort, ":", 2)
20✔
1175
                if len(sortParts) == 2 &&
20✔
1176
                        (sortParts[0] == "name" ||
20✔
1177
                                sortParts[0] == "modified" ||
20✔
1178
                                sortParts[0] == "artifacts_count" ||
20✔
1179
                                sortParts[0] == "tags") {
40✔
1180
                        sortField := sortParts[0]
20✔
1181
                        sortOrder := 1
20✔
1182
                        if sortParts[1] == model.SortDirectionDescending {
32✔
1183
                                sortOrder = -1
12✔
1184
                        }
12✔
1185
                        return sortField, sortOrder
20✔
1186
                }
1187
        }
1188
        return "", 0
83✔
1189
}
1190

1191
// ListImages lists all images
1192
func (db *DataStoreMongo) ListImages(
1193
        ctx context.Context,
1194
        filt *model.ReleaseOrImageFilter,
1195
) ([]*model.Image, int, error) {
15✔
1196

15✔
1197
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1198
        collImg := database.Collection(CollectionImages)
15✔
1199

15✔
1200
        filters := bson.M{}
15✔
1201
        if filt != nil {
25✔
1202
                if filt.Name != "" {
14✔
1203
                        filters[StorageKeyImageName] = bson.M{
4✔
1204
                                "$regex": primitive.Regex{
4✔
1205
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
4✔
1206
                                        Options: "i",
4✔
1207
                                },
4✔
1208
                        }
4✔
1209
                }
4✔
1210
                if filt.Description != "" {
12✔
1211
                        filters[StorageKeyImageDescription] = bson.M{
2✔
1212
                                "$regex": primitive.Regex{
2✔
1213
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
1214
                                        Options: "i",
2✔
1215
                                },
2✔
1216
                        }
2✔
1217
                }
2✔
1218
                if filt.DeviceType != "" {
11✔
1219
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1220
                                "$regex": primitive.Regex{
1✔
1221
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1222
                                        Options: "i",
1✔
1223
                                },
1✔
1224
                        }
1✔
1225
                }
1✔
1226

1227
        }
1228

1229
        projection := bson.M{
15✔
1230
                StorageKeyImageDependsIdx:  0,
15✔
1231
                StorageKeyImageProvidesIdx: 0,
15✔
1232
        }
15✔
1233
        findOptions := &mopts.FindOptions{}
15✔
1234
        findOptions.SetProjection(projection)
15✔
1235
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
16✔
1236
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1237
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1238
        }
1✔
1239

1240
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
15✔
1241
        if sortField == "" || sortField == "name" {
28✔
1242
                sortField = StorageKeyImageName
13✔
1243
        }
13✔
1244
        if sortOrder == 0 {
27✔
1245
                sortOrder = 1
12✔
1246
        }
12✔
1247
        findOptions.SetSort(bson.D{
15✔
1248
                {Key: sortField, Value: sortOrder},
15✔
1249
                {Key: "_id", Value: sortOrder},
15✔
1250
        })
15✔
1251

15✔
1252
        cursor, err := collImg.Find(ctx, filters, findOptions)
15✔
1253
        if err != nil {
15✔
1254
                return nil, 0, err
×
1255
        }
×
1256

1257
        // NOTE: cursor.All closes the cursor before returning
1258
        var images []*model.Image
15✔
1259
        if err := cursor.All(ctx, &images); err != nil {
15✔
1260
                if err == mongo.ErrNoDocuments {
×
1261
                        return nil, 0, nil
×
1262
                }
×
1263
                return nil, 0, err
×
1264
        }
1265

1266
        count, err := collImg.CountDocuments(ctx, filters)
15✔
1267
        if err != nil {
15✔
1268
                return nil, -1, ErrDevicesCountFailed
×
1269
        }
×
1270

1271
        return images, int(count), nil
15✔
1272
}
1273

1274
func (db *DataStoreMongo) DeleteImagesByNames(ctx context.Context, names []string) error {
1✔
1275
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1276
        collDevs := database.Collection(CollectionImages)
1✔
1277
        query := bson.M{
1✔
1278
                StorageKeyImageName: bson.M{
1✔
1279
                        "$in": names,
1✔
1280
                },
1✔
1281
        }
1✔
1282
        _, err := collDevs.DeleteMany(ctx, query)
1✔
1283
        return err
1✔
1284
}
1✔
1285

1286
// device deployment log
1287
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1288
        log model.DeploymentLog) error {
9✔
1289

9✔
1290
        if err := log.Validate(); err != nil {
12✔
1291
                return err
3✔
1292
        }
3✔
1293

1294
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1295
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1296

6✔
1297
        query := bson.D{
6✔
1298
                {Key: StorageKeyDeviceDeploymentDeviceId,
6✔
1299
                        Value: log.DeviceID},
6✔
1300
                {Key: StorageKeyDeviceDeploymentDeploymentID,
6✔
1301
                        Value: log.DeploymentID},
6✔
1302
        }
6✔
1303

6✔
1304
        // update log messages
6✔
1305
        // if the deployment log is already present than messages will be overwritten
6✔
1306
        update := bson.D{
6✔
1307
                {Key: "$set", Value: bson.M{
6✔
1308
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
6✔
1309
                }},
6✔
1310
        }
6✔
1311
        updateOptions := mopts.Update()
6✔
1312
        updateOptions.SetUpsert(true)
6✔
1313
        if _, err := collLogs.UpdateOne(
6✔
1314
                ctx, query, update, updateOptions); err != nil {
6✔
1315
                return err
×
1316
        }
×
1317

1318
        return nil
6✔
1319
}
1320

1321
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1322
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
6✔
1323

6✔
1324
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1325
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1326

6✔
1327
        query := bson.M{
6✔
1328
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
6✔
1329
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1330
        }
6✔
1331

6✔
1332
        var depl model.DeploymentLog
6✔
1333
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
8✔
1334
                if err == mongo.ErrNoDocuments {
4✔
1335
                        return nil, nil
2✔
1336
                }
2✔
1337
                return nil, err
×
1338
        }
1339

1340
        return &depl, nil
4✔
1341
}
1342

1343
// device deployments
1344

1345
// Insert persists device deployment object
1346
func (db *DataStoreMongo) InsertDeviceDeployment(
1347
        ctx context.Context,
1348
        deviceDeployment *model.DeviceDeployment,
1349
        incrementDeviceCount bool,
1350
) error {
29✔
1351
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1352
        c := database.Collection(CollectionDevices)
29✔
1353

29✔
1354
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
49✔
1355
                startedTime := time.Now().UTC()
20✔
1356
                deviceDeployment.Started = &startedTime
20✔
1357
        }
20✔
1358

1359
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
29✔
1360
                return err
×
1361
        }
×
1362

1363
        if incrementDeviceCount {
58✔
1364
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
29✔
1365
                if err != nil {
29✔
1366
                        return err
×
1367
                }
×
1368
        }
1369

1370
        return nil
29✔
1371
}
1372

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

43✔
1378
        if len(deployments) == 0 {
55✔
1379
                return nil
12✔
1380
        }
12✔
1381

1382
        deviceCountIncrements := make(map[string]int)
31✔
1383

31✔
1384
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
31✔
1385
        var list []interface{}
31✔
1386
        for _, deployment := range deployments {
104✔
1387

73✔
1388
                if deployment == nil {
74✔
1389
                        return ErrStorageInvalidDeviceDeployment
1✔
1390
                }
1✔
1391

1392
                if err := deployment.Validate(); err != nil {
74✔
1393
                        return errors.Wrap(err, "Validating device deployment")
2✔
1394
                }
2✔
1395

1396
                list = append(list, deployment)
70✔
1397
                if deployment.Status != model.DeviceDeploymentStatusPending {
84✔
1398
                        startedTime := time.Now().UTC()
14✔
1399
                        deployment.Started = &startedTime
14✔
1400
                }
14✔
1401
                deviceCountIncrements[deployment.DeploymentId]++
70✔
1402
        }
1403

1404
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
28✔
1405
        collDevs := database.Collection(CollectionDevices)
28✔
1406

28✔
1407
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
28✔
1408
                return err
×
1409
        }
×
1410

1411
        for deploymentID := range deviceCountIncrements {
63✔
1412
                err := db.IncrementDeploymentDeviceCount(
35✔
1413
                        ctx,
35✔
1414
                        deploymentID,
35✔
1415
                        deviceCountIncrements[deploymentID],
35✔
1416
                )
35✔
1417
                if err != nil {
35✔
1418
                        return err
×
1419
                }
×
1420
        }
1421

1422
        return nil
28✔
1423
}
1424

1425
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1426
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1427
        ctx context.Context,
1428
        deviceID string,
1429
) (*model.DeviceDeployment, error) {
6✔
1430

6✔
1431
        // Verify ID formatting
6✔
1432
        if len(deviceID) == 0 {
7✔
1433
                return nil, ErrStorageInvalidID
1✔
1434
        }
1✔
1435

1436
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1437
        collDevs := database.Collection(CollectionDevices)
5✔
1438

5✔
1439
        // Device should know only about deployments that are not finished
5✔
1440
        query := bson.D{
5✔
1441
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
5✔
1442
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1443
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1444
                        {Key: "$exists", Value: false},
5✔
1445
                }},
5✔
1446
        }
5✔
1447

5✔
1448
        // Find the oldest one by sorting the creation timestamp
5✔
1449
        // in ascending order.
5✔
1450
        findOptions := mopts.FindOne()
5✔
1451
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
5✔
1452

5✔
1453
        // Select only the oldest one that have not been finished yet.
5✔
1454
        deployment := new(model.DeviceDeployment)
5✔
1455
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1456
                Decode(deployment); err != nil {
8✔
1457
                if err == mongo.ErrNoDocuments {
5✔
1458
                        return nil, nil
2✔
1459
                }
2✔
1460
                return nil, err
1✔
1461
        }
1462

1463
        return deployment, nil
3✔
1464
}
1465

1466
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1467
// matching device id that has not finished yet.
1468
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1469
        ctx context.Context,
1470
        deviceID string,
1471
) (*model.DeviceDeployment, error) {
6✔
1472

6✔
1473
        // Verify ID formatting
6✔
1474
        if len(deviceID) == 0 {
7✔
1475
                return nil, ErrStorageInvalidID
1✔
1476
        }
1✔
1477

1478
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1479
        collDevs := database.Collection(CollectionDevices)
5✔
1480

5✔
1481
        query := bson.D{
5✔
1482
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
5✔
1483
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1484
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1485
                        {Key: "$exists", Value: false},
5✔
1486
                }},
5✔
1487
        }
5✔
1488

5✔
1489
        // Find the latest one by sorting by the creation timestamp
5✔
1490
        // in ascending order.
5✔
1491
        findOptions := mopts.FindOne()
5✔
1492
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
5✔
1493

5✔
1494
        // Select only the latest one that have not been finished yet.
5✔
1495
        var deployment *model.DeviceDeployment
5✔
1496
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1497
                Decode(&deployment); err != nil {
8✔
1498
                if err == mongo.ErrNoDocuments {
5✔
1499
                        return nil, nil
2✔
1500
                }
2✔
1501
                return nil, err
1✔
1502
        }
1503

1504
        return deployment, nil
3✔
1505
}
1506

1507
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1508
        ctx context.Context,
1509
        deviceID string,
1510
        deploymentID string,
1511
        ddState model.DeviceDeploymentState,
1512
        currentStatus model.DeviceDeploymentStatus,
1513
) (model.DeviceDeploymentStatus, error) {
12✔
1514

12✔
1515
        // Verify ID formatting
12✔
1516
        if len(deviceID) == 0 ||
12✔
1517
                len(deploymentID) == 0 {
14✔
1518
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
2✔
1519
        }
2✔
1520

1521
        if err := ddState.Validate(); err != nil {
11✔
1522
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1523
        }
1✔
1524

1525
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1526
        collDevs := database.Collection(CollectionDevices)
9✔
1527

9✔
1528
        // Device should know only about deployments that are not finished
9✔
1529
        query := bson.D{
9✔
1530
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1531
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1532
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1533
                        {Key: "$exists", Value: false},
9✔
1534
                }},
9✔
1535
        }
9✔
1536

9✔
1537
        // update status field
9✔
1538
        set := bson.M{
9✔
1539
                StorageKeyDeviceDeploymentStatus: ddState.Status,
9✔
1540
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
9✔
1541
        }
9✔
1542
        // and finish time if provided
9✔
1543
        if ddState.FinishTime != nil {
11✔
1544
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
2✔
1545
        }
2✔
1546

1547
        if len(ddState.SubState) > 0 {
12✔
1548
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
3✔
1549
        }
3✔
1550

1551
        if currentStatus == model.DeviceDeploymentStatusPending &&
9✔
1552
                ddState.Status != currentStatus {
11✔
1553
                startedTime := time.Now().UTC()
2✔
1554
                set[StorageKeyDeviceDeploymentStarted] = startedTime
2✔
1555
        }
2✔
1556

1557
        update := bson.D{
9✔
1558
                {Key: "$set", Value: set},
9✔
1559
        }
9✔
1560

9✔
1561
        var old model.DeviceDeployment
9✔
1562

9✔
1563
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
9✔
1564
                Decode(&old); err != nil {
11✔
1565
                if err == mongo.ErrNoDocuments {
4✔
1566
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
2✔
1567
                }
2✔
1568
                return model.DeviceDeploymentStatusNull, err
×
1569

1570
        }
1571

1572
        return old.Status, nil
7✔
1573
}
1574

1575
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1576
        deviceID string, deploymentID string, log bool) error {
7✔
1577

7✔
1578
        // Verify ID formatting
7✔
1579
        if len(deviceID) == 0 ||
7✔
1580
                len(deploymentID) == 0 {
9✔
1581
                return ErrStorageInvalidID
2✔
1582
        }
2✔
1583

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

5✔
1587
        selector := bson.D{
5✔
1588
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1589
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
5✔
1590
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1591
                        {Key: "$exists", Value: false},
5✔
1592
                }},
5✔
1593
        }
5✔
1594

5✔
1595
        update := bson.D{
5✔
1596
                {Key: "$set", Value: bson.M{
5✔
1597
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
5✔
1598
        }
5✔
1599

5✔
1600
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
5✔
1601
                return err
×
1602
        } else if res.MatchedCount == 0 {
7✔
1603
                return ErrStorageNotFound
2✔
1604
        }
2✔
1605

1606
        return nil
3✔
1607
}
1608

1609
// SaveDeviceDeploymentRequest saves device deployment request
1610
// with the device deployment object
1611
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1612
        ctx context.Context,
1613
        ID string,
1614
        request *model.DeploymentNextRequest,
1615
) error {
4✔
1616

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

4✔
1620
        res, err := collDevs.UpdateOne(
4✔
1621
                ctx,
4✔
1622
                bson.D{{Key: StorageKeyId, Value: ID}},
4✔
1623
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
4✔
1624
        )
4✔
1625
        if err != nil {
4✔
1626
                return err
×
1627
        } else if res.MatchedCount == 0 {
5✔
1628
                return ErrStorageNotFound
1✔
1629
        }
1✔
1630
        return nil
3✔
1631
}
1632

1633
// AssignArtifact assigns artifact to the device deployment
1634
func (db *DataStoreMongo) AssignArtifact(
1635
        ctx context.Context,
1636
        deviceID string,
1637
        deploymentID string,
1638
        artifact *model.Image,
1639
) error {
1✔
1640

1✔
1641
        // Verify ID formatting
1✔
1642
        if len(deviceID) == 0 ||
1✔
1643
                len(deploymentID) == 0 {
1✔
1644
                return ErrStorageInvalidID
×
1645
        }
×
1646

1647
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1648
        collDevs := database.Collection(CollectionDevices)
1✔
1649

1✔
1650
        selector := bson.D{
1✔
1651
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1652
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1653
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1654
                        {Key: "$exists", Value: false},
1✔
1655
                }},
1✔
1656
        }
1✔
1657

1✔
1658
        update := bson.D{
1✔
1659
                {Key: "$set", Value: bson.M{
1✔
1660
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1661
                }},
1✔
1662
        }
1✔
1663

1✔
1664
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1665
                return err
×
1666
        } else if res.MatchedCount == 0 {
1✔
1667
                return ErrStorageNotFound
×
1668
        }
×
1669

1670
        return nil
1✔
1671
}
1672

1673
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1674
        id string) (model.Stats, error) {
6✔
1675

6✔
1676
        if len(id) == 0 {
6✔
1677
                return nil, ErrStorageInvalidID
×
1678
        }
×
1679

1680
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1681
        collDevs := database.Collection(CollectionDevices)
6✔
1682

6✔
1683
        match := bson.D{
6✔
1684
                {Key: "$match", Value: bson.M{
6✔
1685
                        StorageKeyDeviceDeploymentDeploymentID: id,
6✔
1686
                        StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1687
                                {Key: "$exists", Value: false},
6✔
1688
                        },
6✔
1689
                }},
6✔
1690
        }
6✔
1691
        group := bson.D{
6✔
1692
                {Key: "$group", Value: bson.D{
6✔
1693
                        {Key: "_id",
6✔
1694
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
6✔
1695
                        {Key: "count",
6✔
1696
                                Value: bson.M{"$sum": 1}}},
6✔
1697
                },
6✔
1698
        }
6✔
1699
        pipeline := []bson.D{
6✔
1700
                match,
6✔
1701
                group,
6✔
1702
        }
6✔
1703
        var results []struct {
6✔
1704
                Status model.DeviceDeploymentStatus `bson:"_id"`
6✔
1705
                Count  int
6✔
1706
        }
6✔
1707
        cursor, err := collDevs.Aggregate(ctx, pipeline)
6✔
1708
        if err != nil {
6✔
1709
                return nil, err
×
1710
        }
×
1711
        if err := cursor.All(ctx, &results); err != nil {
6✔
1712
                if err == mongo.ErrNoDocuments {
×
1713
                        return nil, nil
×
1714
                }
×
1715
                return nil, err
×
1716
        }
1717

1718
        raw := model.NewDeviceDeploymentStats()
6✔
1719
        for _, res := range results {
17✔
1720
                raw.Set(res.Status, res.Count)
11✔
1721
        }
11✔
1722
        return raw, nil
6✔
1723
}
1724

1725
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1726
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1727
        deploymentID string) ([]model.DeviceDeployment, error) {
6✔
1728

6✔
1729
        statuses := []model.DeviceDeployment{}
6✔
1730
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1731
        collDevs := database.Collection(CollectionDevices)
6✔
1732

6✔
1733
        query := bson.M{
6✔
1734
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1735
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1736
                        {Key: "$exists", Value: false},
6✔
1737
                },
6✔
1738
        }
6✔
1739

6✔
1740
        cursor, err := collDevs.Find(ctx, query)
6✔
1741
        if err != nil {
6✔
1742
                return nil, err
×
1743
        }
×
1744

1745
        if err = cursor.All(ctx, &statuses); err != nil {
6✔
1746
                if err == mongo.ErrNoDocuments {
×
1747
                        return nil, nil
×
1748
                }
×
1749
                return nil, err
×
1750
        }
1751

1752
        return statuses, nil
6✔
1753
}
1754

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

15✔
1758
        statuses := []model.DeviceDeployment{}
15✔
1759
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1760
        collDevs := database.Collection(CollectionDevices)
15✔
1761

15✔
1762
        query := bson.D{
15✔
1763
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
15✔
1764
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
15✔
1765
                        {Key: "$exists", Value: false},
15✔
1766
                }},
15✔
1767
        }
15✔
1768
        if q.Status != nil {
19✔
1769
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
5✔
1770
                        query = append(query, bson.E{
1✔
1771
                                Key: "status", Value: bson.D{{
1✔
1772
                                        Key:   "$gte",
1✔
1773
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1774
                                }, {
1✔
1775
                                        Key:   "$lte",
1✔
1776
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1777
                                }},
1✔
1778
                        })
1✔
1779
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
4✔
1780
                        query = append(query, bson.E{
×
1781
                                Key: "status", Value: bson.D{{
×
1782
                                        Key:   "$gte",
×
1783
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1784
                                }, {
×
1785
                                        Key:   "$lte",
×
1786
                                        Value: model.DeviceDeploymentStatusPending,
×
1787
                                }},
×
1788
                        })
×
1789
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
4✔
1790
                        query = append(query, bson.E{
1✔
1791
                                Key: "status", Value: bson.D{{
1✔
1792
                                        Key: "$in",
1✔
1793
                                        Value: []model.DeviceDeploymentStatus{
1✔
1794
                                                model.DeviceDeploymentStatusFailure,
1✔
1795
                                                model.DeviceDeploymentStatusAborted,
1✔
1796
                                                model.DeviceDeploymentStatusSuccess,
1✔
1797
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1798
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1799
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1800
                                        },
1✔
1801
                                }},
1✔
1802
                        })
1✔
1803
                } else {
3✔
1804
                        var status model.DeviceDeploymentStatus
2✔
1805
                        err := status.UnmarshalText([]byte(*q.Status))
2✔
1806
                        if err != nil {
3✔
1807
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1808
                        }
1✔
1809
                        query = append(query, bson.E{
1✔
1810
                                Key: "status", Value: status,
1✔
1811
                        })
1✔
1812
                }
1813
        }
1814

1815
        options := mopts.Find()
14✔
1816
        sortFieldQuery := bson.D{
14✔
1817
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
14✔
1818
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
14✔
1819
        }
14✔
1820
        options.SetSort(sortFieldQuery)
14✔
1821
        if q.Skip > 0 {
17✔
1822
                options.SetSkip(int64(q.Skip))
3✔
1823
        }
3✔
1824
        if q.Limit > 0 {
19✔
1825
                options.SetLimit(int64(q.Limit))
5✔
1826
        } else {
14✔
1827
                options.SetLimit(DefaultDocumentLimit)
9✔
1828
        }
9✔
1829

1830
        cursor, err := collDevs.Find(ctx, query, options)
14✔
1831
        if err != nil {
15✔
1832
                return nil, -1, err
1✔
1833
        }
1✔
1834

1835
        if err = cursor.All(ctx, &statuses); err != nil {
13✔
1836
                if err == mongo.ErrNoDocuments {
×
1837
                        return nil, -1, nil
×
1838
                }
×
1839
                return nil, -1, err
×
1840
        }
1841

1842
        count, err := collDevs.CountDocuments(ctx, query)
13✔
1843
        if err != nil {
13✔
1844
                return nil, -1, ErrDevicesCountFailed
×
1845
        }
×
1846

1847
        return statuses, int(count), nil
13✔
1848
}
1849

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

10✔
1853
        statuses := []model.DeviceDeployment{}
10✔
1854
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
10✔
1855
        collDevs := database.Collection(CollectionDevices)
10✔
1856

10✔
1857
        query := bson.D{}
10✔
1858
        if q.DeviceID != "" {
19✔
1859
                query = append(query, bson.E{
9✔
1860
                        Key:   StorageKeyDeviceDeploymentDeviceId,
9✔
1861
                        Value: q.DeviceID,
9✔
1862
                })
9✔
1863
        } else if len(q.IDs) > 0 {
11✔
1864
                query = append(query, bson.E{
1✔
1865
                        Key: StorageKeyId,
1✔
1866
                        Value: bson.D{{
1✔
1867
                                Key:   "$in",
1✔
1868
                                Value: q.IDs,
1✔
1869
                        }},
1✔
1870
                })
1✔
1871
        }
1✔
1872

1873
        if q.Status != nil {
18✔
1874
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
9✔
1875
                        query = append(query, bson.E{
1✔
1876
                                Key: "status", Value: bson.D{{
1✔
1877
                                        Key:   "$gte",
1✔
1878
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1879
                                }, {
1✔
1880
                                        Key:   "$lte",
1✔
1881
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1882
                                }},
1✔
1883
                        })
1✔
1884
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
9✔
1885
                        query = append(query, bson.E{
1✔
1886
                                Key: "status", Value: bson.D{{
1✔
1887
                                        Key:   "$gte",
1✔
1888
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1889
                                }, {
1✔
1890
                                        Key:   "$lte",
1✔
1891
                                        Value: model.DeviceDeploymentStatusPending,
1✔
1892
                                }},
1✔
1893
                        })
1✔
1894
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
8✔
1895
                        query = append(query, bson.E{
1✔
1896
                                Key: "status", Value: bson.D{{
1✔
1897
                                        Key: "$in",
1✔
1898
                                        Value: []model.DeviceDeploymentStatus{
1✔
1899
                                                model.DeviceDeploymentStatusFailure,
1✔
1900
                                                model.DeviceDeploymentStatusAborted,
1✔
1901
                                                model.DeviceDeploymentStatusSuccess,
1✔
1902
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1903
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1904
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1905
                                        },
1✔
1906
                                }},
1✔
1907
                        })
1✔
1908
                } else {
6✔
1909
                        var status model.DeviceDeploymentStatus
5✔
1910
                        err := status.UnmarshalText([]byte(*q.Status))
5✔
1911
                        if err != nil {
6✔
1912
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1913
                        }
1✔
1914
                        query = append(query, bson.E{
4✔
1915
                                Key: "status", Value: status,
4✔
1916
                        })
4✔
1917
                }
1918
        }
1919

1920
        options := mopts.Find()
9✔
1921
        sortFieldQuery := bson.D{
9✔
1922
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
9✔
1923
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
9✔
1924
        }
9✔
1925
        options.SetSort(sortFieldQuery)
9✔
1926
        if q.Skip > 0 {
10✔
1927
                options.SetSkip(int64(q.Skip))
1✔
1928
        }
1✔
1929
        if q.Limit > 0 {
18✔
1930
                options.SetLimit(int64(q.Limit))
9✔
1931
        } else {
9✔
1932
                options.SetLimit(DefaultDocumentLimit)
×
1933
        }
×
1934

1935
        cursor, err := collDevs.Find(ctx, query, options)
9✔
1936
        if err != nil {
9✔
1937
                return nil, -1, err
×
1938
        }
×
1939

1940
        if err = cursor.All(ctx, &statuses); err != nil {
9✔
1941
                if err == mongo.ErrNoDocuments {
×
1942
                        return nil, 0, nil
×
1943
                }
×
1944
                return nil, -1, err
×
1945
        }
1946

1947
        maxCount := maxCountDocuments
9✔
1948
        countOptions := &mopts.CountOptions{
9✔
1949
                Limit: &maxCount,
9✔
1950
        }
9✔
1951
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
9✔
1952
        if err != nil {
9✔
1953
                return nil, -1, ErrDevicesCountFailed
×
1954
        }
×
1955

1956
        return statuses, int(count), nil
9✔
1957
}
1958

1959
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1960
// `deviceID`, false otherwise. In case of errors returns false and an error
1961
// that occurred
1962
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1963
        deploymentID string, deviceID string) (bool, error) {
7✔
1964

7✔
1965
        var dep model.DeviceDeployment
7✔
1966
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1967
        collDevs := database.Collection(CollectionDevices)
7✔
1968

7✔
1969
        query := bson.D{
7✔
1970
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
1971
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
1972
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
1973
                        {Key: "$exists", Value: false},
7✔
1974
                }},
7✔
1975
        }
7✔
1976

7✔
1977
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
10✔
1978
                if err == mongo.ErrNoDocuments {
6✔
1979
                        return false, nil
3✔
1980
                } else {
3✔
1981
                        return false, err
×
1982
                }
×
1983
        }
1984

1985
        return true, nil
4✔
1986
}
1987

1988
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
1989
        deploymentId string) error {
3✔
1990

3✔
1991
        if len(deploymentId) == 0 {
4✔
1992
                return ErrStorageInvalidID
1✔
1993
        }
1✔
1994

1995
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
1996
        collDevs := database.Collection(CollectionDevices)
2✔
1997
        selector := bson.M{
2✔
1998
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
2✔
1999
                StorageKeyDeviceDeploymentActive:       true,
2✔
2000
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2001
                        {Key: "$exists", Value: false},
2✔
2002
                },
2✔
2003
        }
2✔
2004

2✔
2005
        update := bson.M{
2✔
2006
                "$set": bson.M{
2✔
2007
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2008
                        StorageKeyDeviceDeploymentActive: false,
2✔
2009
                },
2✔
2010
        }
2✔
2011

2✔
2012
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2013
                return err
×
2014
        }
×
2015

2016
        return nil
2✔
2017
}
2018

2019
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2020
        deviceID string) error {
2✔
2021
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2022
        collDevs := database.Collection(CollectionDevices)
2✔
2023
        selector := bson.M{
2✔
2024
                StorageKeyDeviceDeploymentDeviceId: deviceID,
2✔
2025
                StorageKeyDeviceDeploymentActive:   false,
2✔
2026
                StorageKeyDeviceDeploymentDeleted: bson.M{
2✔
2027
                        "$exists": false,
2✔
2028
                },
2✔
2029
        }
2✔
2030

2✔
2031
        now := time.Now()
2✔
2032
        update := bson.M{
2✔
2033
                "$set": bson.M{
2✔
2034
                        StorageKeyDeviceDeploymentDeleted: &now,
2✔
2035
                },
2✔
2036
        }
2✔
2037

2✔
2038
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2039
                return err
×
2040
        }
×
2041

2042
        database = db.client.Database(DatabaseName)
2✔
2043
        collDevs = database.Collection(CollectionDevicesLastStatus)
2✔
2044
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
2✔
2045

2✔
2046
        return err
2✔
2047
}
2048

2049
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2050
        deviceId string) error {
2✔
2051

2✔
2052
        if len(deviceId) == 0 {
3✔
2053
                return ErrStorageInvalidID
1✔
2054
        }
1✔
2055

2056
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2057
        collDevs := database.Collection(CollectionDevices)
1✔
2058
        selector := bson.M{
1✔
2059
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2060
                StorageKeyDeviceDeploymentActive:   true,
1✔
2061
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2062
                        {Key: "$exists", Value: false},
1✔
2063
                },
1✔
2064
        }
1✔
2065

1✔
2066
        update := bson.M{
1✔
2067
                "$set": bson.M{
1✔
2068
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2069
                        StorageKeyDeviceDeploymentActive: false,
1✔
2070
                },
1✔
2071
        }
1✔
2072

1✔
2073
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2074
                return err
×
2075
        }
×
2076

2077
        return nil
1✔
2078
}
2079

2080
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2081
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2082

1✔
2083
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2084
        collDevs := database.Collection(CollectionDevices)
1✔
2085

1✔
2086
        filter := bson.M{
1✔
2087
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2088
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2089
        }
1✔
2090
        if !includeDeleted {
2✔
2091
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2092
                        {Key: "$exists", Value: false},
1✔
2093
                }
1✔
2094
        }
1✔
2095

2096
        opts := &mopts.FindOneOptions{}
1✔
2097
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2098

1✔
2099
        var dd model.DeviceDeployment
1✔
2100
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2101
                if err == mongo.ErrNoDocuments {
2✔
2102
                        return nil, ErrStorageNotFound
1✔
2103
                }
1✔
2104
                return nil, err
×
2105
        }
2106

2107
        return &dd, nil
1✔
2108
}
2109

2110
func (db *DataStoreMongo) GetDeviceDeployments(
2111
        ctx context.Context,
2112
        skip int,
2113
        limit int,
2114
        deviceID string,
2115
        active *bool,
2116
        includeDeleted bool,
2117
) ([]model.DeviceDeployment, error) {
4✔
2118

4✔
2119
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2120
        collDevs := database.Collection(CollectionDevices)
4✔
2121

4✔
2122
        filter := bson.M{}
4✔
2123
        if !includeDeleted {
6✔
2124
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2125
                        {Key: "$exists", Value: false},
2✔
2126
                }
2✔
2127
        }
2✔
2128
        if deviceID != "" {
5✔
2129
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2130
        }
1✔
2131
        if active != nil {
5✔
2132
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2133
        }
1✔
2134

2135
        opts := &mopts.FindOptions{}
4✔
2136
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
4✔
2137
        if skip > 0 {
5✔
2138
                opts.SetSkip(int64(skip))
1✔
2139
        }
1✔
2140
        if limit > 0 {
5✔
2141
                opts.SetLimit(int64(limit))
1✔
2142
        }
1✔
2143

2144
        var deviceDeployments []model.DeviceDeployment
4✔
2145
        cursor, err := collDevs.Find(ctx, filter, opts)
4✔
2146
        if err != nil {
4✔
2147
                return nil, err
×
2148
        }
×
2149
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
4✔
2150
                return nil, err
×
2151
        }
×
2152

2153
        return deviceDeployments, nil
4✔
2154
}
2155

2156
// deployments
2157

2158
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2159
        indexes ...mongo.IndexModel) error {
466✔
2160
        ctx := context.Background()
466✔
2161
        dataBase := db.client.Database(dbName)
466✔
2162

466✔
2163
        coll := dataBase.Collection(collName)
466✔
2164
        idxView := coll.Indexes()
466✔
2165
        _, err := idxView.CreateMany(ctx, indexes)
466✔
2166
        return err
466✔
2167
}
466✔
2168

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

16✔
2172
        var idx bson.M
16✔
2173
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
16✔
2174
        collDpl := database.Collection(CollectionDeployments)
16✔
2175
        idxView := collDpl.Indexes()
16✔
2176

16✔
2177
        cursor, err := idxView.List(ctx)
16✔
2178
        if err != nil {
16✔
2179
                // check failed, assume indexing is not there
×
2180
                return false
×
2181
        }
×
2182

2183
        has := map[string]bool{}
16✔
2184
        for cursor.Next(ctx) {
46✔
2185
                if err = cursor.Decode(&idx); err != nil {
30✔
2186
                        continue
×
2187
                }
2188
                if _, ok := idx["weights"]; ok {
45✔
2189
                        // text index
15✔
2190
                        for k := range idx["weights"].(bson.M) {
45✔
2191
                                has[k] = true
30✔
2192
                        }
30✔
2193
                } else {
15✔
2194
                        for i := range idx["key"].(bson.M) {
30✔
2195
                                has[i] = true
15✔
2196
                        }
15✔
2197

2198
                }
2199
        }
2200
        if err != nil {
16✔
2201
                return false
×
2202
        }
×
2203

2204
        for _, key := range StorageIndexes.Keys.(bson.D) {
47✔
2205
                _, ok := has[key.Key]
31✔
2206
                if !ok {
32✔
2207
                        return false
1✔
2208
                }
1✔
2209
        }
2210

2211
        return true
15✔
2212
}
2213

2214
// Insert persists object
2215
func (db *DataStoreMongo) InsertDeployment(
2216
        ctx context.Context,
2217
        deployment *model.Deployment,
2218
) error {
216✔
2219

216✔
2220
        if deployment == nil {
217✔
2221
                return ErrDeploymentStorageInvalidDeployment
1✔
2222
        }
1✔
2223

2224
        if err := deployment.Validate(); err != nil {
217✔
2225
                return err
2✔
2226
        }
2✔
2227

2228
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
214✔
2229
        collDpl := database.Collection(CollectionDeployments)
214✔
2230

214✔
2231
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
216✔
2232
                if mongo.IsDuplicateKeyError(err) {
4✔
2233
                        return ErrConflictingDeployment
2✔
2234
                }
2✔
2235
                return err
×
2236
        }
2237
        return nil
213✔
2238
}
2239

2240
// Delete removed entry by ID
2241
// Noop on ID not found
2242
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
4✔
2243

4✔
2244
        if len(id) == 0 {
5✔
2245
                return ErrStorageInvalidID
1✔
2246
        }
1✔
2247

2248
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2249
        collDpl := database.Collection(CollectionDeployments)
3✔
2250

3✔
2251
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
2252
                return err
×
2253
        }
×
2254

2255
        return nil
3✔
2256
}
2257

2258
func (db *DataStoreMongo) FindDeploymentByID(
2259
        ctx context.Context,
2260
        id string,
2261
) (*model.Deployment, error) {
10✔
2262

10✔
2263
        if len(id) == 0 {
11✔
2264
                return nil, ErrStorageInvalidID
1✔
2265
        }
1✔
2266

2267
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2268
        collDpl := database.Collection(CollectionDeployments)
9✔
2269

9✔
2270
        deployment := new(model.Deployment)
9✔
2271
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
9✔
2272
                Decode(deployment); err != nil {
12✔
2273
                if err == mongo.ErrNoDocuments {
6✔
2274
                        return nil, nil
3✔
2275
                }
3✔
2276
                return nil, err
×
2277
        }
2278

2279
        return deployment, nil
6✔
2280
}
2281

2282
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2283
        ctx context.Context,
2284
        ids ...string,
2285
) (deploymentStats []*model.DeploymentStats, err error) {
2✔
2286

2✔
2287
        if len(ids) == 0 {
2✔
2288
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2289
        }
×
2290

2291
        for _, id := range ids {
6✔
2292
                if len(id) == 0 {
4✔
2293
                        return nil, ErrStorageInvalidID
×
2294
                }
×
2295
        }
2296

2297
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2298
        collDpl := database.Collection(CollectionDeployments)
2✔
2299

2✔
2300
        query := bson.M{
2✔
2301
                "_id": bson.M{
2✔
2302
                        "$in": ids,
2✔
2303
                },
2✔
2304
        }
2✔
2305
        statsProjection := &mopts.FindOptions{
2✔
2306
                Projection: bson.M{"stats": 1},
2✔
2307
        }
2✔
2308

2✔
2309
        results, err := collDpl.Find(
2✔
2310
                ctx,
2✔
2311
                query,
2✔
2312
                statsProjection,
2✔
2313
        )
2✔
2314
        if err != nil {
2✔
2315
                return nil, err
×
2316
        }
×
2317

2318
        for results.Next(context.Background()) {
6✔
2319
                depl := new(model.DeploymentStats)
4✔
2320
                if err = results.Decode(&depl); err != nil {
4✔
2321
                        if err == mongo.ErrNoDocuments {
×
2322
                                return nil, nil
×
2323
                        }
×
2324
                        return nil, err
×
2325
                }
2326
                deploymentStats = append(deploymentStats, depl)
4✔
2327
        }
2328

2329
        return deploymentStats, nil
2✔
2330
}
2331

2332
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2333
        id string) (*model.Deployment, error) {
8✔
2334

8✔
2335
        if len(id) == 0 {
9✔
2336
                return nil, ErrStorageInvalidID
1✔
2337
        }
1✔
2338

2339
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2340
        collDpl := database.Collection(CollectionDeployments)
7✔
2341

7✔
2342
        var deployment *model.Deployment
7✔
2343
        filter := bson.D{
7✔
2344
                {Key: "_id", Value: id},
7✔
2345
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2346
        }
7✔
2347
        if err := collDpl.FindOne(ctx, filter).
7✔
2348
                Decode(&deployment); err != nil {
12✔
2349
                if err == mongo.ErrNoDocuments {
10✔
2350
                        return nil, nil
5✔
2351
                }
5✔
2352
                return nil, err
×
2353
        }
2354

2355
        return deployment, nil
3✔
2356
}
2357

2358
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2359
        ctx context.Context,
2360
        deploymentID string,
2361
        increment int,
2362
) error {
64✔
2363
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
64✔
2364
        collection := database.Collection(CollectionDeployments)
64✔
2365

64✔
2366
        filter := bson.M{
64✔
2367
                "_id": deploymentID,
64✔
2368
                StorageKeyDeploymentDeviceCount: bson.M{
64✔
2369
                        "$ne": nil,
64✔
2370
                },
64✔
2371
        }
64✔
2372

64✔
2373
        update := bson.M{
64✔
2374
                "$inc": bson.M{
64✔
2375
                        StorageKeyDeploymentDeviceCount: increment,
64✔
2376
                },
64✔
2377
        }
64✔
2378

64✔
2379
        _, err := collection.UpdateOne(ctx, filter, update)
64✔
2380
        return err
64✔
2381
}
64✔
2382

2383
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2384
        ctx context.Context,
2385
        deploymentID string,
2386
        count int,
2387
) error {
3✔
2388
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2389
        collection := database.Collection(CollectionDeployments)
3✔
2390

3✔
2391
        filter := bson.M{
3✔
2392
                "_id": deploymentID,
3✔
2393
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2394
                        "$eq": nil,
3✔
2395
                },
3✔
2396
        }
3✔
2397

3✔
2398
        update := bson.M{
3✔
2399
                "$set": bson.M{
3✔
2400
                        StorageKeyDeploymentDeviceCount: count,
3✔
2401
                },
3✔
2402
        }
3✔
2403

3✔
2404
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2405
        return err
3✔
2406
}
3✔
2407

2408
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2409
        id string) (int, error) {
3✔
2410

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

3✔
2414
        filter := bson.M{
3✔
2415
                StorageKeyDeviceDeploymentDeploymentID: id,
3✔
2416
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
2417
                        {Key: "$exists", Value: false},
3✔
2418
                },
3✔
2419
        }
3✔
2420

3✔
2421
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
3✔
2422
        if err != nil {
3✔
2423
                return 0, err
×
2424
        }
×
2425

2426
        return int(deviceCount), nil
3✔
2427
}
2428

2429
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2430
        id string, stats model.Stats) error {
6✔
2431

6✔
2432
        if len(id) == 0 {
7✔
2433
                return ErrStorageInvalidID
1✔
2434
        }
1✔
2435

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

5✔
2439
        deployment, err := model.NewDeployment()
5✔
2440
        if err != nil {
5✔
2441
                return errors.Wrap(err, "failed to create deployment")
×
2442
        }
×
2443

2444
        deployment.Stats = stats
5✔
2445
        var update bson.M
5✔
2446
        if deployment.IsFinished() {
5✔
2447
                now := time.Now()
×
2448

×
2449
                update = bson.M{
×
2450
                        "$set": bson.M{
×
2451
                                StorageKeyDeploymentStats:    stats,
×
2452
                                StorageKeyDeploymentFinished: &now,
×
2453
                        },
×
2454
                }
×
2455
        } else {
5✔
2456
                update = bson.M{
5✔
2457
                        "$set": bson.M{
5✔
2458
                                StorageKeyDeploymentStats: stats,
5✔
2459
                        },
5✔
2460
                }
5✔
2461
        }
5✔
2462

2463
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
5✔
2464
        if res != nil && res.MatchedCount == 0 {
7✔
2465
                return ErrStorageInvalidID
2✔
2466
        }
2✔
2467
        return err
3✔
2468
}
2469

2470
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2471
        stateFrom, stateTo model.DeviceDeploymentStatus) (model.Stats, error) {
8✔
2472

8✔
2473
        if len(id) == 0 {
9✔
2474
                return nil, ErrStorageInvalidID
1✔
2475
        }
1✔
2476

2477
        if _, err := stateTo.MarshalText(); err != nil {
7✔
2478
                return nil, ErrStorageInvalidInput
×
2479
        }
×
2480

2481
        // does not need any extra operations
2482
        // following query won't handle this case well and increase the state_to value
2483
        if stateFrom == stateTo {
8✔
2484
                return nil, nil
1✔
2485
        }
1✔
2486

2487
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2488
        collDpl := database.Collection(CollectionDeployments)
6✔
2489

6✔
2490
        var update bson.M
6✔
2491

6✔
2492
        if stateFrom == model.DeviceDeploymentStatusNull {
8✔
2493
                // note dot notation on embedded document
2✔
2494
                update = bson.M{
2✔
2495
                        "$inc": bson.M{
2✔
2496
                                "stats." + stateTo.String(): 1,
2✔
2497
                        },
2✔
2498
                }
2✔
2499
        } else {
7✔
2500
                // note dot notation on embedded document
5✔
2501
                update = bson.M{
5✔
2502
                        "$inc": bson.M{
5✔
2503
                                "stats." + stateFrom.String(): -1,
5✔
2504
                                "stats." + stateTo.String():   1,
5✔
2505
                        },
5✔
2506
                }
5✔
2507
        }
5✔
2508

2509
        var res struct {
6✔
2510
                Stats model.Stats `bson:"stats"`
6✔
2511
        }
6✔
2512
        err := collDpl.FindOneAndUpdate(ctx,
6✔
2513
                bson.M{StorageKeyId: id},
6✔
2514
                update,
6✔
2515
                mopts.FindOneAndUpdate().
6✔
2516
                        SetReturnDocument(mopts.After).
6✔
2517
                        SetProjection(bson.M{
6✔
2518
                                StorageKeyDeploymentStats: 1,
6✔
2519
                        }),
6✔
2520
        ).Decode(&res)
6✔
2521

6✔
2522
        if errors.Is(err, mongo.ErrNoDocuments) {
7✔
2523
                return nil, ErrStorageInvalidID
1✔
2524
        }
1✔
2525

2526
        return res.Stats, err
5✔
2527
}
2528

2529
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2530
        ctx context.Context,
2531
        deploymentID string,
2532
        increment int64,
2533
) error {
3✔
2534
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2535
        collection := database.Collection(CollectionDeployments)
3✔
2536

3✔
2537
        filter := bson.M{
3✔
2538
                "_id": deploymentID,
3✔
2539
        }
3✔
2540

3✔
2541
        update := bson.M{
3✔
2542
                "$inc": bson.M{
3✔
2543
                        StorageKeyDeploymentTotalSize: increment,
3✔
2544
                },
3✔
2545
        }
3✔
2546

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

2551
func (db *DataStoreMongo) Find(ctx context.Context,
2552
        match model.Query) ([]*model.Deployment, int64, error) {
36✔
2553

36✔
2554
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
36✔
2555
        collDpl := database.Collection(CollectionDeployments)
36✔
2556

36✔
2557
        andq := []bson.M{}
36✔
2558

36✔
2559
        // filter by IDs
36✔
2560
        if match.IDs != nil {
36✔
2561
                tq := bson.M{
×
2562
                        "_id": bson.M{
×
2563
                                "$in": match.IDs,
×
2564
                        },
×
2565
                }
×
2566
                andq = append(andq, tq)
×
2567
        }
×
2568

2569
        // build deployment by name part of the query
2570
        if match.SearchText != "" {
52✔
2571
                // we must have indexing for text search
16✔
2572
                if !db.hasIndexing(ctx, db.client) {
17✔
2573
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
1✔
2574
                }
1✔
2575

2576
                tq := bson.M{
15✔
2577
                        "$text": bson.M{
15✔
2578
                                "$search": "\"" + match.SearchText + "\"",
15✔
2579
                        },
15✔
2580
                }
15✔
2581

15✔
2582
                andq = append(andq, tq)
15✔
2583
        }
2584

2585
        // build deployment by status part of the query
2586
        if match.Status != model.StatusQueryAny {
45✔
2587
                var status model.DeploymentStatus
10✔
2588
                if match.Status == model.StatusQueryPending {
12✔
2589
                        status = model.DeploymentStatusPending
2✔
2590
                } else if match.Status == model.StatusQueryInProgress {
14✔
2591
                        status = model.DeploymentStatusInProgress
4✔
2592
                } else {
8✔
2593
                        status = model.DeploymentStatusFinished
4✔
2594
                }
4✔
2595
                stq := bson.M{StorageKeyDeploymentStatus: status}
10✔
2596
                andq = append(andq, stq)
10✔
2597
        }
2598

2599
        // build deployment by type part of the query
2600
        if match.Type != "" {
37✔
2601
                if match.Type == model.DeploymentTypeConfiguration {
4✔
2602
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
2✔
2603
                } else if match.Type == model.DeploymentTypeSoftware {
2✔
2604
                        andq = append(andq, bson.M{
×
2605
                                "$or": []bson.M{
×
2606
                                        {StorageKeyDeploymentType: match.Type},
×
2607
                                        {StorageKeyDeploymentType: ""},
×
2608
                                },
×
2609
                        })
×
2610
                }
×
2611
        }
2612

2613
        query := bson.M{}
35✔
2614
        if len(andq) != 0 {
58✔
2615
                // use search criteria if any
23✔
2616
                query = bson.M{
23✔
2617
                        "$and": andq,
23✔
2618
                }
23✔
2619
        }
23✔
2620

2621
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
35✔
2622
                query["created"] = bson.M{
×
2623
                        "$gte": match.CreatedAfter,
×
2624
                        "$lte": match.CreatedBefore,
×
2625
                }
×
2626
        } else if match.CreatedAfter != nil {
35✔
2627
                query["created"] = bson.M{
×
2628
                        "$gte": match.CreatedAfter,
×
2629
                }
×
2630
        } else if match.CreatedBefore != nil {
35✔
2631
                query["created"] = bson.M{
×
2632
                        "$lte": match.CreatedBefore,
×
2633
                }
×
2634
        }
×
2635

2636
        options := db.findOptions(match)
35✔
2637

35✔
2638
        var deployments []*model.Deployment
35✔
2639
        cursor, err := collDpl.Find(ctx, query, options)
35✔
2640
        if err != nil {
35✔
2641
                return nil, 0, err
×
2642
        }
×
2643
        if err := cursor.All(ctx, &deployments); err != nil {
35✔
2644
                return nil, 0, err
×
2645
        }
×
2646
        // Count documents if we didn't find all already.
2647
        count := int64(0)
35✔
2648
        if !match.DisableCount {
70✔
2649
                count = int64(len(deployments))
35✔
2650
                if count >= int64(match.Limit) {
69✔
2651
                        count, err = collDpl.CountDocuments(ctx, query)
34✔
2652
                        if err != nil {
34✔
2653
                                return nil, 0, err
×
2654
                        }
×
2655
                } else {
1✔
2656
                        // Don't forget to add the skipped documents
1✔
2657
                        count += int64(match.Skip)
1✔
2658
                }
1✔
2659
        }
2660

2661
        return deployments, count, nil
35✔
2662
}
2663

2664
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
35✔
2665
        options := &mopts.FindOptions{}
35✔
2666
        if match.Sort == model.SortDirectionAscending {
36✔
2667
                options.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
2668
        } else {
35✔
2669
                options.SetSort(bson.D{{Key: "created", Value: -1}})
34✔
2670
        }
34✔
2671
        if match.Skip > 0 {
37✔
2672
                options.SetSkip(int64(match.Skip))
2✔
2673
        }
2✔
2674
        if match.Limit > 0 {
40✔
2675
                options.SetLimit(int64(match.Limit))
5✔
2676
        } else {
35✔
2677
                options.SetLimit(DefaultDocumentLimit)
30✔
2678
        }
30✔
2679
        return options
35✔
2680
}
2681

2682
// FindNewerActiveDeployments finds active deployments which were created
2683
// after createdAfter
2684
// Deprecated: No longer in use
2685
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2686
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
4✔
2687

4✔
2688
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2689
        c := database.Collection(CollectionDeployments)
4✔
2690

4✔
2691
        queryFilters := make([]bson.M, 0)
4✔
2692
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
4✔
2693
        queryFilters = append(queryFilters,
4✔
2694
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
4✔
2695
        findQuery := bson.M{}
4✔
2696
        findQuery["$and"] = queryFilters
4✔
2697

4✔
2698
        findOptions := &mopts.FindOptions{}
4✔
2699
        findOptions.SetSkip(int64(skip))
4✔
2700
        findOptions.SetLimit(int64(limit))
4✔
2701

4✔
2702
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
4✔
2703
        cursor, err := c.Find(ctx, findQuery, findOptions)
4✔
2704
        if err != nil {
4✔
2705
                return nil, errors.Wrap(err, "failed to get deployments")
×
2706
        }
×
2707
        defer cursor.Close(ctx)
4✔
2708

4✔
2709
        var deployments []*model.Deployment
4✔
2710

4✔
2711
        if err = cursor.All(ctx, &deployments); err != nil {
4✔
2712
                return nil, errors.Wrap(err, "failed to get deployments")
×
2713
        }
×
2714

2715
        return deployments, nil
4✔
2716
}
2717

2718
// FindNewerActiveDeployment finds active deployments which were created
2719
// after createdAfter where deviceID is part of the device list.
2720
func (db *DataStoreMongo) FindNewerActiveDeployment(ctx context.Context,
2721
        createdAfter *time.Time, deviceID string) (*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
        findQuery := bson.D{
1✔
2727
                {Key: StorageKeyDeploymentActive, Value: true},
1✔
2728
                {Key: StorageKeyDeploymentCreated, Value: bson.M{"$gt": createdAfter}},
1✔
2729
                {Key: StorageKeyDeploymentDeviceList, Value: deviceID},
1✔
2730
        }
1✔
2731
        findOptions := mopts.FindOne().
1✔
2732
                SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}}).
1✔
2733
                SetProjection(bson.M{
1✔
2734
                        // Discard information we don't need
1✔
2735
                        StorageKeyDeploymentConstructorChecksum: 0,
1✔
2736
                        StorageKeyDeploymentDeviceList:          0,
1✔
2737
                })
1✔
2738

1✔
2739
        var deployment = new(model.Deployment)
1✔
2740
        err := c.FindOne(ctx, findQuery, findOptions).
1✔
2741
                Decode(deployment)
1✔
2742
        if err != nil {
2✔
2743
                if errors.Is(err, mongo.ErrNoDocuments) {
2✔
2744
                        return nil, nil
1✔
2745
                }
1✔
2746
                return nil, errors.Wrap(err, "failed to get deployments")
×
2747
        }
2748

2749
        return deployment, nil
1✔
2750
}
2751

2752
// SetDeploymentStatus simply sets the status field
2753
// optionally sets 'finished time' if deployment is indeed finished
2754
func (db *DataStoreMongo) SetDeploymentStatus(
2755
        ctx context.Context,
2756
        id string,
2757
        status model.DeploymentStatus,
2758
        now time.Time,
2759
) error {
6✔
2760
        if len(id) == 0 {
6✔
2761
                return ErrStorageInvalidID
×
2762
        }
×
2763

2764
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2765
        collDpl := database.Collection(CollectionDeployments)
6✔
2766

6✔
2767
        var update bson.M
6✔
2768
        if status == model.DeploymentStatusFinished {
8✔
2769
                update = bson.M{
2✔
2770
                        "$set": bson.M{
2✔
2771
                                StorageKeyDeploymentActive:   false,
2✔
2772
                                StorageKeyDeploymentStatus:   status,
2✔
2773
                                StorageKeyDeploymentFinished: &now,
2✔
2774
                        },
2✔
2775
                }
2✔
2776
        } else {
7✔
2777
                update = bson.M{
5✔
2778
                        "$set": bson.M{
5✔
2779
                                StorageKeyDeploymentActive: true,
5✔
2780
                                StorageKeyDeploymentStatus: status,
5✔
2781
                        },
5✔
2782
                }
5✔
2783
        }
5✔
2784

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

6✔
2787
        if res != nil && res.MatchedCount == 0 {
7✔
2788
                return ErrStorageInvalidID
1✔
2789
        }
1✔
2790

2791
        return err
5✔
2792
}
2793

2794
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2795
// given artifact
2796
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2797
        id string) (bool, error) {
4✔
2798

4✔
2799
        if len(id) == 0 {
4✔
2800
                return false, ErrStorageInvalidID
×
2801
        }
×
2802

2803
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2804
        collDpl := database.Collection(CollectionDeployments)
4✔
2805

4✔
2806
        var tmp interface{}
4✔
2807
        query := bson.D{
4✔
2808
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2809
                {Key: StorageKeyDeploymentArtifacts, Value: id},
4✔
2810
        }
4✔
2811
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
7✔
2812
                if err == mongo.ErrNoDocuments {
6✔
2813
                        return false, nil
3✔
2814
                }
3✔
2815
                return false, err
×
2816
        }
2817

2818
        return true, nil
2✔
2819
}
2820

2821
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2822
// given artifact
2823
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2824
        artifactName string) (bool, error) {
4✔
2825

4✔
2826
        if len(artifactName) == 0 {
4✔
2827
                return false, ErrImagesStorageInvalidArtifactName
×
2828
        }
×
2829

2830
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2831
        collDpl := database.Collection(CollectionDeployments)
4✔
2832

4✔
2833
        var tmp interface{}
4✔
2834
        query := bson.D{
4✔
2835
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2836
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
4✔
2837
        }
4✔
2838

4✔
2839
        projection := bson.M{
4✔
2840
                "_id": 1,
4✔
2841
        }
4✔
2842
        findOptions := mopts.FindOne()
4✔
2843
        findOptions.SetProjection(projection)
4✔
2844

4✔
2845
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
7✔
2846
                if err == mongo.ErrNoDocuments {
6✔
2847
                        return false, nil
3✔
2848
                }
3✔
2849
                return false, err
×
2850
        }
2851

2852
        return true, nil
1✔
2853
}
2854

2855
// ExistByArtifactId check if there is any deployment that uses give artifact
2856
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2857
        id string) (bool, error) {
×
2858

×
2859
        if len(id) == 0 {
×
2860
                return false, ErrStorageInvalidID
×
2861
        }
×
2862

2863
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2864
        collDpl := database.Collection(CollectionDeployments)
×
2865

×
2866
        var tmp interface{}
×
2867
        query := bson.D{
×
2868
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2869
        }
×
2870
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2871
                if err == mongo.ErrNoDocuments {
×
2872
                        return false, nil
×
2873
                }
×
2874
                return false, err
×
2875
        }
2876

2877
        return true, nil
×
2878
}
2879

2880
// Per-tenant storage settings
2881
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
2✔
2882
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2883
        collection := database.Collection(CollectionStorageSettings)
2✔
2884

2✔
2885
        settings := new(model.StorageSettings)
2✔
2886
        // supposed that it's only one document in the collection
2✔
2887
        query := bson.M{
2✔
2888
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2889
        }
2✔
2890
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
3✔
2891
                if err == mongo.ErrNoDocuments {
2✔
2892
                        return nil, nil
1✔
2893
                }
1✔
2894
                return nil, err
×
2895
        }
2896

2897
        return settings, nil
2✔
2898
}
2899

2900
func (db *DataStoreMongo) SetStorageSettings(
2901
        ctx context.Context,
2902
        storageSettings *model.StorageSettings,
2903
) error {
2✔
2904
        var err error
2✔
2905
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2906
        collection := database.Collection(CollectionStorageSettings)
2✔
2907

2✔
2908
        filter := bson.M{
2✔
2909
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2910
        }
2✔
2911
        if storageSettings != nil {
4✔
2912
                replaceOptions := mopts.Replace()
2✔
2913
                replaceOptions.SetUpsert(true)
2✔
2914
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
2✔
2915
        } else {
3✔
2916
                _, err = collection.DeleteOne(ctx, filter)
1✔
2917
        }
1✔
2918

2919
        return err
2✔
2920
}
2921

2922
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2923
        ctx context.Context,
2924
        artifactName string,
2925
        artifactIDs []string,
2926
) error {
1✔
2927
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2928
        collDpl := database.Collection(CollectionDeployments)
1✔
2929

1✔
2930
        query := bson.D{
1✔
2931
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2932
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2933
        }
1✔
2934
        update := bson.M{
1✔
2935
                "$set": bson.M{
1✔
2936
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
2937
                },
1✔
2938
        }
1✔
2939

1✔
2940
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
2941
        return err
1✔
2942
}
1✔
2943

2944
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
2945
        ctx context.Context,
2946
        artifactNames []string,
2947
) ([]string, error) {
2✔
2948

2✔
2949
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2950
        collDpl := database.Collection(CollectionDeployments)
2✔
2951

2✔
2952
        query := bson.M{
2✔
2953
                StorageKeyDeploymentArtifactName: bson.M{
2✔
2954
                        "$in": artifactNames,
2✔
2955
                },
2✔
2956
        }
2✔
2957

2✔
2958
        projection := bson.M{
2✔
2959
                "_id": 1,
2✔
2960
        }
2✔
2961
        findOptions := mopts.Find()
2✔
2962
        findOptions.SetProjection(projection)
2✔
2963

2✔
2964
        cursor, err := collDpl.Find(ctx, query, findOptions)
2✔
2965
        if err != nil {
2✔
2966
                return []string{}, err
×
2967
        }
×
2968
        defer cursor.Close(ctx)
2✔
2969

2✔
2970
        var deployments []*model.Deployment
2✔
2971
        if err = cursor.All(ctx, &deployments); err != nil {
2✔
2972
                if err == mongo.ErrNoDocuments {
×
2973
                        err = nil
×
2974
                }
×
2975
                return []string{}, err
×
2976
        }
2977

2978
        ids := make([]string, len(deployments))
2✔
2979
        for i, d := range deployments {
4✔
2980
                ids[i] = d.Id
2✔
2981
        }
2✔
2982

2983
        return ids, nil
2✔
2984
}
2985

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