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

mendersoftware / deployments / 972678690

18 Aug 2023 07:14PM UTC coverage: 79.678% (+0.07%) from 79.613%
972678690

Pull #901

gitlab-ci

merlin-northern
test: docs and acc test

Ticket: MEN-6623
Signed-off-by: Peter Grzybowski <peter@northern.tech>
Pull Request #901: feat: save and get the update types

74 of 85 new or added lines in 4 files covered. (87.06%)

391 existing lines in 3 files now uncovered.

7810 of 9802 relevant lines covered (79.68%)

34.01 hits per line

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

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

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

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

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

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

42
const (
43
        DatabaseName                   = "deployment_service"
44
        CollectionLimits               = "limits"
45
        CollectionImages               = "images"
46
        CollectionDeployments          = "deployments"
47
        CollectionDeviceDeploymentLogs = "devices.logs"
48
        CollectionDevices              = "devices"
49
        CollectionDevicesLastStatus    = "devices_last_status"
50
        CollectionStorageSettings      = "settings"
51
        CollectionUploadIntents        = "uploads"
52
        CollectionReleases             = "releases"
53
        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.16
102
        IndexNameReleaseTags = "release_tags"
103

104
        // Indexes 1.2.17
105
        IndexNameReleaseUpdateTypes = "release_update_types"
106

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

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

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

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

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

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

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

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

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

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

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

386
        ErrLimitNotFound      = errors.New("limit not found")
387
        ErrDevicesCountFailed = errors.New("failed to count devices")
388
        ErrConflictingDepends = errors.New(
389
                "an artifact with the same name and depends already exists",
390
        )
391
)
392

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

399
        StorageKeyImageProvides    = "meta_artifact.provides"
400
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
401
        StorageKeyImageDepends     = "meta_artifact.depends"
402
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
403
        StorageKeyImageSize        = "size"
404
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
405
        StorageKeyImageName        = "meta_artifact.name"
406
        StorageKeyUpdateType       = "meta_artifact.updates.typeinfo.type"
407
        StorageKeyImageDescription = "meta.description"
408
        StorageKeyImageModified    = "modified"
409

410
        // releases
411
        StorageKeyReleaseName                      = "_id"
412
        StorageKeyReleaseModified                  = "modified"
413
        StorageKeyReleaseTags                      = "tags"
414
        StorageKeyReleaseNotes                     = "notes"
415
        StorageKeyReleaseArtifacts                 = "artifacts"
416
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
417
                StorageKeyImageDescription
418
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
419
                StorageKeyImageDescription
420
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
421
                StorageKeyImageDeviceTypes
422
        StorageKeyReleaseArtifactsUpdateTypes = StorageKeyReleaseArtifacts + "." +
423
                StorageKeyUpdateType
424
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
425
                StorageKeyImageModified
426
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
427
                StorageKeyId
428
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
429
                StorageKeyImageDependsIdx
430
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
431
                StorageKeyImageProvidesIdx
432

433
        StorageKeyDeviceDeploymentLogMessages = "messages"
434

435
        StorageKeyDeviceDeploymentAssignedImage   = "image"
436
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
437
                "." + StorageKeyId
438

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

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

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

476
        StorageKeyStorageReleaseUpdateTypes = "update_types"
477

478
        ArtifactDependsDeviceType = "device_type"
479
)
480

481
type DataStoreMongo struct {
482
        client *mongo.Client
483
}
484

485
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
384✔
486
        return &DataStoreMongo{
384✔
487
                client: client,
384✔
488
        }
384✔
489
}
384✔
490

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

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

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

514
        if c.GetBool(dconfig.SettingDbSSL) {
1✔
UNCOV
515
                tlsConfig := &tls.Config{}
×
UNCOV
516
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
UNCOV
517
                clientOptions.SetTLSConfig(tlsConfig)
×
518
        }
×
519

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

528
        // Validate connection
529
        if err = client.Ping(ctx, nil); err != nil {
1✔
UNCOV
530
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
UNCOV
531
        }
×
532

533
        return client, nil
1✔
534
}
535

536
func (db *DataStoreMongo) Ping(ctx context.Context) error {
1✔
537
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
1✔
538
        return res.Err()
1✔
539
}
1✔
540

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

564
func (db *DataStoreMongo) getCurrentDbVersion(
565
        ctx context.Context,
566
) (*migrate.Version, error) {
1✔
567
        if currentDbVersion == nil ||
1✔
568
                currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] == nil {
2✔
569
                if err := db.setCurrentDbVersion(ctx); err != nil {
1✔
UNCOV
570
                        return nil, err
×
UNCOV
571
                }
×
572
        }
573
        return currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)], nil
1✔
574
}
575

576
func (db *DataStoreMongo) GetReleases(
577
        ctx context.Context,
578
        filt *model.ReleaseOrImageFilter,
579
) ([]model.Release, int, error) {
1✔
580
        current, err := db.getCurrentDbVersion(ctx)
1✔
581
        if err != nil {
1✔
UNCOV
582
                return nil, 0, err
×
583
        } else if current == nil {
1✔
UNCOV
584
                return nil, 0, errors.New("couldn't get current database version")
×
585
        }
×
586
        target, err := migrate.NewVersion(DbVersion)
1✔
587
        if err != nil {
1✔
588
                return nil, 0, errors.Wrap(err, "failed to get latest DB version")
×
UNCOV
589
        }
×
590
        if migrate.VersionIsLess(*current, *target) {
1✔
591
                return db.getReleases_1_2_14(ctx, filt)
×
592
        } else {
1✔
593
                return db.getReleases_1_2_15(ctx, filt)
1✔
594
        }
1✔
595
}
596

597
func (db *DataStoreMongo) getReleases_1_2_14(
598
        ctx context.Context,
599
        filt *model.ReleaseOrImageFilter,
600
) ([]model.Release, int, error) {
9✔
601
        l := log.FromContext(ctx)
9✔
602
        l.Infof("get releases method version 1.2.14")
9✔
603
        var pipe []bson.D
9✔
604

9✔
605
        pipe = []bson.D{}
9✔
606
        if filt != nil && filt.Name != "" {
11✔
607
                pipe = append(pipe, bson.D{
2✔
608
                        {Key: "$match", Value: bson.M{
2✔
609
                                StorageKeyImageName: bson.M{
2✔
610
                                        "$regex": primitive.Regex{
2✔
611
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
2✔
612
                                                Options: "i",
2✔
613
                                        },
2✔
614
                                },
2✔
615
                        }},
2✔
616
                })
2✔
617
        }
2✔
618

619
        pipe = append(pipe, bson.D{
9✔
620
                // Remove (possibly expensive) sub-documents from pipeline
9✔
621
                {
9✔
622
                        Key: "$project",
9✔
623
                        Value: bson.M{
9✔
624
                                StorageKeyImageDependsIdx:  0,
9✔
625
                                StorageKeyImageProvidesIdx: 0,
9✔
626
                        },
9✔
627
                },
9✔
628
        })
9✔
629

9✔
630
        pipe = append(pipe, bson.D{
9✔
631
                {Key: "$group", Value: bson.D{
9✔
632
                        {Key: "_id", Value: "$" + StorageKeyImageName},
9✔
633
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
9✔
634
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
9✔
635
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
9✔
636
                }},
9✔
637
        })
9✔
638

9✔
639
        if filt != nil && filt.Description != "" {
11✔
640
                pipe = append(pipe, bson.D{
2✔
641
                        {Key: "$match", Value: bson.M{
2✔
642
                                "artifacts." + StorageKeyImageDescription: bson.M{
2✔
643
                                        "$regex": primitive.Regex{
2✔
644
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
645
                                                Options: "i",
2✔
646
                                        },
2✔
647
                                },
2✔
648
                        }},
2✔
649
                })
2✔
650
        }
2✔
651
        if filt != nil && filt.DeviceType != "" {
10✔
652
                pipe = append(pipe, bson.D{
1✔
653
                        {Key: "$match", Value: bson.M{
1✔
654
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
1✔
655
                                        "$regex": primitive.Regex{
1✔
656
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
657
                                                Options: "i",
1✔
658
                                        },
1✔
659
                                },
1✔
660
                        }},
1✔
661
                })
1✔
662
        }
1✔
663

664
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
9✔
665
        if sortField == "" {
15✔
666
                sortField = "name"
6✔
667
        }
6✔
668
        if sortOrder == 0 {
15✔
669
                sortOrder = 1
6✔
670
        }
6✔
671

672
        page := 1
9✔
673
        perPage := math.MaxInt64
9✔
674
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
10✔
675
                page = filt.Page
1✔
676
                perPage = filt.PerPage
1✔
677
        }
1✔
678
        pipe = append(pipe,
9✔
679
                bson.D{{Key: "$facet", Value: bson.D{
9✔
680
                        {Key: "results", Value: []bson.D{
9✔
681
                                {
9✔
682
                                        {Key: "$sort", Value: bson.D{
9✔
683
                                                {Key: sortField, Value: sortOrder},
9✔
684
                                                {Key: "_id", Value: 1},
9✔
685
                                        }},
9✔
686
                                },
9✔
687
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
9✔
688
                                {{Key: "$limit", Value: int64(perPage)}},
9✔
689
                        }},
9✔
690
                        {Key: "count", Value: []bson.D{
9✔
691
                                {{Key: "$count", Value: "count"}},
9✔
692
                        }},
9✔
693
                }}},
9✔
694
        )
9✔
695

9✔
696
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
697
        collImg := database.Collection(CollectionImages)
9✔
698

9✔
699
        cursor, err := collImg.Aggregate(ctx, pipe)
9✔
700
        if err != nil {
9✔
UNCOV
701
                return nil, 0, err
×
UNCOV
702
        }
×
703
        defer cursor.Close(ctx)
9✔
704

9✔
705
        result := struct {
9✔
706
                Results []model.Release       `bson:"results"`
9✔
707
                Count   []struct{ Count int } `bson:"count"`
9✔
708
        }{}
9✔
709
        if !cursor.Next(ctx) {
9✔
UNCOV
710
                return nil, 0, nil
×
711
        } else if err = cursor.Decode(&result); err != nil {
9✔
UNCOV
712
                return nil, 0, err
×
713
        } else if len(result.Count) == 0 {
10✔
714
                return []model.Release{}, 0, err
1✔
715
        }
