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

mendersoftware / deployments / 890874106

pending completion
890874106

Pull #867

gitlab-ci

kjaskiewiczz
feat: make releases persistent in the database

Changelog: Title
Ticket: MEN-5180

Signed-off-by: Krzysztof Jaskiewicz <krzysztof.jaskiewicz@northern.tech>
Pull Request #867: feat: make releases persistent in the database

81 of 239 new or added lines in 5 files covered. (33.89%)

1 existing line in 1 file now uncovered.

7174 of 9174 relevant lines covered (78.2%)

67.85 hits per line

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

81.82
/store/mongo/datastore_mongo.go
1
// Copyright 2023 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
)
54

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

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

65
var (
66
        // Indexes (version: 1.2.2)
67
        IndexUniqueNameAndDeviceTypeName          = "uniqueNameAndDeviceTypeIndex"
68
        IndexDeploymentArtifactName               = "deploymentArtifactNameIndex"
69
        IndexDeploymentDeviceStatusesName         = "deviceIdWithStatusByCreated"
70
        IndexDeploymentDeviceIdStatusName         = "devicesIdWithStatus"
71
        IndexDeploymentDeviceCreatedStatusName    = "devicesIdWithCreatedStatus"
72
        IndexDeploymentDeviceDeploymentIdName     = "devicesDeploymentId"
73
        IndexDeploymentStatusFinishedName         = "deploymentStatusFinished"
74
        IndexDeploymentStatusPendingName          = "deploymentStatusPending"
75
        IndexDeploymentCreatedName                = "deploymentCreated"
76
        IndexDeploymentDeviceStatusRebootingName  = "deploymentsDeviceStatusRebooting"
77
        IndexDeploymentDeviceStatusPendingName    = "deploymentsDeviceStatusPending"
78
        IndexDeploymentDeviceStatusInstallingName = "deploymentsDeviceStatusInstalling"
79
        IndexDeploymentDeviceStatusFinishedName   = "deploymentsFinished"
80

81
        // Indexes (version: 1.2.3)
82
        IndexArtifactNameDependsName = "artifactNameDepends"
83
        IndexNameAndDeviceTypeName   = "artifactNameAndDeviceTypeIndex"
84

85
        // Indexes (version: 1.2.4)
86
        IndexDeploymentStatus = "deploymentStatus"
87

88
        // Indexes 1.2.6
89
        IndexDeviceDeploymentStatusName = "deploymentid_status_deviceid"
90

91
        // Indexes 1.2.13
92
        IndexArtifactProvidesName = "artifact_provides"
93

94
        // Indexes 1.2.15
95
        IndexReleaseNameName = "release_name"
96

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

267
        // 1.2.3
268
        IndexArtifactNameDepends = mongo.IndexModel{
269
                Keys: bson.D{
270
                        {Key: StorageKeyImageName,
271
                                Value: 1},
272
                        {Key: StorageKeyImageDependsIdx,
273
                                Value: 1},
274
                },
275
                Options: &mopts.IndexOptions{
276
                        Background: &_false,
277
                        Name:       &IndexArtifactNameDependsName,
278
                        Unique:     &_true,
279
                },
280
        }
281

282
        // Indexes 1.2.7
283
        IndexImageMetaDescription      = "image_meta_description"
284
        IndexImageMetaDescriptionModel = mongo.IndexModel{
285
                Keys: bson.D{
286
                        {Key: StorageKeyImageDescription, Value: 1},
287
                },
288
                Options: &mopts.IndexOptions{
289
                        Background: &_false,
290
                        Name:       &IndexImageMetaDescription,
291
                },
292
        }
293

294
        IndexImageMetaArtifactDeviceTypeCompatible      = "image_meta_artifact_device_type_compatible"
295
        IndexImageMetaArtifactDeviceTypeCompatibleModel = mongo.IndexModel{
296
                Keys: bson.D{
297
                        {Key: StorageKeyImageDeviceTypes, Value: 1},
298
                },
299
                Options: &mopts.IndexOptions{
300
                        Background: &_false,
301
                        Name:       &IndexImageMetaArtifactDeviceTypeCompatible,
302
                },
303
        }
304

305
        // Indexes 1.2.8
306
        IndexDeploymentsActiveCreated      = "active_created"
307
        IndexDeploymentsActiveCreatedModel = mongo.IndexModel{
308
                Keys: bson.D{
309
                        {Key: StorageKeyDeploymentCreated, Value: 1},
310
                },
311
                Options: &mopts.IndexOptions{
312
                        Background: &_false,
313
                        Name:       &IndexDeploymentsActiveCreated,
314
                        PartialFilterExpression: bson.M{
315
                                StorageKeyDeploymentActive: true,
316
                        },
317
                },
318
        }
319

320
        // Index 1.2.9
321
        IndexDeviceDeploymentsActiveCreated      = "active_deviceid_created"
322
        IndexDeviceDeploymentsActiveCreatedModel = mongo.IndexModel{
323
                Keys: bson.D{
324
                        {Key: StorageKeyDeviceDeploymentActive, Value: 1},
325
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
326
                        {Key: StorageKeyDeviceDeploymentCreated, Value: 1},
327
                },
328
                Options: mopts.Index().
329
                        SetName(IndexDeviceDeploymentsActiveCreated),
330
        }
331

332
        // Index 1.2.11
333
        IndexDeviceDeploymentsLogs      = "devices_logs"
334
        IndexDeviceDeploymentsLogsModel = mongo.IndexModel{
335
                Keys: bson.D{
336
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
337
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
338
                },
339
                Options: mopts.Index().
340
                        SetName(IndexDeviceDeploymentsLogs),
341
        }
342

343
        // 1.2.13
344
        IndexArtifactProvides = mongo.IndexModel{
345
                Keys: bson.D{
346
                        {Key: model.StorageKeyImageProvidesIdxKey,
347
                                Value: 1},
348
                        {Key: model.StorageKeyImageProvidesIdxValue,
349
                                Value: 1},
350
                },
351
                Options: &mopts.IndexOptions{
352
                        Background: &_false,
353
                        Sparse:     &_true,
354
                        Name:       &IndexArtifactProvidesName,
355
                },
356
        }
357
        // 1.2.15
358
        IndexReleaseName = mongo.IndexModel{
359
                Keys: bson.D{
360
                        {Key: StorageKeyReleaseName, Value: 1},
361
                },
362
                Options: &mopts.IndexOptions{
363
                        Background: &_false,
364
                        Name:       &IndexReleaseNameName,
365
                        Unique:     &_true,
366
                },
367
        }
368
)
369

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

378
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
379

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

387
        ErrLimitNotFound      = errors.New("limit not found")
388
        ErrDevicesCountFailed = errors.New("failed to count devices")
389
)
390

391
const (
392
        ErrMsgConflictingDepends = "An artifact with the same name has " +
393
                "conflicting depends"
394
)
395

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

402
        StorageKeyImageProvides    = "meta_artifact.provides"
403
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
404
        StorageKeyImageDepends     = "meta_artifact.depends"
405
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
406
        StorageKeyImageSize        = "size"
407
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
408
        StorageKeyImageName        = "meta_artifact.name"
409
        StorageKeyImageDescription = "meta.description"
410
        StorageKeyImageModified    = "modified"
411

412
        // releases
413
        StorageKeyReleaseName                      = "name"
414
        StorageKeyReleaseModified                  = "modified"
415
        StorageKeyReleaseArtifacts                 = "artifacts"
416
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
417
                StorageKeyImageDescription
418
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
419
                StorageKeyImageDescription
420
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
421
                StorageKeyImageDeviceTypes
422
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
423
                StorageKeyImageModified
424
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
425
                StorageKeyId
426
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
427
                StorageKeyImageDependsIdx
428
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
429
                StorageKeyImageProvidesIdx
430

431
        StorageKeyDeviceDeploymentLogMessages = "messages"
432

433
        StorageKeyDeviceDeploymentAssignedImage   = "image"
434
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
435
                "." + StorageKeyId
436

437
        StorageKeyDeviceDeploymentActive         = "active"
438
        StorageKeyDeviceDeploymentCreated        = "created"
439
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
440
        StorageKeyDeviceDeploymentStatus         = "status"
441
        StorageKeyDeviceDeploymentSubState       = "substate"
442
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
443
        StorageKeyDeviceDeploymentFinished       = "finished"
444
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
445
        StorageKeyDeviceDeploymentArtifact       = "image"
446
        StorageKeyDeviceDeploymentRequest        = "request"
447
        StorageKeyDeviceDeploymentDeleted        = "deleted"
448

449
        StorageKeyDeploymentName         = "deploymentconstructor.name"
450
        StorageKeyDeploymentArtifactName = "deploymentconstructor.artifactname"
451
        StorageKeyDeploymentStats        = "stats"
452
        StorageKeyDeploymentActive       = "active"
453
        StorageKeyDeploymentStatus       = "status"
454
        StorageKeyDeploymentCreated      = "created"
455
        StorageKeyDeploymentStatsCreated = "created"
456
        StorageKeyDeploymentFinished     = "finished"
457
        StorageKeyDeploymentArtifacts    = "artifacts"
458
        StorageKeyDeploymentDeviceCount  = "device_count"
459
        StorageKeyDeploymentMaxDevices   = "max_devices"
460
        StorageKeyDeploymentType         = "type"
461
        StorageKeyDeploymentTotalSize    = "statistics.total_size"
462

463
        StorageKeyStorageSettingsDefaultID      = "settings"
464
        StorageKeyStorageSettingsBucket         = "bucket"
465
        StorageKeyStorageSettingsRegion         = "region"
466
        StorageKeyStorageSettingsKey            = "key"
467
        StorageKeyStorageSettingsSecret         = "secret"
468
        StorageKeyStorageSettingsURI            = "uri"
469
        StorageKeyStorageSettingsExternalURI    = "external_uri"
470
        StorageKeyStorageSettingsToken          = "token"
471
        StorageKeyStorageSettingsForcePathStyle = "force_path_style"
472
        StorageKeyStorageSettingsUseAccelerate  = "use_accelerate"
473

474
        ArtifactDependsDeviceType = "device_type"
475
)
476

477
type DataStoreMongo struct {
478
        client *mongo.Client
479
}
480

481
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
579✔
482
        return &DataStoreMongo{
579✔
483
                client: client,
579✔
484
        }
579✔
485
}
579✔
486

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

1✔
489
        clientOptions := mopts.Client()
1✔
490
        mongoURL := c.GetString(dconfig.SettingMongo)
1✔
491
        if !strings.Contains(mongoURL, "://") {
1✔
492
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
493
                        mongoURL)
×
494
        }
×
495
        clientOptions.ApplyURI(mongoURL)
1✔
496

1✔
497
        username := c.GetString(dconfig.SettingDbUsername)
