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

mendersoftware / deployments / 1280684078

06 May 2024 08:13PM UTC coverage: 79.689% (+0.08%) from 79.606%
1280684078

Pull #1018

gitlab-ci

tranchitella
feat: return 409 on conflicts where creating a deployment with the same parameters as an already existing and active deployment

Changelog: none
Ticket: MEN-6622

Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #1018: feat: prevent the creation of deployments if there is already an active deployment with the same constructor parameters

61 of 66 new or added lines in 7 files covered. (92.42%)

12 existing lines in 2 files now uncovered.

8102 of 10167 relevant lines covered (79.69%)

34.63 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

280
        // 1.2.3
281
        IndexArtifactNameDepends = mongo.IndexModel{
282
                Keys: bson.D{
283
                        {Key: StorageKeyImageName,
284
                                Value: 1},
285
                        {Key: StorageKeyImageDependsIdx,
286
                                Value: 1},
287
                },
288
                Options: &mopts.IndexOptions{
289
                        Background: &_false,
290
                        Name:       &IndexArtifactNameDependsName,
291
                        Unique:     &_true,
292
                },
293
        }
294

295
        // Indexes 1.2.7
296
        IndexImageMetaDescription      = "image_meta_description"
297
        IndexImageMetaDescriptionModel = mongo.IndexModel{
298
                Keys: bson.D{
299
                        {Key: StorageKeyImageDescription, Value: 1},
300
                },
301
                Options: &mopts.IndexOptions{
302
                        Background: &_false,
303
                        Name:       &IndexImageMetaDescription,
304
                },
305
        }
306

307
        IndexImageMetaArtifactDeviceTypeCompatible      = "image_meta_artifact_device_type_compatible"
308
        IndexImageMetaArtifactDeviceTypeCompatibleModel = mongo.IndexModel{
309
                Keys: bson.D{
310
                        {Key: StorageKeyImageDeviceTypes, Value: 1},
311
                },
312
                Options: &mopts.IndexOptions{
313
                        Background: &_false,
314
                        Name:       &IndexImageMetaArtifactDeviceTypeCompatible,
315
                },
316
        }
317

318
        // Indexes 1.2.8
319
        IndexDeploymentsActiveCreated      = "active_created"
320
        IndexDeploymentsActiveCreatedModel = mongo.IndexModel{
321
                Keys: bson.D{
322
                        {Key: StorageKeyDeploymentCreated, Value: 1},
323
                },
324
                Options: &mopts.IndexOptions{
325
                        Background: &_false,
326
                        Name:       &IndexDeploymentsActiveCreated,
327
                        PartialFilterExpression: bson.M{
328
                                StorageKeyDeploymentActive: true,
329
                        },
330
                },
331
        }
332

333
        // Index 1.2.9
334
        IndexDeviceDeploymentsActiveCreated      = "active_deviceid_created"
335
        IndexDeviceDeploymentsActiveCreatedModel = mongo.IndexModel{
336
                Keys: bson.D{
337
                        {Key: StorageKeyDeviceDeploymentActive, Value: 1},
338
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
339
                        {Key: StorageKeyDeviceDeploymentCreated, Value: 1},
340
                },
341
                Options: mopts.Index().
342
                        SetName(IndexDeviceDeploymentsActiveCreated),
343
        }
344

345
        // Index 1.2.11
346
        IndexDeviceDeploymentsLogs      = "devices_logs"
347
        IndexDeviceDeploymentsLogsModel = mongo.IndexModel{
348
                Keys: bson.D{
349
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
350
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
351
                },
352
                Options: mopts.Index().
353
                        SetName(IndexDeviceDeploymentsLogs),
354
        }
355

356
        // 1.2.13
357
        IndexArtifactProvides = mongo.IndexModel{
358
                Keys: bson.D{
359
                        {Key: model.StorageKeyImageProvidesIdxKey,
360
                                Value: 1},
361
                        {Key: model.StorageKeyImageProvidesIdxValue,
362
                                Value: 1},
363
                },
364
                Options: &mopts.IndexOptions{
365
                        Background: &_false,
366
                        Sparse:     &_true,
367
                        Name:       &IndexArtifactProvidesName,
368
                },
369
        }
370
)
371

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

380
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
381

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

389
        ErrLimitNotFound      = errors.New("limit not found")
390
        ErrDevicesCountFailed = errors.New("failed to count devices")
391
        ErrConflictingDepends = errors.New(
392
                "an artifact with the same name and depends already exists",
393
        )
394
        ErrConflictingDeployment = errors.New(
395
                "an active deployment with the same parameter already exists",
396
        )
397
)
398

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

405
        StorageKeyImageProvides    = "meta_artifact.provides"
406
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
407
        StorageKeyImageDepends     = "meta_artifact.depends"
408
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
409
        StorageKeyImageSize        = "size"
410
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
411
        StorageKeyImageName        = "meta_artifact.name"
412
        StorageKeyUpdateType       = "meta_artifact.updates.typeinfo.type"
413
        StorageKeyImageDescription = "meta.description"
414
        StorageKeyImageModified    = "modified"
415

416
        // releases
417
        StorageKeyReleaseName                      = "_id"
418
        StorageKeyReleaseModified                  = "modified"
419
        StorageKeyReleaseTags                      = "tags"
420
        StorageKeyReleaseNotes                     = "notes"
421
        StorageKeyReleaseArtifacts                 = "artifacts"
422
        StorageKeyReleaseArtifactsCount            = "artifacts_count"
423
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
424
                StorageKeyImageDescription
425
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
426
                StorageKeyImageDescription
427
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
428
                StorageKeyImageDeviceTypes
429
        StorageKeyReleaseArtifactsUpdateTypes = StorageKeyReleaseArtifacts + "." +
430
                StorageKeyUpdateType
431
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
432
                StorageKeyImageModified
433
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
434
                StorageKeyId
435
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
436
                StorageKeyImageDependsIdx
437
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
438
                StorageKeyImageProvidesIdx
439

440
        StorageKeyDeviceDeploymentLogMessages = "messages"
441

442
        StorageKeyDeviceDeploymentAssignedImage   = "image"
443
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
444
                "." + StorageKeyId
445

446
        StorageKeyDeviceDeploymentActive         = "active"
447
        StorageKeyDeviceDeploymentCreated        = "created"
448
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
449
        StorageKeyDeviceDeploymentStatus         = "status"
450
        StorageKeyDeviceDeploymentStarted        = "started"
451
        StorageKeyDeviceDeploymentSubState       = "substate"
452
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
453
        StorageKeyDeviceDeploymentFinished       = "finished"
454
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
455
        StorageKeyDeviceDeploymentArtifact       = "image"
456
        StorageKeyDeviceDeploymentRequest        = "request"
457
        StorageKeyDeviceDeploymentDeleted        = "deleted"
458

459
        StorageKeyDeploymentName                = "deploymentconstructor.name"
460
        StorageKeyDeploymentArtifactName        = "deploymentconstructor.artifactname"
461
        StorageKeyDeploymentConstructorChecksum = "deploymentconstructor_checksum"
462
        StorageKeyDeploymentStats               = "stats"
463
        StorageKeyDeploymentActive              = "active"
464
        StorageKeyDeploymentStatus              = "status"
465
        StorageKeyDeploymentCreated             = "created"
466
        StorageKeyDeploymentStatsCreated        = "created"
467
        StorageKeyDeploymentFinished            = "finished"
468
        StorageKeyDeploymentArtifacts           = "artifacts"
469
        StorageKeyDeploymentDeviceCount         = "device_count"
470
        StorageKeyDeploymentMaxDevices          = "max_devices"
471
        StorageKeyDeploymentType                = "type"
472
        StorageKeyDeploymentTotalSize           = "statistics.total_size"
473

474
        StorageKeyStorageSettingsDefaultID      = "settings"
475
        StorageKeyStorageSettingsBucket         = "bucket"
476
        StorageKeyStorageSettingsRegion         = "region"
477
        StorageKeyStorageSettingsKey            = "key"
478
        StorageKeyStorageSettingsSecret         = "secret"
479
        StorageKeyStorageSettingsURI            = "uri"
480
        StorageKeyStorageSettingsExternalURI    = "external_uri"
481
        StorageKeyStorageSettingsToken          = "token"
482
        StorageKeyStorageSettingsForcePathStyle = "force_path_style"
483
        StorageKeyStorageSettingsUseAccelerate  = "use_accelerate"
484

485
        StorageKeyStorageReleaseUpdateTypes = "update_types"
486

487
        ArtifactDependsDeviceType = "device_type"
488
)
489

490
type DataStoreMongo struct {
491
        client *mongo.Client
492
}
493

494
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
425✔
495
        return &DataStoreMongo{
425✔
496
                client: client,
425✔
497
        }
425✔
498
}
425✔
499

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

1✔
502
        clientOptions := mopts.Client()
1✔
503
        mongoURL := c.GetString(dconfig.SettingMongo)
1✔
504
        if !strings.Contains(mongoURL, "://") {
1✔
505
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
506
                        mongoURL)
×
507
        }
×
508
        clientOptions.ApplyURI(mongoURL)
1✔
509

1✔
510
        username := c.GetString(dconfig.SettingDbUsername)
1✔
511
        if username != "" {
1✔
512
                credentials := mopts.Credential{
×
513
                        Username: c.GetString(dconfig.SettingDbUsername),
×
514
                }
×
515
                password := c.GetString(dconfig.SettingDbPassword)
×
516
                if password != "" {
×
517
                        credentials.Password = password
×
518
                        credentials.PasswordSet = true
×
519
                }
×
520
                clientOptions.SetAuth(credentials)
×
521
        }
522

523
        if c.GetBool(dconfig.SettingDbSSL) {
1✔
524
                tlsConfig := &tls.Config{}
×
525
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
526
                clientOptions.SetTLSConfig(tlsConfig)
×
527
        }
×
528

529
        // Set 10s timeout
530
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
1✔
531
        defer cancel()
1✔
532
        client, err := mongo.Connect(ctx, clientOptions)
1✔
533
        if err != nil {
1✔
534
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
535
        }
×
536

537
        // Validate connection
538
        if err = client.Ping(ctx, nil); err != nil {
1✔
539
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
540
        }
×
541

542
        return client, nil
1✔
543
}
544

545
func (db *DataStoreMongo) Ping(ctx context.Context) error {
1✔
546
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
1✔
547
        return res.Err()
1✔
548
}
1✔
549

550
func (db *DataStoreMongo) setCurrentDbVersion(
551
        ctx context.Context,
552
) error {
1✔
553
        versions, err := migrate.GetMigrationInfo(
1✔
554
                ctx, db.client, mstore.DbFromContext(ctx, DatabaseName))
1✔
555
        if err != nil {
1✔
556
                return errors.Wrap(err, "failed to list applied migrations")
×
557
        }
×
558
        var current migrate.Version
1✔
559
        if len(versions) > 0 {
2✔
560
                // sort applied migrations wrt. version
1✔
561
                sort.Slice(versions, func(i int, j int) bool {
2✔
562
                        return migrate.VersionIsLess(versions[i].Version, versions[j].Version)
1✔
563
                })
1✔
564
                current = versions[len(versions)-1].Version
1✔
565
        }
566
        if currentDbVersion == nil {
2✔
567
                currentDbVersion = map[string]*migrate.Version{}
1✔
568
        }
1✔
569
        currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] = &current
1✔
570
        return nil
1✔
571
}
572

573
func (db *DataStoreMongo) getCurrentDbVersion(
574
        ctx context.Context,
575
) (*migrate.Version, error) {
1✔
576
        if currentDbVersion == nil ||
1✔
577
                currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] == nil {
2✔
578
                if err := db.setCurrentDbVersion(ctx); err != nil {
1✔
579
                        return nil, err
×
580
                }
×
581
        }
582
        return currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)], nil