1✔
716
        return result.Results, result.Count[0].Count, nil
8✔
717
}
718

719
func (db *DataStoreMongo) getReleases_1_2_15(
720
        ctx context.Context,
721
        filt *model.ReleaseOrImageFilter,
722
) ([]model.Release, int, error) {
51✔
723
        l := log.FromContext(ctx)
51✔
724
        l.Infof("get releases method version 1.2.15")
51✔
725

51✔
726
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
51✔
727
        if sortField == "" {
96✔
728
                sortField = "_id"
45✔
729
        } else if sortField == "name" {
53✔
730
                sortField = StorageKeyReleaseName
2✔
731
        }
2✔
732
        if sortOrder == 0 {
96✔
733
                sortOrder = 1
45✔
734
        }
45✔
735

736
        page := 1
51✔
737
        perPage := DefaultDocumentLimit
51✔
738
        if filt != nil {
100✔
739
                if filt.Page > 0 {
51✔
740
                        page = filt.Page
2✔
741
                }
2✔
742
                if filt.PerPage > 0 {
51✔
743
                        perPage = filt.PerPage
2✔
744
                }
2✔
745
        }
746

747
        opts := &mopts.FindOptions{}
51✔
748
        opts.SetSort(bson.D{{Key: sortField, Value: sortOrder}})
51✔
749
        opts.SetSkip(int64((page - 1) * perPage))
51✔
750
        opts.SetLimit(int64(perPage))
51✔
751
        projection := bson.M{
51✔
752
                StorageKeyReleaseImageDependsIdx:  0,
51✔
753
                StorageKeyReleaseImageProvidesIdx: 0,
51✔
754
        }
51✔
755
        opts.SetProjection(projection)
51✔
756

51✔
757
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
51✔
758
        collReleases := database.Collection(CollectionReleases)
51✔
759

51✔
760
        filter := bson.M{}
51✔
761
        if filt != nil {
100✔
762
                if filt.Name != "" {
84✔
763
                        filter[StorageKeyReleaseName] = bson.M{"$regex": filt.Name}
35✔
764
                }
35✔
765
                if filt.Description != "" {
53✔
766
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": filt.Description}
4✔
767
                }
4✔
768
                if filt.DeviceType != "" {
51✔
769
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = bson.M{"$regex": filt.DeviceType}
2✔
770
                }
2✔
771
                if filt.UpdateType != "" {
52✔
772
                        filter[StorageKeyReleaseArtifactsUpdateTypes] = bson.M{"$eq": filt.UpdateType}
3✔
773
                }
3✔
774
        }
775
        releases := []model.Release{}
51✔
776
        cursor, err := collReleases.Find(ctx, filter, opts)
51✔
777
        if err != nil {
51✔
UNCOV
778
                return nil, 0, err
×
UNCOV
779
        }
×
780
        if err := cursor.All(ctx, &releases); err != nil {
51✔
781
                return nil, 0, err
×
782
        }
×
783

784
        // TODO: can we return number of all documents in the collection
785
        // using EstimatedDocumentCount?
786
        count, err := collReleases.CountDocuments(ctx, filter)
51✔
787
        if err != nil {
51✔
UNCOV
788
                return nil, 0, err
×
UNCOV
789
        }
×
790

791
        return releases, int(count), nil
51✔
792
}
793

794
// limits
795
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
4✔
796

4✔
797
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
798
        collLim := database.Collection(CollectionLimits)
4✔
799

4✔
800
        limit := new(model.Limit)
4✔
801
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
4✔
802
                Decode(limit); err != nil {
6✔
803
                if err == mongo.ErrNoDocuments {
4✔
804
                        return nil, ErrLimitNotFound
2✔
805
                }
2✔
UNCOV
806
                return nil, err
×
807
        }
808

809
        return limit, nil
2✔
810
}
811

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

5✔
814
        dbname := mstore.DbNameForTenant(tenantId, DbName)
5✔
815

5✔
816
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
5✔
817
}
5✔
818

819
//images
820

821
// Exists checks if object with ID exists
UNCOV
822
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
UNCOV
823
        var result interface{}
×
UNCOV
824

×
825
        if len(id) == 0 {
×
826
                return false, ErrImagesStorageInvalidID
×
827
        }
×
828

829
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
830
        collImg := database.Collection(CollectionImages)
×
UNCOV
831

×
832
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
833
                Decode(&result); err != nil {
×
834
                if err == mongo.ErrNoDocuments {
×
835
                        return false, nil
×
836
                }
×
837
                return false, err
×
838
        }
839

840
        return true, nil
×
841
}
842

843
// Update provided Image
844
// Return false if not found
845
func (db *DataStoreMongo) Update(ctx context.Context,
846
        image *model.Image) (bool, error) {
1✔
847

1✔
848
        if err := image.Validate(); err != nil {
1✔
UNCOV
849
                return false, err
×
UNCOV
850
        }
×
851

852
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
853
        collImg := database.Collection(CollectionImages)
1✔
854

1✔
855
        // add special representation of artifact provides
1✔
856
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
857

1✔
858
        image.SetModified(time.Now())
1✔
859
        if res, err := collImg.ReplaceOne(
1✔
860
                ctx, bson.M{"_id": image.Id}, image,
1✔
861
        ); err != nil {
1✔
UNCOV
862
                return false, err
×
863
        } else if res.MatchedCount == 0 {
1✔
UNCOV
864
                return false, nil
×
865
        }
×
866

867
        return true, nil
1✔
868
}
869

870
// ImageByNameAndDeviceType finds image with specified application name and target device type
871
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
872
        name, deviceType string) (*model.Image, error) {
9✔
873

9✔
874
        if len(name) == 0 {
10✔
875
                return nil, ErrImagesStorageInvalidArtifactName
1✔
876
        }
1✔
877

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

882
        // equal to device type & software version (application name + version)
883
        query := bson.M{
7✔
884
                StorageKeyImageName:        name,
7✔
885
                StorageKeyImageDeviceTypes: deviceType,
7✔
886
        }
7✔
887

7✔
888
        // If multiple entries matches, pick the smallest one.
7✔
889
        findOpts := mopts.FindOne()
7✔
890
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
7✔
891

7✔
892
        dbName := mstore.DbFromContext(ctx, DatabaseName)
7✔
893
        database := db.client.Database(dbName)
7✔
894
        collImg := database.Collection(CollectionImages)
7✔
895

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

906
        return &image, nil
3✔
907
}
908

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

1✔
913
        if len(deviceType) == 0 {
1✔
UNCOV
914
                return nil, ErrImagesStorageInvalidDeviceType
×
UNCOV
915
        }
×
916

917
        if len(ids) == 0 {
1✔
918
                return nil, ErrImagesStorageInvalidID
×
UNCOV
919
        }
×
920

921
        query := bson.D{
1✔
922
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
923
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
924
        }
1✔
925

1✔
926
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
927
        collImg := database.Collection(CollectionImages)
1✔
928

1✔
929
        // If multiple entries matches, pick the smallest one
1✔
930
        findOpts := mopts.FindOne()
1✔
931
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
932

1✔
933
        // Both we lookup unique object, should be one or none.
1✔
934
        var image model.Image
1✔
935
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
936
                Decode(&image); err != nil {
1✔
UNCOV
937
                if err == mongo.ErrNoDocuments {
×
UNCOV
938
                        return nil, nil
×
UNCOV
939
                }
×
940
                return nil, err
×
941
        }
942

943
        return &image, nil
1✔
944
}
945

946
// ImagesByName finds images with specified artifact name
947
func (db *DataStoreMongo) ImagesByName(
948
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
949

1✔
950
        var images []*model.Image
1✔
951

1✔
952
        if len(name) == 0 {
1✔
UNCOV
953
                return nil, ErrImagesStorageInvalidName
×
UNCOV
954
        }
×
955

956
        // equal to artifact name
957
        query := bson.M{
1✔
958
                StorageKeyImageName: name,
1✔
959
        }
1✔
960

1✔
961
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
962
        collImg := database.Collection(CollectionImages)
1✔
963
        cursor, err := collImg.Find(ctx, query)
1✔
964
        if err != nil {
1✔
UNCOV
965
                return nil, err
×
UNCOV
966
        }
×
967
        // Both we lookup unique object, should be one or none.
968
        if err = cursor.All(ctx, &images); err != nil {
1✔
969
                return nil, err
×
UNCOV
970
        }
×
971

972
        return images, nil
1✔
973
}
974

975
func newDependsConflictError(mgoErr mongo.WriteError) *model.ConflictError {
7✔
976
        var err error
7✔
977
        conflictErr := model.NewConflictError(ErrConflictingDepends)
7✔
978
        // Try to lookup the document that caused the index violation:
7✔
979
        if raw, ok := mgoErr.Raw.Lookup("keyValue").DocumentOK(); ok {
14✔
980
                if raw, ok = raw.Lookup(StorageKeyImageDependsIdx).DocumentOK(); ok {
14✔
981
                        var conflicts map[string]interface{}
7✔
982
                        err = bson.Unmarshal([]byte(raw), &conflicts)
7✔
983
                        if err == nil {
14✔
984
                                _ = conflictErr.WithMetadata(
7✔
985
                                        map[string]interface{}{
7✔
986
                                                "conflict": conflicts,
7✔
987
                                        },
7✔
988
                                )
7✔
989
                        }
7✔
990
                }
991
        }
992
        return conflictErr
7✔
993
}
994

995
// Insert persists object
996
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
69✔
997

69✔
998
        if image == nil {
69✔
UNCOV
999
                return ErrImagesStorageInvalidImage
×
UNCOV
1000
        }
×
1001

1002
        if err := image.Validate(); err != nil {
69✔
1003
                return err
×
UNCOV
1004
        }
×
1005

1006
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
69✔
1007
        collImg := database.Collection(CollectionImages)
69✔
1008

69✔
1009
        // add special representation of artifact provides
69✔
1010
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
69✔
1011

69✔
1012
        _, err := collImg.InsertOne(ctx, image)
69✔
1013
        if err != nil {
76✔
1014
                var wExc mongo.WriteException
7✔
1015
                if errors.As(err, &wExc) {
14✔
1016
                        for _, wErr := range wExc.WriteErrors {
14✔
1017
                                if !mongo.IsDuplicateKeyError(wErr) {
7✔
UNCOV
1018
                                        continue
×
1019
                                }
1020
                                return newDependsConflictError(wErr)
7✔
1021
                        }
1022
                }
UNCOV
1023
                return err
×
1024
        }