1✔
498
        if username != "" {
1✔
499
                credentials := mopts.Credential{
×
500
                        Username: c.GetString(dconfig.SettingDbUsername),
×
501
                }
×
502
                password := c.GetString(dconfig.SettingDbPassword)
×
503
                if password != "" {
×
504
                        credentials.Password = password
×
505
                        credentials.PasswordSet = true
×
506
                }
×
507
                clientOptions.SetAuth(credentials)
×
508
        }
509

510
        if c.GetBool(dconfig.SettingDbSSL) {
1✔
511
                tlsConfig := &tls.Config{}
×
512
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
513
                clientOptions.SetTLSConfig(tlsConfig)
×
514
        }
×
515

516
        // Set 10s timeout
517
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
1✔
518
        defer cancel()
1✔
519
        client, err := mongo.Connect(ctx, clientOptions)
1✔
520
        if err != nil {
1✔
521
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
522
        }
×
523

524
        // Validate connection
525
        if err = client.Ping(ctx, nil); err != nil {
1✔
526
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
527
        }
×
528

529
        return client, nil
1✔
530
}
531

532
func (db *DataStoreMongo) Ping(ctx context.Context) error {
2✔
533
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
2✔
534
        return res.Err()
2✔
535
}
2✔
536

537
func (db *DataStoreMongo) GetReleases(
538
        ctx context.Context,
539
        filt *model.ReleaseOrImageFilter,
540
) ([]model.Release, int, error) {
1✔
541
        versions, err := migrate.GetMigrationInfo(
1✔
542
                ctx, db.client, mstore.DbFromContext(ctx, DatabaseName))
1✔
543
        if err != nil {
1✔
NEW
544
                return nil, 0, errors.Wrap(err, "failed to list applied migrations")
×
NEW
545
        }
×
546
        var current migrate.Version
1✔
547
        if len(versions) > 0 {
1✔
NEW
548
                // sort applied migrations wrt. version
×
NEW
549
                sort.Slice(versions, func(i int, j int) bool {
×
NEW
550
                        return migrate.VersionIsLess(versions[i].Version, versions[j].Version)
×
NEW
551
                })
×
NEW
552
                current = versions[len(versions)-1].Version
×
553
        }
554
        latest, err := migrate.NewVersion(DbVersion)
1✔
555
        if err != nil {
1✔
NEW
556
                return nil, 0, errors.Wrap(err, "failed to get latest DB version")
×
NEW
557
        }
×
558
        if migrate.VersionIsLess(current, *latest) {
2✔
559
                return db.getReleases_1_2_14(ctx, filt)
1✔
560
        } else {
1✔
NEW
561
                return db.getReleases_1_2_15(ctx, filt)
×
NEW
562
        }
×
563
}
564

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

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

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

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

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

632
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
37✔
633
        if sortField == "" {
62✔
634
                sortField = "name"
25✔
635
        }
25✔
636
        if sortOrder == 0 {
62✔
637
                sortOrder = 1
25✔
638
        }
25✔
639

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

37✔
664
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
37✔
665
        collImg := database.Collection(CollectionImages)
37✔
666

37✔
667
        cursor, err := collImg.Aggregate(ctx, pipe)
37✔
668
        if err != nil {
37✔
669
                return nil, 0, err
×
670
        }
×
671
        defer cursor.Close(ctx)
37✔
672

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

687
func (db *DataStoreMongo) getReleases_1_2_15(
688
        ctx context.Context,
689
        filt *model.ReleaseOrImageFilter,
NEW
690
) ([]model.Release, int, error) {
×
NEW
691
        l := log.FromContext(ctx)
×
NEW
692
        l.Infof("get releases method version 1.2.15")
×
NEW
693

×
NEW
694
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
×
NEW
695
        if sortField == "" {
×
NEW
696
                sortField = "name"
×
NEW
697
        }
×
NEW
698
        if sortOrder == 0 {
×
NEW
699
                sortOrder = 1
×
NEW
700
        }
×
701

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

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

×
NEW
723
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
NEW
724
        collReleases := database.Collection(CollectionReleases)
×
NEW
725

×
NEW
726
        filter := bson.M{}
×
NEW
727
        if filt != nil {
×
NEW
728
                if filt.Name != "" {
×
NEW
729
                        filter[StorageKeyReleaseName] = bson.M{"$regex": filt.Name}
×
NEW
730
                }
×
NEW
731
                if filt.Description != "" {
×
NEW
732
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": filt.Description}
×
NEW
733
                }
×
NEW
734
                if filt.DeviceType != "" {
×
NEW
735
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = bson.M{"$regex": filt.DeviceType}
×
NEW
736
                }
×
737
        }
NEW
738
        releases := []model.Release{}
×
NEW
739
        cursor, err := collReleases.Find(ctx, filter, opts)
×
NEW
740
        if err != nil {
×
NEW
741
                return nil, 0, err
×
NEW
742
        }
×
NEW
743
        if err := cursor.All(ctx, &releases); err != nil {
×
NEW
744
                return nil, 0, err
×
NEW
745
        }
×
746

747
        // TODO: can we return number of all documents in the collection
748
        // using EstimatedDocumentCount?
NEW
749
        count, err := collReleases.CountDocuments(ctx, filter)
×
NEW
750
        if err != nil {
×
NEW
751
                return nil, 0, err
×
NEW
752
        }
×
753

NEW
754
        return releases, int(count), nil
×
755
}
756

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

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

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

772
        return limit, nil
4✔
773
}
774

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

9✔
777
        dbname := mstore.DbNameForTenant(tenantId, DbName)
9✔
778

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

782
//images
783

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

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

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

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

803
        return true, nil
×
804
}
805

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

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

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

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

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

830
        return true, nil
2✔
831
}
832

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

18✔
837
        if len(name) == 0 {
20✔
838
                return nil, ErrImagesStorageInvalidArtifactName
2✔
839
        }
2✔
840

841
        if len(deviceType) == 0 {
18✔
842
                return nil, ErrImagesStorageInvalidDeviceType
2✔
843
        }
2✔
844

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

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

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

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

869
        return &image, nil
6✔
870
}
871

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

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

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

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

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

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

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

906
        return &image, nil
1✔
907
}
908

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

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

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

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

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

935
        return images, nil
1✔
936
}
937

938
// Insert persists object
939
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
135✔
940

135✔
941
        if image == nil {
135✔
942
                return ErrImagesStorageInvalidImage
×
943
        }
×
944

945
        if err := image.Validate(); err != nil {
135✔
946
                return err
×
947
        }
×
948

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

135✔
952
        // add special representation of artifact provides
135✔
953
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
135✔
954

135✔
955
        _, err := collImg.InsertOne(ctx, image)
135✔
956
        if err != nil {
149✔
957
                if except, ok := err.(mongo.WriteException); ok {
28✔
958
                        var conflicts string
14✔
959
                        if len(except.WriteErrors) > 0 {
28✔
960
                                err := except.WriteErrors[0]
14✔
961
                                yamlStart := strings.IndexByte(err.Message, '{')
14✔
962
                                if yamlStart != -1 {
28✔
963
                                        conflicts = err.Message[yamlStart:]
14✔
964
                                }
14✔
965
                        }
966
                        conflictErr := model.NewConflictError(
14✔
967
                                ErrMsgConflictingDepends,
14✔
968
                                conflicts,
14✔
969
                        )
14✔
970
                        return conflictErr
14✔
971
                }
972
        }
973

974
        return nil
121✔
975
}
976

977
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
3✔
978
        collUploads := db.client.
3✔
979
                Database(DatabaseName).
3✔
980
                Collection(CollectionUploadIntents)
3✔
981
        if idty := identity.FromContext(ctx); idty != nil {
4✔
982
                link.TenantID = idty.Tenant
1✔
983
        }
1✔
984
        _, err := collUploads.InsertOne(ctx, link)
3✔
985
        return err
3✔
986
}
987

988
func (db *DataStoreMongo) UpdateUploadIntentStatus(
989
        ctx context.Context,
990
        id string,
991
        from, to model.LinkStatus,
992
) error {
11✔
993
        collUploads := db.client.
11✔
994
                Database(DatabaseName).
11✔
995
                Collection(CollectionUploadIntents)
11✔
996
        q := bson.D{
11✔
997
                {Key: "_id", Value: id},
11✔
998
                {Key: "status", Value: from},
11✔
999
        }
11✔
1000
        if idty := identity.FromContext(ctx); idty != nil {
20✔
1001
                q = append(q, bson.E{
9✔
1002
                        Key:   StorageKeyTenantId,
9✔
1003
                        Value: idty.Tenant,
9✔
1004
                })
9✔
1005
        }
9✔
1006
        update := bson.D{{
11✔
1007
                Key: "updated_ts", Value: time.Now(),
11✔
1008
        }}
11✔
1009
        if from != to {
22✔
1010
                update = append(update, bson.E{
11✔
1011
                        Key: "status", Value: to,
11✔
1012
                })
11✔
1013
        }
11✔
1014
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
11✔
1015
                {Key: "$set", Value: update},
11✔
1016
        })
11✔
1017
        if err != nil {
13✔
1018
                return err
2✔
1019
        } else if res.MatchedCount == 0 {
15✔
1020
                return store.ErrNotFound
4✔
1021
        }
4✔
1022
        return nil
5✔
1023
}
1024

1025
func (db *DataStoreMongo) FindUploadLinks(
1026
        ctx context.Context,
1027
        expiredAt time.Time,
1028
) (store.Iterator[model.UploadLink], error) {
2✔
1029
        collUploads := db.client.
2✔
1030
                Database(DatabaseName).
2✔
1031
                Collection(CollectionUploadIntents)
2✔
1032

2✔
1033
        q := bson.D{{
2✔
1034
                Key: "status",
2✔
1035
                Value: bson.D{{
2✔
1036
                        Key:   "$lt",
2✔
1037
                        Value: model.LinkStatusProcessedBit,
2✔
1038
                }},
2✔
1039
        }, {
2✔
1040
                Key: "expire",
2✔
1041
                Value: bson.D{{
2✔
1042
                        Key:   "$lt",
2✔
1043
                        Value: expiredAt,
2✔
1044
                }},
2✔
1045
        }}
2✔
1046
        cur, err := collUploads.Find(ctx, q)
2✔
1047
        return IteratorFromCursor[model.UploadLink](cur), err
2✔
1048
}
2✔
1049

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

1✔
1054
        if len(id) == 0 {
1✔
1055
                return nil, ErrImagesStorageInvalidID
×
1056
        }
×
1057

1058
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1059
        collImg := database.Collection(CollectionImages)
1✔
1060
        projection := bson.M{
1✔
1061
                StorageKeyImageDependsIdx:  0,
1✔
1062
                StorageKeyImageProvidesIdx: 0,
1✔
1063
        }
1✔
1064
        findOptions := mopts.FindOne()
1✔
1065
        findOptions.SetProjection(projection)
1✔
1066

1✔
1067
        var image model.Image
1✔
1068
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
1069
                Decode(&image); err != nil {
2✔
1070
                if err == mongo.ErrNoDocuments {
2✔
1071
                        return nil, nil
1✔
1072
                }
1✔
1073
                return nil, err
×
1074
        }