1✔
583
}
584

585
func (db *DataStoreMongo) GetReleases(
586
        ctx context.Context,
587
        filt *model.ReleaseOrImageFilter,
588
) ([]model.Release, int, error) {
1✔
589
        current, err := db.getCurrentDbVersion(ctx)
1✔
590
        if err != nil {
1✔
591
                return []model.Release{}, 0, err
×
592
        } else if current == nil {
1✔
593
                return []model.Release{}, 0, errors.New("couldn't get current database version")
×
594
        }
×
595
        target, err := migrate.NewVersion(DbVersion)
1✔
596
        if err != nil {
1✔
597
                return []model.Release{}, 0, errors.Wrap(err, "failed to get latest DB version")
×
598
        }
×
599
        if migrate.VersionIsLess(*current, *target) {
1✔
600
                return db.getReleases_1_2_14(ctx, filt)
×
601
        } else {
1✔
602
                return db.getReleases_1_2_15(ctx, filt)
1✔
603
        }
1✔
604
}
605

606
func (db *DataStoreMongo) getReleases_1_2_14(
607
        ctx context.Context,
608
        filt *model.ReleaseOrImageFilter,
609
) ([]model.Release, int, error) {
8✔
610
        l := log.FromContext(ctx)
8✔
611
        l.Infof("get releases method version 1.2.14")
8✔
612
        var pipe []bson.D
8✔
613

8✔
614
        pipe = []bson.D{}
8✔
615
        if filt != nil && filt.Name != "" {
10✔
616
                pipe = append(pipe, bson.D{
2✔
617
                        {Key: "$match", Value: bson.M{
2✔
618
                                StorageKeyImageName: bson.M{
2✔
619
                                        "$regex": primitive.Regex{
2✔
620
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
2✔
621
                                                Options: "i",
2✔
622
                                        },
2✔
623
                                },
2✔
624
                        }},
2✔
625
                })
2✔
626
        }
2✔
627

628
        pipe = append(pipe, bson.D{
8✔
629
                // Remove (possibly expensive) sub-documents from pipeline
8✔
630
                {
8✔
631
                        Key: "$project",
8✔
632
                        Value: bson.M{
8✔
633
                                StorageKeyImageDependsIdx:  0,
8✔
634
                                StorageKeyImageProvidesIdx: 0,
8✔
635
                        },
8✔
636
                },
8✔
637
        })
8✔
638

8✔
639
        pipe = append(pipe, bson.D{
8✔
640
                {Key: "$group", Value: bson.D{
8✔
641
                        {Key: "_id", Value: "$" + StorageKeyImageName},
8✔
642
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
8✔
643
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
8✔
644
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
8✔
645
                }},
8✔
646
        })
8✔
647

8✔
648
        if filt != nil && filt.Description != "" {
10✔
649
                pipe = append(pipe, bson.D{
2✔
650
                        {Key: "$match", Value: bson.M{
2✔
651
                                "artifacts." + StorageKeyImageDescription: bson.M{
2✔
652
                                        "$regex": primitive.Regex{
2✔
653
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
654
                                                Options: "i",
2✔
655
                                        },
2✔
656
                                },
2✔
657
                        }},
2✔
658
                })
2✔
659
        }
2✔
660
        if filt != nil && filt.DeviceType != "" {
8✔
661
                pipe = append(pipe, bson.D{
×
662
                        {Key: "$match", Value: bson.M{
×
663
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
×
664
                                        "$regex": primitive.Regex{
×
665
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
×
666
                                                Options: "i",
×
667
                                        },
×
668
                                },
×
669
                        }},
×
670
                })
×
671
        }
×
672

673
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
8✔
674
        if sortField == "" {
13✔
675
                sortField = "name"
5✔
676
        }
5✔
677
        if sortOrder == 0 {
13✔
678
                sortOrder = 1
5✔
679
        }
5✔
680

681
        page := 1
8✔
682
        perPage := math.MaxInt64
8✔
683
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
9✔
684
                page = filt.Page
1✔
685
                perPage = filt.PerPage
1✔
686
        }
1✔
687
        pipe = append(pipe,
8✔
688
                bson.D{{Key: "$facet", Value: bson.D{
8✔
689
                        {Key: "results", Value: []bson.D{
8✔
690
                                {
8✔
691
                                        {Key: "$sort", Value: bson.D{
8✔
692
                                                {Key: sortField, Value: sortOrder},
8✔
693
                                                {Key: "_id", Value: 1},
8✔
694
                                        }},
8✔
695
                                },
8✔
696
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
8✔
697
                                {{Key: "$limit", Value: int64(perPage)}},
8✔
698
                        }},
8✔
699
                        {Key: "count", Value: []bson.D{
8✔
700
                                {{Key: "$count", Value: "count"}},
8✔
701
                        }},
8✔
702
                }}},
8✔
703
        )
8✔
704

8✔
705
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
706
        collImg := database.Collection(CollectionImages)
8✔
707

8✔
708
        cursor, err := collImg.Aggregate(ctx, pipe)
8✔
709
        if err != nil {
8✔
710
                return []model.Release{}, 0, err
×
711
        }
×
712
        defer cursor.Close(ctx)
8✔
713

8✔
714
        result := struct {
8✔
715
                Results []model.Release       `bson:"results"`
8✔
716
                Count   []struct{ Count int } `bson:"count"`
8✔
717
        }{}
8✔
718
        if !cursor.Next(ctx) {
8✔
719
                return []model.Release{}, 0, nil
×
720
        } else if err = cursor.Decode(&result); err != nil {
8✔
721
                return []model.Release{}, 0, err
×
722
        } else if len(result.Count) == 0 {
9✔
723
                return []model.Release{}, 0, err
1✔
724
        }
1✔
725
        return result.Results, result.Count[0].Count, nil
7✔
726
}
727

728
func (db *DataStoreMongo) getReleases_1_2_15(
729
        ctx context.Context,
730
        filt *model.ReleaseOrImageFilter,
731
) ([]model.Release, int, error) {
76✔
732
        l := log.FromContext(ctx)
76✔
733
        l.Infof("get releases method version 1.2.15")
76✔
734

76✔
735
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
76✔
736
        if sortField == "" {
138✔
737
                sortField = "_id"
62✔
738
        } else if sortField == "name" {
78✔
739
                sortField = StorageKeyReleaseName
2✔
740
        }
2✔
741
        if sortOrder == 0 {
138✔
742
                sortOrder = 1
62✔
743
        }
62✔
744

745
        page := 1
76✔
746
        perPage := DefaultDocumentLimit
76✔
747
        if filt != nil {
150✔
748
                if filt.Page > 0 {
77✔
749
                        page = filt.Page
3✔
750
                }
3✔
751
                if filt.PerPage > 0 {
77✔
752
                        perPage = filt.PerPage
3✔
753
                }
3✔
754
        }
755

756
        opts := &mopts.FindOptions{}
76✔
757
        opts.SetSort(bson.D{{Key: sortField, Value: sortOrder}})
76✔
758
        opts.SetSkip(int64((page - 1) * perPage))
76✔
759
        opts.SetLimit(int64(perPage))
76✔
760
        projection := bson.M{
76✔
761
                StorageKeyReleaseImageDependsIdx:  0,
76✔
762
                StorageKeyReleaseImageProvidesIdx: 0,
76✔
763
        }
76✔
764
        opts.SetProjection(projection)
76✔
765

76✔
766
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
76✔
767
        collReleases := database.Collection(CollectionReleases)
76✔
768

76✔
769
        filter := bson.M{}
76✔
770
        if filt != nil {
150✔
771
                if filt.Name != "" {
124✔
772
                        filter[StorageKeyReleaseName] = bson.M{"$regex": primitive.Regex{
50✔
773
                                Pattern: regexp.QuoteMeta(filt.Name) + ".*",
50✔
774
                                Options: "i",
50✔
775
                        }}
50✔
776
                }
50✔
777
                if len(filt.Tags) > 0 {
76✔
778
                        filter[StorageKeyReleaseTags] = bson.M{"$all": filt.Tags}
2✔
779
                }
2✔
780
                if filt.Description != "" {
78✔
781
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": primitive.Regex{
4✔
782
                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
4✔
783
                                Options: "i",
4✔
784
                        }}
4✔
785
                }
4✔
786
                if filt.DeviceType != "" {
76✔
787
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = filt.DeviceType
2✔
788
                }
2✔
789
                if filt.UpdateType != "" {
77✔
790
                        filter[StorageKeyReleaseArtifactsUpdateTypes] = filt.UpdateType
3✔
791
                }
3✔
792
        }
793
        releases := []model.Release{}
76✔
794
        cursor, err := collReleases.Find(ctx, filter, opts)
76✔
795
        if err != nil {
76✔
796
                return []model.Release{}, 0, err
×
797
        }
×
798
        if err := cursor.All(ctx, &releases); err != nil {
76✔
799
                return []model.Release{}, 0, err
×
800
        }
×
801

802
        // TODO: can we return number of all documents in the collection
803
        // using EstimatedDocumentCount?
804
        count, err := collReleases.CountDocuments(ctx, filter)
76✔
805
        if err != nil {
76✔
806
                return []model.Release{}, 0, err
×
807
        }
×
808

809
        if count < 1 {
93✔
810
                return []model.Release{}, int(count), nil
17✔
811
        }
17✔
812
        return releases, int(count), nil
60✔
813
}
814

815
// limits
816
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
4✔
817

4✔
818
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
819
        collLim := database.Collection(CollectionLimits)
4✔
820

4✔
821
        limit := new(model.Limit)
4✔
822
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
4✔
823
                Decode(limit); err != nil {
6✔
824
                if err == mongo.ErrNoDocuments {
4✔
825
                        return nil, ErrLimitNotFound
2✔
826
                }
2✔
827
                return nil, err
×
828
        }
829

830
        return limit, nil
2✔
831
}
832

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

5✔
835
        dbname := mstore.DbNameForTenant(tenantId, DbName)
5✔
836

5✔
837
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
5✔
838
}
5✔
839

840
//images
841

842
// Exists checks if object with ID exists
843
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
844
        var result interface{}
×
845

×
846
        if len(id) == 0 {
×
847
                return false, ErrImagesStorageInvalidID
×
848
        }
×
849

850
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
851
        collImg := database.Collection(CollectionImages)
×
852

×
853
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
854
                Decode(&result); err != nil {
×
855
                if err == mongo.ErrNoDocuments {
×
856
                        return false, nil
×
857
                }
×
858
                return false, err
×
859
        }
860

861
        return true, nil
×
862
}
863

864
// Update provided Image
865
// Return false if not found
866
func (db *DataStoreMongo) Update(ctx context.Context,
867
        image *model.Image) (bool, error) {
1✔
868

1✔
869
        if err := image.Validate(); err != nil {
1✔
870
                return false, err
×
871
        }
×
872

873
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
874
        collImg := database.Collection(CollectionImages)
1✔
875

1✔
876
        // add special representation of artifact provides
1✔
877
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
878

1✔
879
        image.SetModified(time.Now())
1✔
880
        if res, err := collImg.ReplaceOne(
1✔
881
                ctx, bson.M{"_id": image.Id}, image,
1✔
882
        ); err != nil {
1✔
883
                return false, err
×
884
        } else if res.MatchedCount == 0 {
1✔
885
                return false, nil
×
886
        }
×
887

888
        return true, nil
1✔
889
}
890

