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

mendersoftware / deployments / 1072216342

14 Nov 2023 12:49PM UTC coverage: 79.663% (+1.8%) from 77.876%
1072216342

Pull #961

gitlab-ci

kjaskiewiczz
test(mongo): move release related tests to separate file

Changelog: Title
Ticket: None

Signed-off-by: Krzysztof Jaskiewicz <krzysztof.jaskiewicz@northern.tech>
Pull Request #961: add support for bulk removal of releases by names

86 of 98 new or added lines in 6 files covered. (87.76%)

11 existing lines in 1 file now uncovered.

8046 of 10100 relevant lines covered (79.66%)

33.85 hits per line

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

85.2
/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.15
102
        IndexNameReleaseTags           = "release_tags"
103
        IndexNameReleaseUpdateTypes    = "release_update_types"
104
        IndexNameAggregatedUpdateTypes = "aggregated_release_update_types"
105
        IndexNameReleaseArtifactsCount = "release_artifacts_count"
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
        StorageKeyReleaseArtifactsCount            = "artifacts_count"
417
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
418
                StorageKeyImageDescription
419
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
420
                StorageKeyImageDescription
421
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
422
                StorageKeyImageDeviceTypes
423
        StorageKeyReleaseArtifactsUpdateTypes = StorageKeyReleaseArtifacts + "." +
424
                StorageKeyUpdateType
425
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
426
                StorageKeyImageModified
427
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
428
                StorageKeyId
429
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
430
                StorageKeyImageDependsIdx
431
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
432
                StorageKeyImageProvidesIdx
433

434
        StorageKeyDeviceDeploymentLogMessages = "messages"
435

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

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

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

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

478
        StorageKeyStorageReleaseUpdateTypes = "update_types"
479

480
        ArtifactDependsDeviceType = "device_type"
481
)
482

483
type DataStoreMongo struct {
484
        client *mongo.Client
485
}
486

487
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
395✔
488
        return &DataStoreMongo{
395✔
489
                client: client,
395✔
490
        }
395✔
491
}
395✔
492

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

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

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

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

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

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

535
        return client, nil
1✔
536
}
537

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

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

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

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

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

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

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

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

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

666
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
8✔
667
        if sortField == "" {
13✔
668
                sortField = "name"
5✔
669
        }
5✔
670
        if sortOrder == 0 {
13✔
671
                sortOrder = 1
5✔
672
        }
5✔
673

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

8✔
698
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
8✔
699
        collImg := database.Collection(CollectionImages)
8✔
700

8✔
701
        cursor, err := collImg.Aggregate(ctx, pipe)
8✔
702
        if err != nil {
8✔
703
                return nil, 0, err
×
704
        }
×
705
        defer cursor.Close(ctx)
8✔
706

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

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

76✔
728
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
76✔
729
        if sortField == "" {
138✔
730
                sortField = "_id"
62✔
731
        } else if sortField == "name" {
78✔
732
                sortField = StorageKeyReleaseName
2✔
733
        }
2✔
734
        if sortOrder == 0 {
138✔
735
                sortOrder = 1
62✔
736
        }
62✔
737

738
        page := 1
76✔
739
        perPage := DefaultDocumentLimit
76✔
740
        if filt != nil {
150✔
741
                if filt.Page > 0 {
77✔
742
                        page = filt.Page
3✔
743
                }
3✔
744
                if filt.PerPage > 0 {
77✔
745
                        perPage = filt.PerPage
3✔
746
                }
3✔
747
        }
748

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

76✔
759
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
76✔
760
        collReleases := database.Collection(CollectionReleases)
76✔
761

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

795
        // TODO: can we return number of all documents in the collection
796
        // using EstimatedDocumentCount?
797
        count, err := collReleases.CountDocuments(ctx, filter)
76✔
798
        if err != nil {
76✔
799
                return nil, 0, err
×
800
        }
×
801

802
        return releases, int(count), nil
76✔
803
}
804

805
// limits
806
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
4✔
807

4✔
808
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
809
        collLim := database.Collection(CollectionLimits)
4✔
810

4✔
811
        limit := new(model.Limit)
4✔
812
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
4✔
813
                Decode(limit); err != nil {
6✔
814
                if err == mongo.ErrNoDocuments {
4✔
815
                        return nil, ErrLimitNotFound
2✔
816
                }
2✔
817
                return nil, err
×
818
        }
819

820
        return limit, nil
2✔
821
}
822

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

5✔
825
        dbname := mstore.DbNameForTenant(tenantId, DbName)
5✔
826

5✔
827
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
5✔
828
}
5✔
829

830
//images
831

832
// Exists checks if object with ID exists
833
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
834
        var result interface{}
×
835

×
836
        if len(id) == 0 {
×
837
                return false, ErrImagesStorageInvalidID
×
838
        }
×
839

840
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
841
        collImg := database.Collection(CollectionImages)
×
842

×
843
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
844
                Decode(&result); err != nil {
×
845
                if err == mongo.ErrNoDocuments {
×
846
                        return false, nil
×
847
                }
×
848
                return false, err
×
849
        }
850

851
        return true, nil
×
852
}
853

854
// Update provided Image
855
// Return false if not found
856
func (db *DataStoreMongo) Update(ctx context.Context,
857
        image *model.Image) (bool, error) {
1✔
858

1✔
859
        if err := image.Validate(); err != nil {
1✔
860
                return false, err
×
861
        }
×
862

863
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
864
        collImg := database.Collection(CollectionImages)
1✔
865

1✔
866
        // add special representation of artifact provides
1✔
867
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
868

1✔
869
        image.SetModified(time.Now())
1✔
870
        if res, err := collImg.ReplaceOne(
1✔
871
                ctx, bson.M{"_id": image.Id}, image,
1✔
872
        ); err != nil {
1✔
873
                return false, err
×
874
        } else if res.MatchedCount == 0 {
1✔
875
                return false, nil
×
876
        }
×
877

878
        return true, nil
1✔
879
}
880

881
// ImageByNameAndDeviceType finds image with specified application name and target device type
882
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
883
        name, deviceType string) (*model.Image, error) {
9✔
884

9✔
885
        if len(name) == 0 {
10✔
886
                return nil, ErrImagesStorageInvalidArtifactName
1✔
887
        }
1✔
888

889
        if len(deviceType) == 0 {
9✔
890
                return nil, ErrImagesStorageInvalidDeviceType
1✔
891
        }
1✔
892

893
        // equal to device type & software version (application name + version)
894
        query := bson.M{
7✔
895
                StorageKeyImageName:        name,
7✔
896
                StorageKeyImageDeviceTypes: deviceType,
7✔
897
        }
7✔
898

7✔
899
        // If multiple entries matches, pick the smallest one.
7✔
900
        findOpts := mopts.FindOne()
7✔
901
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
7✔
902

7✔
903
        dbName := mstore.DbFromContext(ctx, DatabaseName)
7✔
904
        database := db.client.Database(dbName)
7✔
905
        collImg := database.Collection(CollectionImages)
7✔
906

7✔
907
        // Both we lookup unique object, should be one or none.
7✔
908
        var image model.Image
7✔
909
        if err := collImg.FindOne(ctx, query, findOpts).
7✔
910
                Decode(&image); err != nil {
11✔
911
                if err == mongo.ErrNoDocuments {
8✔
912
                        return nil, nil
4✔
913
                }
4✔
914
                return nil, err
×
915
        }
916

917
        return &image, nil
3✔
918
}
919

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