1075

1076
        return &image, nil
1✔
1077
}
1078

1079
// IsArtifactUnique checks if there is no artifact with the same artifactName
1080
// supporting one of the device types from deviceTypesCompatible list.
1081
// Returns true, nil if artifact is unique;
1082
// false, nil if artifact is not unique;
1083
// false, error in case of error.
1084
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1085
        artifactName string, deviceTypesCompatible []string) (bool, error) {
11✔
1086

11✔
1087
        if len(artifactName) == 0 {
13✔
1088
                return false, ErrImagesStorageInvalidArtifactName
2✔
1089
        }
2✔
1090

1091
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1092
        collImg := database.Collection(CollectionImages)
9✔
1093

9✔
1094
        query := bson.M{
9✔
1095
                "$and": []bson.M{
9✔
1096
                        {
9✔
1097
                                StorageKeyImageName: artifactName,
9✔
1098
                        },
9✔
1099
                        {
9✔
1100
                                StorageKeyImageDeviceTypes: bson.M{
9✔
1101
                                        "$in": deviceTypesCompatible},
9✔
1102
                        },
9✔
1103
                },
9✔
1104
        }
9✔
1105

9✔
1106
        // do part of the job manually
9✔
1107
        // if candidate images have any extra 'depends' - guaranteed non-overlap
9✔
1108
        // otherwise it's a match
9✔
1109
        cur, err := collImg.Find(ctx, query)
9✔
1110
        if err != nil {
9✔
1111
                return false, err
×
1112
        }
×
1113

1114
        var images []model.Image
9✔
1115
        err = cur.All(ctx, &images)
9✔
1116
        if err != nil {
9✔
1117
                return false, err
×
1118
        }
×
1119

1120
        for _, i := range images {
11✔
1121
                // the artifact already has same name and overlapping dev type
2✔
1122
                // if there are no more depends than dev type - it's not unique
2✔
1123
                if len(i.ArtifactMeta.Depends) == 1 {
4✔
1124
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
4✔
1125
                                return false, nil
2✔
1126
                        }
2✔
1127
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1128
                        return false, nil
×
1129
                }
×
1130
        }
1131

1132
        return true, nil
7✔
1133
}
1134

1135
// Delete image specified by ID
1136
// Noop on if not found.
1137
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
1✔
1138

1✔
1139
        if len(id) == 0 {
1✔
1140
                return ErrImagesStorageInvalidID
×
1141
        }
×
1142

1143
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1144
        collImg := database.Collection(CollectionImages)
1✔
1145

1✔
1146
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
1147
                if res.DeletedCount == 0 {
×
1148
                        return nil
×
1149
                }
×
1150
                return err
×
1151
        }
1152

1153
        return nil
1✔
1154
}
1155

1156
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
65✔
1157
        if filt != nil && filt.Sort != "" {
83✔
1158
                sortParts := strings.SplitN(filt.Sort, ":", 2)
18✔
1159
                if len(sortParts) == 2 && (sortParts[0] == "name" || sortParts[0] == "modified") {
36✔
1160
                        sortField := sortParts[0]
18✔
1161
                        sortOrder := 1
18✔
1162
                        if sortParts[1] == model.SortDirectionDescending {
30✔
1163
                                sortOrder = -1
12✔
1164
                        }
12✔
1165
                        return sortField, sortOrder
18✔
1166
                }
1167
        }
1168
        return "", 0
47✔
1169
}
1170

1171
// ListImages lists all images
1172
func (db *DataStoreMongo) ListImages(
1173
        ctx context.Context,
1174
        filt *model.ReleaseOrImageFilter,
1175
) ([]*model.Image, int, error) {
29✔
1176

29✔
1177
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1178
        collImg := database.Collection(CollectionImages)
29✔
1179

29✔
1180
        filters := bson.M{}
29✔
1181
        if filt != nil {
48✔
1182
                if filt.Name != "" {
27✔
1183
                        filters[StorageKeyImageName] = bson.M{
8✔
1184
                                "$regex": primitive.Regex{
8✔
1185
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
8✔
1186
                                        Options: "i",
8✔
1187
                                },
8✔
1188
                        }
8✔
1189
                }
8✔
1190
                if filt.Description != "" {
23✔
1191
                        filters[StorageKeyImageDescription] = bson.M{
4✔
1192
                                "$regex": primitive.Regex{
4✔
1193
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
1194
                                        Options: "i",
4✔
1195
                                },
4✔
1196
                        }
4✔
1197
                }
4✔
1198
                if filt.DeviceType != "" {
21✔
1199
                        filters[StorageKeyImageDeviceTypes] = bson.M{
2✔
1200
                                "$regex": primitive.Regex{
2✔
1201
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
2✔
1202
                                        Options: "i",
2✔
1203
                                },
2✔
1204
                        }
2✔
1205
                }
2✔
1206

1207
        }
1208

1209
        projection := bson.M{
29✔
1210
                StorageKeyImageDependsIdx:  0,
29✔
1211
                StorageKeyImageProvidesIdx: 0,
29✔
1212
        }
29✔
1213
        findOptions := &mopts.FindOptions{}
29✔
1214
        findOptions.SetProjection(projection)
29✔
1215
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
31✔
1216
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
2✔
1217
                findOptions.SetLimit(int64(filt.PerPage))
2✔
1218
        }
2✔
1219

1220
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
29✔
1221
        if sortField == "" || sortField == "name" {
54✔
1222
                sortField = StorageKeyImageName
25✔
1223
        }
25✔
1224
        if sortOrder == 0 {
52✔
1225
                sortOrder = 1
23✔
1226
        }
23✔
1227
        findOptions.SetSort(bson.D{
29✔
1228
                {Key: sortField, Value: sortOrder},
29✔
1229
                {Key: "_id", Value: sortOrder},
29✔
1230
        })
29✔
1231

29✔
1232
        cursor, err := collImg.Find(ctx, filters, findOptions)
29✔
1233
        if err != nil {
29✔
1234
                return nil, 0, err
×
1235
        }
×
1236

1237
        // NOTE: cursor.All closes the cursor before returning
1238
        var images []*model.Image
29✔
1239
        if err := cursor.All(ctx, &images); err != nil {
29✔
1240
                if err == mongo.ErrNoDocuments {
×
1241
                        return nil, 0, nil
×
1242
                }
×
1243
                return nil, 0, err
×
1244
        }
1245

1246
        count, err := collImg.CountDocuments(ctx, filters)
29✔
1247
        if err != nil {
29✔
1248
                return nil, -1, ErrDevicesCountFailed
×
1249
        }
×
1250

1251
        return images, int(count), nil
29✔
1252
}
1253

1254
// device deployment log
1255
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1256
        log model.DeploymentLog) error {
17✔
1257

17✔
1258
        if err := log.Validate(); err != nil {
23✔
1259
                return err
6✔
1260
        }
6✔
1261

1262
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1263
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1264

11✔
1265
        query := bson.D{
11✔
1266
                {Key: StorageKeyDeviceDeploymentDeviceId,
11✔
1267
                        Value: log.DeviceID},
11✔
1268
                {Key: StorageKeyDeviceDeploymentDeploymentID,
11✔
1269
                        Value: log.DeploymentID},
11✔
1270
        }
11✔
1271

11✔
1272
        // update log messages
11✔
1273
        // if the deployment log is already present than messages will be overwritten
11✔
1274
        update := bson.D{
11✔
1275
                {Key: "$set", Value: bson.M{
11✔
1276
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
11✔
1277
                }},
11✔
1278
        }
11✔
1279
        updateOptions := mopts.Update()
11✔
1280
        updateOptions.SetUpsert(true)
11✔
1281
        if _, err := collLogs.UpdateOne(
11✔
1282
                ctx, query, update, updateOptions); err != nil {
11✔
1283
                return err
×
1284
        }
×
1285

1286
        return nil
11✔
1287
}
1288

1289
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1290
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
11✔
1291

11✔
1292
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1293
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
11✔
1294

11✔
1295
        query := bson.M{
11✔
1296
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
11✔
1297
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1298
        }
11✔
1299

11✔
1300
        var depl model.DeploymentLog
11✔
1301
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
15✔
1302
                if err == mongo.ErrNoDocuments {
8✔
1303
                        return nil, nil
4✔
1304
                }
4✔
1305
                return nil, err
×
1306
        }
1307

1308
        return &depl, nil
7✔
1309
}
1310

1311
// device deployments
1312

1313
// Insert persists device deployment object
1314
func (db *DataStoreMongo) InsertDeviceDeployment(
1315
        ctx context.Context,
1316
        deviceDeployment *model.DeviceDeployment,
1317
        incrementDeviceCount bool,
1318
) error {
53✔
1319
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
53✔
1320
        c := database.Collection(CollectionDevices)
53✔
1321

53✔
1322
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
53✔
1323
                return err
×
1324
        }
×
1325

1326
        if incrementDeviceCount {
106✔
1327
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
53✔
1328
                if err != nil {
53✔
1329
                        return err
×
1330
                }
×
1331
        }
1332

1333
        return nil
53✔
1334
}
1335

1336
// InsertMany stores multiple device deployment objects.
1337
// TODO: Handle error cleanup, multi insert is not atomic, loop into two-phase commits
1338
func (db *DataStoreMongo) InsertMany(ctx context.Context,
1339
        deployments ...*model.DeviceDeployment) error {
78✔
1340

78✔
1341
        if len(deployments) == 0 {
102✔
1342
                return nil
24✔
1343
        }
24✔
1344

1345
        deviceCountIncrements := make(map[string]int)
54✔
1346

54✔
1347
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
54✔
1348
        var list []interface{}
54✔
1349
        for _, deployment := range deployments {
186✔
1350

132✔
1351
                if deployment == nil {
134✔
1352
                        return ErrStorageInvalidDeviceDeployment
2✔
1353
                }
2✔
1354

1355
                if err := deployment.Validate(); err != nil {
134✔
1356
                        return errors.Wrap(err, "Validating device deployment")
4✔
1357
                }
4✔
1358

1359
                list = append(list, deployment)
126✔
1360
                deviceCountIncrements[deployment.DeploymentId]++
126✔
1361
        }
1362

1363
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
48✔
1364
        collDevs := database.Collection(CollectionDevices)
48✔
1365

48✔
1366
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
48✔
1367
                return err
×
1368
        }
×
1369

1370
        for deploymentID := range deviceCountIncrements {
104✔
1371
                err := db.IncrementDeploymentDeviceCount(
56✔
1372
                        ctx,
56✔
1373
                        deploymentID,
56✔
1374
                        deviceCountIncrements[deploymentID],
56✔
1375
                )
56✔
1376
                if err != nil {
56✔
1377
                        return err
×
1378
                }
×
1379
        }
1380

1381
        return nil
48✔
1382
}
1383