891
// ImageByNameAndDeviceType finds image with specified application name and target device type
892
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
893
        name, deviceType string) (*model.Image, error) {
9✔
894

9✔
895
        if len(name) == 0 {
10✔
896
                return nil, ErrImagesStorageInvalidArtifactName
1✔
897
        }
1✔
898

899
        if len(deviceType) == 0 {
9✔
900
                return nil, ErrImagesStorageInvalidDeviceType
1✔
901
        }
1✔
902

903
        // equal to device type & software version (application name + version)
904
        query := bson.M{
7✔
905
                StorageKeyImageName:        name,
7✔
906
                StorageKeyImageDeviceTypes: deviceType,
7✔
907
        }
7✔
908

7✔
909
        // If multiple entries matches, pick the smallest one.
7✔
910
        findOpts := mopts.FindOne()
7✔
911
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
7✔
912

7✔
913
        dbName := mstore.DbFromContext(ctx, DatabaseName)
7✔
914
        database := db.client.Database(dbName)
7✔
915
        collImg := database.Collection(CollectionImages)
7✔
916

7✔
917
        // Both we lookup unique object, should be one or none.
7✔
918
        var image model.Image
7✔
919
        if err := collImg.FindOne(ctx, query, findOpts).
7✔
920
                Decode(&image); err != nil {
11✔
921
                if err == mongo.ErrNoDocuments {
8✔
922
                        return nil, nil
4✔
923
                }
4✔
924
                return nil, err
×
925
        }
926

927
        return &image, nil
3✔
928
}
929

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

1✔
934
        if len(deviceType) == 0 {
1✔
935
                return nil, ErrImagesStorageInvalidDeviceType
×
936
        }
×
937

938
        if len(ids) == 0 {
1✔
939
                return nil, ErrImagesStorageInvalidID
×
940
        }
×
941

942
        query := bson.D{
1✔
943
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
944
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
945
        }
1✔
946

1✔
947
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
948
        collImg := database.Collection(CollectionImages)
1✔
949

1✔
950
        // If multiple entries matches, pick the smallest one
1✔
951
        findOpts := mopts.FindOne()
1✔
952
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
953

1✔
954
        // Both we lookup unique object, should be one or none.
1✔
955
        var image model.Image
1✔
956
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
957
                Decode(&image); err != nil {
1✔
958
                if err == mongo.ErrNoDocuments {
×
959
                        return nil, nil
×
960
                }
×
961
                return nil, err
×
962
        }
963

964
        return &image, nil
1✔
965
}
966

967
// ImagesByName finds images with specified artifact name
968
func (db *DataStoreMongo) ImagesByName(
969
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
970

1✔
971
        var images []*model.Image
1✔
972

1✔
973
        if len(name) == 0 {
1✔
974
                return nil, ErrImagesStorageInvalidName
×
975
        }
×
976

977
        // equal to artifact name
978
        query := bson.M{
1✔
979
                StorageKeyImageName: name,
1✔
980
        }
1✔
981

1✔
982
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
983
        collImg := database.Collection(CollectionImages)
1✔
984
        cursor, err := collImg.Find(ctx, query)
1✔
985
        if err != nil {
1✔
986
                return nil, err
×
987
        }
×
988
        // Both we lookup unique object, should be one or none.
989
        if err = cursor.All(ctx, &images); err != nil {
1✔
990
                return nil, err
×
991
        }
×
992

993
        return images, nil
1✔
994
}
995

996
func newDependsConflictError(mgoErr mongo.WriteError) *model.ConflictError {
7✔
997
        var err error
7✔
998
        conflictErr := model.NewConflictError(ErrConflictingDepends)
7✔
999
        // Try to lookup the document that caused the index violation:
7✔
1000
        if raw, ok := mgoErr.Raw.Lookup("keyValue").DocumentOK(); ok {
14✔
1001
                if raw, ok = raw.Lookup(StorageKeyImageDependsIdx).DocumentOK(); ok {
14✔
1002
                        var conflicts map[string]interface{}
7✔
1003
                        err = bson.Unmarshal([]byte(raw), &conflicts)
7✔
1004
                        if err == nil {
14✔
1005
                                _ = conflictErr.WithMetadata(
7✔
1006
                                        map[string]interface{}{
7✔
1007
                                                "conflict": conflicts,
7✔
1008
                                        },
7✔
1009
                                )
7✔
1010
                        }
7✔
1011
                }
1012
        }
1013
        return conflictErr
7✔
1014
}
1015

1016
// Insert persists object
1017
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
69✔
1018

69✔
1019
        if image == nil {
69✔
1020
                return ErrImagesStorageInvalidImage
×
1021
        }
×
1022

1023
        if err := image.Validate(); err != nil {
69✔
1024
                return err
×
1025
        }
×
1026

1027
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
69✔
1028
        collImg := database.Collection(CollectionImages)
69✔
1029

69✔
1030
        // add special representation of artifact provides
69✔
1031
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
69✔
1032

69✔
1033
        _, err := collImg.InsertOne(ctx, image)
69✔
1034
        if err != nil {
76✔
1035
                var wExc mongo.WriteException
7✔
1036
                if errors.As(err, &wExc) {
14✔
1037
                        for _, wErr := range wExc.WriteErrors {
14✔
1038
                                if !mongo.IsDuplicateKeyError(wErr) {
7✔
1039
                                        continue
×
1040
                                }
1041
                                return newDependsConflictError(wErr)
7✔
1042
                        }
1043
                }
1044
                return err
×
1045
        }
1046

1047
        return nil
62✔
1048
}
1049

1050
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
2✔
1051
        collUploads := db.client.
2✔
1052
                Database(DatabaseName).
2✔
1053
                Collection(CollectionUploadIntents)
2✔
1054
        if idty := identity.FromContext(ctx); idty != nil {
3✔
1055
                link.TenantID = idty.Tenant
1✔
1056
        }
1✔
1057
        _, err := collUploads.InsertOne(ctx, link)
2✔
1058
        return err
2✔
1059
}
1060

1061
func (db *DataStoreMongo) UpdateUploadIntentStatus(
1062
        ctx context.Context,
1063
        id string,
1064
        from, to model.LinkStatus,
1065
) error {
6✔
1066
        collUploads := db.client.
6✔
1067
                Database(DatabaseName).
6✔
1068
                Collection(CollectionUploadIntents)
6✔
1069
        q := bson.D{
6✔
1070
                {Key: "_id", Value: id},
6✔
1071
                {Key: "status", Value: from},
6✔
1072
        }
6✔
1073
        if idty := identity.FromContext(ctx); idty != nil {
11✔
1074
                q = append(q, bson.E{
5✔
1075
                        Key:   StorageKeyTenantId,
5✔
1076
                        Value: idty.Tenant,
5✔
1077
                })
5✔
1078
        }
5✔
1079
        update := bson.D{{
6✔
1080
                Key: "updated_ts", Value: time.Now(),
6✔
1081
        }}
6✔
1082
        if from != to {
12✔
1083
                update = append(update, bson.E{
6✔
1084
                        Key: "status", Value: to,
6✔
1085
                })
6✔
1086
        }
6✔
1087
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
6✔
1088
                {Key: "$set", Value: update},
6✔
1089
        })
6✔
1090
        if err != nil {
7✔
1091
                return err
1✔
1092
        } else if res.MatchedCount == 0 {
8✔
1093
                return store.ErrNotFound
2✔
1094
        }
2✔
1095
        return nil
3✔
1096
}
1097

1098
func (db *DataStoreMongo) FindUploadLinks(
1099
        ctx context.Context,
1100
        expiredAt time.Time,
1101
) (store.Iterator[model.UploadLink], error) {
1✔
1102
        collUploads := db.client.
1✔
1103
                Database(DatabaseName).
1✔
1104
                Collection(CollectionUploadIntents)
1✔
1105

1✔
1106
        q := bson.D{{
1✔
1107
                Key: "status",
1✔
1108
                Value: bson.D{{
1✔
1109
                        Key:   "$lt",
1✔
1110
                        Value: model.LinkStatusProcessedBit,
1✔
1111
                }},
1✔
1112
        }, {
1✔
1113
                Key: "expire",
1✔
1114
                Value: bson.D{{
1✔
1115
                        Key:   "$lt",
1✔
1116
                        Value: expiredAt,
1✔
1117
                }},
1✔
1118
        }}
1✔
1119
        cur, err := collUploads.Find(ctx, q)
1✔
1120
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1121
}
1✔
1122

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

1✔
1127
        if len(id) == 0 {
1✔
1128
                return nil, ErrImagesStorageInvalidID
×
1129
        }
×
1130

1131
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1132
        collImg := database.Collection(CollectionImages)
1✔
1133
        projection := bson.M{
1✔
1134
                StorageKeyImageDependsIdx:  0,
1✔
1135
                StorageKeyImageProvidesIdx: 0,
1✔
1136
        }
1✔
1137
        findOptions := mopts.FindOne()
1✔
1138
        findOptions.SetProjection(projection)
1✔
1139

1✔
1140
        var image model.Image
1✔
1141
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
1142
                Decode(&image); err != nil {
2✔
1143
                if err == mongo.ErrNoDocuments {
2✔
1144
                        return nil, nil
1✔
1145
                }
1✔
1146
                return nil, err
×
1147
        }
1148

1149
        return &image, nil
1✔
1150
}
1151

1152
// IsArtifactUnique checks if there is no artifact with the same artifactName
1153
// supporting one of the device types from deviceTypesCompatible list.
1154
// Returns true, nil if artifact is unique;
1155
// false, nil if artifact is not unique;
1156
// false, error in case of error.
1157
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1158
        artifactName string, deviceTypesCompatible []string) (bool, error) {
6✔
1159

6✔
1160
        if len(artifactName) == 0 {
7✔
1161
                return false, ErrImagesStorageInvalidArtifactName
1✔
1162
        }
1✔
1163

1164
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1165
        collImg := database.Collection(CollectionImages)
5✔
1166

5✔
1167
        query := bson.M{
5✔
1168
                "$and": []bson.M{
5✔
1169
                        {
5✔
1170
                                StorageKeyImageName: artifactName,
5✔
1171
                        },
5✔
1172
                        {
5✔
1173
                                StorageKeyImageDeviceTypes: bson.M{
5✔
1174
                                        "$in": deviceTypesCompatible},
5✔
1175
                        },
5✔
1176
                },
5✔
1177
        }
5✔
1178

5✔
1179
        // do part of the job manually
5✔
1180
        // if candidate images have any extra 'depends' - guaranteed non-overlap
5✔
1181
        // otherwise it's a match
5✔
1182
        cur, err := collImg.Find(ctx, query)
5✔
1183
        if err != nil {
5✔
1184
                return false, err
×
1185
        }
×
1186

1187
        var images []model.Image
5✔
1188
        err = cur.All(ctx, &images)
5✔
1189
        if err != nil {
5✔
1190
                return false, err
×
1191
        }
×
1192

1193
        for _, i := range images {
6✔
1194
                // the artifact already has same name and overlapping dev type
1✔
1195
                // if there are no more depends than dev type - it's not unique
1✔
1196
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1197
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1198
                                return false, nil
1✔
1199
                        }
1✔
1200
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1201
                        return false, nil
×
1202
                }
×
1203
        }
1204

1205
        return true, nil
4✔
1206
}
1207

1208
// Delete image specified by ID
1209
// Noop on if not found.
1210
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
3✔
1211

3✔
1212
        if len(id) == 0 {
3✔
1213
                return ErrImagesStorageInvalidID
×
1214
        }
×
1215

1216
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1217
        collImg := database.Collection(CollectionImages)
3✔
1218

3✔
1219
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
1220
                if res.DeletedCount == 0 {
×
1221
                        return nil
×
1222
                }
×
1223
                return err
×
1224
        }
1225

1226
        return nil
3✔
1227
}
1228

1229
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
98✔
1230
        if filt != nil && filt.Sort != "" {
118✔
1231
                sortParts := strings.SplitN(filt.Sort, ":", 2)
20✔
1232
                if len(sortParts) == 2 &&
20✔
1233
                        (sortParts[0] == "name" ||
20✔
1234
                                sortParts[0] == "modified" ||
20✔
1235
                                sortParts[0] == "artifacts_count" ||
20✔
1236
                                sortParts[0] == "tags") {
40✔
1237
                        sortField := sortParts[0]
20✔
1238
                        sortOrder := 1
20✔
1239
                        if sortParts[1] == model.SortDirectionDescending {
32✔
1240
                                sortOrder = -1
12✔
1241
                        }
12✔
1242
                        return sortField, sortOrder
20✔
1243
                }
1244
        }