1✔
924
        if len(deviceType) == 0 {
1✔
925
                return nil, ErrImagesStorageInvalidDeviceType
×
926
        }
×
927

928
        if len(ids) == 0 {
1✔
929
                return nil, ErrImagesStorageInvalidID
×
930
        }
×
931

932
        query := bson.D{
1✔
933
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
934
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
935
        }
1✔
936

1✔
937
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
938
        collImg := database.Collection(CollectionImages)
1✔
939

1✔
940
        // If multiple entries matches, pick the smallest one
1✔
941
        findOpts := mopts.FindOne()
1✔
942
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
943

1✔
944
        // Both we lookup unique object, should be one or none.
1✔
945
        var image model.Image
1✔
946
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
947
                Decode(&image); err != nil {
1✔
948
                if err == mongo.ErrNoDocuments {
×
949
                        return nil, nil
×
950
                }
×
951
                return nil, err
×
952
        }
953

954
        return &image, nil
1✔
955
}
956

957
// ImagesByName finds images with specified artifact name
958
func (db *DataStoreMongo) ImagesByName(
959
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
960

1✔
961
        var images []*model.Image
1✔
962

1✔
963
        if len(name) == 0 {
1✔
964
                return nil, ErrImagesStorageInvalidName
×
965
        }
×
966

967
        // equal to artifact name
968
        query := bson.M{
1✔
969
                StorageKeyImageName: name,
1✔
970
        }
1✔
971

1✔
972
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
973
        collImg := database.Collection(CollectionImages)
1✔
974
        cursor, err := collImg.Find(ctx, query)
1✔
975
        if err != nil {
1✔
976
                return nil, err
×
977
        }
×
978
        // Both we lookup unique object, should be one or none.
979
        if err = cursor.All(ctx, &images); err != nil {
1✔
980
                return nil, err
×
981
        }
×
982

983
        return images, nil
1✔
984
}
985

986
func newDependsConflictError(mgoErr mongo.WriteError) *model.ConflictError {
7✔
987
        var err error
7✔
988
        conflictErr := model.NewConflictError(ErrConflictingDepends)
7✔
989
        // Try to lookup the document that caused the index violation:
7✔
990
        if raw, ok := mgoErr.Raw.Lookup("keyValue").DocumentOK(); ok {
14✔
991
                if raw, ok = raw.Lookup(StorageKeyImageDependsIdx).DocumentOK(); ok {
14✔
992
                        var conflicts map[string]interface{}
7✔
993
                        err = bson.Unmarshal([]byte(raw), &conflicts)
7✔
994
                        if err == nil {
14✔
995
                                _ = conflictErr.WithMetadata(
7✔
996
                                        map[string]interface{}{
7✔
997
                                                "conflict": conflicts,
7✔
998
                                        },
7✔
999
                                )
7✔
1000
                        }
7✔
1001
                }
1002
        }
1003
        return conflictErr
7✔
1004
}
1005

1006
// Insert persists object
1007
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
69✔
1008

69✔
1009
        if image == nil {
69✔
1010
                return ErrImagesStorageInvalidImage
×
1011
        }
×
1012

1013
        if err := image.Validate(); err != nil {
69✔
1014
                return err
×
1015
        }
×
1016

1017
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
69✔
1018
        collImg := database.Collection(CollectionImages)
69✔
1019

69✔
1020
        // add special representation of artifact provides
69✔
1021
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
69✔
1022

69✔
1023
        _, err := collImg.InsertOne(ctx, image)
69✔
1024
        if err != nil {
76✔
1025
                var wExc mongo.WriteException
7✔
1026
                if errors.As(err, &wExc) {
14✔
1027
                        for _, wErr := range wExc.WriteErrors {
14✔
1028
                                if !mongo.IsDuplicateKeyError(wErr) {
7✔
1029
                                        continue
×
1030
                                }
1031
                                return newDependsConflictError(wErr)
7✔
1032
                        }
1033
                }
1034
                return err
×
1035
        }
1036

1037
        return nil
62✔
1038
}
1039

1040
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
2✔
1041
        collUploads := db.client.
2✔
1042
                Database(DatabaseName).
2✔
1043
                Collection(CollectionUploadIntents)
2✔
1044
        if idty := identity.FromContext(ctx); idty != nil {
3✔
1045
                link.TenantID = idty.Tenant
1✔
1046
        }
1✔
1047
        _, err := collUploads.InsertOne(ctx, link)
2✔
1048
        return err
2✔
1049
}
1050

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

1088
func (db *DataStoreMongo) FindUploadLinks(
1089
        ctx context.Context,
1090
        expiredAt time.Time,
1091
) (store.Iterator[model.UploadLink], error) {
1✔
1092
        collUploads := db.client.
1✔
1093
                Database(DatabaseName).
1✔
1094
                Collection(CollectionUploadIntents)
1✔
1095

1✔
1096
        q := bson.D{{
1✔
1097
                Key: "status",
1✔
1098
                Value: bson.D{{
1✔
1099
                        Key:   "$lt",
1✔
1100
                        Value: model.LinkStatusProcessedBit,
1✔
1101
                }},
1✔
1102
        }, {
1✔
1103
                Key: "expire",
1✔
1104
                Value: bson.D{{
1✔
1105
                        Key:   "$lt",
1✔
1106
                        Value: expiredAt,
1✔
1107
                }},
1✔
1108
        }}
1✔
1109
        cur, err := collUploads.Find(ctx, q)
1✔
1110
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1111
}
1✔
1112

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

1✔
1117
        if len(id) == 0 {
1✔
1118
                return nil, ErrImagesStorageInvalidID
×
1119
        }
×
1120

1121
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1122
        collImg := database.Collection(CollectionImages)
1✔
1123
        projection := bson.M{
1✔
1124
                StorageKeyImageDependsIdx:  0,
1✔
1125
                StorageKeyImageProvidesIdx: 0,
1✔
1126
        }
1✔
1127
        findOptions := mopts.FindOne()
1✔
1128
        findOptions.SetProjection(projection)
1✔
1129

1✔
1130
        var image model.Image
1✔
1131
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
1132
                Decode(&image); err != nil {
2✔
1133
                if err == mongo.ErrNoDocuments {
2✔
1134
                        return nil, nil
1✔
1135
                }
1✔
1136
                return nil, err
×
1137
        }
1138

1139
        return &image, nil
1✔
1140
}
1141

1142
// IsArtifactUnique checks if there is no artifact with the same artifactName
1143
// supporting one of the device types from deviceTypesCompatible list.
1144
// Returns true, nil if artifact is unique;
1145
// false, nil if artifact is not unique;
1146
// false, error in case of error.
1147
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1148
        artifactName string, deviceTypesCompatible []string) (bool, error) {
6✔
1149

6✔
1150
        if len(artifactName) == 0 {
7✔
1151
                return false, ErrImagesStorageInvalidArtifactName
1✔
1152
        }
1✔
1153

1154
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1155
        collImg := database.Collection(CollectionImages)
5✔
1156

5✔
1157
        query := bson.M{
5✔
1158
                "$and": []bson.M{
5✔
1159
                        {
5✔
1160
                                StorageKeyImageName: artifactName,
5✔
1161
                        },
5✔
1162
                        {
5✔
1163
                                StorageKeyImageDeviceTypes: bson.M{
5✔
1164
                                        "$in": deviceTypesCompatible},
5✔
1165
                        },
5✔
1166
                },
5✔
1167
        }
5✔
1168

5✔
1169
        // do part of the job manually
5✔
1170
        // if candidate images have any extra 'depends' - guaranteed non-overlap
5✔
1171
        // otherwise it's a match
5✔
1172
        cur, err := collImg.Find(ctx, query)
5✔
1173
        if err != nil {
5✔
1174
                return false, err
×
1175
        }
×
1176

1177
        var images []model.Image
5✔
1178
        err = cur.All(ctx, &images)
5✔
1179
        if err != nil {
5✔
1180
                return false, err
×
1181
        }
×
1182

1183
        for _, i := range images {
6✔
1184
                // the artifact already has same name and overlapping dev type
1✔
1185
                // if there are no more depends than dev type - it's not unique
1✔
1186
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1187
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1188
                                return false, nil
1✔
1189
                        }
1✔
1190
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1191
                        return false, nil
×
1192
                }