1384
// ExistAssignedImageWithIDAndStatuses checks if image is used by deployment with specified status.
1385
func (db *DataStoreMongo) ExistAssignedImageWithIDAndStatuses(ctx context.Context,
1386
        imageID string, statuses ...model.DeviceDeploymentStatus) (bool, error) {
1✔
1387

1✔
1388
        // Verify ID formatting
1✔
1389
        if len(imageID) == 0 {
1✔
1390
                return false, ErrStorageInvalidID
×
1391
        }
×
1392

1393
        query := bson.M{StorageKeyDeviceDeploymentAssignedImageId: imageID}
1✔
1394

1✔
1395
        if len(statuses) > 0 {
2✔
1396
                query[StorageKeyDeviceDeploymentStatus] = bson.M{
1✔
1397
                        "$in": statuses,
1✔
1398
                }
1✔
1399
        }
1✔
1400

1401
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1402
        collDevs := database.Collection(CollectionDevices)
1✔
1403

1✔
1404
        // if found at least one then image in active deployment
1✔
1405
        var tmp interface{}
1✔
1406
        if err := collDevs.FindOne(ctx, query).Decode(&tmp); err != nil {
2✔
1407
                if err == mongo.ErrNoDocuments {
2✔
1408
                        return false, nil
1✔
1409
                }
1✔
1410
                return false, err
×
1411
        }
1412

1413
        return true, nil
×
1414
}
1415

1416
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1417
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1418
        ctx context.Context,
1419
        deviceID string,
1420
) (*model.DeviceDeployment, error) {
11✔
1421

11✔
1422
        // Verify ID formatting
11✔
1423
        if len(deviceID) == 0 {
13✔
1424
                return nil, ErrStorageInvalidID
2✔
1425
        }
2✔
1426

1427
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1428
        collDevs := database.Collection(CollectionDevices)
9✔
1429

9✔
1430
        // Device should know only about deployments that are not finished
9✔
1431
        query := bson.D{
9✔
1432
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
9✔
1433
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1434
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1435
                        {Key: "$exists", Value: false},
9✔
1436
                }},
9✔
1437
        }
9✔
1438

9✔
1439
        // Find the oldest one by sorting the creation timestamp
9✔
1440
        // in ascending order.
9✔
1441
        findOptions := mopts.FindOne()
9✔
1442
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
9✔
1443

9✔
1444
        // Select only the oldest one that have not been finished yet.
9✔
1445
        deployment := new(model.DeviceDeployment)
9✔
1446
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1447
                Decode(deployment); err != nil {
14✔
1448
                if err == mongo.ErrNoDocuments {
8✔
1449
                        return nil, nil
3✔
1450
                }
3✔
1451
                return nil, err
2✔
1452
        }
1453

1454
        return deployment, nil
5✔
1455
}
1456

1457
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1458
// matching device id that has not finished yet.
1459
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1460
        ctx context.Context,
1461
        deviceID string,
1462
) (*model.DeviceDeployment, error) {
11✔
1463

11✔
1464
        // Verify ID formatting
11✔
1465
        if len(deviceID) == 0 {
13✔
1466
                return nil, ErrStorageInvalidID
2✔
1467
        }
2✔
1468

1469
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1470
        collDevs := database.Collection(CollectionDevices)
9✔
1471

9✔
1472
        query := bson.D{
9✔
1473
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
9✔
1474
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1475
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1476
                        {Key: "$exists", Value: false},
9✔
1477
                }},
9✔
1478
        }
9✔
1479

9✔
1480
        // Find the latest one by sorting by the creation timestamp
9✔
1481
        // in ascending order.
9✔
1482
        findOptions := mopts.FindOne()
9✔
1483
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
9✔
1484

9✔
1485
        // Select only the latest one that have not been finished yet.
9✔
1486
        var deployment *model.DeviceDeployment
9✔
1487
        if err := collDevs.FindOne(ctx, query, findOptions).
9✔
1488
                Decode(&deployment); err != nil {
14✔
1489
                if err == mongo.ErrNoDocuments {
8✔
1490
                        return nil, nil
3✔
1491
                }
3✔
1492
                return nil, err
2✔
1493
        }
1494

1495
        return deployment, nil
5✔
1496
}
1497

1498
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1499
        ctx context.Context,
1500
        deviceID string,
1501
        deploymentID string,
1502
        ddState model.DeviceDeploymentState,
1503
) (model.DeviceDeploymentStatus, error) {
19✔
1504

19✔
1505
        // Verify ID formatting
19✔
1506
        if len(deviceID) == 0 ||
19✔
1507
                len(deploymentID) == 0 {
23✔
1508
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
4✔
1509
        }
4✔
1510

1511
        if err := ddState.Validate(); err != nil {
17✔
1512
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
2✔
1513
        }
2✔
1514

1515
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1516
        collDevs := database.Collection(CollectionDevices)
13✔
1517

13✔
1518
        // Device should know only about deployments that are not finished
13✔
1519
        query := bson.D{
13✔
1520
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1521
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1522
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1523
                        {Key: "$exists", Value: false},
13✔
1524
                }},
13✔
1525
        }
13✔
1526

13✔
1527
        // update status field
13✔
1528
        set := bson.M{
13✔
1529
                StorageKeyDeviceDeploymentStatus: ddState.Status,
13✔
1530
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
13✔
1531
        }
13✔
1532
        // and finish time if provided
13✔
1533
        if ddState.FinishTime != nil {
16✔
1534
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
3✔
1535
        }
3✔
1536

1537
        if len(ddState.SubState) > 0 {
15✔
1538
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
2✔
1539
        }
2✔
1540

1541
        update := bson.D{
13✔
1542
                {Key: "$set", Value: set},
13✔
1543
        }
13✔
1544

13✔
1545
        var old model.DeviceDeployment
13✔
1546

13✔
1547
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
13✔
1548
                Decode(&old); err != nil {
17✔
1549
                if err == mongo.ErrNoDocuments {
8✔
1550
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
4✔
1551
                }
4✔
1552
                return model.DeviceDeploymentStatusNull, err
×
1553

1554
        }
1555

1556
        return old.Status, nil
9✔
1557
}
1558

1559
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1560
        deviceID string, deploymentID string, log bool) error {
13✔
1561

13✔
1562
        // Verify ID formatting
13✔
1563
        if len(deviceID) == 0 ||
13✔
1564
                len(deploymentID) == 0 {
17✔
1565
                return ErrStorageInvalidID
4✔
1566
        }
4✔
1567

1568
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1569
        collDevs := database.Collection(CollectionDevices)
9✔
1570

9✔
1571
        selector := bson.D{
9✔
1572
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1573
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1574
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1575
                        {Key: "$exists", Value: false},
9✔
1576
                }},
9✔
1577
        }
9✔
1578

9✔
1579
        update := bson.D{
9✔
1580
                {Key: "$set", Value: bson.M{
9✔
1581
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
9✔
1582
        }
9✔
1583

9✔
1584
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
9✔
1585
                return err
×
1586
        } else if res.MatchedCount == 0 {
13✔
1587
                return ErrStorageNotFound
4✔
1588
        }
4✔
1589

1590
        return nil
5✔
1591
}
1592

1593
// SaveDeviceDeploymentRequest saves device deployment request
1594
// with the device deployment object
1595
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1596
        ctx context.Context,
1597
        ID string,
1598
        request *model.DeploymentNextRequest,
1599
) error {
7✔
1600

7✔
1601
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1602
        collDevs := database.Collection(CollectionDevices)
7✔
1603

7✔
1604
        res, err := collDevs.UpdateOne(
7✔
1605
                ctx,
7✔
1606
                bson.D{{Key: StorageKeyId, Value: ID}},
7✔
1607
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
7✔
1608
        )
7✔
1609
        if err != nil {
7✔
1610
                return err
×
1611
        } else if res.MatchedCount == 0 {
9✔
1612
                return ErrStorageNotFound
2✔
1613
        }
2✔
1614
        return nil
5✔
1615
}
1616

1617
// AssignArtifact assigns artifact to the device deployment
1618
func (db *DataStoreMongo) AssignArtifact(
1619
        ctx context.Context,
1620
        deviceID string,
1621
        deploymentID string,
1622
        artifact *model.Image,
1623
) error {
1✔
1624

1✔
1625
        // Verify ID formatting
1✔
1626
        if len(deviceID) == 0 ||
1✔
1627
                len(deploymentID) == 0 {
1✔
1628
                return ErrStorageInvalidID
×
1629
        }
×
1630

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

1✔
1634
        selector := bson.D{
1✔
1635
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1636
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1637
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1638
                        {Key: "$exists", Value: false},
1✔
1639
                }},
1✔
1640
        }
1✔
1641

1✔
1642
        update := bson.D{
1✔
1643
                {Key: "$set", Value: bson.M{
1✔
1644
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1645
                }},
1✔
1646
        }
1✔
1647

1✔
1648
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1649
                return err
×
1650
        } else if res.MatchedCount == 0 {
1✔
1651
                return ErrStorageNotFound
×
1652
        }
×
1653

1654
        return nil
1✔
1655
}
1656

1657
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1658
        id string) (model.Stats, error) {
11✔
1659

11✔
1660
        if len(id) == 0 {
11✔
1661
                return nil, ErrStorageInvalidID
×
1662
        }
×
1663

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

11✔
1667
        match := bson.D{
11✔
1668
                {Key: "$match", Value: bson.M{
11✔
1669
                        StorageKeyDeviceDeploymentDeploymentID: id,
11✔
1670
                        StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1671
                                {Key: "$exists", Value: false},
11✔
1672
                        },
11✔
1673
                }},
11✔
1674
        }
11✔
1675
        group := bson.D{
11✔
1676
                {Key: "$group", Value: bson.D{
11✔
1677
                        {Key: "_id",
11✔
1678
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
11✔
1679
                        {Key: "count",
11✔
1680
                                Value: bson.M{"$sum": 1}}},
11✔
1681
                },
11✔
1682
        }
11✔
1683
        pipeline := []bson.D{
11✔
1684
                match,
11✔
1685
                group,
11✔
1686
        }
11✔
1687
        var results []struct {
11✔
1688
                Status model.DeviceDeploymentStatus `bson:"_id"`
11✔
1689
                Count  int
11✔
1690
        }
11✔
1691
        cursor, err := collDevs.Aggregate(ctx, pipeline)
11✔
1692
        if err != nil {
11✔
1693
                return nil, err
×
1694
        }
×
1695
        if err := cursor.All(ctx, &results); err != nil {
11✔
1696
                if err == mongo.ErrNoDocuments {
×
1697
                        return nil, nil
×
1698
                }
×
1699
                return nil, err
×
1700
        }
1701

1702
        raw := model.NewDeviceDeploymentStats()
11✔
1703
        for _, res := range results {
32✔
1704
                raw.Set(res.Status, res.Count)
21✔
1705
        }
21✔
1706
        return raw, nil
11✔
1707
}
1708