1245
        return "", 0
78✔
1246
}
1247

1248
// ListImages lists all images
1249
func (db *DataStoreMongo) ListImages(
1250
        ctx context.Context,
1251
        filt *model.ReleaseOrImageFilter,
1252
) ([]*model.Image, int, error) {
15✔
1253

15✔
1254
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1255
        collImg := database.Collection(CollectionImages)
15✔
1256

15✔
1257
        filters := bson.M{}
15✔
1258
        if filt != nil {
25✔
1259
                if filt.Name != "" {
14✔
1260
                        filters[StorageKeyImageName] = bson.M{
4✔
1261
                                "$regex": primitive.Regex{
4✔
1262
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
4✔
1263
                                        Options: "i",
4✔
1264
                                },
4✔
1265
                        }
4✔
1266
                }
4✔
1267
                if filt.Description != "" {
12✔
1268
                        filters[StorageKeyImageDescription] = bson.M{
2✔
1269
                                "$regex": primitive.Regex{
2✔
1270
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
1271
                                        Options: "i",
2✔
1272
                                },
2✔
1273
                        }
2✔
1274
                }
2✔
1275
                if filt.DeviceType != "" {
11✔
1276
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1277
                                "$regex": primitive.Regex{
1✔
1278
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1279
                                        Options: "i",
1✔
1280
                                },
1✔
1281
                        }
1✔
1282
                }
1✔
1283

1284
        }
1285

1286
        projection := bson.M{
15✔
1287
                StorageKeyImageDependsIdx:  0,
15✔
1288
                StorageKeyImageProvidesIdx: 0,
15✔
1289
        }
15✔
1290
        findOptions := &mopts.FindOptions{}
15✔
1291
        findOptions.SetProjection(projection)
15✔
1292
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
16✔
1293
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1294
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1295
        }
1✔
1296

1297
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
15✔
1298
        if sortField == "" || sortField == "name" {
28✔
1299
                sortField = StorageKeyImageName
13✔
1300
        }
13✔
1301
        if sortOrder == 0 {
27✔
1302
                sortOrder = 1
12✔
1303
        }
12✔
1304
        findOptions.SetSort(bson.D{
15✔
1305
                {Key: sortField, Value: sortOrder},
15✔
1306
                {Key: "_id", Value: sortOrder},
15✔
1307
        })
15✔
1308

15✔
1309
        cursor, err := collImg.Find(ctx, filters, findOptions)
15✔
1310
        if err != nil {
15✔
1311
                return nil, 0, err
×
1312
        }
×
1313

1314
        // NOTE: cursor.All closes the cursor before returning
1315
        var images []*model.Image
15✔
1316
        if err := cursor.All(ctx, &images); err != nil {
15✔
1317
                if err == mongo.ErrNoDocuments {
×
1318
                        return nil, 0, nil
×
1319
                }
×
1320
                return nil, 0, err
×
1321
        }
1322

1323
        count, err := collImg.CountDocuments(ctx, filters)
15✔
1324
        if err != nil {
15✔
1325
                return nil, -1, ErrDevicesCountFailed
×
1326
        }
×
1327

1328
        return images, int(count), nil
15✔
1329
}
1330

1331
func (db *DataStoreMongo) DeleteImagesByNames(ctx context.Context, names []string) error {
1✔
1332
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1333
        collDevs := database.Collection(CollectionImages)
1✔
1334
        query := bson.M{
1✔
1335
                StorageKeyImageName: bson.M{
1✔
1336
                        "$in": names,
1✔
1337
                },
1✔
1338
        }
1✔
1339
        _, err := collDevs.DeleteMany(ctx, query)
1✔
1340
        return err
1✔
1341
}
1✔
1342

1343
// device deployment log
1344
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1345
        log model.DeploymentLog) error {
9✔
1346

9✔
1347
        if err := log.Validate(); err != nil {
12✔
1348
                return err
3✔
1349
        }
3✔
1350

1351
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1352
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1353

6✔
1354
        query := bson.D{
6✔
1355
                {Key: StorageKeyDeviceDeploymentDeviceId,
6✔
1356
                        Value: log.DeviceID},
6✔
1357
                {Key: StorageKeyDeviceDeploymentDeploymentID,
6✔
1358
                        Value: log.DeploymentID},
6✔
1359
        }
6✔
1360

6✔
1361
        // update log messages
6✔
1362
        // if the deployment log is already present than messages will be overwritten
6✔
1363
        update := bson.D{
6✔
1364
                {Key: "$set", Value: bson.M{
6✔
1365
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
6✔
1366
                }},
6✔
1367
        }
6✔
1368
        updateOptions := mopts.Update()
6✔
1369
        updateOptions.SetUpsert(true)
6✔
1370
        if _, err := collLogs.UpdateOne(
6✔
1371
                ctx, query, update, updateOptions); err != nil {
6✔
1372
                return err
×
1373
        }
×
1374

1375
        return nil
6✔
1376
}
1377

1378
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1379
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
6✔
1380

6✔
1381
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1382
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1383

6✔
1384
        query := bson.M{
6✔
1385
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
6✔
1386
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1387
        }
6✔
1388

6✔
1389
        var depl model.DeploymentLog
6✔
1390
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
8✔
1391
                if err == mongo.ErrNoDocuments {
4✔
1392
                        return nil, nil
2✔
1393
                }
2✔
1394
                return nil, err
×
1395
        }
1396

1397
        return &depl, nil
4✔
1398
}
1399

1400
// device deployments
1401

1402
// Insert persists device deployment object
1403
func (db *DataStoreMongo) InsertDeviceDeployment(
1404
        ctx context.Context,
1405
        deviceDeployment *model.DeviceDeployment,
1406
        incrementDeviceCount bool,
1407
) error {
29✔
1408
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1409
        c := database.Collection(CollectionDevices)
29✔
1410

29✔
1411
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
49✔
1412
                startedTime := time.Now().UTC()
20✔
1413
                deviceDeployment.Started = &startedTime
20✔
1414
        }
20✔
1415

1416
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
29✔
1417
                return err
×
1418
        }
×
1419

1420
        if incrementDeviceCount {
58✔
1421
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
29✔
1422
                if err != nil {
29✔
1423
                        return err
×
1424
                }
×
1425
        }
1426

1427
        return nil
29✔
1428
}
1429

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

43✔
1435
        if len(deployments) == 0 {
55✔
1436
                return nil
12✔
1437
        }
12✔
1438

1439
        deviceCountIncrements := make(map[string]int)
31✔
1440

31✔
1441
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
31✔
1442
        var list []interface{}
31✔
1443
        for _, deployment := range deployments {
104✔
1444

73✔
1445
                if deployment == nil {
74✔
1446
                        return ErrStorageInvalidDeviceDeployment
1✔
1447
                }
1✔
1448

1449
                if err := deployment.Validate(); err != nil {
74✔
1450
                        return errors.Wrap(err, "Validating device deployment")
2✔
1451
                }
2✔
1452

1453
                list = append(list, deployment)
70✔
1454
                if deployment.Status != model.DeviceDeploymentStatusPending {
84✔
1455
                        startedTime := time.Now().UTC()
14✔
1456
                        deployment.Started = &startedTime
14✔
1457
                }
14✔
1458
                deviceCountIncrements[deployment.DeploymentId]++
70✔
1459
        }
1460

1461
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
28✔
1462
        collDevs := database.Collection(CollectionDevices)
28✔
1463

28✔
1464
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
28✔
1465
                return err
×
1466
        }
×
1467

1468
        for deploymentID := range deviceCountIncrements {
63✔
1469
                err := db.IncrementDeploymentDeviceCount(
35✔
1470
                        ctx,
35✔
1471
                        deploymentID,
35✔
1472
                        deviceCountIncrements[deploymentID],
35✔
1473
                )
35✔
1474
                if err != nil {
35✔
1475
                        return err
×
1476
                }
×
1477
        }
1478

1479
        return nil
28✔
1480
}
1481

1482
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1483
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1484
        ctx context.Context,
1485
        deviceID string,
1486
) (*model.DeviceDeployment, error) {
6✔
1487

6✔
1488
        // Verify ID formatting
6✔
1489
        if len(deviceID) == 0 {
7✔
1490
                return nil, ErrStorageInvalidID
1✔
1491
        }
1✔
1492

1493
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1494
        collDevs := database.Collection(CollectionDevices)
5✔
1495

5✔
1496
        // Device should know only about deployments that are not finished
5✔
1497
        query := bson.D{
5✔
1498
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
5✔
1499
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1500
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1501
                        {Key: "$exists", Value: false},
5✔
1502
                }},
5✔
1503
        }
5✔
1504

5✔
1505
        // Find the oldest one by sorting the creation timestamp
5✔
1506
        // in ascending order.
5✔
1507
        findOptions := mopts.FindOne()
5✔
1508
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
5✔
1509

5✔
1510
        // Select only the oldest one that have not been finished yet.
5✔
1511
        deployment := new(model.DeviceDeployment)
5✔
1512
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1513
                Decode(deployment); err != nil {
8✔
1514
                if err == mongo.ErrNoDocuments {
5✔
1515
                        return nil, nil
2✔
1516
                }
2✔
1517
                return nil, err
1✔
1518
        }
1519

1520
        return deployment, nil
3✔
1521
}
1522

1523
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1524
// matching device id that has not finished yet.
1525
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1526
        ctx context.Context,
1527
        deviceID string,
1528
) (*model.DeviceDeployment, error) {
6✔
1529

6✔
1530
        // Verify ID formatting
6✔
1531
        if len(deviceID) == 0 {
7✔
1532
                return nil, ErrStorageInvalidID
1✔
1533
        }
1✔
1534

1535
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1536
        collDevs := database.Collection(CollectionDevices)
5✔
1537

5✔
1538
        query := bson.D{
5✔
1539
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
5✔
1540
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1541
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1542
                        {Key: "$exists", Value: false},
5✔
1543
                }},
5✔
1544
        }
5✔
1545

5✔
1546
        // Find the latest one by sorting by the creation timestamp
5✔
1547
        // in ascending order.
5✔
1548
        findOptions := mopts.FindOne()
5✔
1549
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
5✔
1550

5✔
1551
        // Select only the latest one that have not been finished yet.
5✔
1552
        var deployment *model.DeviceDeployment
5✔
1553
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1554
                Decode(&deployment); err != nil {
8✔
1555
                if err == mongo.ErrNoDocuments {
5✔
1556
                        return nil, nil
2✔
1557
                }
2✔
1558
                return nil, err
1✔
1559
        }
1560

1561
        return deployment, nil
3✔
1562
}
1563

1564
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1565
        ctx context.Context,
1566
        deviceID string,
1567
        deploymentID string,
1568
        ddState model.DeviceDeploymentState,
1569
        currentStatus model.DeviceDeploymentStatus,
1570
) (model.DeviceDeploymentStatus, error) {
12✔
1571

12✔
1572
        // Verify ID formatting
12✔
1573
        if len(deviceID) == 0 ||
12✔
1574
                len(deploymentID) == 0 {
14✔
1575
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
2✔
1576
        }
2✔
1577

1578
        if err := ddState.Validate(); err != nil {
11✔
1579
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1580
        }
1✔
1581

1582
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1583
        collDevs := database.Collection(CollectionDevices)
9✔
1584

9✔
1585
        // Device should know only about deployments that are not finished
9✔
1586
        query := bson.D{
9✔
1587
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1588
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1589
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1590
                        {Key: "$exists", Value: false},
9✔
1591
                }},