×
1193
        }
1194

1195
        return true, nil
4✔
1196
}
1197

1198
// Delete image specified by ID
1199
// Noop on if not found.
1200
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
3✔
1201

3✔
1202
        if len(id) == 0 {
3✔
1203
                return ErrImagesStorageInvalidID
×
1204
        }
×
1205

1206
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
1207
        collImg := database.Collection(CollectionImages)
3✔
1208

3✔
1209
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
1210
                if res.DeletedCount == 0 {
×
1211
                        return nil
×
1212
                }
×
1213
                return err
×
1214
        }
1215

1216
        return nil
3✔
1217
}
1218

1219
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
98✔
1220
        if filt != nil && filt.Sort != "" {
118✔
1221
                sortParts := strings.SplitN(filt.Sort, ":", 2)
20✔
1222
                if len(sortParts) == 2 &&
20✔
1223
                        (sortParts[0] == "name" ||
20✔
1224
                                sortParts[0] == "modified" ||
20✔
1225
                                sortParts[0] == "artifacts_count" ||
20✔
1226
                                sortParts[0] == "tags") {
40✔
1227
                        sortField := sortParts[0]
20✔
1228
                        sortOrder := 1
20✔
1229
                        if sortParts[1] == model.SortDirectionDescending {
32✔
1230
                                sortOrder = -1
12✔
1231
                        }
12✔
1232
                        return sortField, sortOrder
20✔
1233
                }
1234
        }
1235
        return "", 0
78✔
1236
}
1237

1238
// ListImages lists all images
1239
func (db *DataStoreMongo) ListImages(
1240
        ctx context.Context,
1241
        filt *model.ReleaseOrImageFilter,
1242
) ([]*model.Image, int, error) {
15✔
1243

15✔
1244
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1245
        collImg := database.Collection(CollectionImages)
15✔
1246

15✔
1247
        filters := bson.M{}
15✔
1248
        if filt != nil {
25✔
1249
                if filt.Name != "" {
14✔
1250
                        filters[StorageKeyImageName] = bson.M{
4✔
1251
                                "$regex": primitive.Regex{
4✔
1252
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
4✔
1253
                                        Options: "i",
4✔
1254
                                },
4✔
1255
                        }
4✔
1256
                }
4✔
1257
                if filt.Description != "" {
12✔
1258
                        filters[StorageKeyImageDescription] = bson.M{
2✔
1259
                                "$regex": primitive.Regex{
2✔
1260
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
1261
                                        Options: "i",
2✔
1262
                                },
2✔
1263
                        }
2✔
1264
                }
2✔
1265
                if filt.DeviceType != "" {
11✔
1266
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1267
                                "$regex": primitive.Regex{
1✔
1268
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1269
                                        Options: "i",
1✔
1270
                                },
1✔
1271
                        }
1✔
1272
                }
1✔
1273

1274
        }
1275

1276
        projection := bson.M{
15✔
1277
                StorageKeyImageDependsIdx:  0,
15✔
1278
                StorageKeyImageProvidesIdx: 0,
15✔
1279
        }
15✔
1280
        findOptions := &mopts.FindOptions{}
15✔
1281
        findOptions.SetProjection(projection)
15✔
1282
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
16✔
1283
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1284
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1285
        }
1✔
1286

1287
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
15✔
1288
        if sortField == "" || sortField == "name" {
28✔
1289
                sortField = StorageKeyImageName
13✔
1290
        }
13✔
1291
        if sortOrder == 0 {
27✔
1292
                sortOrder = 1
12✔
1293
        }
12✔
1294
        findOptions.SetSort(bson.D{
15✔
1295
                {Key: sortField, Value: sortOrder},
15✔
1296
                {Key: "_id", Value: sortOrder},
15✔
1297
        })
15✔
1298

15✔
1299
        cursor, err := collImg.Find(ctx, filters, findOptions)
15✔
1300
        if err != nil {
15✔
1301
                return nil, 0, err
×
1302
        }
×
1303

1304
        // NOTE: cursor.All closes the cursor before returning
1305
        var images []*model.Image
15✔
1306
        if err := cursor.All(ctx, &images); err != nil {
15✔
1307
                if err == mongo.ErrNoDocuments {
×
1308
                        return nil, 0, nil
×
1309
                }
×
1310
                return nil, 0, err
×
1311
        }
1312

1313
        count, err := collImg.CountDocuments(ctx, filters)
15✔
1314
        if err != nil {
15✔
1315
                return nil, -1, ErrDevicesCountFailed
×
1316
        }
×
1317

1318
        return images, int(count), nil
15✔
1319
}
1320

1321
func (db *DataStoreMongo) DeleteImagesByNames(ctx context.Context, names []string) error {
1✔
1322
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1323
        collDevs := database.Collection(CollectionImages)
1✔
1324
        query := bson.M{
1✔
1325
                StorageKeyImageName: bson.M{
1✔
1326
                        "$in": names,
1✔
1327
                },
1✔
1328
        }
1✔
1329
        _, err := collDevs.DeleteMany(ctx, query)
1✔
1330
        return err
1✔
1331
}
1✔
1332

1333
// device deployment log
1334
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1335
        log model.DeploymentLog) error {
9✔
1336

9✔
1337
        if err := log.Validate(); err != nil {
12✔
1338
                return err
3✔
1339
        }
3✔
1340

1341
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1342
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1343

6✔
1344
        query := bson.D{
6✔
1345
                {Key: StorageKeyDeviceDeploymentDeviceId,
6✔
1346
                        Value: log.DeviceID},
6✔
1347
                {Key: StorageKeyDeviceDeploymentDeploymentID,
6✔
1348
                        Value: log.DeploymentID},
6✔
1349
        }
6✔
1350

6✔
1351
        // update log messages
6✔
1352
        // if the deployment log is already present than messages will be overwritten
6✔
1353
        update := bson.D{
6✔
1354
                {Key: "$set", Value: bson.M{
6✔
1355
                        StorageKeyDeviceDeploymentLogMessages: log.Messages,
6✔
1356
                }},
6✔
1357
        }
6✔
1358
        updateOptions := mopts.Update()
6✔
1359
        updateOptions.SetUpsert(true)
6✔
1360
        if _, err := collLogs.UpdateOne(
6✔
1361
                ctx, query, update, updateOptions); err != nil {
6✔
1362
                return err
×
1363
        }
×
1364

1365
        return nil
6✔
1366
}
1367

1368
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1369
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
6✔
1370

6✔
1371
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1372
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
6✔
1373

6✔
1374
        query := bson.M{
6✔
1375
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
6✔
1376
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1377
        }
6✔
1378

6✔
1379
        var depl model.DeploymentLog