1709
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1710
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1711
        deploymentID string) ([]model.DeviceDeployment, error) {
11✔
1712

11✔
1713
        statuses := []model.DeviceDeployment{}
11✔
1714
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
1715
        collDevs := database.Collection(CollectionDevices)
11✔
1716

11✔
1717
        query := bson.M{
11✔
1718
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
11✔
1719
                StorageKeyDeviceDeploymentDeleted: bson.D{
11✔
1720
                        {Key: "$exists", Value: false},
11✔
1721
                },
11✔
1722
        }
11✔
1723

11✔
1724
        cursor, err := collDevs.Find(ctx, query)
11✔
1725
        if err != nil {
11✔
1726
                return nil, err
×
1727
        }
×
1728

1729
        if err = cursor.All(ctx, &statuses); err != nil {
11✔
1730
                if err == mongo.ErrNoDocuments {
×
1731
                        return nil, nil
×
1732
                }
×
1733
                return nil, err
×
1734
        }
1735

1736
        return statuses, nil
11✔
1737
}
1738

1739
func (db *DataStoreMongo) GetDevicesListForDeployment(ctx context.Context,
1740
        q store.ListQuery) ([]model.DeviceDeployment, int, error) {
29✔
1741

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

29✔
1746
        query := bson.D{
29✔
1747
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
29✔
1748
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
29✔
1749
                        {Key: "$exists", Value: false},
29✔
1750
                }},
29✔
1751
        }
29✔
1752
        if q.Status != nil {
37✔
1753
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
10✔
1754
                        query = append(query, bson.E{
2✔
1755
                                Key: "status", Value: bson.D{{
2✔
1756
                                        Key:   "$gte",
2✔
1757
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1758
                                }, {
2✔
1759
                                        Key:   "$lte",
2✔
1760
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1761
                                }},
2✔
1762
                        })
2✔
1763
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
8✔
1764
                        query = append(query, bson.E{
×
1765
                                Key: "status", Value: bson.D{{
×
1766
                                        Key:   "$gte",
×
1767
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1768
                                }, {
×
1769
                                        Key:   "$lte",
×
1770
                                        Value: model.DeviceDeploymentStatusPending,
×
1771
                                }},
×
1772
                        })
×
1773
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
8✔
1774
                        query = append(query, bson.E{
2✔
1775
                                Key: "status", Value: bson.D{{
2✔
1776
                                        Key: "$in",
2✔
1777
                                        Value: []model.DeviceDeploymentStatus{
2✔
1778
                                                model.DeviceDeploymentStatusFailure,
2✔
1779
                                                model.DeviceDeploymentStatusAborted,
2✔
1780
                                                model.DeviceDeploymentStatusSuccess,
2✔
1781
                                                model.DeviceDeploymentStatusNoArtifact,
2✔
1782
                                                model.DeviceDeploymentStatusAlreadyInst,
2✔
1783
                                                model.DeviceDeploymentStatusDecommissioned,
2✔
1784
                                        },
2✔
1785
                                }},
2✔
1786
                        })
2✔
1787
                } else {
6✔
1788
                        var status model.DeviceDeploymentStatus
4✔
1789
                        err := status.UnmarshalText([]byte(*q.Status))
4✔
1790
                        if err != nil {
6✔
1791
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1792
                        }
2✔
1793
                        query = append(query, bson.E{
2✔
1794
                                Key: "status", Value: status,
2✔
1795
                        })
2✔
1796
                }
1797
        }
1798

1799
        options := mopts.Find()
27✔
1800
        sortFieldQuery := bson.D{
27✔
1801
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
27✔
1802
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
27✔
1803
        }
27✔
1804
        options.SetSort(sortFieldQuery)
27✔
1805
        if q.Skip > 0 {
32✔
1806
                options.SetSkip(int64(q.Skip))
5✔
1807
        }
5✔
1808
        if q.Limit > 0 {
36✔
1809
                options.SetLimit(int64(q.Limit))
9✔
1810
        } else {
27✔
1811
                options.SetLimit(DefaultDocumentLimit)
18✔
1812
        }
18✔
1813

1814
        cursor, err := collDevs.Find(ctx, query, options)
27✔
1815
        if err != nil {
29✔
1816
                return nil, -1, err
2✔
1817
        }
2✔
1818

1819
        if err = cursor.All(ctx, &statuses); err != nil {
25✔
1820
                if err == mongo.ErrNoDocuments {
×
1821
                        return nil, -1, nil
×
1822
                }
×
1823
                return nil, -1, err
×
1824
        }
1825

1826
        count, err := collDevs.CountDocuments(ctx, query)
25✔
1827
        if err != nil {
25✔
1828
                return nil, -1, ErrDevicesCountFailed
×
1829
        }
×
1830

1831
        return statuses, int(count), nil
25✔
1832
}
1833

1834
func (db *DataStoreMongo) GetDeviceDeploymentsForDevice(ctx context.Context,
1835
        q store.ListQueryDeviceDeployments) ([]model.DeviceDeployment, int, error) {
20✔
1836

20✔
1837
        statuses := []model.DeviceDeployment{}
20✔
1838
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
20✔
1839
        collDevs := database.Collection(CollectionDevices)
20✔
1840

20✔
1841
        query := bson.D{}
20✔
1842
        if q.DeviceID != "" {
38✔
1843
                query = append(query, bson.E{
18✔
1844
                        Key:   StorageKeyDeviceDeploymentDeviceId,
18✔
1845
                        Value: q.DeviceID,
18✔
1846
                })
18✔
1847
        } else if len(q.IDs) > 0 {
22✔
1848
                query = append(query, bson.E{
2✔
1849
                        Key: StorageKeyId,
2✔
1850
                        Value: bson.D{{
2✔
1851
                                Key:   "$in",
2✔
1852
                                Value: q.IDs,
2✔
1853
                        }},
2✔
1854
                })
2✔
1855
        }
2✔
1856

1857
        if q.Status != nil {
36✔
1858
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
18✔
1859
                        query = append(query, bson.E{
2✔
1860
                                Key: "status", Value: bson.D{{
2✔
1861
                                        Key:   "$gte",
2✔
1862
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1863
                                }, {
2✔
1864
                                        Key:   "$lte",
2✔
1865
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
2✔
1866
                                }},
2✔
1867
                        })
2✔
1868
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
18✔
1869
                        query = append(query, bson.E{
2✔
1870
                                Key: "status", Value: bson.D{{
2✔
1871
                                        Key:   "$gte",
2✔
1872
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
2✔
1873
                                }, {
2✔
1874
                                        Key:   "$lte",
2✔
1875
                                        Value: model.DeviceDeploymentStatusPending,
2✔
1876
                                }},
2✔
1877
                        })
2✔
1878
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
16✔
1879
                        query = append(query, bson.E{
2✔
1880
                                Key: "status", Value: bson.D{{
2✔
1881
                                        Key: "$in",
2✔
1882
                                        Value: []model.DeviceDeploymentStatus{
2✔
1883
                                                model.DeviceDeploymentStatusFailure,
2✔
1884
                                                model.DeviceDeploymentStatusAborted,
2✔
1885
                                                model.DeviceDeploymentStatusSuccess,
2✔
1886
                                                model.DeviceDeploymentStatusNoArtifact,
2✔
1887
                                                model.DeviceDeploymentStatusAlreadyInst,
2✔
1888
                                                model.DeviceDeploymentStatusDecommissioned,
2✔
1889
                                        },
2✔
1890
                                }},
2✔
1891
                        })
2✔
1892
                } else {
12✔
1893
                        var status model.DeviceDeploymentStatus
10✔
1894
                        err := status.UnmarshalText([]byte(*q.Status))
10✔
1895
                        if err != nil {
12✔
1896
                                return nil, -1, errors.Wrap(err, "invalid status query")
2✔
1897
                        }
2✔
1898
                        query = append(query, bson.E{
8✔
1899
                                Key: "status", Value: status,
8✔
1900
                        })
8✔
1901
                }
1902
        }
1903

1904
        options := mopts.Find()
18✔
1905
        sortFieldQuery := bson.D{
18✔
1906
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
18✔
1907
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
18✔
1908
        }
18✔
1909
        options.SetSort(sortFieldQuery)
18✔
1910
        if q.Skip > 0 {
20✔
1911
                options.SetSkip(int64(q.Skip))
2✔
1912
        }
2✔
1913
        if q.Limit > 0 {
36✔
1914
                options.SetLimit(int64(q.Limit))
18✔
1915
        } else {
18✔
1916
                options.SetLimit(DefaultDocumentLimit)
×
1917
        }
×
1918

1919
        cursor, err := collDevs.Find(ctx, query, options)
18✔
1920
        if err != nil {
18✔
1921
                return nil, -1, err
×
1922
        }
×
1923

1924
        if err = cursor.All(ctx, &statuses); err != nil {
18✔
1925
                if err == mongo.ErrNoDocuments {
×
1926
                        return nil, 0, nil
×
1927
                }
×
1928
                return nil, -1, err
×
1929
        }
1930

1931
        maxCount := maxCountDocuments
18✔
1932
        countOptions := &mopts.CountOptions{
18✔
1933
                Limit: &maxCount,
18✔
1934
        }
18✔
1935
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
18✔
1936
        if err != nil {
18✔
1937
                return nil, -1, ErrDevicesCountFailed
×
1938
        }
×
1939

1940
        return statuses, int(count), nil
18✔
1941
}
1942

1943
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1944
// `deviceID`, false otherwise. In case of errors returns false and an error
1945
// that occurred
1946
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1947
        deploymentID string, deviceID string) (bool, error) {
13✔
1948

13✔
1949
        var dep model.DeviceDeployment
13✔
1950
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
1951
        collDevs := database.Collection(CollectionDevices)
13✔
1952

13✔
1953
        query := bson.D{
13✔
1954
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
13✔
1955
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
13✔
1956
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
13✔
1957
                        {Key: "$exists", Value: false},
13✔
1958
                }},
13✔
1959
        }
13✔
1960

13✔
1961
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
19✔
1962
                if err == mongo.ErrNoDocuments {
12✔
1963
                        return false, nil
6✔
1964
                } else {
6✔
1965
                        return false, err
×
1966
                }
×
1967
        }
1968

1969
        return true, nil
7✔
1970
}
1971

1972
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
1973
        deploymentId string) error {
5✔
1974

5✔
1975
        if len(deploymentId) == 0 {
7✔
1976
                return ErrStorageInvalidID
2✔
1977
        }
2✔
1978

1979
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1980
        collDevs := database.Collection(CollectionDevices)
3✔
1981
        selector := bson.M{
3✔
1982
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
3✔
1983
                StorageKeyDeviceDeploymentActive:       true,
3✔
1984
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
1985
                        {Key: "$exists", Value: false},
3✔
1986
                },
3✔
1987
        }
3✔
1988

3✔
1989
        update := bson.M{
3✔
1990
                "$set": bson.M{
3✔
1991
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
3✔
1992
                        StorageKeyDeviceDeploymentActive: false,
3✔
1993
                },
3✔
1994
        }