1025

1026
        return nil
62✔
1027
}
1028

1029
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
2✔
1030
        collUploads := db.client.
2✔
1031
                Database(DatabaseName).
2✔
1032
                Collection(CollectionUploadIntents)
2✔
1033
        if idty := identity.FromContext(ctx); idty != nil {
3✔
1034
                link.TenantID = idty.Tenant
1✔
1035
        }
1✔
1036
        _, err := collUploads.InsertOne(ctx, link)
2✔
1037
        return err
2✔
1038
}
1039

1040
func (db *DataStoreMongo) UpdateUploadIntentStatus(
1041
        ctx context.Context,
1042
        id string,
1043
        from, to model.LinkStatus,
1044
) error {
6✔
1045
        collUploads := db.client.
6✔
1046
                Database(DatabaseName).
6✔
1047
                Collection(CollectionUploadIntents)
6✔
1048
        q := bson.D{
6✔
1049
                {Key: "_id", Value: id},
6✔
1050
                {Key: "status", Value: from},
6✔
1051
        }
6✔
1052
        if idty := identity.FromContext(ctx); idty != nil {
11✔
1053
                q = append(q, bson.E{
5✔
1054
                        Key:   StorageKeyTenantId,
5✔
1055
                        Value: idty.Tenant,
5✔
1056
                })
5✔
1057
        }
5✔
1058
        update := bson.D{{
6✔
1059
                Key: "updated_ts", Value: time.Now(),
6✔
1060
        }}
6✔
1061
        if from != to {
12✔
1062
                update = append(update, bson.E{
6✔
1063
                        Key: "status", Value: to,
6✔
1064
                })
6✔
1065
        }
6✔
1066
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
6✔
1067
                {Key: "$set", Value: update},
6✔
1068
        })
6✔
1069
        if err != nil {
7✔
1070
                return err
1✔
1071
        } else if res.MatchedCount == 0 {
8✔
1072
                return store.ErrNotFound
2✔
1073
        }
2✔
1074
        return nil
3✔
1075
}
1076

1077
func (db *DataStoreMongo) FindUploadLinks(
1078
        ctx context.Context,
1079
        expiredAt time.Time,
1080
) (store.Iterator[model.UploadLink], error) {
1✔
1081
        collUploads := db.client.
1✔
1082
                Database(DatabaseName).
1✔
1083
                Collection(CollectionUploadIntents)
1✔
1084

1✔
1085
        q := bson.D{{
1✔
1086
                Key: "status",
1✔
1087
                Value: bson.D{{
1✔
1088
                        Key:   "$lt",
1✔
1089
                        Value: model.LinkStatusProcessedBit,
1✔
1090
                }},
1✔
1091
        }, {
1✔
1092
                Key: "expire",
1✔
1093
                Value: bson.D{{
1✔
1094
                        Key:   "$lt",
1✔
1095
                        Value: expiredAt,
1✔
1096
                }},
1✔
1097
        }}
1✔
1098
        cur, err := collUploads.Find(ctx, q)
1✔
1099
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1100
}
1✔
1101

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

1✔
1106
        if len(id) == 0 {
1✔
UNCOV
1107
                return nil, ErrImagesStorageInvalidID
×
UNCOV
1108
        }
×
1109

1110
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1111
        collImg := database.Collection(CollectionImages)
1✔
1112
        projection := bson.M{
1✔
1113
                StorageKeyImageDependsIdx:  0,
1✔
1114
                StorageKeyImageProvidesIdx: 0,
1✔
1115
        }
1✔
1116
        findOptions := mopts.FindOne()
1✔
1117
        findOptions.SetProjection(projection)
1✔
1118

1✔
1119
        var image model.Image
1✔
1120
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
1121
                Decode(&image); err != nil {
2✔
1122
                if err == mongo.ErrNoDocuments {
2✔
1123
                        return nil, nil
1✔
1124
                }
1✔
UNCOV
1125
                return nil, err
×
1126
        }
1127

1128
        return &image, nil
1✔
1129
}
1130

1131
// IsArtifactUnique checks if there is no artifact with the same artifactName
1132
// supporting one of the device types from deviceTypesCompatible list.
1133
// Returns true, nil if artifact is unique;
1134
// false, nil if artifact is not unique;
1135
// false, error in case of error.
1136
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1137
        artifactName string, deviceTypesCompatible []string) (bool, error) {
6✔
1138

6✔
1139
        if len(artifactName) == 0 {
7✔
1140
                return false, ErrImagesStorageInvalidArtifactName
1✔
1141
        }
1✔
1142

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

5✔
1146
        query := bson.M{
5✔
1147
                "$and": []bson.M{
5✔
1148
                        {
5✔
1149
                                StorageKeyImageName: artifactName,
5✔
1150
                        },
5✔
1151
                        {
5✔
1152
                                StorageKeyImageDeviceTypes: bson.M{
5✔
1153
                                        "$in": deviceTypesCompatible},
5✔
1154
                        },
5✔
1155
                },
5✔
1156
        }
5✔
1157

5✔
1158
        // do part of the job manually
5✔
1159
        // if candidate images have any extra 'depends' - guaranteed non-overlap
5✔
1160
        // otherwise it's a match
5✔
1161
        cur, err := collImg.Find(ctx, query)
5✔
1162
        if err != nil {
5✔
UNCOV
1163
                return false, err
×
UNCOV
1164
        }
×
1165

1166
        var images []model.Image
5✔
1167
        err = cur.All(ctx, &images)
5✔
1168
        if err != nil {
5✔
UNCOV
1169
                return false, err
×
UNCOV
1170
        }
×
1171

1172
        for _, i := range images {
6✔
1173
                // the artifact already has same name and overlapping dev type
1✔
1174
                // if there are no more depends than dev type - it's not unique
1✔
1175
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1176
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1177
                                return false, nil
1✔
1178
                        }
1✔
UNCOV
1179
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
UNCOV
1180
                        return false, nil
×
UNCOV
1181
                }
×
1182
        }
1183

1184
        return true, nil
4✔
1185
}
1186

1187
// Delete image specified by ID
1188
// Noop on if not found.
1189
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
1✔
1190

1✔
1191
        if len(id) == 0 {
1✔
UNCOV
1192
                return ErrImagesStorageInvalidID
×
UNCOV
1193
        }
×
1194

1195
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1196
        collImg := database.Collection(CollectionImages)
1✔
1197

1✔
1198
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
UNCOV
1199
                if res.DeletedCount == 0 {
×
UNCOV
1200
                        return nil
×
UNCOV
1201
                }
×
1202
                return err
×
1203
        }
1204

1205
        return nil
1✔
1206
}
1207

1208
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
74✔
1209
        if filt != nil && filt.Sort != "" {
86✔
1210
                sortParts := strings.SplitN(filt.Sort, ":", 2)
12✔
1211
                if len(sortParts) == 2 && (sortParts[0] == "name" || sortParts[0] == "modified") {
24✔
1212
                        sortField := sortParts[0]
12✔
1213
                        sortOrder := 1
12✔
1214
                        if sortParts[1] == model.SortDirectionDescending {
20✔
1215
                                sortOrder = -1
8✔
1216
                        }
8✔
1217
                        return sortField, sortOrder
12✔
1218
                }
1219
        }
1220
        return "", 0
62✔
1221
}
1222

1223
// ListImages lists all images
1224
func (db *DataStoreMongo) ListImages(
1225
        ctx context.Context,
1226
        filt *model.ReleaseOrImageFilter,
1227
) ([]*model.Image, int, error) {
15✔
1228

15✔
1229
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1230
        collImg := database.Collection(CollectionImages)
15✔
1231

15✔
1232
        filters := bson.M{}
15✔
1233
        if filt != nil {
25✔
1234
                if filt.Name != "" {
14✔
1235
                        filters[StorageKeyImageName] = bson.M{
4✔
1236
                                "$regex": primitive.Regex{
4✔
1237
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
4✔
1238
                                        Options: "i",
4✔
1239
                                },
4✔
1240
                        }
4✔
1241
                }
4✔
1242
                if filt.Description != "" {
12✔
1243
                        filters[StorageKeyImageDescription] = bson.M{
2✔
1244
                                "$regex": primitive.Regex{
2✔
1245
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
1246
                                        Options: "i",
2✔
1247
                                },
2✔
1248
                        }
2✔
1249
                }
2✔
1250
                if filt.DeviceType != "" {
11✔
1251
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1252
                                "$regex": primitive.Regex{
1✔
1253
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1254
                                        Options: "i",
1✔
1255
                                },
1✔
1256
                        }
1✔
1257
                }
1✔
1258

1259
        }
1260

1261
        projection := bson.M{
15✔
1262
                StorageKeyImageDependsIdx:  0,
15✔
1263
                StorageKeyImageProvidesIdx: 0,
15✔
1264
        }
15✔
1265
        findOptions := &mopts.FindOptions{}
15✔
1266
        findOptions.SetProjection(projection)
15✔
1267
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
16✔
1268
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1269
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1270
        }
1✔
1271

1272
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
15✔
1273
        if sortField == "" || sortField == "name" {
28✔
1274
                sortField = StorageKeyImageName
13✔
1275
        }
13✔
1276
        if sortOrder == 0 {
27✔
1277
                sortOrder = 1
12✔
1278
        }
12✔
1279
        findOptions.SetSort(bson.D{
15✔
1280
                {Key: sortField, Value: sortOrder},
15✔
1281
                {Key: "_id", Value: sortOrder},
15✔
1282
        })
15✔
1283

15✔
1284
        cursor, err := collImg.Find(ctx, filters, findOptions)
15✔
1285
        if err != nil {
15✔
UNCOV
1286
                return nil, 0, err
×
UNCOV
1287
        }
×
1288

1289
        // NOTE: cursor.All closes the cursor before returning
1290
        var images []*model.Image
15✔
1291
        if err := cursor.All(ctx, &images); err != nil {
15✔
UNCOV
1292
                if err == mongo.ErrNoDocuments {
×
UNCOV
1293
                        return nil, 0, nil
×
UNCOV
1294
                }
×
1295
                return nil, 0, err
×
1296
        }
1297

1298
        count, err := collImg.CountDocuments(ctx, filters)