6✔
1380
        if err := collLogs.FindOne(ctx, query).Decode(&depl); err != nil {
8✔
1381
                if err == mongo.ErrNoDocuments {
4✔
1382
                        return nil, nil
2✔
1383
                }
2✔
1384
                return nil, err
×
1385
        }
1386

1387
        return &depl, nil
4✔
1388
}
1389

1390
// device deployments
1391

1392
// Insert persists device deployment object
1393
func (db *DataStoreMongo) InsertDeviceDeployment(
1394
        ctx context.Context,
1395
        deviceDeployment *model.DeviceDeployment,
1396
        incrementDeviceCount bool,
1397
) error {
29✔
1398
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
29✔
1399
        c := database.Collection(CollectionDevices)
29✔
1400

29✔
1401
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
49✔
1402
                startedTime := time.Now().UTC()
20✔
1403
                deviceDeployment.Started = &startedTime
20✔
1404
        }
20✔
1405

1406
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
29✔
1407
                return err
×
1408
        }
×
1409

1410
        if incrementDeviceCount {
58✔
1411
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
29✔
1412
                if err != nil {
29✔
1413
                        return err
×
1414
                }
×
1415
        }
1416

1417
        return nil
29✔
1418
}
1419

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

43✔
1425
        if len(deployments) == 0 {
55✔
1426
                return nil
12✔
1427
        }
12✔
1428

1429
        deviceCountIncrements := make(map[string]int)
31✔
1430

31✔
1431
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
31✔
1432
        var list []interface{}
31✔
1433
        for _, deployment := range deployments {
104✔
1434

73✔
1435
                if deployment == nil {
74✔
1436
                        return ErrStorageInvalidDeviceDeployment
1✔
1437
                }
1✔
1438

1439
                if err := deployment.Validate(); err != nil {
74✔
1440
                        return errors.Wrap(err, "Validating device deployment")
2✔
1441
                }
2✔
1442

1443
                list = append(list, deployment)
70✔
1444
                if deployment.Status != model.DeviceDeploymentStatusPending {
84✔
1445
                        startedTime := time.Now().UTC()
14✔
1446
                        deployment.Started = &startedTime
14✔
1447
                }
14✔
1448
                deviceCountIncrements[deployment.DeploymentId]++
70✔
1449
        }
1450

1451
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
28✔
1452
        collDevs := database.Collection(CollectionDevices)
28✔
1453

28✔
1454
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
28✔
1455
                return err
×
1456
        }
×
1457

1458
        for deploymentID := range deviceCountIncrements {
63✔
1459
                err := db.IncrementDeploymentDeviceCount(
35✔
1460
                        ctx,
35✔
1461
                        deploymentID,
35✔
1462
                        deviceCountIncrements[deploymentID],
35✔
1463
                )
35✔
1464
                if err != nil {
35✔
1465
                        return err
×
1466
                }
×
1467
        }
1468

1469
        return nil
28✔
1470
}
1471

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

6✔
1478
        // Verify ID formatting
6✔
1479
        if len(deviceID) == 0 {
7✔
1480
                return nil, ErrStorageInvalidID
1✔
1481
        }
1✔
1482

1483
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1484
        collDevs := database.Collection(CollectionDevices)
5✔
1485

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

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

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

1510
        return deployment, nil
3✔
1511
}
1512

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

6✔
1520
        // Verify ID formatting
6✔
1521
        if len(deviceID) == 0 {
7✔
1522
                return nil, ErrStorageInvalidID
1✔
1523
        }
1✔
1524

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

5✔
1528
        query := bson.D{
5✔
1529
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
5✔
1530
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1531
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1532
                        {Key: "$exists", Value: false},
5✔
1533
                }},
5✔
1534
        }
5✔
1535

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

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

1551
        return deployment, nil
3✔
1552
}
1553

1554
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1555
        ctx context.Context,
1556
        deviceID string,
1557
        deploymentID string,
1558
        ddState model.DeviceDeploymentState,
1559
        currentStatus model.DeviceDeploymentStatus,
1560
) (model.DeviceDeploymentStatus, error) {
12✔
1561

12✔
1562
        // Verify ID formatting
12✔
1563
        if len(deviceID) == 0 ||
12✔
1564
                len(deploymentID) == 0 {
14✔
1565
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
2✔
1566
        }
2✔
1567

1568
        if err := ddState.Validate(); err != nil {
11✔
1569
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1570
        }
1✔
1571

1572
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
1573
        collDevs := database.Collection(CollectionDevices)
9✔
1574

9✔
1575
        // Device should know only about deployments that are not finished
9✔
1576
        query := bson.D{
9✔
1577
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
9✔
1578
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
9✔
1579
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
9✔
1580
                        {Key: "$exists", Value: false},
9✔
1581
                }},
9✔
1582
        }
9✔
1583

9✔
1584
        // update status field
9✔
1585
        set := bson.M{
9✔
1586
                StorageKeyDeviceDeploymentStatus: ddState.Status,
9✔
1587
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
9✔
1588
        }
9✔
1589
        // and finish time if provided
9✔
1590
        if ddState.FinishTime != nil {
11✔
1591
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
2✔
1592
        }
2✔
1593

1594
        if len(ddState.SubState) > 0 {
12✔
1595
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
3✔
1596
        }
3✔
1597

1598
        if currentStatus == model.DeviceDeploymentStatusPending &&
9✔
1599
                ddState.Status != currentStatus {
11✔
1600
                startedTime := time.Now().UTC()
2✔
1601
                set[StorageKeyDeviceDeploymentStarted] = startedTime
2✔
1602
        }
2✔
1603

1604
        update := bson.D{
9✔
1605
                {Key: "$set", Value: set},
9✔
1606
        }
9✔
1607

9✔
1608
        var old model.DeviceDeployment
9✔
1609

9✔
1610
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
9✔
1611
                Decode(&old); err != nil {
11✔
1612
                if err == mongo.ErrNoDocuments {
4✔
1613
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
2✔
1614
                }
2✔
1615
                return model.DeviceDeploymentStatusNull, err
×
1616

1617
        }
1618

1619
        return old.Status, nil
7✔
1620
}
1621

1622
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1623
        deviceID string, deploymentID string, log bool) error {
7✔
1624

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

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

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

5✔
1642
        update := bson.D{
5✔
1643
                {Key: "$set", Value: bson.M{
5✔
1644
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
5✔
1645
        }
5✔
1646

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

1653
        return nil
3✔
1654
}
1655

1656
// SaveDeviceDeploymentRequest saves device deployment request
1657
// with the device deployment object
1658
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1659
        ctx context.Context,
1660
        ID string,
1661
        request *model.DeploymentNextRequest,
1662
) error {
4✔
1663

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

4✔
1667
        res, err := collDevs.UpdateOne(
4✔
1668
                ctx,
4✔
1669
                bson.D{{Key: StorageKeyId, Value: ID}},
4✔
1670
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
4✔
1671
        )
4✔
1672
        if err != nil {
4✔
1673
                return err
×
1674
        } else if res.MatchedCount == 0 {
5✔
1675
                return ErrStorageNotFound
1✔
1676
        }
1✔
1677
        return nil
3✔
1678
}
1679

1680
// AssignArtifact assigns artifact to the device deployment
1681
func (db *DataStoreMongo) AssignArtifact(
1682
        ctx context.Context,
1683
        deviceID string,
1684
        deploymentID string,
1685
        artifact *model.Image,
1686
) error {
1✔
1687

1✔
1688
        // Verify ID formatting
1✔
1689
        if len(deviceID) == 0 ||
1✔
1690
                len(deploymentID) == 0 {
1✔
1691
                return ErrStorageInvalidID
×
1692
        }
×
1693

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

1✔
1697
        selector := bson.D{
1✔
1698
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1699
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1700
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1701
                        {Key: "$exists", Value: false},
1✔
1702
                }},
1✔
1703
        }