3✔
1995

3✔
1996
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
3✔
1997
                return err
×
1998
        }
×
1999

2000
        return nil
3✔
2001
}
2002

2003
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2004
        deviceID string) error {
4✔
2005
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2006
        collDevs := database.Collection(CollectionDevices)
4✔
2007
        selector := bson.M{
4✔
2008
                StorageKeyDeviceDeploymentDeviceId: deviceID,
4✔
2009
                StorageKeyDeviceDeploymentActive:   false,
4✔
2010
                StorageKeyDeviceDeploymentDeleted: bson.M{
4✔
2011
                        "$exists": false,
4✔
2012
                },
4✔
2013
        }
4✔
2014

4✔
2015
        now := time.Now()
4✔
2016
        update := bson.M{
4✔
2017
                "$set": bson.M{
4✔
2018
                        StorageKeyDeviceDeploymentDeleted: &now,
4✔
2019
                },
4✔
2020
        }
4✔
2021

4✔
2022
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
4✔
2023
                return err
×
2024
        }
×
2025

2026
        database = db.client.Database(DatabaseName)
4✔
2027
        collDevs = database.Collection(CollectionDevicesLastStatus)
4✔
2028
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
4✔
2029

4✔
2030
        return err
4✔
2031
}
2032

2033
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2034
        deviceId string) error {
4✔
2035

4✔
2036
        if len(deviceId) == 0 {
6✔
2037
                return ErrStorageInvalidID
2✔
2038
        }
2✔
2039

2040
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2041
        collDevs := database.Collection(CollectionDevices)
2✔
2042
        selector := bson.M{
2✔
2043
                StorageKeyDeviceDeploymentDeviceId: deviceId,
2✔
2044
                StorageKeyDeviceDeploymentActive:   true,
2✔
2045
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2046
                        {Key: "$exists", Value: false},
2✔
2047
                },
2✔
2048
        }
2✔
2049

2✔
2050
        update := bson.M{
2✔
2051
                "$set": bson.M{
2✔
2052
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
2✔
2053
                        StorageKeyDeviceDeploymentActive: false,
2✔
2054
                },
2✔
2055
        }
2✔
2056

2✔
2057
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2058
                return err
×
2059
        }
×
2060

2061
        return nil
2✔
2062
}
2063

2064
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2065
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2066

1✔
2067
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2068
        collDevs := database.Collection(CollectionDevices)
1✔
2069

1✔
2070
        filter := bson.M{
1✔
2071
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2072
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2073
        }
1✔
2074
        if !includeDeleted {
2✔
2075
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2076
                        {Key: "$exists", Value: false},
1✔
2077
                }
1✔
2078
        }
1✔
2079

2080
        opts := &mopts.FindOneOptions{}
1✔
2081
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2082

1✔
2083
        var dd model.DeviceDeployment
1✔
2084
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2085
                if err == mongo.ErrNoDocuments {
2✔
2086
                        return nil, ErrStorageNotFound
1✔
2087
                }
1✔
2088
                return nil, err
×
2089
        }
2090

2091
        return &dd, nil
1✔
2092
}
2093

2094
func (db *DataStoreMongo) GetDeviceDeployments(
2095
        ctx context.Context,
2096
        skip int,
2097
        limit int,
2098
        deviceID string,
2099
        active *bool,
2100
        includeDeleted bool,
2101
) ([]model.DeviceDeployment, error) {
8✔
2102

8✔
2103
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
2104
        collDevs := database.Collection(CollectionDevices)
8✔
2105

8✔
2106
        filter := bson.M{}
8✔
2107
        if !includeDeleted {
12✔
2108
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
4✔
2109
                        {Key: "$exists", Value: false},
4✔
2110
                }
4✔
2111
        }
4✔
2112
        if deviceID != "" {
10✔
2113
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
2✔
2114
        }
2✔
2115
        if active != nil {
10✔
2116
                filter[StorageKeyDeviceDeploymentActive] = *active
2✔
2117
        }
2✔
2118

2119
        opts := &mopts.FindOptions{}
8✔
2120
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
8✔
2121
        if skip > 0 {
10✔
2122
                opts.SetSkip(int64(skip))
2✔
2123
        }
2✔
2124
        if limit > 0 {
10✔
2125
                opts.SetLimit(int64(limit))
2✔
2126
        }
2✔
2127

2128
        var deviceDeployments []model.DeviceDeployment
8✔
2129
        cursor, err := collDevs.Find(ctx, filter, opts)
8✔
2130
        if err != nil {
8✔
2131
                return nil, err
×
2132
        }
×
2133
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
8✔
2134
                return nil, err
×
2135
        }
×
2136

2137
        return deviceDeployments, nil
8✔
2138
}
2139

2140
// deployments
2141

2142
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2143
        indexes ...mongo.IndexModel) error {
699✔
2144
        ctx := context.Background()
699✔
2145
        dataBase := db.client.Database(dbName)
699✔
2146

699✔
2147
        coll := dataBase.Collection(collName)
699✔
2148
        idxView := coll.Indexes()
699✔
2149
        _, err := idxView.CreateMany(ctx, indexes)
699✔
2150
        return err
699✔
2151
}
699✔
2152

2153
// return true if required indexing was set up
2154
func (db *DataStoreMongo) hasIndexing(ctx context.Context, client *mongo.Client) bool {
32✔
2155

32✔
2156
        var idx bson.M
32✔
2157
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
32✔
2158
        collDpl := database.Collection(CollectionDeployments)
32✔
2159
        idxView := collDpl.Indexes()
32✔
2160

32✔
2161
        cursor, err := idxView.List(ctx)
32✔
2162
        if err != nil {
32✔
2163
                // check failed, assume indexing is not there
×
2164
                return false
×
2165
        }
×
2166

2167
        has := map[string]bool{}
32✔
2168
        for cursor.Next(ctx) {
92✔
2169
                if err = cursor.Decode(&idx); err != nil {
60✔
2170
                        continue
×
2171
                }
2172
                if _, ok := idx["weights"]; ok {
90✔
2173
                        // text index
30✔
2174
                        for k := range idx["weights"].(bson.M) {
90✔
2175
                                has[k] = true
60✔
2176
                        }
60✔
2177
                } else {
30✔
2178
                        for i := range idx["key"].(bson.M) {
60✔
2179
                                has[i] = true
30✔
2180
                        }
30✔
2181

2182
                }
2183
        }
2184
        if err != nil {
32✔
2185
                return false
×
2186
        }
×
2187

2188
        for _, key := range StorageIndexes.Keys.(bson.D) {
94✔
2189
                _, ok := has[key.Key]
62✔
2190
                if !ok {
64✔
2191
                        return false
2✔
2192
                }
2✔
2193
        }
2194

2195
        return true
30✔
2196
}
2197

2198
// Insert persists object
2199
func (db *DataStoreMongo) InsertDeployment(
2200
        ctx context.Context,
2201
        deployment *model.Deployment,
2202
) error {
419✔
2203

419✔
2204
        if deployment == nil {
421✔
2205
                return ErrDeploymentStorageInvalidDeployment
2✔
2206
        }
2✔
2207

2208
        if err := deployment.Validate(); err != nil {
420✔
2209
                return err
3✔
2210
        }
3✔
2211

2212
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
415✔
2213
        collDpl := database.Collection(CollectionDeployments)
415✔
2214

415✔
2215
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
416✔
2216
                return err
1✔
2217
        }
1✔
2218
        return nil
415✔
2219
}
2220

2221
// Delete removed entry by ID
2222
// Noop on ID not found
2223
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
8✔
2224

8✔
2225
        if len(id) == 0 {
10✔
2226
                return ErrStorageInvalidID
2✔
2227
        }
2✔
2228

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

6✔
2232
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
6✔
2233
                return err
×
2234
        }
×
2235

2236
        return nil
6✔
2237
}
2238

2239
func (db *DataStoreMongo) FindDeploymentByID(
2240
        ctx context.Context,
2241
        id string,
2242
) (*model.Deployment, error) {
19✔
2243

19✔
2244
        if len(id) == 0 {
21✔
2245
                return nil, ErrStorageInvalidID
2✔
2246
        }
2✔
2247

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

17✔
2251
        deployment := new(model.Deployment)
17✔
2252
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
17✔
2253
                Decode(deployment); err != nil {
23✔
2254
                if err == mongo.ErrNoDocuments {
12✔
2255
                        return nil, nil
6✔
2256
                }
6✔
2257
                return nil, err
×
2258
        }
2259

2260
        return deployment, nil
11✔
2261
}
2262

2263
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2264
        ctx context.Context,
2265
        ids ...string,
2266
) (deploymentStats []*model.DeploymentStats, err error) {
4✔
2267

4✔
2268
        if len(ids) == 0 {
4✔
2269
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2270
        }
×
2271

2272
        for _, id := range ids {
12✔
2273
                if len(id) == 0 {
8✔
2274
                        return nil, ErrStorageInvalidID
×
2275
                }
×
2276
        }
2277

2278
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2279
        collDpl := database.Collection(CollectionDeployments)
4✔
2280

4✔
2281
        query := bson.M{
4✔
2282
                "_id": bson.M{
4✔
2283
                        "$in": ids,
4✔
2284
                },
4✔
2285
        }
4✔
2286
        statsProjection := &mopts.FindOptions{
4✔
2287
                Projection: bson.M{"stats": 1},
4✔
2288
        }
4✔
2289

4✔
2290
        results, err := collDpl.Find(
4✔
2291
                ctx,
4✔
2292
                query,
4✔
2293
                statsProjection,
4✔
2294
        )
4✔
2295
        if err != nil {
4✔
2296
                return nil, err
×
2297
        }
×
2298

2299
        for results.Next(context.Background()) {
12✔
2300
                depl := new(model.DeploymentStats)
8✔
2301
                if err = results.Decode(&depl); err != nil {
8✔
2302
                        if err == mongo.ErrNoDocuments {
×
2303
                                return nil, nil
×
2304
                        }
×
2305
                        return nil, err
×
2306
                }
2307
                deploymentStats = append(deploymentStats, depl)
8✔
2308
        }
2309

2310
        return deploymentStats, nil
4✔
2311
}
2312

2313
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2314
        id string) (*model.Deployment, error) {
15✔
2315

15✔
2316
        if len(id) == 0 {
17✔
2317
                return nil, ErrStorageInvalidID
2✔
2318
        }
2✔
2319

2320
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
13✔
2321
        collDpl := database.Collection(CollectionDeployments)
13✔
2322

13✔
2323
        var deployment *model.Deployment
13✔
2324
        filter := bson.D{
13✔
2325
                {Key: "_id", Value: id},
13✔
2326
                {Key: StorageKeyDeploymentFinished, Value: nil},
13✔
2327
        }
13✔
2328
        if err := collDpl.FindOne(ctx, filter).
13✔
2329
                Decode(&deployment); err != nil {
22✔
2330
                if err == mongo.ErrNoDocuments {
18✔
2331
                        return nil, nil
9✔
2332
                }
9✔
2333
                return nil, err
×
2334
        }
2335

2336
        return deployment, nil
5✔
2337
}
2338