9✔
1592
        }
9✔
1593

9✔
1594
        // update status field
9✔
1595
        set := bson.M{
9✔
1596
                StorageKeyDeviceDeploymentStatus: ddState.Status,
9✔
1597
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
9✔
1598
        }
9✔
1599
        // and finish time if provided
9✔
1600
        if ddState.FinishTime != nil {
11✔
1601
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
2✔
1602
        }
2✔
1603

1604
        if len(ddState.SubState) > 0 {
12✔
1605
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
3✔
1606
        }
3✔
1607

1608
        if currentStatus == model.DeviceDeploymentStatusPending &&
9✔
1609
                ddState.Status != currentStatus {
11✔
1610
                startedTime := time.Now().UTC()
2✔
1611
                set[StorageKeyDeviceDeploymentStarted] = startedTime
2✔
1612
        }
2✔
1613

1614
        update := bson.D{
9✔
1615
                {Key: "$set", Value: set},
9✔
1616
        }
9✔
1617

9✔
1618
        var old model.DeviceDeployment
9✔
1619

9✔
1620
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
9✔
1621
                Decode(&old); err != nil {
11✔
1622
                if err == mongo.ErrNoDocuments {
4✔
1623
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
2✔
1624
                }
2✔
1625
                return model.DeviceDeploymentStatusNull, err
×
1626

1627
        }
1628

1629
        return old.Status, nil
7✔
1630
}
1631

1632
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1633
        deviceID string, deploymentID string, log bool) error {
7✔
1634

7✔
1635
        // Verify ID formatting
7✔
1636
        if len(deviceID) == 0 ||
7✔
1637
                len(deploymentID) == 0 {
9✔
1638
                return ErrStorageInvalidID
2✔
1639
        }
2✔
1640

1641
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1642
        collDevs := database.Collection(CollectionDevices)
5✔
1643

5✔
1644
        selector := bson.D{
5✔
1645
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1646
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
5✔
1647
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1648
                        {Key: "$exists", Value: false},
5✔
1649
                }},
5✔
1650
        }
5✔
1651

5✔
1652
        update := bson.D{
5✔
1653
                {Key: "$set", Value: bson.M{
5✔
1654
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
5✔
1655
        }
5✔
1656

5✔
1657
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
5✔
1658
                return err
×
1659
        } else if res.MatchedCount == 0 {
7✔
1660
                return ErrStorageNotFound
2✔
1661
        }
2✔
1662

1663
        return nil
3✔
1664
}
1665

1666
// SaveDeviceDeploymentRequest saves device deployment request
1667
// with the device deployment object
1668
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1669
        ctx context.Context,
1670
        ID string,
1671
        request *model.DeploymentNextRequest,
1672
) error {
4✔
1673

4✔
1674
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
1675
        collDevs := database.Collection(CollectionDevices)
4✔
1676

4✔
1677
        res, err := collDevs.UpdateOne(
4✔
1678
                ctx,
4✔
1679
                bson.D{{Key: StorageKeyId, Value: ID}},
4✔
1680
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
4✔
1681
        )
4✔
1682
        if err != nil {
4✔
1683
                return err
×
1684
        } else if res.MatchedCount == 0 {
5✔
1685
                return ErrStorageNotFound
1✔
1686
        }
1✔
1687
        return nil
3✔
1688
}
1689

1690
// AssignArtifact assigns artifact to the device deployment
1691
func (db *DataStoreMongo) AssignArtifact(
1692
        ctx context.Context,
1693
        deviceID string,
1694
        deploymentID string,
1695
        artifact *model.Image,
1696
) error {
1✔
1697

1✔
1698
        // Verify ID formatting
1✔
1699
        if len(deviceID) == 0 ||
1✔
1700
                len(deploymentID) == 0 {
1✔
1701
                return ErrStorageInvalidID
×
1702
        }
×
1703

1704
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1705
        collDevs := database.Collection(CollectionDevices)
1✔
1706

1✔
1707
        selector := bson.D{
1✔
1708
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1709
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1710
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1711
                        {Key: "$exists", Value: false},
1✔
1712
                }},
1✔
1713
        }
1✔
1714

1✔
1715
        update := bson.D{
1✔
1716
                {Key: "$set", Value: bson.M{
1✔
1717
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1718
                }},
1✔
1719
        }
1✔
1720

1✔
1721
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1722
                return err
×
1723
        } else if res.MatchedCount == 0 {
1✔
1724
                return ErrStorageNotFound
×
1725
        }
×
1726

1727
        return nil
1✔
1728
}
1729

1730
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1731
        id string) (model.Stats, error) {
6✔
1732

6✔
1733
        if len(id) == 0 {
6✔
1734
                return nil, ErrStorageInvalidID
×
1735
        }
×
1736

1737
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1738
        collDevs := database.Collection(CollectionDevices)
6✔
1739

6✔
1740
        match := bson.D{
6✔
1741
                {Key: "$match", Value: bson.M{
6✔
1742
                        StorageKeyDeviceDeploymentDeploymentID: id,
6✔
1743
                        StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1744
                                {Key: "$exists", Value: false},
6✔
1745
                        },
6✔
1746
                }},
6✔
1747
        }
6✔
1748
        group := bson.D{
6✔
1749
                {Key: "$group", Value: bson.D{
6✔
1750
                        {Key: "_id",
6✔
1751
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
6✔
1752
                        {Key: "count",
6✔
1753
                                Value: bson.M{"$sum": 1}}},
6✔
1754
                },
6✔
1755
        }
6✔
1756
        pipeline := []bson.D{
6✔
1757
                match,
6✔
1758
                group,
6✔
1759
        }
6✔
1760
        var results []struct {
6✔
1761
                Status model.DeviceDeploymentStatus `bson:"_id"`
6✔
1762
                Count  int
6✔
1763
        }
6✔
1764
        cursor, err := collDevs.Aggregate(ctx, pipeline)
6✔
1765
        if err != nil {
6✔
1766
                return nil, err
×
1767
        }
×
1768
        if err := cursor.All(ctx, &results); err != nil {
6✔
1769
                if err == mongo.ErrNoDocuments {
×
1770
                        return nil, nil
×
1771
                }
×
1772
                return nil, err
×
1773
        }
1774

1775
        raw := model.NewDeviceDeploymentStats()
6✔
1776
        for _, res := range results {
17✔
1777
                raw.Set(res.Status, res.Count)
11✔
1778
        }
11✔
1779
        return raw, nil
6✔
1780
}
1781

1782
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1783
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1784
        deploymentID string) ([]model.DeviceDeployment, error) {
6✔
1785

6✔
1786
        statuses := []model.DeviceDeployment{}
6✔
1787
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1788
        collDevs := database.Collection(CollectionDevices)
6✔
1789

6✔
1790
        query := bson.M{
6✔
1791
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1792
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1793
                        {Key: "$exists", Value: false},
6✔
1794
                },
6✔
1795
        }
6✔
1796

6✔
1797
        cursor, err := collDevs.Find(ctx, query)
6✔
1798
        if err != nil {
6✔
1799
                return nil, err
×
1800
        }
×
1801

1802
        if err = cursor.All(ctx, &statuses); err != nil {
6✔
1803
                if err == mongo.ErrNoDocuments {
×
1804
                        return nil, nil
×
1805
                }
×
1806
                return nil, err
×
1807
        }
1808

1809
        return statuses, nil
6✔
1810
}
1811

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

15✔
1815
        statuses := []model.DeviceDeployment{}
15✔
1816
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1817
        collDevs := database.Collection(CollectionDevices)
15✔
1818

15✔
1819
        query := bson.D{
15✔
1820
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
15✔
1821
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
15✔
1822
                        {Key: "$exists", Value: false},
15✔
1823
                }},
15✔
1824
        }
15✔
1825
        if q.Status != nil {
19✔
1826
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
5✔
1827
                        query = append(query, bson.E{
1✔
1828
                                Key: "status", Value: bson.D{{
1✔
1829
                                        Key:   "$gte",
1✔
1830
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1831
                                }, {
1✔
1832
                                        Key:   "$lte",
1✔
1833
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1834
                                }},
1✔
1835
                        })
1✔
1836
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
4✔
1837
                        query = append(query, bson.E{
×
1838
                                Key: "status", Value: bson.D{{
×
1839
                                        Key:   "$gte",
×
1840
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1841
                                }, {
×
1842
                                        Key:   "$lte",
×
1843
                                        Value: model.DeviceDeploymentStatusPending,
×
1844
                                }},
×
1845
                        })
×
1846
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
4✔
1847
                        query = append(query, bson.E{
1✔
1848
                                Key: "status", Value: bson.D{{
1✔
1849
                                        Key: "$in",
1✔
1850
                                        Value: []model.DeviceDeploymentStatus{
1✔
1851
                                                model.DeviceDeploymentStatusFailure,
1✔
1852
                                                model.DeviceDeploymentStatusAborted,
1✔
1853
                                                model.DeviceDeploymentStatusSuccess,
1✔
1854
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1855
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1856
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1857
                                        },
1✔
1858
                                }},
1✔
1859
                        })
1✔
1860
                } else {
3✔
1861
                        var status model.DeviceDeploymentStatus
2✔
1862
                        err := status.UnmarshalText([]byte(*q.Status))
2✔
1863
                        if err != nil {
3✔
1864
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1865
                        }
1✔
1866
                        query = append(query, bson.E{
1✔
1867
                                Key: "status", Value: status,
1✔
1868
                        })
1✔
1869
                }
1870
        }
1871

1872
        options := mopts.Find()
14✔
1873
        sortFieldQuery := bson.D{
14✔
1874
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
14✔
1875
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
14✔
1876
        }
14✔
1877
        options.SetSort(sortFieldQuery)
14✔
1878
        if q.Skip > 0 {
17✔
1879
                options.SetSkip(int64(q.Skip))
3✔
1880
        }
3✔
1881
        if q.Limit > 0 {
19✔
1882
                options.SetLimit(int64(q.Limit))
5✔
1883
        } else {
14✔
1884
                options.SetLimit(DefaultDocumentLimit)
9✔
1885
        }
9✔
1886

1887
        cursor, err := collDevs.Find(ctx, query, options)
14✔
1888
        if err != nil {
15✔
1889
                return nil, -1, err
1✔
1890
        }
1✔
1891

1892
        if err = cursor.All(ctx, &statuses); err != nil {
13✔
1893
                if err == mongo.ErrNoDocuments {
×
1894
                        return nil, -1, nil
×
1895
                }
×
1896
                return nil, -1, err
×
1897
        }
1898

1899
        count, err := collDevs.CountDocuments(ctx, query)
13✔
1900
        if err != nil {
13✔
1901
                return nil, -1, ErrDevicesCountFailed
×
1902
        }
×
1903

1904
        return statuses, int(count), nil
13✔
1905
}
1906

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

10✔
1910
        statuses := []model.DeviceDeployment{}
10✔
1911
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
10✔
1912
        collDevs := database.Collection(CollectionDevices)
10✔
1913

10✔
1914
        query := bson.D{}
10✔
1915
        if q.DeviceID != "" {
19✔
1916
                query = append(query, bson.E{
9✔
1917
                        Key:   StorageKeyDeviceDeploymentDeviceId,
9✔
1918
                        Value: q.DeviceID,
9✔
1919
                })
9✔
1920
        } else if len(q.IDs) > 0 {
11✔
1921
                query = append(query, bson.E{
1✔
1922
                        Key: StorageKeyId,
1✔
1923
                        Value: bson.D{{
1✔
1924
                                Key:   "$in",
1✔
1925
                                Value: q.IDs,
1✔
1926
                        }},
1✔
1927
                })
1✔
1928
        }
1✔
1929