15✔
1299
        if err != nil {
15✔
UNCOV
1300
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
1301
        }
×
1302

1303
        return images, int(count), nil
15✔
1304
}
1305

1306
// device deployment log
1307
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1308
        log model.DeploymentLog) error {
9✔
1309

9✔
1310
        if err := log.Validate(); err != nil {
12✔
1311
                return err
3✔
1312
        }
3✔
1313

1314
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1315
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1316

6✔
1317
        query := bson.D{
6✔
1318
                {Key: StorageKeyDeviceDeploymentDeviceId,
6✔
1319
                        Value: log.DeviceID},
6✔
1320
                {Key: StorageKeyDeviceDeploymentDeploymentID,
6✔
1321
                        Value: log.DeploymentID},
6✔
1322
        }
6✔
1323

6✔
1324
        // update log messages
6✔
1325
        // if the deployment log is already present than messages will be overwritten
6✔
1326
        update := bson.D{
6✔
1327
                {Key: "$set", Value: bson.M{
6✔
1328
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
6✔
1329
                }},
6✔
1330
        }
6✔
1331
        updateOptions := mopts.Update()
6✔
1332
        updateOptions.SetUpsert(true)
6✔
1333
        if _, err := collLogs.UpdateOne(
6✔
1334
                ctx, query, update, updateOptions); err != nil {
6✔
UNCOV
1335
                return err
×
UNCOV
1336
        }
×
1337

1338
        return nil
6✔
1339
}
1340

1341
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1342
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
6✔
1343

6✔
1344
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1345
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1346

6✔
1347
        query := bson.M{
6✔
1348
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
6✔
1349
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1350
        }
6✔
1351

6✔
1352
        var depl model.DeploymentLog
6✔
1353
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
8✔
1354
                if err == mongo.ErrNoDocuments {
4✔
1355
                        return nil, nil
2✔
1356
                }
2✔
UNCOV
1357
                return nil, err
×
1358
        }
1359

1360
        return &depl, nil
4✔
1361
}
1362

1363
// device deployments
1364

1365
// Insert persists device deployment object
1366
func (db *DataStoreMongo) InsertDeviceDeployment(
1367
        ctx context.Context,
1368
        deviceDeployment *model.DeviceDeployment,
1369
        incrementDeviceCount bool,
1370
) error {
27✔
1371
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
27✔
1372
        c := database.Collection(CollectionDevices)
27✔
1373

27✔
1374
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
27✔
UNCOV
1375
                return err
×
UNCOV
1376
        }
×
1377

1378
        if incrementDeviceCount {
54✔
1379
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
27✔
1380
                if err != nil {
27✔
UNCOV
1381
                        return err
×
UNCOV
1382
                }
×
1383
        }
1384

1385
        return nil
27✔
1386
}
1387

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

39✔
1393
        if len(deployments) == 0 {
51✔
1394
                return nil
12✔
1395
        }
12✔
1396

1397
        deviceCountIncrements := make(map[string]int)
27✔
1398

27✔
1399
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
27✔
1400
        var list []interface{}
27✔
1401
        for _, deployment := range deployments {
93✔
1402

66✔
1403
                if deployment == nil {
67✔
1404
                        return ErrStorageInvalidDeviceDeployment
1✔
1405
                }
1✔
1406

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

1411
                list = append(list, deployment)
63✔
1412
                deviceCountIncrements[deployment.DeploymentId]++
63✔
1413
        }
1414

1415
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
24✔
1416
        collDevs := database.Collection(CollectionDevices)
24✔
1417

24✔
1418
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
24✔
UNCOV
1419
                return err
×
UNCOV
1420
        }
×
1421

1422
        for deploymentID := range deviceCountIncrements {
52✔
1423
                err := db.IncrementDeploymentDeviceCount(
28✔
1424
                        ctx,
28✔
1425
                        deploymentID,
28✔
1426
                        deviceCountIncrements[deploymentID],
28✔
1427
                )
28✔
1428
                if err != nil {
28✔
UNCOV
1429
                        return err
×
UNCOV
1430
                }
×
1431
        }
1432

1433
        return nil
24✔
1434
}
1435

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

1✔
1440
        // Verify ID formatting
1✔
1441
        if len(imageID) == 0 {
1✔
UNCOV
1442
                return false, ErrStorageInvalidID
×
UNCOV
1443
        }
×
1444

1445
        query := bson.M{StorageKeyDeviceDeploymentAssignedImageId: imageID}
1✔
1446

1✔
1447
        if len(statuses) > 0 {
2✔
1448
                query[StorageKeyDeviceDeploymentStatus] = bson.M{
1✔
1449
                        "$in": statuses,
1✔
1450
                }
1✔
1451
        }
1✔
1452

1453
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1454
        collDevs := database.Collection(CollectionDevices)
1✔
1455

1✔
1456
        // if found at least one then image in active deployment
1✔
1457
        var tmp interface{}
1✔
1458
        if err := collDevs.FindOne(ctx, query).Decode(&tmp); err != nil {
2✔
1459
                if err == mongo.ErrNoDocuments {
2✔
1460
                        return false, nil
1✔
1461
                }
1✔
UNCOV
1462
                return false, err
×
1463
        }
1464

1465
        return true, nil
×
1466
}
1467

1468
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1469
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1470
        ctx context.Context,
1471
        deviceID string,
1472
) (*model.DeviceDeployment, error) {
6✔
1473

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

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

5✔
1482
        // Device should know only about deployments that are not finished
5✔
1483
        query := bson.D{
5✔
1484
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
5✔
1485
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1486
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1487
                        {Key: "$exists", Value: false},
5✔
1488
                }},
5✔
1489
        }
5✔
1490

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

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

1506
        return deployment, nil
3✔
1507
}
1508

1509
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1510
// matching device id that has not finished yet.
1511
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1512
        ctx context.Context,
1513
        deviceID string,
1514
) (*model.DeviceDeployment, error) {
6✔
1515

6✔
1516
        // Verify ID formatting
6✔
1517
        if len(deviceID) == 0 {
7✔
1518
                return nil, ErrStorageInvalidID
1✔
1519
        }
1✔
1520

1521
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1522
        collDevs := database.Collection(CollectionDevices)
5✔
1523

5✔
1524
        query := bson.D{
5✔
1525
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
5✔
1526
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1527
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1528
                        {Key: "$exists", Value: false},
5✔
1529
                }},
5✔
1530
        }
5✔
1531

5✔
1532
        // Find the latest one by sorting by the creation timestamp
5✔
1533
        // in ascending order.
5✔
1534
        findOptions := mopts.FindOne()
5✔
1535
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
5✔
1536

5✔
1537
        // Select only the latest one that have not been finished yet.
5✔
1538
        var deployment *model.DeviceDeployment
5✔
1539
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1540
                Decode(&deployment); err != nil {
8✔
1541
                if err == mongo.ErrNoDocuments {
5✔
1542
                        return nil, nil
2✔
1543
                }
2✔
1544
                return nil, err
1✔
1545
        }
1546

1547
        return deployment, nil
3✔
1548
}
1549

1550
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1551
        ctx context.Context,
1552
        deviceID string,
1553
        deploymentID string,
1554
        ddState model.DeviceDeploymentState,
1555
) (model.DeviceDeploymentStatus, error) {
10✔
1556

10✔
1557
        // Verify ID formatting
10✔
1558
        if len(deviceID) == 0 ||
10✔
1559
                len(deploymentID) == 0 {
12✔
1560
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
2✔
1561
        }
2✔
1562

1563
        if err := ddState.Validate(); err != nil {
9✔
1564
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1565
        }
1✔
1566

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

7✔
1570
        // Device should know only about deployments that are not finished
7✔
1571
        query := bson.D{
7✔
1572
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
1573
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
1574
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
1575
                        {Key: "$exists", Value: false},
7✔
1576
                }},
7✔
1577
        }
7✔
1578

7✔
1579
        // update status field
7✔
1580
        set := bson.M{
7✔
1581
                StorageKeyDeviceDeploymentStatus: ddState.Status,
7✔
1582
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
7✔
1583
        }
7✔
1584
        // and finish time if provided
7✔
1585
        if ddState.FinishTime != nil {
9✔
1586
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
2✔
1587
        }
2✔
1588

1589
        if len(ddState.SubState) > 0 {
8✔
1590
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
1✔
1591
        }
1✔
1592

1593
        update := bson.D{
7✔
1594
                {Key: "$set", Value: set},
7✔
1595
        }
7✔
1596

7✔
1597
        var old model.DeviceDeployment
7✔
1598

7✔
1599
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
7✔
1600
                Decode(&old); err != nil {
9✔
1601
                if err == mongo.ErrNoDocuments {
4✔
1602
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
2✔
1603
                }
2✔
UNCOV
1604
                return model.DeviceDeploymentStatusNull, err
×
1605

1606
        }
1607

1608
        return old.Status, nil
5✔
1609
}
1610

1611
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1612
        deviceID string, deploymentID string, log bool) error {
7✔
1613

7✔
1614
        // Verify ID formatting
7✔
1615
        if len(deviceID) == 0 ||
7✔
1616
                len(deploymentID) == 0 {
9✔
1617
                return ErrStorageInvalidID
2✔
1618
        }
2✔
1619

1620
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1621
        collDevs := database.Collection(CollectionDevices)
5✔
1622

5✔
1623
        selector := bson.D{
5✔
1624
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1625
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
5✔
1626
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1627
                        {Key: "$exists", Value: false},
5✔
1628
                }},
5✔
1629
        }
5✔
1630

5✔
1631
        update := bson.D{
5✔
1632
                {Key: "$set", Value: bson.M{
5✔
1633
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
5✔
1634
        }
5✔
1635

5✔
1636
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
5✔
UNCOV
1637
                return err
×
1638
        } else if res.MatchedCount == 0 {
7✔
1639
                return ErrStorageNotFound
2✔
1640
        }
2✔
1641

1642
        return nil
3✔
1643
}
1644

1645
// SaveDeviceDeploymentRequest saves device deployment request
1646
// with the device deployment object
1647
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1648
        ctx context.Context,
1649
        ID string,
1650
        request *model.DeploymentNextRequest,