2339
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2340
        ctx context.Context,
2341
        deploymentID string,
2342
        increment int,
2343
) error {
109✔
2344
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
109✔
2345
        collection := database.Collection(CollectionDeployments)
109✔
2346

109✔
2347
        filter := bson.M{
109✔
2348
                "_id": deploymentID,
109✔
2349
                StorageKeyDeploymentDeviceCount: bson.M{
109✔
2350
                        "$ne": nil,
109✔
2351
                },
109✔
2352
        }
109✔
2353

109✔
2354
        update := bson.M{
109✔
2355
                "$inc": bson.M{
109✔
2356
                        StorageKeyDeploymentDeviceCount: increment,
109✔
2357
                },
109✔
2358
        }
109✔
2359

109✔
2360
        _, err := collection.UpdateOne(ctx, filter, update)
109✔
2361
        return err
109✔
2362
}
109✔
2363

2364
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2365
        ctx context.Context,
2366
        deploymentID string,
2367
        count int,
2368
) error {
6✔
2369
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2370
        collection := database.Collection(CollectionDeployments)
6✔
2371

6✔
2372
        filter := bson.M{
6✔
2373
                "_id": deploymentID,
6✔
2374
                StorageKeyDeploymentDeviceCount: bson.M{
6✔
2375
                        "$eq": nil,
6✔
2376
                },
6✔
2377
        }
6✔
2378

6✔
2379
        update := bson.M{
6✔
2380
                "$set": bson.M{
6✔
2381
                        StorageKeyDeploymentDeviceCount: count,
6✔
2382
                },
6✔
2383
        }
6✔
2384

6✔
2385
        _, err := collection.UpdateOne(ctx, filter, update)
6✔
2386
        return err
6✔
2387
}
6✔
2388

2389
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2390
        id string) (int, error) {
6✔
2391

6✔
2392
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2393
        collDevs := database.Collection(CollectionDevices)
6✔
2394

6✔
2395
        filter := bson.M{
6✔
2396
                StorageKeyDeviceDeploymentDeploymentID: id,
6✔
2397
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
2398
                        {Key: "$exists", Value: false},
6✔
2399
                },
6✔
2400
        }
6✔
2401

6✔
2402
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
6✔
2403
        if err != nil {
6✔
2404
                return 0, err
×
2405
        }
×
2406

2407
        return int(deviceCount), nil
6✔
2408
}
2409

2410
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2411
        id string, stats model.Stats) error {
11✔
2412

11✔
2413
        if len(id) == 0 {
13✔
2414
                return ErrStorageInvalidID
2✔
2415
        }
2✔
2416

2417
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2418
        collDpl := database.Collection(CollectionDeployments)
9✔
2419

9✔
2420
        deployment, err := model.NewDeployment()
9✔
2421
        if err != nil {
9✔
2422
                return errors.Wrap(err, "failed to create deployment")
×
2423
        }
×
2424

2425
        deployment.Stats = stats
9✔
2426
        var update bson.M
9✔
2427
        if deployment.IsFinished() {
9✔
2428
                now := time.Now()
×
2429

×
2430
                update = bson.M{
×
2431
                        "$set": bson.M{
×
2432
                                StorageKeyDeploymentStats:    stats,
×
2433
                                StorageKeyDeploymentFinished: &now,
×
2434
                        },
×
2435
                }
×
2436
        } else {
9✔
2437
                update = bson.M{
9✔
2438
                        "$set": bson.M{
9✔
2439
                                StorageKeyDeploymentStats: stats,
9✔
2440
                        },
9✔
2441
                }
9✔
2442
        }
9✔
2443

2444
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
9✔
2445
        if res != nil && res.MatchedCount == 0 {
13✔
2446
                return ErrStorageInvalidID
4✔
2447
        }
4✔
2448
        return err
5✔
2449
}
2450

2451
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2452
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
15✔
2453

15✔
2454
        if len(id) == 0 {
17✔
2455
                return ErrStorageInvalidID
2✔
2456
        }
2✔
2457

2458
        if _, err := stateTo.MarshalText(); err != nil {
13✔
2459
                return ErrStorageInvalidInput
×
2460
        }
×
2461

2462
        // does not need any extra operations
2463
        // following query won't handle this case well and increase the state_to value
2464
        if stateFrom == stateTo {
15✔
2465
                return nil
2✔
2466
        }
2✔
2467

2468
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
11✔
2469
        collDpl := database.Collection(CollectionDeployments)
11✔
2470

11✔
2471
        var update bson.M
11✔
2472

11✔
2473
        if stateFrom == model.DeviceDeploymentStatusNull {
14✔
2474
                // note dot notation on embedded document
3✔
2475
                update = bson.M{
3✔
2476
                        "$inc": bson.M{
3✔
2477
                                "stats." + stateTo.String(): 1,
3✔
2478
                        },
3✔
2479
                }
3✔
2480
        } else {
12✔
2481
                // note dot notation on embedded document
9✔
2482
                update = bson.M{
9✔
2483
                        "$inc": bson.M{
9✔
2484
                                "stats." + stateFrom.String(): -1,
9✔
2485
                                "stats." + stateTo.String():   1,
9✔
2486
                        },
9✔
2487
                }
9✔
2488
        }
9✔
2489

2490
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
11✔
2491

11✔
2492
        if res != nil && res.MatchedCount == 0 {
13✔
2493
                return ErrStorageInvalidID
2✔
2494
        }
2✔
2495

2496
        return err
9✔
2497
}
2498

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

5✔
2507
        filter := bson.M{
5✔
2508
                "_id": deploymentID,
5✔
2509
        }
5✔
2510

5✔
2511
        update := bson.M{
5✔
2512
                "$inc": bson.M{
5✔
2513
                        StorageKeyDeploymentTotalSize: increment,
5✔
2514
                },
5✔
2515
        }
5✔
2516

5✔
2517
        _, err := collection.UpdateOne(ctx, filter, update)
5✔
2518
        return err
5✔
2519
}
5✔
2520

2521
func (db *DataStoreMongo) Find(ctx context.Context,
2522
        match model.Query) ([]*model.Deployment, int64, error) {
71✔
2523

71✔
2524
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
71✔
2525
        collDpl := database.Collection(CollectionDeployments)
71✔
2526

71✔
2527
        andq := []bson.M{}
71✔
2528

71✔
2529
        // filter by IDs
71✔
2530
        if len(match.IDs) > 0 {
71✔
2531
                tq := bson.M{
×
2532
                        "_id": bson.M{
×
2533
                                "$in": match.IDs,
×
2534
                        },
×
2535
                }
×
2536
                andq = append(andq, tq)
×
2537
        }
×
2538

2539
        // build deployment by name part of the query
2540
        if match.SearchText != "" {
103✔
2541
                // we must have indexing for text search
32✔
2542
                if !db.hasIndexing(ctx, db.client) {
34✔
2543
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
2✔
2544
                }
2✔
2545

2546
                tq := bson.M{
30✔
2547
                        "$text": bson.M{
30✔
2548
                                "$search": match.SearchText,
30✔
2549
                        },
30✔
2550
                }
30✔
2551

30✔
2552
                andq = append(andq, tq)
30✔
2553
        }
2554

2555
        // build deployment by status part of the query
2556
        if match.Status != model.StatusQueryAny {
89✔
2557
                var status model.DeploymentStatus
20✔
2558
                if match.Status == model.StatusQueryPending {
24✔
2559
                        status = model.DeploymentStatusPending
4✔
2560
                } else if match.Status == model.StatusQueryInProgress {
28✔
2561
                        status = model.DeploymentStatusInProgress
8✔
2562
                } else {
16✔
2563
                        status = model.DeploymentStatusFinished
8✔
2564
                }
8✔
2565
                stq := bson.M{StorageKeyDeploymentStatus: status}
20✔
2566
                andq = append(andq, stq)
20✔
2567
        }
2568

2569
        // build deployment by type part of the query
2570
        if match.Type != "" {
73✔
2571
                if match.Type == model.DeploymentTypeConfiguration {
8✔
2572
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
4✔
2573
                } else if match.Type == model.DeploymentTypeSoftware {
4✔
2574
                        andq = append(andq, bson.M{
×
2575
                                "$or": []bson.M{
×
2576
                                        {StorageKeyDeploymentType: match.Type},
×
2577
                                        {StorageKeyDeploymentType: ""},
×
2578
                                },
×
2579
                        })
×
2580
                }
×
2581
        }
2582

2583
        query := bson.M{}
69✔
2584
        if len(andq) != 0 {
115✔
2585
                // use search criteria if any
46✔
2586
                query = bson.M{
46✔
2587
                        "$and": andq,
46✔
2588
                }
46✔
2589
        }
46✔
2590

2591
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
69✔
2592
                query["created"] = bson.M{
×
2593
                        "$gte": match.CreatedAfter,
×
2594
                        "$lte": match.CreatedBefore,
×
2595
                }
×
2596
        } else if match.CreatedAfter != nil {
69✔
2597
                query["created"] = bson.M{
×
2598
                        "$gte": match.CreatedAfter,
×
2599
                }
×
2600
        } else if match.CreatedBefore != nil {
69✔
2601
                query["created"] = bson.M{
×
2602
                        "$lte": match.CreatedBefore,
×
2603
                }
×
2604
        }
×
2605

2606
        options := db.findOptions(match)
69✔
2607

69✔
2608
        var deployments []*model.Deployment
69✔
2609
        cursor, err := collDpl.Find(ctx, query, options)
69✔
2610
        if err != nil {
69✔
2611
                return nil, 0, err
×
2612
        }
×
2613
        if err := cursor.All(ctx, &deployments); err != nil {
69✔
2614
                return nil, 0, err
×
2615
        }
×
2616
        // Count documents if we didn't find all already.
2617
        count := int64(0)
69✔
2618
        if !match.DisableCount {
138✔
2619
                count = int64(len(deployments))
69✔
2620
                if count >= int64(match.Limit) {
137✔
2621
                        count, err = collDpl.CountDocuments(ctx, query)
68✔
2622
                        if err != nil {
68✔
2623
                                return nil, 0, err
×
2624
                        }
×
2625
                } else {
1✔
2626
                        // Don't forget to add the skipped documents
1✔
2627
                        count += int64(match.Skip)
1✔
2628
                }
1✔
2629
        }
2630

2631
        return deployments, count, nil
69✔
2632
}
2633

2634
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
69✔
2635
        options := &mopts.FindOptions{}
69✔
2636
        if match.Sort == model.SortDirectionAscending {
71✔
2637
                options.SetSort(bson.D{{Key: "created", Value: 1}})
2✔
2638
        } else {
69✔
2639
                options.SetSort(bson.D{{Key: "created", Value: -1}})
67✔
2640
        }