1930
        if q.Status != nil {
18✔
1931
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
9✔
1932
                        query = append(query, bson.E{
1✔
1933
                                Key: "status", Value: bson.D{{
1✔
1934
                                        Key:   "$gte",
1✔
1935
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1936
                                }, {
1✔
1937
                                        Key:   "$lte",
1✔
1938
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1939
                                }},
1✔
1940
                        })
1✔
1941
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
9✔
1942
                        query = append(query, bson.E{
1✔
1943
                                Key: "status", Value: bson.D{{
1✔
1944
                                        Key:   "$gte",
1✔
1945
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1946
                                }, {
1✔
1947
                                        Key:   "$lte",
1✔
1948
                                        Value: model.DeviceDeploymentStatusPending,
1✔
1949
                                }},
1✔
1950
                        })
1✔
1951
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
8✔
1952
                        query = append(query, bson.E{
1✔
1953
                                Key: "status", Value: bson.D{{
1✔
1954
                                        Key: "$in",
1✔
1955
                                        Value: []model.DeviceDeploymentStatus{
1✔
1956
                                                model.DeviceDeploymentStatusFailure,
1✔
1957
                                                model.DeviceDeploymentStatusAborted,
1✔
1958
                                                model.DeviceDeploymentStatusSuccess,
1✔
1959
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1960
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1961
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1962
                                        },
1✔
1963
                                }},
1✔
1964
                        })
1✔
1965
                } else {
6✔
1966
                        var status model.DeviceDeploymentStatus
5✔
1967
                        err := status.UnmarshalText([]byte(*q.Status))
5✔
1968
                        if err != nil {
6✔
1969
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1970
                        }
1✔
1971
                        query = append(query, bson.E{
4✔
1972
                                Key: "status", Value: status,
4✔
1973
                        })
4✔
1974
                }
1975
        }
1976

1977
        options := mopts.Find()
9✔
1978
        sortFieldQuery := bson.D{
9✔
1979
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
9✔
1980
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
9✔
1981
        }
9✔
1982
        options.SetSort(sortFieldQuery)
9✔
1983
        if q.Skip > 0 {
10✔
1984
                options.SetSkip(int64(q.Skip))
1✔
1985
        }
1✔
1986
        if q.Limit > 0 {
18✔
1987
                options.SetLimit(int64(q.Limit))
9✔
1988
        } else {
9✔
1989
                options.SetLimit(DefaultDocumentLimit)
×
1990
        }
×
1991

1992
        cursor, err := collDevs.Find(ctx, query, options)
9✔
1993
        if err != nil {
9✔
1994
                return nil, -1, err
×
1995
        }
×
1996

1997
        if err = cursor.All(ctx, &statuses); err != nil {
9✔
1998
                if err == mongo.ErrNoDocuments {
×
1999
                        return nil, 0, nil
×
2000
                }
×
2001
                return nil, -1, err
×
2002
        }
2003

2004
        maxCount := maxCountDocuments
9✔
2005
        countOptions := &mopts.CountOptions{
9✔
2006
                Limit: &maxCount,
9✔
2007
        }
9✔
2008
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
9✔
2009
        if err != nil {
9✔
2010
                return nil, -1, ErrDevicesCountFailed
×
2011
        }
×
2012

2013
        return statuses, int(count), nil
9✔
2014
}
2015

2016
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
2017
// `deviceID`, false otherwise. In case of errors returns false and an error
2018
// that occurred
2019
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
2020
        deploymentID string, deviceID string) (bool, error) {
7✔
2021

7✔
2022
        var dep model.DeviceDeployment
7✔
2023
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2024
        collDevs := database.Collection(CollectionDevices)
7✔
2025

7✔
2026
        query := bson.D{
7✔
2027
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
2028
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
2029
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
2030
                        {Key: "$exists", Value: false},
7✔
2031
                }},
7✔
2032
        }
7✔
2033

7✔
2034
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
10✔
2035
                if err == mongo.ErrNoDocuments {
6✔
2036
                        return false, nil
3✔
2037
                } else {
3✔
2038
                        return false, err
×
2039
                }
×
2040
        }
2041

2042
        return true, nil
4✔
2043
}
2044

2045
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2046
        deploymentId string) error {
3✔
2047

3✔
2048
        if len(deploymentId) == 0 {
4✔
2049
                return ErrStorageInvalidID
1✔
2050
        }
1✔
2051

2052
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2053
        collDevs := database.Collection(CollectionDevices)
2✔
2054
        selector := bson.M{
2✔
2055
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
2✔
2056
                StorageKeyDeviceDeploymentActive:       true,
2✔
2057
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2058
                        {Key: "$exists", Value: false},
2✔
2059
                },
2✔
2060
        }
2✔
2061

2✔
2062
        update := bson.M{
2✔
2063
                "$set": bson.M{
2✔
2064
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2065
                        StorageKeyDeviceDeploymentActive: false,
2✔
2066
                },
2✔
2067
        }
2✔
2068

2✔
2069
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2070
                return err
×
2071
        }
×
2072

2073
        return nil
2✔
2074
}
2075

2076
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2077
        deviceID string) error {
2✔
2078
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2079
        collDevs := database.Collection(CollectionDevices)
2✔
2080
        selector := bson.M{
2✔
2081
                StorageKeyDeviceDeploymentDeviceId: deviceID,
2✔
2082
                StorageKeyDeviceDeploymentActive:   false,
2✔
2083
                StorageKeyDeviceDeploymentDeleted: bson.M{
2✔
2084
                        "$exists": false,
2✔
2085
                },
2✔
2086
        }
2✔
2087

2✔
2088
        now := time.Now()
2✔
2089
        update := bson.M{
2✔
2090
                "$set": bson.M{
2✔
2091
                        StorageKeyDeviceDeploymentDeleted: &now,
2✔
2092
                },
2✔
2093
        }
2✔
2094

2✔
2095
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2096
                return err
×
2097
        }
×
2098

2099
        database = db.client.Database(DatabaseName)
2✔
2100
        collDevs = database.Collection(CollectionDevicesLastStatus)
2✔
2101
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
2✔
2102

2✔
2103
        return err
2✔
2104
}
2105

2106
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2107
        deviceId string) error {
2✔
2108

2✔
2109
        if len(deviceId) == 0 {
3✔
2110
                return ErrStorageInvalidID
1✔
2111
        }
1✔
2112

2113
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2114
        collDevs := database.Collection(CollectionDevices)
1✔
2115
        selector := bson.M{
1✔
2116
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2117
                StorageKeyDeviceDeploymentActive:   true,
1✔
2118
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2119
                        {Key: "$exists", Value: false},
1✔
2120
                },
1✔
2121
        }
1✔
2122

1✔
2123
        update := bson.M{
1✔
2124
                "$set": bson.M{
1✔
2125
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2126
                        StorageKeyDeviceDeploymentActive: false,
1✔
2127
                },
1✔
2128
        }
1✔
2129

1✔
2130
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2131
                return err
×
2132
        }
×
2133

2134
        return nil
1✔
2135
}
2136

2137
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2138
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2139

1✔
2140
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2141
        collDevs := database.Collection(CollectionDevices)
1✔
2142

1✔
2143
        filter := bson.M{
1✔
2144
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2145
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2146
        }
1✔
2147
        if !includeDeleted {
2✔
2148
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2149
                        {Key: "$exists", Value: false},
1✔
2150
                }
1✔
2151
        }
1✔
2152

2153
        opts := &mopts.FindOneOptions{}
1✔
2154
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2155

1✔
2156
        var dd model.DeviceDeployment
1✔
2157
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2158
                if err == mongo.ErrNoDocuments {
2✔
2159
                        return nil, ErrStorageNotFound
1✔
2160
                }
1✔
2161
                return nil, err
×
2162
        }
2163

2164
        return &dd, nil
1✔
2165
}
2166

2167
func (db *DataStoreMongo) GetDeviceDeployments(
2168
        ctx context.Context,
2169
        skip int,
2170
        limit int,
2171
        deviceID string,
2172
        active *bool,
2173
        includeDeleted bool,
2174
) ([]model.DeviceDeployment, error) {
4✔
2175

4✔
2176
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2177
        collDevs := database.Collection(CollectionDevices)
4✔
2178

4✔
2179
        filter := bson.M{}
4✔
2180
        if !includeDeleted {
6✔
2181
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2182
                        {Key: "$exists", Value: false},
2✔
2183
                }
2✔
2184
        }
2✔
2185
        if deviceID != "" {
5✔
2186
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2187
        }
1✔
2188
        if active != nil {
5✔
2189
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2190
        }
1✔
2191

2192
        opts := &mopts.FindOptions{}
4✔
2193
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
4✔
2194
        if skip > 0 {
5✔
2195
                opts.SetSkip(int64(skip))
1✔
2196
        }
1✔
2197
        if limit > 0 {
5✔
2198
                opts.SetLimit(int64(limit))
1✔
2199
        }
1✔
2200

2201
        var deviceDeployments []model.DeviceDeployment
4✔
2202
        cursor, err := collDevs.Find(ctx, filter, opts)
4✔
2203
        if err != nil {
4✔
2204
                return nil, err
×
2205
        }
×
2206
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
4✔
2207
                return nil, err
×
2208
        }
×
2209

2210
        return deviceDeployments, nil
4✔
2211
}
2212

2213
// deployments
2214

2215
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2216
        indexes ...mongo.IndexModel) error {
466✔
2217
        ctx := context.Background()
466✔
2218
        dataBase := db.client.Database(dbName)
466✔
2219

466✔
2220
        coll := dataBase.Collection(collName)
466✔
2221
        idxView := coll.Indexes()
466✔
2222
        _, err := idxView.CreateMany(ctx, indexes)
466✔
2223
        return err
466✔
2224
}
466✔
2225

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

16✔
2229
        var idx bson.M
16✔
2230
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
16✔
2231
        collDpl := database.Collection(CollectionDeployments)
16✔
2232
        idxView := collDpl.Indexes()
16✔
2233

16✔
2234
        cursor, err := idxView.List(ctx)
16✔
2235
        if err != nil {
16✔
2236
                // check failed, assume indexing is not there
×
2237
                return false
×
2238
        }
×
2239

2240
        has := map[string]bool{}
16✔
2241
        for cursor.Next(ctx) {
46✔
2242
                if err = cursor.Decode(&idx); err != nil {
30✔
2243
                        continue
×
2244
                }
2245
                if _, ok := idx["weights"]; ok {
45✔
2246
                        // text index
15✔
2247
                        for k := range idx["weights"].(bson.M) {
45✔
2248
                                has[k] = true
30✔
2249
                        }
30✔
2250
                } else {
15✔
2251
                        for i := range idx["key"].(bson.M) {
30✔
2252
                                has[i] = true
15✔
2253
                        }
15✔
2254

2255
                }
2256
        }
2257
        if err != nil {
16✔
2258
                return false
×
2259
        }
×
2260

2261
        for _, key := range StorageIndexes.Keys.(bson.D) {
47✔
2262
                _, ok := has[key.Key]
31✔
2263
                if !ok {
32✔
2264
                        return false
1✔
2265
                }
1✔
2266
        }
2267

2268
        return true
15✔
2269
}
2270

2271
// Insert persists object
2272
func (db *DataStoreMongo) InsertDeployment(
2273
        ctx context.Context,
2274
        deployment *model.Deployment,
2275
) error {
216✔
2276

216✔
2277
        if deployment == nil {
217✔
2278
                return ErrDeploymentStorageInvalidDeployment
1✔
2279
        }
1✔
2280

2281
        if err := deployment.Validate(); err != nil {
217✔
2282
                return err
2✔
2283
        }
2✔
2284

2285
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
214✔
2286
        collDpl := database.Collection(CollectionDeployments)
214✔
2287

214✔
2288
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
216✔
2289
                if mongo.IsDuplicateKeyError(err) {
4✔
2290
                        return ErrConflictingDeployment
2✔
2291
                }
2✔
UNCOV
2292
                return err
×
2293
        }