1✔
1704

1✔
1705
        update := bson.D{
1✔
1706
                {Key: "$set", Value: bson.M{
1✔
1707
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1708
                }},
1✔
1709
        }
1✔
1710

1✔
1711
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1712
                return err
×
1713
        } else if res.MatchedCount == 0 {
1✔
1714
                return ErrStorageNotFound
×
1715
        }
×
1716

1717
        return nil
1✔
1718
}
1719

1720
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1721
        id string) (model.Stats, error) {
6✔
1722

6✔
1723
        if len(id) == 0 {
6✔
1724
                return nil, ErrStorageInvalidID
×
1725
        }
×
1726

1727
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1728
        collDevs := database.Collection(CollectionDevices)
6✔
1729

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

1765
        raw := model.NewDeviceDeploymentStats()
6✔
1766
        for _, res := range results {
17✔
1767
                raw.Set(res.Status, res.Count)
11✔
1768
        }
11✔
1769
        return raw, nil
6✔
1770
}
1771

1772
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1773
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1774
        deploymentID string) ([]model.DeviceDeployment, error) {
6✔
1775

6✔
1776
        statuses := []model.DeviceDeployment{}
6✔
1777
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1778
        collDevs := database.Collection(CollectionDevices)
6✔
1779

6✔
1780
        query := bson.M{
6✔
1781
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1782
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1783
                        {Key: "$exists", Value: false},
6✔
1784
                },
6✔
1785
        }
6✔
1786

6✔
1787
        cursor, err := collDevs.Find(ctx, query)
6✔
1788
        if err != nil {
6✔
1789
                return nil, err
×
1790
        }
×
1791

1792
        if err = cursor.All(ctx, &statuses); err != nil {
6✔
1793
                if err == mongo.ErrNoDocuments {
×
1794
                        return nil, nil
×
1795
                }
×
1796
                return nil, err
×
1797
        }
1798

1799
        return statuses, nil
6✔
1800
}
1801

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

15✔
1805
        statuses := []model.DeviceDeployment{}
15✔
1806
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1807
        collDevs := database.Collection(CollectionDevices)
15✔
1808

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

1862
        options := mopts.Find()
14✔
1863
        sortFieldQuery := bson.D{
14✔
1864
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
14✔
1865
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
14✔
1866
        }
14✔
1867
        options.SetSort(sortFieldQuery)
14✔
1868
        if q.Skip > 0 {
17✔
1869
                options.SetSkip(int64(q.Skip))
3✔
1870
        }
3✔
1871
        if q.Limit > 0 {
19✔
1872
                options.SetLimit(int64(q.Limit))
5✔
1873
        } else {
14✔
1874
                options.SetLimit(DefaultDocumentLimit)
9✔
1875
        }
9✔
1876

1877
        cursor, err := collDevs.Find(ctx, query, options)
14✔
1878
        if err != nil {
15✔
1879
                return nil, -1, err
1✔
1880
        }
1✔
1881

1882
        if err = cursor.All(ctx, &statuses); err != nil {
13✔
1883
                if err == mongo.ErrNoDocuments {
×
1884
                        return nil, -1, nil
×
1885
                }
×
1886
                return nil, -1, err
×
1887
        }
1888

1889
        count, err := collDevs.CountDocuments(ctx, query)
13✔
1890
        if err != nil {
13✔
1891
                return nil, -1, ErrDevicesCountFailed
×
1892
        }
×
1893

1894
        return statuses, int(count), nil
13✔
1895
}
1896

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

10✔
1900
        statuses := []model.DeviceDeployment{}
10✔
1901
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
10✔
1902
        collDevs := database.Collection(CollectionDevices)
10✔
1903

10✔
1904
        query := bson.D{}
10✔
1905
        if q.DeviceID != "" {
19✔
1906
                query = append(query, bson.E{
9✔
1907
                        Key:   StorageKeyDeviceDeploymentDeviceId,
9✔
1908
                        Value: q.DeviceID,
9✔
1909
                })
9✔
1910
        } else if len(q.IDs) > 0 {
11✔
1911
                query = append(query, bson.E{
1✔
1912
                        Key: StorageKeyId,
1✔
1913
                        Value: bson.D{{
1✔
1914
                                Key:   "$in",
1✔
1915
                                Value: q.IDs,
1✔
1916
                        }},
1✔
1917
                })
1✔
1918
        }
1✔
1919

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

1967
        options := mopts.Find()
9✔
1968
        sortFieldQuery := bson.D{
9✔
1969
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
9✔
1970
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
9✔
1971
        }
9✔
1972
        options.SetSort(sortFieldQuery)
9✔
1973
        if q.Skip > 0 {
10✔
1974
                options.SetSkip(int64(q.Skip))
1✔
1975
        }
1✔
1976
        if q.Limit > 0 {
18✔
1977
                options.SetLimit(int64(q.Limit))
9✔
1978
        } else {
9✔
1979
                options.SetLimit(DefaultDocumentLimit)
×
1980
        }
×
1981

1982
        cursor, err := collDevs.Find(ctx, query, options)
9✔
1983
        if err != nil {
9✔
1984
                return nil, -1, err
×
1985
        }
×
1986

1987
        if err = cursor.All(ctx, &statuses); err != nil {
9✔
1988
                if err == mongo.ErrNoDocuments {
×
1989
                        return nil, 0, nil
×
1990
                }
×
1991
                return nil, -1, err
×
1992
        }
1993

1994
        maxCount := maxCountDocuments
9✔
1995
        countOptions := &mopts.CountOptions{
9✔
1996
                Limit: &maxCount,
9✔
1997
        }
9✔
1998
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
9✔
1999
        if err != nil {
9✔
2000
                return nil, -1, ErrDevicesCountFailed
×
2001
        }
×
2002

2003
        return statuses, int(count), nil
9✔
2004
}
2005

2006
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
2007
// `deviceID`, false otherwise. In case of errors returns false and an error
2008
// that occurred
2009
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
2010
        deploymentID string, deviceID string) (bool, error) {
7✔
2011

7✔
2012
        var dep model.DeviceDeployment
7✔
2013
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2014
        collDevs := database.Collection(CollectionDevices)
7✔
2015

7✔
2016
        query := bson.D{
7✔
2017
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
2018
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
2019
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
2020
                        {Key: "$exists", Value: false},
7✔
2021
                }},
7✔
2022
        }
7✔
2023

7✔
2024
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
10✔
2025
                if err == mongo.ErrNoDocuments {
6✔
2026
                        return false, nil
3✔
2027
                } else {
3✔
2028
                        return false, err
×
2029
                }
×
2030
        }
2031

2032
        return true, nil
4✔
2033
}
2034

2035
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2036
        deploymentId string) error {
3✔
2037

3✔
2038
        if len(deploymentId) == 0 {
4✔
2039
                return ErrStorageInvalidID
1✔
2040
        }
1✔
2041

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

2✔
2052
        update := bson.M{
2✔
2053
                "$set": bson.M{
2✔
2054
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2055
                        StorageKeyDeviceDeploymentActive: false,
2✔
2056
                },
2✔
2057
        }
2✔
2058

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

2063
        return nil
2✔
2064
}
2065