67✔
2641
        if match.Skip > 0 {
73✔
2642
                options.SetSkip(int64(match.Skip))
4✔
2643
        }
4✔
2644
        if match.Limit > 0 {
78✔
2645
                options.SetLimit(int64(match.Limit))
9✔
2646
        }
9✔
2647
        return options
69✔
2648
}
2649

2650
// FindNewerActiveDeployments finds active deployments which were created
2651
// after createdAfter
2652
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2653
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
9✔
2654

9✔
2655
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2656
        c := database.Collection(CollectionDeployments)
9✔
2657

9✔
2658
        queryFilters := make([]bson.M, 0)
9✔
2659
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
9✔
2660
        queryFilters = append(queryFilters,
9✔
2661
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
9✔
2662
        findQuery := bson.M{}
9✔
2663
        findQuery["$and"] = queryFilters
9✔
2664

9✔
2665
        findOptions := &mopts.FindOptions{}
9✔
2666
        findOptions.SetSkip(int64(skip))
9✔
2667
        findOptions.SetLimit(int64(limit))
9✔
2668

9✔
2669
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
9✔
2670
        cursor, err := c.Find(ctx, findQuery, findOptions)
9✔
2671
        if err != nil {
9✔
2672
                return nil, errors.Wrap(err, "failed to get deployments")
×
2673
        }
×
2674
        defer cursor.Close(ctx)
9✔
2675

9✔
2676
        var deployments []*model.Deployment
9✔
2677

9✔
2678
        if err = cursor.All(ctx, &deployments); err != nil {
9✔
2679
                return nil, errors.Wrap(err, "failed to get deployments")
×
2680
        }
×
2681

2682
        return deployments, nil
9✔
2683
}
2684

2685
// SetDeploymentStatus simply sets the status field
2686
// optionally sets 'finished time' if deployment is indeed finished
2687
func (db *DataStoreMongo) SetDeploymentStatus(
2688
        ctx context.Context,
2689
        id string,
2690
        status model.DeploymentStatus,
2691
        now time.Time,
2692
) error {
11✔
2693
        if len(id) == 0 {
11✔
2694
                return ErrStorageInvalidID
×
2695
        }
×
2696

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

11✔
2700
        var update bson.M
11✔
2701
        if status == model.DeploymentStatusFinished {
14✔
2702
                update = bson.M{
3✔
2703
                        "$set": bson.M{
3✔
2704
                                StorageKeyDeploymentActive:   false,
3✔
2705
                                StorageKeyDeploymentStatus:   status,
3✔
2706
                                StorageKeyDeploymentFinished: &now,
3✔
2707
                        },
3✔
2708
                }
3✔
2709
        } else {
12✔
2710
                update = bson.M{
9✔
2711
                        "$set": bson.M{
9✔
2712
                                StorageKeyDeploymentActive: true,
9✔
2713
                                StorageKeyDeploymentStatus: status,
9✔
2714
                        },
9✔
2715
                }
9✔
2716
        }
9✔
2717

2718
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
11✔
2719

11✔
2720
        if res != nil && res.MatchedCount == 0 {
13✔
2721
                return ErrStorageInvalidID
2✔
2722
        }
2✔
2723

2724
        return err
9✔
2725
}
2726

2727
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2728
// given artifact
2729
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2730
        id string) (bool, error) {
7✔
2731

7✔
2732
        if len(id) == 0 {
7✔
2733
                return false, ErrStorageInvalidID
×
2734
        }
×
2735

2736
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2737
        collDpl := database.Collection(CollectionDeployments)
7✔
2738

7✔
2739
        var tmp interface{}
7✔
2740
        query := bson.D{
7✔
2741
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2742
                {Key: StorageKeyDeploymentArtifacts, Value: id},
7✔
2743
        }
7✔
2744
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
12✔
2745
                if err == mongo.ErrNoDocuments {
10✔
2746
                        return false, nil
5✔
2747
                }
5✔
2748
                return false, err
×
2749
        }
2750

2751
        return true, nil
3✔
2752
}
2753

2754
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2755
// given artifact
2756
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2757
        artifactName string) (bool, error) {
7✔
2758

7✔
2759
        if len(artifactName) == 0 {
7✔
2760
                return false, ErrImagesStorageInvalidArtifactName
×
2761
        }
×
2762

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

7✔
2766
        var tmp interface{}
7✔
2767
        query := bson.D{
7✔
2768
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2769
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
7✔
2770
        }
7✔
2771

7✔
2772
        projection := bson.M{
7✔
2773
                "_id": 1,
7✔
2774
        }
7✔
2775
        findOptions := mopts.FindOne()
7✔
2776
        findOptions.SetProjection(projection)
7✔
2777

7✔
2778
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
12✔
2779
                if err == mongo.ErrNoDocuments {
10✔
2780
                        return false, nil
5✔
2781
                }
5✔
2782
                return false, err
×
2783
        }
2784

2785
        return true, nil
2✔
2786
}
2787

2788
// ExistByArtifactId check if there is any deployment that uses give artifact
2789
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2790
        id string) (bool, error) {
×
2791

×
2792
        if len(id) == 0 {
×
2793
                return false, ErrStorageInvalidID
×
2794
        }
×
2795

2796
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2797
        collDpl := database.Collection(CollectionDeployments)
×
2798

×
2799
        var tmp interface{}
×
2800
        query := bson.D{
×
2801
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2802
        }
×
2803
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2804
                if err == mongo.ErrNoDocuments {
×
2805
                        return false, nil
×
2806
                }
×
2807
                return false, err
×
2808
        }
2809

2810
        return true, nil
×
2811
}
2812

2813
// Per-tenant storage settings
2814
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
3✔
2815
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2816
        collection := database.Collection(CollectionStorageSettings)
3✔
2817

3✔
2818
        settings := new(model.StorageSettings)
3✔
2819
        // supposed that it's only one document in the collection
3✔
2820
        query := bson.M{
3✔
2821
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2822
        }
3✔
2823
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
4✔
2824
                if err == mongo.ErrNoDocuments {
2✔
2825
                        return nil, nil
1✔
2826
                }
1✔
2827
                return nil, err
×
2828
        }
2829

2830
        return settings, nil
3✔
2831
}
2832

2833
func (db *DataStoreMongo) SetStorageSettings(
2834
        ctx context.Context,
2835
        storageSettings *model.StorageSettings,
2836
) error {
3✔
2837
        var err error
3✔
2838
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2839
        collection := database.Collection(CollectionStorageSettings)
3✔
2840

3✔
2841
        filter := bson.M{
3✔
2842
                "_id": StorageKeyStorageSettingsDefaultID,
3✔
2843
        }
3✔
2844
        if storageSettings != nil {
6✔
2845
                replaceOptions := mopts.Replace()
3✔
2846
                replaceOptions.SetUpsert(true)
3✔
2847
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
3✔
2848
        } else {
4✔
2849
                _, err = collection.DeleteOne(ctx, filter)
1✔
2850
        }
1✔
2851

2852
        return err
3✔
2853
}
2854

2855
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2856
        ctx context.Context,
2857
        artifactName string,
2858
        artifactIDs []string,
2859
) error {
2✔
2860
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2861
        collDpl := database.Collection(CollectionDeployments)
2✔
2862

2✔
2863
        query := bson.D{
2✔
2864
                {Key: StorageKeyDeploymentFinished, Value: nil},
2✔
2865
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
2✔
2866
        }
2✔
2867
        update := bson.M{
2✔
2868
                "$set": bson.M{
2✔
2869
                        StorageKeyDeploymentArtifacts: artifactIDs,
2✔
2870
                },
2✔
2871
        }
2✔
2872

2✔
2873
        _, err := collDpl.UpdateMany(ctx, query, update)
2✔
2874
        return err
2✔
2875
}
2✔
2876

2877
func (db *DataStoreMongo) GetTenantDbs() ([]string, error) {
×
2878
        return migrate.GetTenantDbs(context.Background(), db.client, mstore.IsTenantDb(DbName))
×
2879
}
×
2880

2881
func (db *DataStoreMongo) UpdateReleaseArtifactDescription(
2882
        ctx context.Context,
2883
        artifactToEdit *model.Image,
2884
        releaseName string,
NEW
2885
) error {
×
NEW
2886
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
NEW
2887
        collReleases := database.Collection(CollectionReleases)
×
NEW
2888

×
NEW
2889
        update := bson.M{
×
NEW
2890
                "$set": bson.M{
×
NEW
2891
                        StorageKeyReleaseArtifactsIndexDescription: artifactToEdit.ImageMeta.Description,
×
NEW
2892
                        StorageKeyReleaseArtifactsIndexModified:    artifactToEdit.Modified,
×
NEW
2893
                        StorageKeyReleaseModified:                  time.Now(),
×
NEW
2894
                },
×
NEW
2895
        }
×
NEW
2896
        _, err := collReleases.UpdateOne(
×
NEW
2897
                ctx,
×
NEW
2898
                bson.M{
×
NEW
2899
                        StorageKeyReleaseName:        releaseName,
×
NEW
2900
                        StorageKeyReleaseArtifactsId: artifactToEdit.Id,
×
NEW
2901
                },
×
NEW
2902
                update,
×
NEW
2903
        )
×
NEW
2904
        if err != nil {
×
NEW
2905
                return err
×
NEW
2906
        }
×
NEW
2907
        return nil
×
2908
}
2909

2910
func (db *DataStoreMongo) UpdateReleaseArtifacts(
2911
        ctx context.Context,
2912
        artifactToAdd *model.Image,
2913
        artifactToRemove *model.Image,
2914
        releaseName string,
2915
) error {
1✔
2916
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2917
        collReleases := database.Collection(CollectionReleases)
1✔
2918

1✔
2919
        opt := &mopts.UpdateOptions{}
1✔
2920
        update := bson.M{
1✔
2921
                "$set": bson.M{
1✔
2922
                        StorageKeyReleaseName:     releaseName,
1✔
2923
                        StorageKeyReleaseModified: time.Now(),
1✔
2924
                },
1✔
2925
        }
1✔
2926
        if artifactToRemove != nil {
2✔
2927
                update["$pull"] = bson.M{
1✔
2928
                        StorageKeyReleaseArtifacts: bson.M{StorageKeyId: artifactToRemove.Id},
1✔
2929
                }
1✔
2930
        }
1✔
2931
        if artifactToAdd != nil {
2✔
2932
                upsert := true
1✔
2933
                opt.Upsert = &upsert
1✔
2934
                update["$push"] = bson.M{StorageKeyReleaseArtifacts: artifactToAdd}
1✔
2935
        }
1✔
2936
        _, err := collReleases.UpdateOne(
1✔
2937
                ctx,
1✔
2938
                bson.M{StorageKeyReleaseName: releaseName},
1✔
2939
                update,
1✔
2940
                opt,
1✔
2941
        )
1✔
2942
        if err != nil {
1✔
NEW
2943
                return err
×
NEW
2944
        }
×
2945
        return nil
1✔
2946
}
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