1651
) error {
4✔
1652

4✔
1653
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
1654
        collDevs := database.Collection(CollectionDevices)
4✔
1655

4✔
1656
        res, err := collDevs.UpdateOne(
4✔
1657
                ctx,
4✔
1658
                bson.D{{Key: StorageKeyId, Value: ID}},
4✔
1659
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
4✔
1660
        )
4✔
1661
        if err != nil {
4✔
UNCOV
1662
                return err
×
1663
        } else if res.MatchedCount == 0 {
5✔
1664
                return ErrStorageNotFound
1✔
1665
        }
1✔
1666
        return nil
3✔
1667
}
1668

1669
// AssignArtifact assigns artifact to the device deployment
1670
func (db *DataStoreMongo) AssignArtifact(
1671
        ctx context.Context,
1672
        deviceID string,
1673
        deploymentID string,
1674
        artifact *model.Image,
1675
) error {
1✔
1676

1✔
1677
        // Verify ID formatting
1✔
1678
        if len(deviceID) == 0 ||
1✔
1679
                len(deploymentID) == 0 {
1✔
UNCOV
1680
                return ErrStorageInvalidID
×
UNCOV
1681
        }
×
1682

1683
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1684
        collDevs := database.Collection(CollectionDevices)
1✔
1685

1✔
1686
        selector := bson.D{
1✔
1687
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1688
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1689
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1690
                        {Key: "$exists", Value: false},
1✔
1691
                }},
1✔
1692
        }
1✔
1693

1✔
1694
        update := bson.D{
1✔
1695
                {Key: "$set", Value: bson.M{
1✔
1696
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1697
                }},
1✔
1698
        }
1✔
1699

1✔
1700
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
UNCOV
1701
                return err
×
1702
        } else if res.MatchedCount == 0 {
1✔
UNCOV
1703
                return ErrStorageNotFound
×
1704
        }
×
1705

1706
        return nil
1✔
1707
}
1708

1709
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1710
        id string) (model.Stats, error) {
6✔
1711

6✔
1712
        if len(id) == 0 {
6✔
UNCOV
1713
                return nil, ErrStorageInvalidID
×
UNCOV
1714
        }
×
1715

1716
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1717
        collDevs := database.Collection(CollectionDevices)
6✔
1718

6✔
1719
        match := bson.D{
6✔
1720
                {Key: "$match", Value: bson.M{
6✔
1721
                        StorageKeyDeviceDeploymentDeploymentID: id,
6✔
1722
                        StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1723
                                {Key: "$exists", Value: false},
6✔
1724
                        },
6✔
1725
                }},
6✔
1726
        }
6✔
1727
        group := bson.D{
6✔
1728
                {Key: "$group", Value: bson.D{
6✔
1729
                        {Key: "_id",
6✔
1730
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
6✔
1731
                        {Key: "count",
6✔
1732
                                Value: bson.M{"$sum": 1}}},
6✔
1733
                },
6✔
1734
        }
6✔
1735
        pipeline := []bson.D{
6✔
1736
                match,
6✔
1737
                group,
6✔
1738
        }
6✔
1739
        var results []struct {
6✔
1740
                Status model.DeviceDeploymentStatus `bson:"_id"`
6✔
1741
                Count  int
6✔
1742
        }
6✔
1743
        cursor, err := collDevs.Aggregate(ctx, pipeline)
6✔
1744
        if err != nil {
6✔
UNCOV
1745
                return nil, err
×
UNCOV
1746
        }
×
1747
        if err := cursor.All(ctx, &results); err != nil {
6✔
1748
                if err == mongo.ErrNoDocuments {
×
1749
                        return nil, nil
×
UNCOV
1750
                }
×
1751
                return nil, err
×
1752
        }
1753

1754
        raw := model.NewDeviceDeploymentStats()
6✔
1755
        for _, res := range results {
17✔
1756
                raw.Set(res.Status, res.Count)
11✔
1757
        }
11✔
1758
        return raw, nil
6✔
1759
}
1760

1761
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1762
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1763
        deploymentID string) ([]model.DeviceDeployment, error) {
6✔
1764

6✔
1765
        statuses := []model.DeviceDeployment{}
6✔
1766
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1767
        collDevs := database.Collection(CollectionDevices)
6✔
1768

6✔
1769
        query := bson.M{
6✔
1770
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1771
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1772
                        {Key: "$exists", Value: false},
6✔
1773
                },
6✔
1774
        }
6✔
1775

6✔
1776
        cursor, err := collDevs.Find(ctx, query)
6✔
1777
        if err != nil {
6✔
UNCOV
1778
                return nil, err
×
UNCOV
1779
        }
×
1780

1781
        if err = cursor.All(ctx, &statuses); err != nil {
6✔
1782
                if err == mongo.ErrNoDocuments {
×
UNCOV
1783
                        return nil, nil
×
UNCOV
1784
                }
×
1785
                return nil, err
×
1786
        }
1787

1788
        return statuses, nil
6✔
1789
}
1790

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

15✔
1794
        statuses := []model.DeviceDeployment{}
15✔
1795
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1796
        collDevs := database.Collection(CollectionDevices)
15✔
1797

15✔
1798
        query := bson.D{
15✔
1799
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
15✔
1800
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
15✔
1801
                        {Key: "$exists", Value: false},
15✔
1802
                }},
15✔
1803
        }
15✔
1804
        if q.Status != nil {
19✔
1805
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
5✔
1806
                        query = append(query, bson.E{
1✔
1807
                                Key: "status", Value: bson.D{{
1✔
1808
                                        Key:   "$gte",
1✔
1809
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1810
                                }, {
1✔
1811
                                        Key:   "$lte",
1✔
1812
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1813
                                }},
1✔
1814
                        })
1✔
1815
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
4✔
UNCOV
1816
                        query = append(query, bson.E{
×
UNCOV
1817
                                Key: "status", Value: bson.D{{
×
UNCOV
1818
                                        Key:   "$gte",
×
1819
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1820
                                }, {
×
1821
                                        Key:   "$lte",
×
1822
                                        Value: model.DeviceDeploymentStatusPending,
×
1823
                                }},
×
1824
                        })
×
1825
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
4✔
1826
                        query = append(query, bson.E{
1✔
1827
                                Key: "status", Value: bson.D{{
1✔
1828
                                        Key: "$in",
1✔
1829
                                        Value: []model.DeviceDeploymentStatus{
1✔
1830
                                                model.DeviceDeploymentStatusFailure,
1✔
1831
                                                model.DeviceDeploymentStatusAborted,
1✔
1832
                                                model.DeviceDeploymentStatusSuccess,
1✔
1833
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1834
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1835
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1836
                                        },
1✔
1837
                                }},
1✔
1838
                        })
1✔
1839
                } else {
3✔
1840
                        var status model.DeviceDeploymentStatus
2✔
1841
                        err := status.UnmarshalText([]byte(*q.Status))
2✔
1842
                        if err != nil {
3✔
1843
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1844
                        }
1✔
1845
                        query = append(query, bson.E{
1✔
1846
                                Key: "status", Value: status,
1✔
1847
                        })
1✔
1848
                }
1849
        }
1850

1851
        options := mopts.Find()
14✔
1852
        sortFieldQuery := bson.D{
14✔
1853
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
14✔
1854
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
14✔
1855
        }
14✔
1856
        options.SetSort(sortFieldQuery)
14✔
1857
        if q.Skip > 0 {
17✔
1858
                options.SetSkip(int64(q.Skip))
3✔
1859
        }
3✔
1860
        if q.Limit > 0 {
19✔
1861
                options.SetLimit(int64(q.Limit))
5✔
1862
        } else {
14✔
1863
                options.SetLimit(DefaultDocumentLimit)
9✔
1864
        }
9✔
1865

1866
        cursor, err := collDevs.Find(ctx, query, options)
14✔
1867
        if err != nil {
15✔
1868
                return nil, -1, err
1✔
1869
        }
1✔
1870

1871
        if err = cursor.All(ctx, &statuses); err != nil {
13✔
UNCOV
1872
                if err == mongo.ErrNoDocuments {
×
UNCOV
1873
                        return nil, -1, nil
×
UNCOV
1874
                }
×
1875
                return nil, -1, err
×
1876
        }
1877

1878
        count, err := collDevs.CountDocuments(ctx, query)
13✔
1879
        if err != nil {
13✔
UNCOV
1880
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
1881
        }
×
1882

1883
        return statuses, int(count), nil
13✔
1884
}
1885

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

10✔
1889
        statuses := []model.DeviceDeployment{}
10✔
1890
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
10✔
1891
        collDevs := database.Collection(CollectionDevices)
10✔
1892

10✔
1893
        query := bson.D{}
10✔
1894
        if q.DeviceID != "" {
19✔
1895
                query = append(query, bson.E{
9✔
1896
                        Key:   StorageKeyDeviceDeploymentDeviceId,
9✔
1897
                        Value: q.DeviceID,
9✔
1898
                })
9✔
1899
        } else if len(q.IDs) > 0 {
11✔
1900
                query = append(query, bson.E{
1✔
1901
                        Key: StorageKeyId,
1✔
1902
                        Value: bson.D{{
1✔
1903
                                Key:   "$in",
1✔
1904
                                Value: q.IDs,
1✔
1905
                        }},
1✔
1906
                })
1✔
1907
        }
1✔
1908

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

1956
        options := mopts.Find()
9✔
1957
        sortFieldQuery := bson.D{
9✔
1958
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
9✔
1959
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
9✔
1960
        }
9✔
1961
        options.SetSort(sortFieldQuery)
9✔
1962
        if q.Skip > 0 {
10✔
1963
                options.SetSkip(int64(q.Skip))
1✔
1964
        }
1✔
1965
        if q.Limit > 0 {
18✔
1966
                options.SetLimit(int64(q.Limit))
9✔
1967
        } else {
9✔
UNCOV
1968
                options.SetLimit(DefaultDocumentLimit)
×
UNCOV
1969
        }
×
1970

1971
        cursor, err := collDevs.Find(ctx, query, options)
9✔
1972
        if err != nil {
9✔
UNCOV
1973
                return nil, -1, err
×
UNCOV
1974
        }
×
1975

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

1983
        maxCount := maxCountDocuments
9✔
1984
        countOptions := &mopts.CountOptions{
9✔
1985
                Limit: &maxCount,
9✔
1986
        }
9✔
1987
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
9✔
1988
        if err != nil {
9✔
UNCOV
1989
                return nil, -1, ErrDevicesCountFailed
×
UNCOV
1990
        }