2066
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2067
        deviceID string) error {
2✔
2068
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2069
        collDevs := database.Collection(CollectionDevices)
2✔
2070
        selector := bson.M{
2✔
2071
                StorageKeyDeviceDeploymentDeviceId: deviceID,
2✔
2072
                StorageKeyDeviceDeploymentActive:   false,
2✔
2073
                StorageKeyDeviceDeploymentDeleted: bson.M{
2✔
2074
                        "$exists": false,
2✔
2075
                },
2✔
2076
        }
2✔
2077

2✔
2078
        now := time.Now()
2✔
2079
        update := bson.M{
2✔
2080
                "$set": bson.M{
2✔
2081
                        StorageKeyDeviceDeploymentDeleted: &now,
2✔
2082
                },
2✔
2083
        }
2✔
2084

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

2089
        database = db.client.Database(DatabaseName)
2✔
2090
        collDevs = database.Collection(CollectionDevicesLastStatus)
2✔
2091
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
2✔
2092

2✔
2093
        return err
2✔
2094
}
2095

2096
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2097
        deviceId string) error {
2✔
2098

2✔
2099
        if len(deviceId) == 0 {
3✔
2100
                return ErrStorageInvalidID
1✔
2101
        }
1✔
2102

2103
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2104
        collDevs := database.Collection(CollectionDevices)
1✔
2105
        selector := bson.M{
1✔
2106
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2107
                StorageKeyDeviceDeploymentActive:   true,
1✔
2108
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2109
                        {Key: "$exists", Value: false},
1✔
2110
                },
1✔
2111
        }
1✔
2112

1✔
2113
        update := bson.M{
1✔
2114
                "$set": bson.M{
1✔
2115
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2116
                        StorageKeyDeviceDeploymentActive: false,
1✔
2117
                },
1✔
2118
        }
1✔
2119

1✔
2120
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2121
                return err
×
2122
        }
×
2123

2124
        return nil
1✔
2125
}
2126

2127
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2128
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2129

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

1✔
2133
        filter := bson.M{
1✔
2134
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2135
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2136
        }
1✔
2137
        if !includeDeleted {
2✔
2138
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2139
                        {Key: "$exists", Value: false},
1✔
2140
                }
1✔
2141
        }
1✔
2142

2143
        opts := &mopts.FindOneOptions{}
1✔
2144
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2145

1✔
2146
        var dd model.DeviceDeployment
1✔
2147
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2148
                if err == mongo.ErrNoDocuments {
2✔
2149
                        return nil, ErrStorageNotFound
1✔
2150
                }
1✔
2151
                return nil, err
×
2152
        }
2153

2154
        return &dd, nil
1✔
2155
}
2156

2157
func (db *DataStoreMongo) GetDeviceDeployments(
2158
        ctx context.Context,
2159
        skip int,
2160
        limit int,
2161
        deviceID string,
2162
        active *bool,
2163
        includeDeleted bool,
2164
) ([]model.DeviceDeployment, error) {
4✔
2165

4✔
2166
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2167
        collDevs := database.Collection(CollectionDevices)
4✔
2168

4✔
2169
        filter := bson.M{}
4✔
2170
        if !includeDeleted {
6✔
2171
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2172
                        {Key: "$exists", Value: false},
2✔
2173
                }
2✔
2174
        }
2✔
2175
        if deviceID != "" {
5✔
2176
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2177
        }
1✔
2178
        if active != nil {
5✔
2179
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2180
        }
1✔
2181

2182
        opts := &mopts.FindOptions{}
4✔
2183
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
4✔
2184
        if skip > 0 {
5✔
2185
                opts.SetSkip(int64(skip))
1✔
2186
        }
1✔
2187
        if limit > 0 {
5✔
2188
                opts.SetLimit(int64(limit))
1✔
2189
        }
1✔
2190

2191
        var deviceDeployments []model.DeviceDeployment
4✔
2192
        cursor, err := collDevs.Find(ctx, filter, opts)
4✔
2193
        if err != nil {
4✔
2194
                return nil, err
×
2195
        }
×
2196
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
4✔
2197
                return nil, err
×
2198
        }
×
2199

2200
        return deviceDeployments, nil
4✔
2201
}
2202

2203
// deployments
2204

2205
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2206
        indexes ...mongo.IndexModel) error {
436✔
2207
        ctx := context.Background()
436✔
2208
        dataBase := db.client.Database(dbName)
436✔
2209

436✔
2210
        coll := dataBase.Collection(collName)
436✔
2211
        idxView := coll.Indexes()
436✔
2212
        _, err := idxView.CreateMany(ctx, indexes)
436✔
2213
        return err
436✔
2214
}
436✔
2215

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

16✔
2219
        var idx bson.M
16✔
2220
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
16✔
2221
        collDpl := database.Collection(CollectionDeployments)
16✔
2222
        idxView := collDpl.Indexes()
16✔
2223

16✔
2224
        cursor, err := idxView.List(ctx)
16✔
2225
        if err != nil {
16✔
2226
                // check failed, assume indexing is not there
×
2227
                return false
×
2228
        }
×
2229

2230
        has := map[string]bool{}
16✔
2231
        for cursor.Next(ctx) {
46✔
2232
                if err = cursor.Decode(&idx); err != nil {
30✔
2233
                        continue
×
2234
                }
2235
                if _, ok := idx["weights"]; ok {
45✔
2236
                        // text index
15✔
2237
                        for k := range idx["weights"].(bson.M) {
45✔
2238
                                has[k] = true
30✔
2239
                        }
30✔
2240
                } else {
15✔
2241
                        for i := range idx["key"].(bson.M) {
30✔
2242
                                has[i] = true
15✔
2243
                        }
15✔
2244

2245
                }
2246
        }
2247
        if err != nil {
16✔
2248
                return false
×
2249
        }
×
2250

2251
        for _, key := range StorageIndexes.Keys.(bson.D) {
47✔
2252
                _, ok := has[key.Key]
31✔
2253
                if !ok {
32✔
2254
                        return false
1✔
2255
                }
1✔
2256
        }
2257

2258
        return true
15✔
2259
}
2260

2261
// Insert persists object
2262
func (db *DataStoreMongo) InsertDeployment(
2263
        ctx context.Context,
2264
        deployment *model.Deployment,
2265
) error {
210✔
2266

210✔
2267
        if deployment == nil {
211✔
2268
                return ErrDeploymentStorageInvalidDeployment
1✔
2269
        }
1✔
2270

2271
        if err := deployment.Validate(); err != nil {
211✔
2272
                return err
2✔
2273
        }
2✔
2274

2275
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
208✔
2276
        collDpl := database.Collection(CollectionDeployments)
208✔
2277

208✔
2278
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
209✔
2279
                return err
1✔
2280
        }
1✔
2281
        return nil
208✔
2282
}
2283

2284
// Delete removed entry by ID
2285
// Noop on ID not found
2286
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
4✔
2287

4✔
2288
        if len(id) == 0 {
5✔
2289
                return ErrStorageInvalidID
1✔
2290
        }
1✔
2291

2292
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2293
        collDpl := database.Collection(CollectionDeployments)
3✔
2294

3✔
2295
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
2296
                return err
×
2297
        }
×
2298

2299
        return nil
3✔
2300
}
2301