2294
        return nil
213✔
2295
}
2296

2297
// Delete removed entry by ID
2298
// Noop on ID not found
2299
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
4✔
2300

4✔
2301
        if len(id) == 0 {
5✔
2302
                return ErrStorageInvalidID
1✔
2303
        }
1✔
2304

2305
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2306
        collDpl := database.Collection(CollectionDeployments)
3✔
2307

3✔
2308
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
2309
                return err
×
2310
        }
×
2311

2312
        return nil
3✔
2313
}
2314

2315
func (db *DataStoreMongo) FindDeploymentByID(
2316
        ctx context.Context,
2317
        id string,
2318
) (*model.Deployment, error) {
10✔
2319

10✔
2320
        if len(id) == 0 {
11✔
2321
                return nil, ErrStorageInvalidID
1✔
2322
        }
1✔
2323

2324
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2325
        collDpl := database.Collection(CollectionDeployments)
9✔
2326

9✔
2327
        deployment := new(model.Deployment)
9✔
2328
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
9✔
2329
                Decode(deployment); err != nil {
12✔
2330
                if err == mongo.ErrNoDocuments {
6✔
2331
                        return nil, nil
3✔
2332
                }
3✔
2333
                return nil, err
×
2334
        }
2335

2336
        return deployment, nil
6✔
2337
}
2338

2339
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2340
        ctx context.Context,
2341
        ids ...string,
2342
) (deploymentStats []*model.DeploymentStats, err error) {
2✔
2343

2✔
2344
        if len(ids) == 0 {
2✔
2345
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2346
        }
×
2347

2348
        for _, id := range ids {
6✔
2349
                if len(id) == 0 {
4✔
2350
                        return nil, ErrStorageInvalidID
×
2351
                }
×
2352
        }
2353

2354
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2355
        collDpl := database.Collection(CollectionDeployments)
2✔
2356

2✔
2357
        query := bson.M{
2✔
2358
                "_id": bson.M{
2✔
2359
                        "$in": ids,
2✔
2360
                },
2✔
2361
        }
2✔
2362
        statsProjection := &mopts.FindOptions{
2✔
2363
                Projection: bson.M{"stats": 1},
2✔
2364
        }
2✔
2365

2✔
2366
        results, err := collDpl.Find(
2✔
2367
                ctx,
2✔
2368
                query,
2✔
2369
                statsProjection,
2✔
2370
        )
2✔
2371
        if err != nil {
2✔
2372
                return nil, err
×
2373
        }
×
2374

2375
        for results.Next(context.Background()) {
6✔
2376
                depl := new(model.DeploymentStats)
4✔
2377
                if err = results.Decode(&depl); err != nil {
4✔
2378
                        if err == mongo.ErrNoDocuments {
×
2379
                                return nil, nil
×
2380
                        }
×
2381
                        return nil, err
×
2382
                }
2383
                deploymentStats = append(deploymentStats, depl)
4✔
2384
        }
2385

2386
        return deploymentStats, nil
2✔
2387
}
2388

2389
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2390
        id string) (*model.Deployment, error) {
8✔
2391

8✔
2392
        if len(id) == 0 {
9✔
2393
                return nil, ErrStorageInvalidID
1✔
2394
        }
1✔
2395

2396
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2397
        collDpl := database.Collection(CollectionDeployments)
7✔
2398

7✔
2399
        var deployment *model.Deployment
7✔
2400
        filter := bson.D{
7✔
2401
                {Key: "_id", Value: id},
7✔
2402
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2403
        }
7✔
2404
        if err := collDpl.FindOne(ctx, filter).
7✔
2405
                Decode(&deployment); err != nil {
12✔
2406
                if err == mongo.ErrNoDocuments {
10✔
2407
                        return nil, nil
5✔
2408
                }
5✔
2409
                return nil, err
×
2410
        }
2411

2412
        return deployment, nil
3✔
2413
}
2414

2415
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2416
        ctx context.Context,
2417
        deploymentID string,
2418
        increment int,
2419
) error {
64✔
2420
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
64✔
2421
        collection := database.Collection(CollectionDeployments)
64✔
2422

64✔
2423
        filter := bson.M{
64✔
2424
                "_id": deploymentID,
64✔
2425
                StorageKeyDeploymentDeviceCount: bson.M{
64✔
2426
                        "$ne": nil,
64✔
2427
                },
64✔
2428
        }
64✔
2429

64✔
2430
        update := bson.M{
64✔
2431
                "$inc": bson.M{
64✔
2432
                        StorageKeyDeploymentDeviceCount: increment,
64✔
2433
                },
64✔
2434
        }
64✔
2435

64✔
2436
        _, err := collection.UpdateOne(ctx, filter, update)
64✔
2437
        return err
64✔
2438
}
64✔
2439

2440
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2441
        ctx context.Context,
2442
        deploymentID string,
2443
        count int,
2444
) error {
3✔
2445
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2446
        collection := database.Collection(CollectionDeployments)
3✔
2447

3✔
2448
        filter := bson.M{
3✔
2449
                "_id": deploymentID,
3✔
2450
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2451
                        "$eq": nil,
3✔
2452
                },
3✔
2453
        }
3✔
2454

3✔
2455
        update := bson.M{
3✔
2456
                "$set": bson.M{
3✔
2457
                        StorageKeyDeploymentDeviceCount: count,
3✔
2458
                },
3✔
2459
        }
3✔
2460

3✔
2461
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2462
        return err
3✔
2463
}
3✔
2464

2465
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2466
        id string) (int, error) {
3✔
2467

3✔
2468
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2469
        collDevs := database.Collection(CollectionDevices)
3✔
2470

3✔
2471
        filter := bson.M{
3✔
2472
                StorageKeyDeviceDeploymentDeploymentID: id,
3✔
2473
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
2474
                        {Key: "$exists", Value: false},
3✔
2475
                },
3✔
2476
        }
3✔
2477

3✔
2478
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
3✔
2479
        if err != nil {
3✔
2480
                return 0, err
×
2481
        }
×
2482

2483
        return int(deviceCount), nil
3✔
2484
}
2485

2486
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2487
        id string, stats model.Stats) error {
6✔
2488

6✔
2489
        if len(id) == 0 {
7✔
2490
                return ErrStorageInvalidID
1✔
2491
        }
1✔
2492

2493
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2494
        collDpl := database.Collection(CollectionDeployments)
5✔
2495

5✔
2496
        deployment, err := model.NewDeployment()
5✔
2497
        if err != nil {
5✔
2498
                return errors.Wrap(err, "failed to create deployment")
×
2499
        }
×
2500

2501
        deployment.Stats = stats
5✔
2502
        var update bson.M
5✔
2503
        if deployment.IsFinished() {
5✔
2504
                now := time.Now()
×
2505

×
2506
                update = bson.M{
×
2507
                        "$set": bson.M{
×
2508
                                StorageKeyDeploymentStats:    stats,
×
2509
                                StorageKeyDeploymentFinished: &now,
×
2510
                        },
×
2511
                }
×
2512
        } else {
5✔
2513
                update = bson.M{
5✔
2514
                        "$set": bson.M{
5✔
2515
                                StorageKeyDeploymentStats: stats,
5✔
2516
                        },
5✔
2517
                }
5✔
2518
        }
5✔
2519

2520
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
5✔
2521
        if res != nil && res.MatchedCount == 0 {
7✔
2522
                return ErrStorageInvalidID
2✔
2523
        }
2✔
2524
        return err
3✔
2525
}
2526

2527
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2528
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
8✔
2529

8✔
2530
        if len(id) == 0 {
9✔
2531
                return ErrStorageInvalidID
1✔
2532
        }
1✔
2533

2534
        if _, err := stateTo.MarshalText(); err != nil {
7✔
2535
                return ErrStorageInvalidInput
×
2536
        }
×
2537

2538
        // does not need any extra operations
2539
        // following query won't handle this case well and increase the state_to value
2540
        if stateFrom == stateTo {
8✔
2541
                return nil
1✔
2542
        }
1✔
2543

2544
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2545
        collDpl := database.Collection(CollectionDeployments)
6✔
2546

6✔
2547
        var update bson.M
6✔
2548

6✔
2549
        if stateFrom == model.DeviceDeploymentStatusNull {
8✔
2550
                // note dot notation on embedded document
2✔
2551
                update = bson.M{
2✔
2552
                        "$inc": bson.M{
2✔
2553
                                "stats." + stateTo.String(): 1,
2✔
2554
                        },
2✔
2555
                }
2✔
2556
        } else {
7✔
2557
                // note dot notation on embedded document
5✔
2558
                update = bson.M{
5✔
2559
                        "$inc": bson.M{
5✔
2560
                                "stats." + stateFrom.String(): -1,
5✔
2561
                                "stats." + stateTo.String():   1,
5✔
2562
                        },
5✔
2563
                }
5✔
2564
        }
5✔
2565

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

6✔
2568
        if res != nil && res.MatchedCount == 0 {
7✔
2569
                return ErrStorageInvalidID
1✔
2570
        }
1✔
2571

2572
        return err
5✔
2573
}
2574

2575
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2576
        ctx context.Context,
2577
        deploymentID string,
2578
        increment int64,
2579
) error {
3✔
2580
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2581
        collection := database.Collection(CollectionDeployments)
3✔
2582

3✔
2583
        filter := bson.M{
3✔
2584
                "_id": deploymentID,
3✔
2585
        }
3✔
2586

3✔
2587
        update := bson.M{
3✔
2588
                "$inc": bson.M{
3✔
2589
                        StorageKeyDeploymentTotalSize: increment,
3✔
2590
                },
3✔
2591
        }
3✔
2592

3✔
2593
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2594
        return err
3✔
2595
}
3✔
2596

2597
func (db *DataStoreMongo) Find(ctx context.Context,
2598
        match model.Query) ([]*model.Deployment, int64, error) {
36✔
2599

36✔
2600
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
36✔
2601
        collDpl := database.Collection(CollectionDeployments)
36✔
2602

36✔
2603
        andq := []bson.M{}
36✔
2604

36✔
2605
        // filter by IDs
36✔
2606
        if match.IDs != nil {
36✔
2607
                tq := bson.M{
×
2608
                        "_id": bson.M{
×
2609
                                "$in": match.IDs,
×
2610
                        },
×
2611
                }
×
2612
                andq = append(andq, tq)
×
2613
        }
×
2614

2615
        // build deployment by name part of the query
2616
        if match.SearchText != "" {
52✔
2617
                // we must have indexing for text search
16✔
2618
                if !db.hasIndexing(ctx, db.client) {
17✔
2619
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
1✔
2620
                }
1✔
2621

2622
                tq := bson.M{
15✔
2623
                        "$text": bson.M{
15✔
2624
                                "$search": "\"" + match.SearchText + "\"",
15✔
2625
                        },
15✔
2626
                }
15✔
2627

15✔
2628
                andq = append(andq, tq)
15✔
2629
        }
2630

2631
        // build deployment by status part of the query
2632
        if match.Status != model.StatusQueryAny {
45✔
2633
                var status model.DeploymentStatus
10✔
2634
                if match.Status == model.StatusQueryPending {
12✔
2635
                        status = model.DeploymentStatusPending
2✔
2636
                } else if match.Status == model.StatusQueryInProgress {
14✔
2637
                        status = model.DeploymentStatusInProgress
4✔
2638
                } else {
8✔
2639
                        status = model.DeploymentStatusFinished
4✔
2640
                }
4✔
2641
                stq := bson.M{StorageKeyDeploymentStatus: status}
10✔
2642
                andq = append(andq, stq)
10✔
2643
        }
2644

2645
        // build deployment by type part of the query
2646
        if match.Type != "" {
37✔
2647
                if match.Type == model.DeploymentTypeConfiguration {
4✔
2648
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
2✔
2649
                } else if match.Type == model.DeploymentTypeSoftware {
2✔
2650
                        andq = append(andq, bson.M{
×
2651
                                "$or": []bson.M{
×
2652
                                        {StorageKeyDeploymentType: match.Type},
×
2653
                                        {StorageKeyDeploymentType: ""},
×
2654
                                },
×
2655
                        })
×
2656
                }
×
2657
        }
2658

2659
        query := bson.M{}
35✔
2660
        if len(andq) != 0 {
58✔
2661
                // use search criteria if any
23✔
2662
                query = bson.M{
23✔
2663
                        "$and": andq,
23✔
2664
                }
23✔
2665
        }
23✔
2666

2667
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
35✔
2668
                query["created"] = bson.M{
×
2669
                        "$gte": match.CreatedAfter,
×
2670
                        "$lte": match.CreatedBefore,
×
2671
                }
×
2672
        } else if match.CreatedAfter != nil {
35✔
2673
                query["created"] = bson.M{
×
2674
                        "$gte": match.CreatedAfter,
×
2675
                }
×
2676
        } else if match.CreatedBefore != nil {
35✔
2677
                query["created"] = bson.M{
×
2678
                        "$lte": match.CreatedBefore,
×
2679
                }
×
2680
        }