×
1991

1992
        return statuses, int(count), nil
9✔
1993
}
1994

1995
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1996
// `deviceID`, false otherwise. In case of errors returns false and an error
1997
// that occurred
1998
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1999
        deploymentID string, deviceID string) (bool, error) {
7✔
2000

7✔
2001
        var dep model.DeviceDeployment
7✔
2002
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2003
        collDevs := database.Collection(CollectionDevices)
7✔
2004

7✔
2005
        query := bson.D{
7✔
2006
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
2007
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
2008
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
2009
                        {Key: "$exists", Value: false},
7✔
2010
                }},
7✔
2011
        }
7✔
2012

7✔
2013
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
10✔
2014
                if err == mongo.ErrNoDocuments {
6✔
2015
                        return false, nil
3✔
2016
                } else {
3✔
UNCOV
2017
                        return false, err
×
UNCOV
2018
                }
×
2019
        }
2020

2021
        return true, nil
4✔
2022
}
2023

2024
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2025
        deploymentId string) error {
3✔
2026

3✔
2027
        if len(deploymentId) == 0 {
4✔
2028
                return ErrStorageInvalidID
1✔
2029
        }
1✔
2030

2031
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2032
        collDevs := database.Collection(CollectionDevices)
2✔
2033
        selector := bson.M{
2✔
2034
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
2✔
2035
                StorageKeyDeviceDeploymentActive:       true,
2✔
2036
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2037
                        {Key: "$exists", Value: false},
2✔
2038
                },
2✔
2039
        }
2✔
2040

2✔
2041
        update := bson.M{
2✔
2042
                "$set": bson.M{
2✔
2043
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2044
                        StorageKeyDeviceDeploymentActive: false,
2✔
2045
                },
2✔
2046
        }
2✔
2047

2✔
2048
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
UNCOV
2049
                return err
×
UNCOV
2050
        }
×
2051

2052
        return nil
2✔
2053
}
2054

2055
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2056
        deviceID string) error {
2✔
2057
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2058
        collDevs := database.Collection(CollectionDevices)
2✔
2059
        selector := bson.M{
2✔
2060
                StorageKeyDeviceDeploymentDeviceId: deviceID,
2✔
2061
                StorageKeyDeviceDeploymentActive:   false,
2✔
2062
                StorageKeyDeviceDeploymentDeleted: bson.M{
2✔
2063
                        "$exists": false,
2✔
2064
                },
2✔
2065
        }
2✔
2066

2✔
2067
        now := time.Now()
2✔
2068
        update := bson.M{
2✔
2069
                "$set": bson.M{
2✔
2070
                        StorageKeyDeviceDeploymentDeleted: &now,
2✔
2071
                },
2✔
2072
        }
2✔
2073

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

2078
        database = db.client.Database(DatabaseName)
2✔
2079
        collDevs = database.Collection(CollectionDevicesLastStatus)
2✔
2080
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
2✔
2081

2✔
2082
        return err
2✔
2083
}
2084

2085
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2086
        deviceId string) error {
2✔
2087

2✔
2088
        if len(deviceId) == 0 {
3✔
2089
                return ErrStorageInvalidID
1✔
2090
        }
1✔
2091

2092
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2093
        collDevs := database.Collection(CollectionDevices)
1✔
2094
        selector := bson.M{
1✔
2095
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2096
                StorageKeyDeviceDeploymentActive:   true,
1✔
2097
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2098
                        {Key: "$exists", Value: false},
1✔
2099
                },
1✔
2100
        }
1✔
2101

1✔
2102
        update := bson.M{
1✔
2103
                "$set": bson.M{
1✔
2104
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2105
                        StorageKeyDeviceDeploymentActive: false,
1✔
2106
                },
1✔
2107
        }
1✔
2108

1✔
2109
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
UNCOV
2110
                return err
×
UNCOV
2111
        }
×
2112

2113
        return nil
1✔
2114
}
2115

2116
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2117
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2118

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

1✔
2122
        filter := bson.M{
1✔
2123
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2124
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2125
        }
1✔
2126
        if !includeDeleted {
2✔
2127
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2128
                        {Key: "$exists", Value: false},
1✔
2129
                }
1✔
2130
        }
1✔
2131

2132
        opts := &mopts.FindOneOptions{}
1✔
2133
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2134

1✔
2135
        var dd model.DeviceDeployment
1✔
2136
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2137
                if err == mongo.ErrNoDocuments {
2✔
2138
                        return nil, ErrStorageNotFound
1✔
2139
                }
1✔
UNCOV
2140
                return nil, err
×
2141
        }
2142

2143
        return &dd, nil
1✔
2144
}
2145

2146
func (db *DataStoreMongo) GetDeviceDeployments(
2147
        ctx context.Context,
2148
        skip int,
2149
        limit int,
2150
        deviceID string,
2151
        active *bool,
2152
        includeDeleted bool,
2153
) ([]model.DeviceDeployment, error) {
4✔
2154

4✔
2155
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2156
        collDevs := database.Collection(CollectionDevices)
4✔
2157

4✔
2158
        filter := bson.M{}
4✔
2159
        if !includeDeleted {
6✔
2160
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2161
                        {Key: "$exists", Value: false},
2✔
2162
                }
2✔
2163
        }
2✔
2164
        if deviceID != "" {
5✔
2165
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2166
        }
1✔
2167
        if active != nil {
5✔
2168
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2169
        }
1✔
2170

2171
        opts := &mopts.FindOptions{}
4✔
2172
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
4✔
2173
        if skip > 0 {
5✔
2174
                opts.SetSkip(int64(skip))
1✔
2175
        }
1✔
2176
        if limit > 0 {
5✔
2177
                opts.SetLimit(int64(limit))
1✔
2178
        }
1✔
2179

2180
        var deviceDeployments []model.DeviceDeployment
4✔
2181
        cursor, err := collDevs.Find(ctx, filter, opts)
4✔
2182
        if err != nil {
4✔
UNCOV
2183
                return nil, err
×
UNCOV
2184
        }
×
2185
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
4✔
2186
                return nil, err
×
2187
        }
×
2188

2189
        return deviceDeployments, nil
4✔
2190
}
2191

2192
// deployments
2193

2194
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2195
        indexes ...mongo.IndexModel) error {
436✔
2196
        ctx := context.Background()
436✔
2197
        dataBase := db.client.Database(dbName)
436✔
2198

436✔
2199
        coll := dataBase.Collection(collName)
436✔
2200
        idxView := coll.Indexes()
436✔
2201
        _, err := idxView.CreateMany(ctx, indexes)
436✔
2202
        return err
436✔
2203
}
436✔
2204

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

16✔
2208
        var idx bson.M
16✔
2209
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
16✔
2210
        collDpl := database.Collection(CollectionDeployments)
16✔
2211
        idxView := collDpl.Indexes()
16✔
2212

16✔
2213
        cursor, err := idxView.List(ctx)
16✔
2214
        if err != nil {
16✔
UNCOV
2215
                // check failed, assume indexing is not there
×
UNCOV
2216
                return false
×
UNCOV
2217
        }
×
2218

2219
        has := map[string]bool{}
16✔
2220
        for cursor.Next(ctx) {
46✔
2221
                if err = cursor.Decode(&idx); err != nil {
30✔
UNCOV
2222
                        continue
×
2223
                }
2224
                if _, ok := idx["weights"]; ok {
45✔
2225
                        // text index
15✔
2226
                        for k := range idx["weights"].(bson.M) {
45✔
2227
                                has[k] = true
30✔
2228
                        }
30✔
2229
                } else {
15✔
2230
                        for i := range idx["key"].(bson.M) {
30✔
2231
                                has[i] = true
15✔
2232
                        }
15✔
2233

2234
                }
2235
        }
2236
        if err != nil {
16✔
UNCOV
2237
                return false
×
UNCOV
2238
        }
×
2239

2240
        for _, key := range StorageIndexes.Keys.(bson.D) {
47✔
2241
                _, ok := has[key.Key]
31✔
2242
                if !ok {
32✔
2243
                        return false
1✔
2244
                }
1✔
2245
        }
2246

2247
        return true
15✔
2248
}
2249

2250
// Insert persists object
2251
func (db *DataStoreMongo) InsertDeployment(
2252
        ctx context.Context,
2253
        deployment *model.Deployment,
2254
) error {
210✔
2255

210✔
2256
        if deployment == nil {
211✔
2257
                return ErrDeploymentStorageInvalidDeployment
1✔
2258
        }
1✔
2259

2260
        if err := deployment.Validate(); err != nil {
211✔
2261
                return err
2✔
2262
        }
2✔
2263

2264
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
208✔
2265
        collDpl := database.Collection(CollectionDeployments)
208✔
2266

208✔
2267
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
209✔
2268
                return err
1✔
2269
        }
1✔
2270
        return nil
208✔
2271
}
2272

2273
// Delete removed entry by ID
2274
// Noop on ID not found
2275
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
4✔
2276

4✔
2277
        if len(id) == 0 {
5✔
2278
                return ErrStorageInvalidID
1✔
2279
        }
1✔
2280

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

3✔
2284
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
UNCOV
2285
                return err
×
UNCOV
2286
        }
×
2287

2288
        return nil
3✔
2289
}
2290

2291
func (db *DataStoreMongo) FindDeploymentByID(
2292
        ctx context.Context,
2293
        id string,
2294
) (*model.Deployment, error) {
10✔
2295

10✔
2296
        if len(id) == 0 {
11✔
2297
                return nil, ErrStorageInvalidID
1✔
2298
        }
1✔
2299

2300
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2301
        collDpl := database.Collection(CollectionDeployments)
9✔
2302

9✔
2303
        deployment := new(model.Deployment)
9✔
2304
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
9✔
2305
                Decode(deployment); err != nil {
12✔
2306
                if err == mongo.ErrNoDocuments {
6✔
2307
                        return nil, nil
3✔
2308
                }
3✔
UNCOV
2309
                return nil, err
×
2310
        }
2311

2312
        return deployment, nil
6✔
2313
}
2314

2315
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2316
        ctx context.Context,
2317
        ids ...string,