2302
func (db *DataStoreMongo) FindDeploymentByID(
2303
        ctx context.Context,
2304
        id string,
2305
) (*model.Deployment, error) {
10✔
2306

10✔
2307
        if len(id) == 0 {
11✔
2308
                return nil, ErrStorageInvalidID
1✔
2309
        }
1✔
2310

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

9✔
2314
        deployment := new(model.Deployment)
9✔
2315
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
9✔
2316
                Decode(deployment); err != nil {
12✔
2317
                if err == mongo.ErrNoDocuments {
6✔
2318
                        return nil, nil
3✔
2319
                }
3✔
2320
                return nil, err
×
2321
        }
2322

2323
        return deployment, nil
6✔
2324
}
2325

2326
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2327
        ctx context.Context,
2328
        ids ...string,
2329
) (deploymentStats []*model.DeploymentStats, err error) {
2✔
2330

2✔
2331
        if len(ids) == 0 {
2✔
2332
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2333
        }
×
2334

2335
        for _, id := range ids {
6✔
2336
                if len(id) == 0 {
4✔
2337
                        return nil, ErrStorageInvalidID
×
2338
                }
×
2339
        }
2340

2341
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2342
        collDpl := database.Collection(CollectionDeployments)
2✔
2343

2✔
2344
        query := bson.M{
2✔
2345
                "_id": bson.M{
2✔
2346
                        "$in": ids,
2✔
2347
                },
2✔
2348
        }
2✔
2349
        statsProjection := &mopts.FindOptions{
2✔
2350
                Projection: bson.M{"stats": 1},
2✔
2351
        }
2✔
2352

2✔
2353
        results, err := collDpl.Find(
2✔
2354
                ctx,
2✔
2355
                query,
2✔
2356
                statsProjection,
2✔
2357
        )
2✔
2358
        if err != nil {
2✔
2359
                return nil, err
×
2360
        }
×
2361

2362
        for results.Next(context.Background()) {
6✔
2363
                depl := new(model.DeploymentStats)
4✔
2364
                if err = results.Decode(&depl); err != nil {
4✔
2365
                        if err == mongo.ErrNoDocuments {
×
2366
                                return nil, nil
×
2367
                        }
×
2368
                        return nil, err
×
2369
                }
2370
                deploymentStats = append(deploymentStats, depl)
4✔
2371
        }
2372

2373
        return deploymentStats, nil
2✔
2374
}
2375

2376
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2377
        id string) (*model.Deployment, error) {
8✔
2378

8✔
2379
        if len(id) == 0 {
9✔
2380
                return nil, ErrStorageInvalidID
1✔
2381
        }
1✔
2382

2383
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2384
        collDpl := database.Collection(CollectionDeployments)
7✔
2385

7✔
2386
        var deployment *model.Deployment
7✔
2387
        filter := bson.D{
7✔
2388
                {Key: "_id", Value: id},
7✔
2389
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2390
        }
7✔
2391
        if err := collDpl.FindOne(ctx, filter).
7✔
2392
                Decode(&deployment); err != nil {
12✔
2393
                if err == mongo.ErrNoDocuments {
10✔
2394
                        return nil, nil
5✔
2395
                }
5✔
2396
                return nil, err
×
2397
        }
2398

2399
        return deployment, nil
3✔
2400
}
2401

2402
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2403
        ctx context.Context,
2404
        deploymentID string,
2405
        increment int,
2406
) error {
64✔
2407
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
64✔
2408
        collection := database.Collection(CollectionDeployments)
64✔
2409

64✔
2410
        filter := bson.M{
64✔
2411
                "_id": deploymentID,
64✔
2412
                StorageKeyDeploymentDeviceCount: bson.M{
64✔
2413
                        "$ne": nil,
64✔
2414
                },
64✔
2415
        }
64✔
2416

64✔
2417
        update := bson.M{
64✔
2418
                "$inc": bson.M{
64✔
2419
                        StorageKeyDeploymentDeviceCount: increment,
64✔
2420
                },
64✔
2421
        }
64✔
2422

64✔
2423
        _, err := collection.UpdateOne(ctx, filter, update)
64✔
2424
        return err
64✔
2425
}
64✔
2426

2427
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2428
        ctx context.Context,
2429
        deploymentID string,
2430
        count int,
2431
) error {
3✔
2432
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2433
        collection := database.Collection(CollectionDeployments)
3✔
2434

3✔
2435
        filter := bson.M{
3✔
2436
                "_id": deploymentID,
3✔
2437
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2438
                        "$eq": nil,
3✔
2439
                },
3✔
2440
        }
3✔
2441

3✔
2442
        update := bson.M{
3✔
2443
                "$set": bson.M{
3✔
2444
                        StorageKeyDeploymentDeviceCount: count,
3✔
2445
                },
3✔
2446
        }
3✔
2447

3✔
2448
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2449
        return err
3✔
2450
}
3✔
2451

2452
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2453
        id string) (int, error) {
3✔
2454

3✔
2455
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2456
        collDevs := database.Collection(CollectionDevices)
3✔
2457

3✔
2458
        filter := bson.M{
3✔
2459
                StorageKeyDeviceDeploymentDeploymentID: id,
3✔
2460
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
2461
                        {Key: "$exists", Value: false},
3✔
2462
                },
3✔
2463
        }
3✔
2464

3✔
2465
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
3✔
2466
        if err != nil {
3✔
2467
                return 0, err
×
2468
        }
×
2469

2470
        return int(deviceCount), nil
3✔
2471
}
2472

2473
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2474
        id string, stats model.Stats) error {
6✔
2475

6✔
2476
        if len(id) == 0 {
7✔
2477
                return ErrStorageInvalidID
1✔
2478
        }
1✔
2479

2480
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2481
        collDpl := database.Collection(CollectionDeployments)
5✔
2482

5✔
2483
        deployment, err := model.NewDeployment()
5✔
2484
        if err != nil {
5✔
2485
                return errors.Wrap(err, "failed to create deployment")
×
2486
        }
×
2487

2488
        deployment.Stats = stats
5✔
2489
        var update bson.M
5✔
2490
        if deployment.IsFinished() {
5✔
2491
                now := time.Now()
×
2492

×
2493
                update = bson.M{
×
2494
                        "$set": bson.M{
×
2495
                                StorageKeyDeploymentStats:    stats,
×
2496
                                StorageKeyDeploymentFinished: &now,
×
2497
                        },
×
2498
                }
×
2499
        } else {
5✔
2500
                update = bson.M{
5✔
2501
                        "$set": bson.M{
5✔
2502
                                StorageKeyDeploymentStats: stats,
5✔
2503
                        },
5✔
2504
                }
5✔
2505
        }
5✔
2506

2507
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
5✔
2508
        if res != nil && res.MatchedCount == 0 {
7✔
2509
                return ErrStorageInvalidID
2✔
2510
        }
2✔
2511
        return err
3✔
2512
}
2513

2514
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2515
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
8✔
2516

8✔
2517
        if len(id) == 0 {
9✔
2518
                return ErrStorageInvalidID
1✔
2519
        }
1✔
2520

2521
        if _, err := stateTo.MarshalText(); err != nil {
7✔
2522
                return ErrStorageInvalidInput
×
2523
        }
×
2524

2525
        // does not need any extra operations
2526
        // following query won't handle this case well and increase the state_to value
2527
        if stateFrom == stateTo {
8✔
2528
                return nil
1✔
2529
        }
1✔
2530

2531
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2532
        collDpl := database.Collection(CollectionDeployments)
6✔
2533

6✔
2534
        var update bson.M
6✔
2535