×
2681

2682
        options := db.findOptions(match)
35✔
2683

35✔
2684
        var deployments []*model.Deployment
35✔
2685
        cursor, err := collDpl.Find(ctx, query, options)
35✔
2686
        if err != nil {
35✔
2687
                return nil, 0, err
×
2688
        }
×
2689
        if err := cursor.All(ctx, &deployments); err != nil {
35✔
2690
                return nil, 0, err
×
2691
        }
×
2692
        // Count documents if we didn't find all already.
2693
        count := int64(0)
35✔
2694
        if !match.DisableCount {
70✔
2695
                count = int64(len(deployments))
35✔
2696
                if count >= int64(match.Limit) {
69✔
2697
                        count, err = collDpl.CountDocuments(ctx, query)
34✔
2698
                        if err != nil {
34✔
2699
                                return nil, 0, err
×
2700
                        }
×
2701
                } else {
1✔
2702
                        // Don't forget to add the skipped documents
1✔
2703
                        count += int64(match.Skip)
1✔
2704
                }
1✔
2705
        }
2706

2707
        return deployments, count, nil
35✔
2708
}
2709

2710
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
35✔
2711
        options := &mopts.FindOptions{}
35✔
2712
        if match.Sort == model.SortDirectionAscending {
36✔
2713
                options.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
2714
        } else {
35✔
2715
                options.SetSort(bson.D{{Key: "created", Value: -1}})
34✔
2716
        }
34✔
2717
        if match.Skip > 0 {
37✔
2718
                options.SetSkip(int64(match.Skip))
2✔
2719
        }
2✔
2720
        if match.Limit > 0 {
40✔
2721
                options.SetLimit(int64(match.Limit))
5✔
2722
        } else {
35✔
2723
                options.SetLimit(DefaultDocumentLimit)
30✔
2724
        }
30✔
2725
        return options
35✔
2726
}
2727

2728
// FindNewerActiveDeployments finds active deployments which were created
2729
// after createdAfter
2730
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2731
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
5✔
2732

5✔
2733
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2734
        c := database.Collection(CollectionDeployments)
5✔
2735

5✔
2736
        queryFilters := make([]bson.M, 0)
5✔
2737
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
5✔
2738
        queryFilters = append(queryFilters,
5✔
2739
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
5✔
2740
        findQuery := bson.M{}
5✔
2741
        findQuery["$and"] = queryFilters
5✔
2742

5✔
2743
        findOptions := &mopts.FindOptions{}
5✔
2744
        findOptions.SetSkip(int64(skip))
5✔
2745
        findOptions.SetLimit(int64(limit))
5✔
2746

5✔
2747
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
5✔
2748
        cursor, err := c.Find(ctx, findQuery, findOptions)
5✔
2749
        if err != nil {
5✔
2750
                return nil, errors.Wrap(err, "failed to get deployments")
×
2751
        }
×
2752
        defer cursor.Close(ctx)
5✔
2753

5✔
2754
        var deployments []*model.Deployment
5✔
2755

5✔
2756
        if err = cursor.All(ctx, &deployments); err != nil {
5✔
2757
                return nil, errors.Wrap(err, "failed to get deployments")
×
2758
        }
×
2759

2760
        return deployments, nil
5✔
2761
}
2762

2763
// SetDeploymentStatus simply sets the status field
2764
// optionally sets 'finished time' if deployment is indeed finished
2765
func (db *DataStoreMongo) SetDeploymentStatus(
2766
        ctx context.Context,
2767
        id string,
2768
        status model.DeploymentStatus,
2769
        now time.Time,
2770
) error {
6✔
2771
        if len(id) == 0 {
6✔
2772
                return ErrStorageInvalidID
×
2773
        }
×
2774

2775
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2776
        collDpl := database.Collection(CollectionDeployments)
6✔
2777

6✔
2778
        var update bson.M
6✔
2779
        if status == model.DeploymentStatusFinished {
8✔
2780
                update = bson.M{
2✔
2781
                        "$set": bson.M{
2✔
2782
                                StorageKeyDeploymentActive:   false,
2✔
2783
                                StorageKeyDeploymentStatus:   status,
2✔
2784
                                StorageKeyDeploymentFinished: &now,
2✔
2785
                        },
2✔
2786
                }
2✔
2787
        } else {
7✔
2788
                update = bson.M{
5✔
2789
                        "$set": bson.M{
5✔
2790
                                StorageKeyDeploymentActive: true,
5✔
2791
                                StorageKeyDeploymentStatus: status,
5✔
2792
                        },
5✔
2793
                }
5✔
2794
        }
5✔
2795

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

6✔
2798
        if res != nil && res.MatchedCount == 0 {
7✔
2799
                return ErrStorageInvalidID
1✔
2800
        }
1✔
2801

2802
        return err
5✔
2803
}
2804

2805
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2806
// given artifact
2807
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2808
        id string) (bool, error) {
4✔
2809

4✔
2810
        if len(id) == 0 {
4✔
2811
                return false, ErrStorageInvalidID
×
2812
        }
×
2813

2814
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2815
        collDpl := database.Collection(CollectionDeployments)
4✔
2816

4✔
2817
        var tmp interface{}
4✔
2818
        query := bson.D{
4✔
2819
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2820
                {Key: StorageKeyDeploymentArtifacts, Value: id},
4✔
2821
        }
4✔
2822
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
7✔
2823
                if err == mongo.ErrNoDocuments {
6✔
2824
                        return false, nil
3✔
2825
                }
3✔
2826
                return false, err
×
2827
        }
2828

2829
        return true, nil
2✔
2830
}
2831

2832
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2833
// given artifact
2834
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2835
        artifactName string) (bool, error) {
4✔
2836

4✔
2837
        if len(artifactName) == 0 {
4✔
2838
                return false, ErrImagesStorageInvalidArtifactName
×
2839
        }
×
2840

2841
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2842
        collDpl := database.Collection(CollectionDeployments)
4✔
2843

4✔
2844
        var tmp interface{}
4✔
2845
        query := bson.D{
4✔
2846
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2847
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
4✔
2848
        }
4✔
2849

4✔
2850
        projection := bson.M{
4✔
2851
                "_id": 1,
4✔
2852
        }
4✔
2853
        findOptions := mopts.FindOne()
4✔
2854
        findOptions.SetProjection(projection)
4✔
2855

4✔
2856
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
7✔
2857
                if err == mongo.ErrNoDocuments {
6✔
2858
                        return false, nil
3✔
2859
                }
3✔
2860
                return false, err
×
2861
        }
2862

2863
        return true, nil
1✔
2864
}
2865

2866
// ExistByArtifactId check if there is any deployment that uses give artifact
2867
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2868
        id string) (bool, error) {
×
2869

×
2870
        if len(id) == 0 {
×
2871
                return false, ErrStorageInvalidID
×
2872
        }
×
2873

2874
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2875
        collDpl := database.Collection(CollectionDeployments)
×
2876

×
2877
        var tmp interface{}
×
2878
        query := bson.D{
×
2879
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2880
        }
×
2881
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2882
                if err == mongo.ErrNoDocuments {
×
2883
                        return false, nil
×
2884
                }
×
2885
                return false, err
×
2886
        }
2887

2888
        return true, nil
×
2889
}
2890

2891
// Per-tenant storage settings
2892
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
2✔
2893
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2894
        collection := database.Collection(CollectionStorageSettings)
2✔
2895

2✔
2896
        settings := new(model.StorageSettings)
2✔
2897
        // supposed that it's only one document in the collection
2✔
2898
        query := bson.M{
2✔
2899
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2900
        }
2✔
2901
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
3✔
2902
                if err == mongo.ErrNoDocuments {
2✔
2903
                        return nil, nil
1✔
2904
                }
1✔
2905
                return nil, err
×
2906
        }
2907

2908
        return settings, nil
2✔
2909
}
2910

2911
func (db *DataStoreMongo) SetStorageSettings(
2912
        ctx context.Context,
2913
        storageSettings *model.StorageSettings,
2914
) error {
2✔
2915
        var err error
2✔
2916
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2917
        collection := database.Collection(CollectionStorageSettings)
2✔
2918

2✔
2919
        filter := bson.M{
2✔
2920
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2921
        }
2✔
2922
        if storageSettings != nil {
4✔
2923
                replaceOptions := mopts.Replace()
2✔
2924
                replaceOptions.SetUpsert(true)
2✔
2925
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
2✔
2926
        } else {
3✔
2927
                _, err = collection.DeleteOne(ctx, filter)
1✔
2928
        }
1✔
2929

2930
        return err
2✔
2931
}
2932

2933
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2934
        ctx context.Context,
2935
        artifactName string,
2936
        artifactIDs []string,
2937
) error {
1✔
2938
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2939
        collDpl := database.Collection(CollectionDeployments)
1✔
2940

1✔
2941
        query := bson.D{
1✔
2942
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2943
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2944
        }
1✔
2945
        update := bson.M{
1✔
2946
                "$set": bson.M{
1✔
2947
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
2948
                },
1✔
2949
        }
1✔
2950

1✔
2951
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
2952
        return err
1✔
2953
}
1✔
2954

2955
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
2956
        ctx context.Context,
2957
        artifactNames []string,
2958
) ([]string, error) {
2✔
2959

2✔
2960
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2961
        collDpl := database.Collection(CollectionDeployments)
2✔
2962

2✔
2963
        query := bson.M{
2✔
2964
                StorageKeyDeploymentArtifactName: bson.M{
2✔
2965
                        "$in": artifactNames,
2✔
2966
                },
2✔
2967
        }
2✔
2968

2✔
2969
        projection := bson.M{
2✔
2970
                "_id": 1,
2✔
2971
        }
2✔
2972
        findOptions := mopts.Find()
2✔
2973
        findOptions.SetProjection(projection)
2✔
2974

2✔
2975
        cursor, err := collDpl.Find(ctx, query, findOptions)
2✔
2976
        if err != nil {
2✔
2977
                return []string{}, err
×
2978
        }
×
2979
        defer cursor.Close(ctx)
2✔
2980

2✔
2981
        var deployments []*model.Deployment
2✔
2982
        if err = cursor.All(ctx, &deployments); err != nil {
2✔
2983
                if err == mongo.ErrNoDocuments {
×
2984
                        err = nil
×
2985
                }
×
2986
                return []string{}, err
×
2987
        }
2988

2989
        ids := make([]string, len(deployments))
2✔
2990
        for i, d := range deployments {
4✔
2991
                ids[i] = d.Id
2✔
2992
        }
2✔
2993

2994
        return ids, nil
2✔
2995
}
2996

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