2318
) (deploymentStats []*model.DeploymentStats, err error) {
2✔
2319

2✔
2320
        if len(ids) == 0 {
2✔
UNCOV
2321
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
UNCOV
2322
        }
×
2323

2324
        for _, id := range ids {
6✔
2325
                if len(id) == 0 {
4✔
UNCOV
2326
                        return nil, ErrStorageInvalidID
×
UNCOV
2327
                }
×
2328
        }
2329

2330
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2331
        collDpl := database.Collection(CollectionDeployments)
2✔
2332

2✔
2333
        query := bson.M{
2✔
2334
                "_id": bson.M{
2✔
2335
                        "$in": ids,
2✔
2336
                },
2✔
2337
        }
2✔
2338
        statsProjection := &mopts.FindOptions{
2✔
2339
                Projection: bson.M{"stats": 1},
2✔
2340
        }
2✔
2341

2✔
2342
        results, err := collDpl.Find(
2✔
2343
                ctx,
2✔
2344
                query,
2✔
2345
                statsProjection,
2✔
2346
        )
2✔
2347
        if err != nil {
2✔
UNCOV
2348
                return nil, err
×
UNCOV
2349
        }
×
2350

2351
        for results.Next(context.Background()) {
6✔
2352
                depl := new(model.DeploymentStats)
4✔
2353
                if err = results.Decode(&depl); err != nil {
4✔
UNCOV
2354
                        if err == mongo.ErrNoDocuments {
×
UNCOV
2355
                                return nil, nil
×
UNCOV
2356
                        }
×
2357
                        return nil, err
×
2358
                }
2359
                deploymentStats = append(deploymentStats, depl)
4✔
2360
        }
2361

2362
        return deploymentStats, nil
2✔
2363
}
2364

2365
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2366
        id string) (*model.Deployment, error) {
8✔
2367

8✔
2368
        if len(id) == 0 {
9✔
2369
                return nil, ErrStorageInvalidID
1✔
2370
        }
1✔
2371

2372
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2373
        collDpl := database.Collection(CollectionDeployments)
7✔
2374

7✔
2375
        var deployment *model.Deployment
7✔
2376
        filter := bson.D{
7✔
2377
                {Key: "_id", Value: id},
7✔
2378
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2379
        }
7✔
2380
        if err := collDpl.FindOne(ctx, filter).
7✔
2381
                Decode(&deployment); err != nil {
12✔
2382
                if err == mongo.ErrNoDocuments {
10✔
2383
                        return nil, nil
5✔
2384
                }
5✔
UNCOV
2385
                return nil, err
×
2386
        }
2387

2388
        return deployment, nil
3✔
2389
}
2390

2391
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2392
        ctx context.Context,
2393
        deploymentID string,
2394
        increment int,
2395
) error {
55✔
2396
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
55✔
2397
        collection := database.Collection(CollectionDeployments)
55✔
2398

55✔
2399
        filter := bson.M{
55✔
2400
                "_id": deploymentID,
55✔
2401
                StorageKeyDeploymentDeviceCount: bson.M{
55✔
2402
                        "$ne": nil,
55✔
2403
                },
55✔
2404
        }
55✔
2405

55✔
2406
        update := bson.M{
55✔
2407
                "$inc": bson.M{
55✔
2408
                        StorageKeyDeploymentDeviceCount: increment,
55✔
2409
                },
55✔
2410
        }
55✔
2411

55✔
2412
        _, err := collection.UpdateOne(ctx, filter, update)
55✔
2413
        return err
55✔
2414
}
55✔
2415

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

3✔
2424
        filter := bson.M{
3✔
2425
                "_id": deploymentID,
3✔
2426
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2427
                        "$eq": nil,
3✔
2428
                },
3✔
2429
        }
3✔
2430

3✔
2431
        update := bson.M{
3✔
2432
                "$set": bson.M{
3✔
2433
                        StorageKeyDeploymentDeviceCount: count,
3✔
2434
                },
3✔
2435
        }
3✔
2436

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

2441
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2442
        id string) (int, error) {
3✔
2443

3✔
2444
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2445
        collDevs := database.Collection(CollectionDevices)
3✔
2446

3✔
2447
        filter := bson.M{
3✔
2448
                StorageKeyDeviceDeploymentDeploymentID: id,
3✔
2449
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
2450
                        {Key: "$exists", Value: false},
3✔
2451
                },
3✔
2452
        }
3✔
2453

3✔
2454
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
3✔
2455
        if err != nil {
3✔
UNCOV
2456
                return 0, err
×
UNCOV
2457
        }
×
2458

2459
        return int(deviceCount), nil
3✔
2460
}
2461

2462
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2463
        id string, stats model.Stats) error {
6✔
2464

6✔
2465
        if len(id) == 0 {
7✔
2466
                return ErrStorageInvalidID
1✔
2467
        }
1✔
2468

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

5✔
2472
        deployment, err := model.NewDeployment()
5✔
2473
        if err != nil {
5✔
UNCOV
2474
                return errors.Wrap(err, "failed to create deployment")
×
UNCOV
2475
        }
×
2476

2477
        deployment.Stats = stats
5✔
2478
        var update bson.M
5✔
2479
        if deployment.IsFinished() {
5✔
UNCOV
2480
                now := time.Now()
×
UNCOV
2481

×
UNCOV
2482
                update = bson.M{
×
2483
                        "$set": bson.M{
×
2484
                                StorageKeyDeploymentStats:    stats,
×
2485
                                StorageKeyDeploymentFinished: &now,
×
2486
                        },
×
2487
                }
×
2488
        } else {
5✔
2489
                update = bson.M{
5✔
2490
                        "$set": bson.M{
5✔
2491
                                StorageKeyDeploymentStats: stats,
5✔
2492
                        },
5✔
2493
                }
5✔
2494
        }
5✔
2495

2496
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
5✔
2497
        if res != nil && res.MatchedCount == 0 {
7✔
2498
                return ErrStorageInvalidID
2✔
2499
        }
2✔
2500
        return err
3✔
2501
}
2502

2503
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2504
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
8✔
2505

8✔
2506
        if len(id) == 0 {
9✔
2507
                return ErrStorageInvalidID
1✔
2508
        }
1✔
2509

2510
        if _, err := stateTo.MarshalText(); err != nil {
7✔
UNCOV
2511
                return ErrStorageInvalidInput
×
UNCOV
2512
        }
×
2513

2514
        // does not need any extra operations
2515
        // following query won't handle this case well and increase the state_to value
2516
        if stateFrom == stateTo {
8✔
2517
                return nil
1✔
2518
        }
1✔
2519

2520
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2521
        collDpl := database.Collection(CollectionDeployments)
6✔
2522

6✔
2523
        var update bson.M
6✔
2524

6✔
2525
        if stateFrom == model.DeviceDeploymentStatusNull {
8✔
2526
                // note dot notation on embedded document
2✔
2527
                update = bson.M{
2✔
2528
                        "$inc": bson.M{
2✔
2529
                                "stats." + stateTo.String(): 1,
2✔
2530
                        },
2✔
2531
                }
2✔
2532
        } else {
7✔
2533
                // note dot notation on embedded document
5✔
2534
                update = bson.M{
5✔
2535
                        "$inc": bson.M{
5✔
2536
                                "stats." + stateFrom.String(): -1,
5✔
2537
                                "stats." + stateTo.String():   1,
5✔
2538
                        },
5✔
2539
                }
5✔
2540
        }
5✔
2541

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

6✔
2544
        if res != nil && res.MatchedCount == 0 {
7✔
2545
                return ErrStorageInvalidID
1✔
2546
        }
1✔
2547

2548
        return err
5✔
2549
}
2550

2551
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2552
        ctx context.Context,
2553
        deploymentID string,
2554
        increment int64,
2555
) error {
3✔
2556
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2557
        collection := database.Collection(CollectionDeployments)
3✔
2558

3✔
2559
        filter := bson.M{
3✔
2560
                "_id": deploymentID,
3✔
2561
        }
3✔
2562

3✔
2563
        update := bson.M{
3✔
2564
                "$inc": bson.M{
3✔
2565
                        StorageKeyDeploymentTotalSize: increment,
3✔
2566
                },
3✔
2567
        }
3✔
2568

3✔
2569
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2570
        return err
3✔
2571
}
3✔
2572

2573
func (db *DataStoreMongo) Find(ctx context.Context,
2574
        match model.Query) ([]*model.Deployment, int64, error) {
36✔
2575

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

36✔
2579
        andq := []bson.M{}
36✔
2580

36✔
2581
        // filter by IDs
36✔
2582
        if len(match.IDs) > 0 {
36✔
UNCOV
2583
                tq := bson.M{
×
UNCOV
2584
                        "_id": bson.M{
×
UNCOV
2585
                                "$in": match.IDs,
×
2586
                        },
×
2587
                }
×
2588
                andq = append(andq, tq)
×
2589
        }
×
2590

2591
        // build deployment by name part of the query
2592
        if match.SearchText != "" {
52✔
2593
                // we must have indexing for text search
16✔
2594
                if !db.hasIndexing(ctx, db.client) {
17✔
2595
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
1✔
2596
                }
1✔
2597

2598
                tq := bson.M{
15✔
2599
                        "$text": bson.M{
15✔
2600
                                "$search": match.SearchText,
15✔
2601
                        },
15✔
2602
                }
15✔
2603

15✔
2604
                andq = append(andq, tq)
15✔
2605
        }
2606

2607
        // build deployment by status part of the query
2608
        if match.Status != model.StatusQueryAny {
45✔
2609
                var status model.DeploymentStatus
10✔
2610
                if match.Status == model.StatusQueryPending {
12✔
2611
                        status = model.DeploymentStatusPending
2✔
2612
                } else if match.Status == model.StatusQueryInProgress {
14✔
2613
                        status = model.DeploymentStatusInProgress
4✔
2614
                } else {
8✔
2615
                        status = model.DeploymentStatusFinished
4✔
2616
                }
4✔
2617
                stq := bson.M{StorageKeyDeploymentStatus: status}
10✔
2618
                andq = append(andq, stq)
10✔
2619
        }
2620

2621
        // build deployment by type part of the query
2622
        if match.Type != "" {
37✔
2623
                if match.Type == model.DeploymentTypeConfiguration {
4✔
2624
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
2✔
2625
                } else if match.Type == model.DeploymentTypeSoftware {
2✔
UNCOV
2626
                        andq = append(andq, bson.M{
×
UNCOV
2627
                                "$or": []bson.M{
×
UNCOV
2628
                                        {StorageKeyDeploymentType: match.Type},
×
2629
                                        {StorageKeyDeploymentType: ""},
×
2630
                                },
×
2631
                        })
×
2632
                }
×
2633
        }
2634

2635
        query := bson.M{}
35✔
2636
        if len(andq) != 0 {
58✔
2637
                // use search criteria if any
23✔
2638
                query = bson.M{
23✔
2639
                        "$and": andq,
23✔
2640
                }
23✔
2641
        }
23✔
2642

2643
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
35✔
UNCOV
2644
                query["created"] = bson.M{
×
UNCOV
2645
                        "$gte": match.CreatedAfter,
×
UNCOV
2646
                        "$lte": match.CreatedBefore,
×
2647
                }
×
2648
        } else if match.CreatedAfter != nil {
35✔
2649
                query["created"] = bson.M{
×
2650
                        "$gte": match.CreatedAfter,
×
UNCOV
2651
                }
×
2652
        } else if match.CreatedBefore != nil {
35✔
2653
                query["created"] = bson.M{
×
2654
                        "$lte": match.CreatedBefore,
×
UNCOV
2655
                }
×
2656
        }