6✔
2536
        if stateFrom == model.DeviceDeploymentStatusNull {
8✔
2537
                // note dot notation on embedded document
2✔
2538
                update = bson.M{
2✔
2539
                        "$inc": bson.M{
2✔
2540
                                "stats." + stateTo.String(): 1,
2✔
2541
                        },
2✔
2542
                }
2✔
2543
        } else {
7✔
2544
                // note dot notation on embedded document
5✔
2545
                update = bson.M{
5✔
2546
                        "$inc": bson.M{
5✔
2547
                                "stats." + stateFrom.String(): -1,
5✔
2548
                                "stats." + stateTo.String():   1,
5✔
2549
                        },
5✔
2550
                }
5✔
2551
        }
5✔
2552

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

6✔
2555
        if res != nil && res.MatchedCount == 0 {
7✔
2556
                return ErrStorageInvalidID
1✔
2557
        }
1✔
2558

2559
        return err
5✔
2560
}
2561

2562
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2563
        ctx context.Context,
2564
        deploymentID string,
2565
        increment int64,
2566
) error {
3✔
2567
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2568
        collection := database.Collection(CollectionDeployments)
3✔
2569

3✔
2570
        filter := bson.M{
3✔
2571
                "_id": deploymentID,
3✔
2572
        }
3✔
2573

3✔
2574
        update := bson.M{
3✔
2575
                "$inc": bson.M{
3✔
2576
                        StorageKeyDeploymentTotalSize: increment,
3✔
2577
                },
3✔
2578
        }
3✔
2579

3✔
2580
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2581
        return err
3✔
2582
}
3✔
2583

2584
func (db *DataStoreMongo) Find(ctx context.Context,
2585
        match model.Query) ([]*model.Deployment, int64, error) {
36✔
2586

36✔
2587
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
36✔
2588
        collDpl := database.Collection(CollectionDeployments)
36✔
2589

36✔
2590
        andq := []bson.M{}
36✔
2591

36✔
2592
        // filter by IDs
36✔
2593
        if match.IDs != nil {
36✔
2594
                tq := bson.M{
×
2595
                        "_id": bson.M{
×
2596
                                "$in": match.IDs,
×
2597
                        },
×
2598
                }
×
2599
                andq = append(andq, tq)
×
2600
        }
×
2601

2602
        // build deployment by name part of the query
2603
        if match.SearchText != "" {
52✔
2604
                // we must have indexing for text search
16✔
2605
                if !db.hasIndexing(ctx, db.client) {
17✔
2606
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
1✔
2607
                }
1✔
2608

2609
                tq := bson.M{
15✔
2610
                        "$text": bson.M{
15✔
2611
                                "$search": "\"" + match.SearchText + "\"",
15✔
2612
                        },
15✔
2613
                }
15✔
2614

15✔
2615
                andq = append(andq, tq)
15✔
2616
        }
2617

2618
        // build deployment by status part of the query
2619
        if match.Status != model.StatusQueryAny {
45✔
2620
                var status model.DeploymentStatus
10✔
2621
                if match.Status == model.StatusQueryPending {
12✔
2622
                        status = model.DeploymentStatusPending
2✔
2623
                } else if match.Status == model.StatusQueryInProgress {
14✔
2624
                        status = model.DeploymentStatusInProgress
4✔
2625
                } else {
8✔
2626
                        status = model.DeploymentStatusFinished
4✔
2627
                }
4✔
2628
                stq := bson.M{StorageKeyDeploymentStatus: status}
10✔
2629
                andq = append(andq, stq)
10✔
2630
        }
2631

2632
        // build deployment by type part of the query
2633
        if match.Type != "" {
37✔
2634
                if match.Type == model.DeploymentTypeConfiguration {
4✔
2635
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
2✔
2636
                } else if match.Type == model.DeploymentTypeSoftware {
2✔
2637
                        andq = append(andq, bson.M{
×
2638
                                "$or": []bson.M{
×
2639
                                        {StorageKeyDeploymentType: match.Type},
×
2640
                                        {StorageKeyDeploymentType: ""},
×
2641
                                },
×
2642
                        })
×
2643
                }
×
2644
        }
2645

2646
        query := bson.M{}
35✔
2647
        if len(andq) != 0 {
58✔
2648
                // use search criteria if any
23✔
2649
                query = bson.M{
23✔
2650
                        "$and": andq,
23✔
2651
                }
23✔
2652
        }
23✔
2653

2654
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
35✔
2655
                query["created"] = bson.M{
×
2656
                        "$gte": match.CreatedAfter,
×
2657
                        "$lte": match.CreatedBefore,
×
2658
                }
×
2659
        } else if match.CreatedAfter != nil {
35✔
2660
                query["created"] = bson.M{
×
2661
                        "$gte": match.CreatedAfter,
×
2662
                }
×
2663
        } else if match.CreatedBefore != nil {
35✔
2664
                query["created"] = bson.M{
×
2665
                        "$lte": match.CreatedBefore,
×
2666
                }
×
2667
        }
×
2668

2669
        options := db.findOptions(match)
35✔
2670

35✔
2671
        var deployments []*model.Deployment
35✔
2672
        cursor, err := collDpl.Find(ctx, query, options)
35✔
2673
        if err != nil {
35✔
2674
                return nil, 0, err
×
2675
        }
×
2676
        if err := cursor.All(ctx, &deployments); err != nil {
35✔
2677
                return nil, 0, err
×
2678
        }
×
2679
        // Count documents if we didn't find all already.
2680
        count := int64(0)
35✔
2681
        if !match.DisableCount {
70✔
2682
                count = int64(len(deployments))
35✔
2683
                if count >= int64(match.Limit) {
69✔
2684
                        count, err = collDpl.CountDocuments(ctx, query)
34✔
2685
                        if err != nil {
34✔
2686
                                return nil, 0, err
×
2687
                        }
×
2688
                } else {
1✔
2689
                        // Don't forget to add the skipped documents
1✔
2690
                        count += int64(match.Skip)
1✔
2691
                }
1✔
2692
        }
2693

2694
        return deployments, count, nil
35✔
2695
}
2696

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

2715
// FindNewerActiveDeployments finds active deployments which were created
2716
// after createdAfter
2717
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2718
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
5✔
2719

5✔
2720
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2721
        c := database.Collection(CollectionDeployments)
5✔
2722

5✔
2723
        queryFilters := make([]bson.M, 0)
5✔
2724
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
5✔
2725
        queryFilters = append(queryFilters,
5✔
2726
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
5✔
2727
        findQuery := bson.M{}
5✔
2728
        findQuery["$and"] = queryFilters
5✔
2729

5✔
2730
        findOptions := &mopts.FindOptions{}
5✔
2731
        findOptions.SetSkip(int64(skip))
5✔
2732
        findOptions.SetLimit(int64(limit))
5✔
2733

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

5✔
2741
        var deployments []*model.Deployment
5✔
2742

5✔
2743
        if err = cursor.All(ctx, &deployments); err != nil {
5✔
2744
                return nil, errors.Wrap(err, "failed to get deployments")
×
2745
        }
×
2746

2747
        return deployments, nil
5✔
2748
}
2749

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

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

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

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

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

2789
        return err
5✔
2790
}
2791

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

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

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

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

2816
        return true, nil
2✔
2817
}
2818

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

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

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

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

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

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

2850
        return true, nil
1✔
2851
}
2852

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

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

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

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

2875
        return true, nil
×
2876
}
2877

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

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

2895
        return settings, nil
2✔
2896
}
2897

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

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

2917
        return err
2✔
2918
}
2919

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

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

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

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

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

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

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

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

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

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

2981
        return ids, nil
2✔
2982
}
2983

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