×
2657

2658
        options := db.findOptions(match)
35✔
2659

35✔
2660
        var deployments []*model.Deployment
35✔
2661
        cursor, err := collDpl.Find(ctx, query, options)
35✔
2662
        if err != nil {
35✔
UNCOV
2663
                return nil, 0, err
×
UNCOV
2664
        }
×
2665
        if err := cursor.All(ctx, &deployments); err != nil {
35✔
2666
                return nil, 0, err
×
2667
        }
×
2668
        // Count documents if we didn't find all already.
2669
        count := int64(0)
35✔
2670
        if !match.DisableCount {
70✔
2671
                count = int64(len(deployments))
35✔
2672
                if count >= int64(match.Limit) {
69✔
2673
                        count, err = collDpl.CountDocuments(ctx, query)
34✔
2674
                        if err != nil {
34✔
UNCOV
2675
                                return nil, 0, err
×
UNCOV
2676
                        }
×
2677
                } else {
1✔
2678
                        // Don't forget to add the skipped documents
1✔
2679
                        count += int64(match.Skip)
1✔
2680
                }
1✔
2681
        }
2682

2683
        return deployments, count, nil
35✔
2684
}
2685

2686
func (db *DataStoreMongo) findOptions(match model.Query) *mopts.FindOptions {
35✔
2687
        options := &mopts.FindOptions{}
35✔
2688
        if match.Sort == model.SortDirectionAscending {
36✔
2689
                options.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
2690
        } else {
35✔
2691
                options.SetSort(bson.D{{Key: "created", Value: -1}})
34✔
2692
        }
34✔
2693
        if match.Skip > 0 {
37✔
2694
                options.SetSkip(int64(match.Skip))
2✔
2695
        }
2✔
2696
        if match.Limit > 0 {
40✔
2697
                options.SetLimit(int64(match.Limit))
5✔
2698
        }
5✔
2699
        return options
35✔
2700
}
2701

2702
// FindNewerActiveDeployments finds active deployments which were created
2703
// after createdAfter
2704
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2705
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
5✔
2706

5✔
2707
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2708
        c := database.Collection(CollectionDeployments)
5✔
2709

5✔
2710
        queryFilters := make([]bson.M, 0)
5✔
2711
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
5✔
2712
        queryFilters = append(queryFilters,
5✔
2713
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
5✔
2714
        findQuery := bson.M{}
5✔
2715
        findQuery["$and"] = queryFilters
5✔
2716

5✔
2717
        findOptions := &mopts.FindOptions{}
5✔
2718
        findOptions.SetSkip(int64(skip))
5✔
2719
        findOptions.SetLimit(int64(limit))
5✔
2720

5✔
2721
        findOptions.SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}})
5✔
2722
        cursor, err := c.Find(ctx, findQuery, findOptions)
5✔
2723
        if err != nil {
5✔
UNCOV
2724
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2725
        }
×
2726
        defer cursor.Close(ctx)
5✔
2727

5✔
2728
        var deployments []*model.Deployment
5✔
2729

5✔
2730
        if err = cursor.All(ctx, &deployments); err != nil {
5✔
UNCOV
2731
                return nil, errors.Wrap(err, "failed to get deployments")
×
UNCOV
2732
        }
×
2733

2734
        return deployments, nil
5✔
2735
}
2736

2737
// SetDeploymentStatus simply sets the status field
2738
// optionally sets 'finished time' if deployment is indeed finished
2739
func (db *DataStoreMongo) SetDeploymentStatus(
2740
        ctx context.Context,
2741
        id string,
2742
        status model.DeploymentStatus,
2743
        now time.Time,
2744
) error {
6✔
2745
        if len(id) == 0 {
6✔
UNCOV
2746
                return ErrStorageInvalidID
×
UNCOV
2747
        }
×
2748

2749
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2750
        collDpl := database.Collection(CollectionDeployments)
6✔
2751

6✔
2752
        var update bson.M
6✔
2753
        if status == model.DeploymentStatusFinished {
8✔
2754
                update = bson.M{
2✔
2755
                        "$set": bson.M{
2✔
2756
                                StorageKeyDeploymentActive:   false,
2✔
2757
                                StorageKeyDeploymentStatus:   status,
2✔
2758
                                StorageKeyDeploymentFinished: &now,
2✔
2759
                        },
2✔
2760
                }
2✔
2761
        } else {
7✔
2762
                update = bson.M{
5✔
2763
                        "$set": bson.M{
5✔
2764
                                StorageKeyDeploymentActive: true,
5✔
2765
                                StorageKeyDeploymentStatus: status,
5✔
2766
                        },
5✔
2767
                }
5✔
2768
        }
5✔
2769

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

6✔
2772
        if res != nil && res.MatchedCount == 0 {
7✔
2773
                return ErrStorageInvalidID
1✔
2774
        }
1✔
2775

2776
        return err
5✔
2777
}
2778

2779
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2780
// given artifact
2781
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2782
        id string) (bool, error) {
4✔
2783

4✔
2784
        if len(id) == 0 {
4✔
UNCOV
2785
                return false, ErrStorageInvalidID
×
UNCOV
2786
        }
×
2787

2788
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2789
        collDpl := database.Collection(CollectionDeployments)
4✔
2790

4✔
2791
        var tmp interface{}
4✔
2792
        query := bson.D{
4✔
2793
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2794
                {Key: StorageKeyDeploymentArtifacts, Value: id},
4✔
2795
        }
4✔
2796
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
7✔
2797
                if err == mongo.ErrNoDocuments {
6✔
2798
                        return false, nil
3✔
2799
                }
3✔
UNCOV
2800
                return false, err
×
2801
        }
2802

2803
        return true, nil
2✔
2804
}
2805

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

4✔
2811
        if len(artifactName) == 0 {
4✔
UNCOV
2812
                return false, ErrImagesStorageInvalidArtifactName
×
UNCOV
2813
        }
×
2814

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

4✔
2818
        var tmp interface{}
4✔
2819
        query := bson.D{
4✔
2820
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2821
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
4✔
2822
        }
4✔
2823

4✔
2824
        projection := bson.M{
4✔
2825
                "_id": 1,
4✔
2826
        }
4✔
2827
        findOptions := mopts.FindOne()
4✔
2828
        findOptions.SetProjection(projection)
4✔
2829

4✔
2830
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
7✔
2831
                if err == mongo.ErrNoDocuments {
6✔
2832
                        return false, nil
3✔
2833
                }
3✔
UNCOV
2834
                return false, err
×
2835
        }
2836

2837
        return true, nil
1✔
2838
}
2839

2840
// ExistByArtifactId check if there is any deployment that uses give artifact
2841
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
UNCOV
2842
        id string) (bool, error) {
×
UNCOV
2843

×
UNCOV
2844
        if len(id) == 0 {
×
2845
                return false, ErrStorageInvalidID
×
2846
        }
×
2847

2848
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2849
        collDpl := database.Collection(CollectionDeployments)
×
UNCOV
2850

×
2851
        var tmp interface{}
×
2852
        query := bson.D{
×
2853
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2854
        }
×
2855
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2856
                if err == mongo.ErrNoDocuments {
×
2857
                        return false, nil
×
2858
                }
×
2859
                return false, err
×
2860
        }
2861

2862
        return true, nil
×
2863
}
2864

2865
// Per-tenant storage settings
2866
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
2✔
2867
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2868
        collection := database.Collection(CollectionStorageSettings)
2✔
2869

2✔
2870
        settings := new(model.StorageSettings)
2✔
2871
        // supposed that it's only one document in the collection
2✔
2872
        query := bson.M{
2✔
2873
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2874
        }
2✔
2875
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
3✔
2876
                if err == mongo.ErrNoDocuments {
2✔
2877
                        return nil, nil
1✔
2878
                }
1✔
UNCOV
2879
                return nil, err
×
2880
        }
2881

2882
        return settings, nil
2✔
2883
}
2884

2885
func (db *DataStoreMongo) SetStorageSettings(
2886
        ctx context.Context,
2887
        storageSettings *model.StorageSettings,
2888
) error {
2✔
2889
        var err error
2✔
2890
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2891
        collection := database.Collection(CollectionStorageSettings)
2✔
2892

2✔
2893
        filter := bson.M{
2✔
2894
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2895
        }
2✔
2896
        if storageSettings != nil {
4✔
2897
                replaceOptions := mopts.Replace()
2✔
2898
                replaceOptions.SetUpsert(true)
2✔
2899
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
2✔
2900
        } else {
3✔
2901
                _, err = collection.DeleteOne(ctx, filter)
1✔
2902
        }
1✔
2903

2904
        return err
2✔
2905
}
2906

2907
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2908
        ctx context.Context,
2909
        artifactName string,
2910
        artifactIDs []string,
2911
) error {
1✔
2912
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2913
        collDpl := database.Collection(CollectionDeployments)
1✔
2914

1✔
2915
        query := bson.D{
1✔
2916
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2917
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2918
        }
1✔
2919
        update := bson.M{
1✔
2920
                "$set": bson.M{
1✔
2921
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
2922
                },
1✔
2923
        }
1✔
2924

1✔
2925
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
2926
        return err
1✔
2927
}
1✔
2928

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