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

mendersoftware / deployments / 898138042

pending completion
898138042

Pull #867

gitlab-ci

kjaskiewiczz
chore: check database version only once

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

94 of 258 new or added lines in 5 files covered. (36.43%)

55 existing lines in 5 files now uncovered.

7187 of 9193 relevant lines covered (78.18%)

34.24 hits per line

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

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

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

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

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

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

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

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

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

65
var currentDbVersion map[string]*migrate.Version
66

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

83
        // Indexes (version: 1.2.3)
84
        IndexArtifactNameDependsName = "artifactNameDepends"
85
        IndexNameAndDeviceTypeName   = "artifactNameAndDeviceTypeIndex"
86

87
        // Indexes (version: 1.2.4)
88
        IndexDeploymentStatus = "deploymentStatus"
89

90
        // Indexes 1.2.6
91
        IndexDeviceDeploymentStatusName = "deploymentid_status_deviceid"
92

93
        // Indexes 1.2.13
94
        IndexArtifactProvidesName = "artifact_provides"
95

96
        // Indexes 1.2.15
97
        IndexReleaseNameName = "release_name"
98

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

433
        StorageKeyDeviceDeploymentLogMessages = "messages"
434

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

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

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

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

476
        ArtifactDependsDeviceType = "device_type"
477
)
478

479
type DataStoreMongo struct {
480
        client *mongo.Client
481
}
482

483
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
290✔
484
        return &DataStoreMongo{
290✔
485
                client: client,
290✔
486
        }
290✔
487
}
290✔
488

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

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

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

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

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

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

531
        return client, nil
1✔
532
}
533

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

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

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

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

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

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

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

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

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

662
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
19✔
663
        if sortField == "" {
32✔
664
                sortField = "name"
13✔
665
        }
13✔
666
        if sortOrder == 0 {
32✔
667
                sortOrder = 1
13✔
668
        }
13✔
669

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

19✔
694
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
19✔
695
        collImg := database.Collection(CollectionImages)
19✔
696

19✔
697
        cursor, err := collImg.Aggregate(ctx, pipe)
19✔
698
        if err != nil {
19✔
699
                return nil, 0, err
×
700
        }
×
701
        defer cursor.Close(ctx)
19✔
702

19✔
703
        result := struct {
19✔
704
                Results []model.Release       `bson:"results"`
19✔
705
                Count   []struct{ Count int } `bson:"count"`
19✔
706
        }{}
19✔
707
        if !cursor.Next(ctx) {
19✔
708
                return nil, 0, nil
×
709
        } else if err = cursor.Decode(&result); err != nil {
19✔
710
                return nil, 0, err
×
711
        } else if len(result.Count) == 0 {
22✔
712
                return []model.Release{}, 0, err
3✔
713
        }
3✔
714
        return result.Results, result.Count[0].Count, nil
17✔
715
}
716

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

×
NEW
724
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
×
NEW
725
        if sortField == "" {
×
NEW
726
                sortField = "name"
×
NEW
727
        }
×
NEW
728
        if sortOrder == 0 {
×
NEW
729
                sortOrder = 1
×
NEW
730
        }
×
731

NEW
732
        page := 1
×
NEW
733
        perPage := DefaultDocumentLimit
×
NEW
734
        if filt != nil {
×
NEW
735
                if filt.Page > 0 {
×
NEW
736
                        page = filt.Page
×
NEW
737
                }
×
NEW
738
                if filt.PerPage > 0 {
×
NEW
739
                        perPage = filt.PerPage
×
NEW
740
                }
×
741
        }
742

NEW
743
        opts := &mopts.FindOptions{}
×
NEW
744
        opts.SetSort(bson.D{{Key: sortField, Value: sortOrder}})
×
NEW
745
        opts.SetSkip(int64((page - 1) * perPage))
×
NEW
746
        opts.SetLimit(int64(perPage))
×
NEW
747
        projection := bson.M{
×
NEW
748
                StorageKeyReleaseImageDependsIdx:  0,
×
NEW
749
                StorageKeyReleaseImageProvidesIdx: 0,
×
NEW
750
        }
×
NEW
751
        opts.SetProjection(projection)
×
NEW
752

×
NEW
753
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
NEW
754
        collReleases := database.Collection(CollectionReleases)
×
NEW
755

×
NEW
756
        filter := bson.M{}
×
NEW
757
        if filt != nil {
×
NEW
758
                if filt.Name != "" {
×
NEW
759
                        filter[StorageKeyReleaseName] = bson.M{"$regex": filt.Name}
×
NEW
760
                }
×
NEW
761
                if filt.Description != "" {
×
NEW
762
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": filt.Description}
×
NEW
763
                }
×
NEW
764
                if filt.DeviceType != "" {
×
NEW
765
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = bson.M{"$regex": filt.DeviceType}
×
NEW
766
                }
×
767
        }
NEW
768
        releases := []model.Release{}
×
NEW
769
        cursor, err := collReleases.Find(ctx, filter, opts)
×
NEW
770
        if err != nil {
×
NEW
771
                return nil, 0, err
×
NEW
772
        }
×
NEW
773
        if err := cursor.All(ctx, &releases); err != nil {
×
NEW
774
                return nil, 0, err
×
NEW
775
        }
×
776

777
        // TODO: can we return number of all documents in the collection
778
        // using EstimatedDocumentCount?
NEW
779
        count, err := collReleases.CountDocuments(ctx, filter)
×
NEW
780
        if err != nil {
×
NEW
781
                return nil, 0, err
×
NEW
782
        }
×
783

NEW
784
        return releases, int(count), nil
×
785
}
786

787
// limits
788
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
4✔
789

4✔
790
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
791
        collLim := database.Collection(CollectionLimits)
4✔
792

4✔
793
        limit := new(model.Limit)
4✔
794
        if err := collLim.FindOne(ctx, bson.M{"_id": name}).
4✔
795
                Decode(limit); err != nil {
6✔
796
                if err == mongo.ErrNoDocuments {
4✔
797
                        return nil, ErrLimitNotFound
2✔
798
                }
2✔
799
                return nil, err
×
800
        }
801

802
        return limit, nil
2✔
803
}
804

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

5✔
807
        dbname := mstore.DbNameForTenant(tenantId, DbName)
5✔
808

5✔
809
        return MigrateSingle(ctx, dbname, DbVersion, db.client, true)
5✔
810
}
5✔
811

812
//images
813

814
// Exists checks if object with ID exists
815
func (db *DataStoreMongo) Exists(ctx context.Context, id string) (bool, error) {
×
816
        var result interface{}
×
817

×
818
        if len(id) == 0 {
×
819
                return false, ErrImagesStorageInvalidID
×
820
        }
×
821

822
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
823
        collImg := database.Collection(CollectionImages)
×
824

×
825
        if err := collImg.FindOne(ctx, bson.M{"_id": id}).
×
826
                Decode(&result); err != nil {
×
827
                if err == mongo.ErrNoDocuments {
×
828
                        return false, nil
×
829
                }
×
830
                return false, err
×
831
        }
832

833
        return true, nil
×
834
}
835

836
// Update provided Image
837
// Return false if not found
838
func (db *DataStoreMongo) Update(ctx context.Context,
839
        image *model.Image) (bool, error) {
1✔
840

1✔
841
        if err := image.Validate(); err != nil {
1✔
842
                return false, err
×
843
        }
×
844

845
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
846
        collImg := database.Collection(CollectionImages)
1✔
847

1✔
848
        // add special representation of artifact provides
1✔
849
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
850

1✔
851
        image.SetModified(time.Now())
1✔
852
        if res, err := collImg.ReplaceOne(
1✔
853
                ctx, bson.M{"_id": image.Id}, image,
1✔
854
        ); err != nil {
1✔
855
                return false, err
×
856
        } else if res.MatchedCount == 0 {
1✔
857
                return false, nil
×
858
        }
×
859

860
        return true, nil
1✔
861
}
862

863
// ImageByNameAndDeviceType finds image with specified application name and target device type
864
func (db *DataStoreMongo) ImageByNameAndDeviceType(ctx context.Context,
865
        name, deviceType string) (*model.Image, error) {
9✔
866

9✔
867
        if len(name) == 0 {
10✔
868
                return nil, ErrImagesStorageInvalidArtifactName
1✔
869
        }
1✔
870

871
        if len(deviceType) == 0 {
9✔
872
                return nil, ErrImagesStorageInvalidDeviceType
1✔
873
        }
1✔
874

875
        // equal to device type & software version (application name + version)
876
        query := bson.M{
7✔
877
                StorageKeyImageName:        name,
7✔
878
                StorageKeyImageDeviceTypes: deviceType,
7✔
879
        }
7✔
880

7✔
881
        // If multiple entries matches, pick the smallest one.
7✔
882
        findOpts := mopts.FindOne()
7✔
883
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
7✔
884

7✔
885
        dbName := mstore.DbFromContext(ctx, DatabaseName)
7✔
886
        database := db.client.Database(dbName)
7✔
887
        collImg := database.Collection(CollectionImages)
7✔
888

7✔
889
        // Both we lookup unique object, should be one or none.
7✔
890
        var image model.Image
7✔
891
        if err := collImg.FindOne(ctx, query, findOpts).
7✔
892
                Decode(&image); err != nil {
11✔
893
                if err == mongo.ErrNoDocuments {
8✔
894
                        return nil, nil
4✔
895
                }
4✔
896
                return nil, err
×
897
        }
898

899
        return &image, nil
3✔
900
}
901

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

1✔
906
        if len(deviceType) == 0 {
1✔
907
                return nil, ErrImagesStorageInvalidDeviceType
×
908
        }
×
909

910
        if len(ids) == 0 {
1✔
911
                return nil, ErrImagesStorageInvalidID
×
912
        }
×
913

914
        query := bson.D{
1✔
915
                {Key: StorageKeyId, Value: bson.M{"$in": ids}},
1✔
916
                {Key: StorageKeyImageDeviceTypes, Value: deviceType},
1✔
917
        }
1✔
918

1✔
919
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
920
        collImg := database.Collection(CollectionImages)
1✔
921

1✔
922
        // If multiple entries matches, pick the smallest one
1✔
923
        findOpts := mopts.FindOne()
1✔
924
        findOpts.SetSort(bson.D{{Key: StorageKeyImageSize, Value: 1}})
1✔
925

1✔
926
        // Both we lookup unique object, should be one or none.
1✔
927
        var image model.Image
1✔
928
        if err := collImg.FindOne(ctx, query, findOpts).
1✔
929
                Decode(&image); err != nil {
1✔
930
                if err == mongo.ErrNoDocuments {
×
931
                        return nil, nil
×
932
                }
×
933
                return nil, err
×
934
        }
935

936
        return &image, nil
1✔
937
}
938

939
// ImagesByName finds images with specified artifact name
940
func (db *DataStoreMongo) ImagesByName(
941
        ctx context.Context, name string) ([]*model.Image, error) {
1✔
942

1✔
943
        var images []*model.Image
1✔
944

1✔
945
        if len(name) == 0 {
1✔
946
                return nil, ErrImagesStorageInvalidName
×
947
        }
×
948

949
        // equal to artifact name
950
        query := bson.M{
1✔
951
                StorageKeyImageName: name,
1✔
952
        }
1✔
953

1✔
954
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
955
        collImg := database.Collection(CollectionImages)
1✔
956
        cursor, err := collImg.Find(ctx, query)
1✔
957
        if err != nil {
1✔
958
                return nil, err
×
959
        }
×
960
        // Both we lookup unique object, should be one or none.
961
        if err = cursor.All(ctx, &images); err != nil {
1✔
962
                return nil, err
×
963
        }
×
964

965
        return images, nil
1✔
966
}
967

968
// Insert persists object
969
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
68✔
970

68✔
971
        if image == nil {
68✔
972
                return ErrImagesStorageInvalidImage
×
973
        }
×
974

975
        if err := image.Validate(); err != nil {
68✔
976
                return err
×
977
        }
×
978

979
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
68✔
980
        collImg := database.Collection(CollectionImages)
68✔
981

68✔
982
        // add special representation of artifact provides
68✔
983
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
68✔
984

68✔
985
        _, err := collImg.InsertOne(ctx, image)
68✔
986
        if err != nil {
75✔
987
                if except, ok := err.(mongo.WriteException); ok {
14✔
988
                        var conflicts string
7✔
989
                        if len(except.WriteErrors) > 0 {
14✔
990
                                err := except.WriteErrors[0]
7✔
991
                                yamlStart := strings.IndexByte(err.Message, '{')
7✔
992
                                if yamlStart != -1 {
14✔
993
                                        conflicts = err.Message[yamlStart:]
7✔
994
                                }
7✔
995
                        }
996
                        conflictErr := model.NewConflictError(
7✔
997
                                ErrMsgConflictingDepends,
7✔
998
                                conflicts,
7✔
999
                        )
7✔
1000
                        return conflictErr
7✔
1001
                }
1002
        }
1003

1004
        return nil
61✔
1005
}
1006

1007
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
2✔
1008
        collUploads := db.client.
2✔
1009
                Database(DatabaseName).
2✔
1010
                Collection(CollectionUploadIntents)
2✔
1011
        if idty := identity.FromContext(ctx); idty != nil {
3✔
1012
                link.TenantID = idty.Tenant
1✔
1013
        }
1✔
1014
        _, err := collUploads.InsertOne(ctx, link)
2✔
1015
        return err
2✔
1016
}
1017

1018
func (db *DataStoreMongo) UpdateUploadIntentStatus(
1019
        ctx context.Context,
1020
        id string,
1021
        from, to model.LinkStatus,
1022
) error {
6✔
1023
        collUploads := db.client.
6✔
1024
                Database(DatabaseName).
6✔
1025
                Collection(CollectionUploadIntents)
6✔
1026
        q := bson.D{
6✔
1027
                {Key: "_id", Value: id},
6✔
1028
                {Key: "status", Value: from},
6✔
1029
        }
6✔
1030
        if idty := identity.FromContext(ctx); idty != nil {
11✔
1031
                q = append(q, bson.E{
5✔
1032
                        Key:   StorageKeyTenantId,
5✔
1033
                        Value: idty.Tenant,
5✔
1034
                })
5✔
1035
        }
5✔
1036
        update := bson.D{{
6✔
1037
                Key: "updated_ts", Value: time.Now(),
6✔
1038
        }}
6✔
1039
        if from != to {
12✔
1040
                update = append(update, bson.E{
6✔
1041
                        Key: "status", Value: to,
6✔
1042
                })
6✔
1043
        }
6✔
1044
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
6✔
1045
                {Key: "$set", Value: update},
6✔
1046
        })
6✔
1047
        if err != nil {
7✔
1048
                return err
1✔
1049
        } else if res.MatchedCount == 0 {
8✔
1050
                return store.ErrNotFound
2✔
1051
        }
2✔
1052
        return nil
3✔
1053
}
1054

1055
func (db *DataStoreMongo) FindUploadLinks(
1056
        ctx context.Context,
1057
        expiredAt time.Time,
1058
) (store.Iterator[model.UploadLink], error) {
1✔
1059
        collUploads := db.client.
1✔
1060
                Database(DatabaseName).
1✔
1061
                Collection(CollectionUploadIntents)
1✔
1062

1✔
1063
        q := bson.D{{
1✔
1064
                Key: "status",
1✔
1065
                Value: bson.D{{
1✔
1066
                        Key:   "$lt",
1✔
1067
                        Value: model.LinkStatusProcessedBit,
1✔
1068
                }},
1✔
1069
        }, {
1✔
1070
                Key: "expire",
1✔
1071
                Value: bson.D{{
1✔
1072
                        Key:   "$lt",
1✔
1073
                        Value: expiredAt,
1✔
1074
                }},
1✔
1075
        }}
1✔
1076
        cur, err := collUploads.Find(ctx, q)
1✔
1077
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1078
}
1✔
1079

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

1✔
1084
        if len(id) == 0 {
1✔
1085
                return nil, ErrImagesStorageInvalidID
×
1086
        }
×
1087

1088
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1089
        collImg := database.Collection(CollectionImages)
1✔
1090
        projection := bson.M{
1✔
1091
                StorageKeyImageDependsIdx:  0,
1✔
1092
                StorageKeyImageProvidesIdx: 0,
1✔
1093
        }
1✔
1094
        findOptions := mopts.FindOne()
1✔
1095
        findOptions.SetProjection(projection)
1✔
1096

1✔
1097
        var image model.Image
1✔
1098
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
1✔
1099
                Decode(&image); err != nil {
2✔
1100
                if err == mongo.ErrNoDocuments {
2✔
1101
                        return nil, nil
1✔
1102
                }
1✔
1103
                return nil, err
×
1104
        }
1105

1106
        return &image, nil
1✔
1107
}
1108

1109
// IsArtifactUnique checks if there is no artifact with the same artifactName
1110
// supporting one of the device types from deviceTypesCompatible list.
1111
// Returns true, nil if artifact is unique;
1112
// false, nil if artifact is not unique;
1113
// false, error in case of error.
1114
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1115
        artifactName string, deviceTypesCompatible []string) (bool, error) {
6✔
1116

6✔
1117
        if len(artifactName) == 0 {
7✔
1118
                return false, ErrImagesStorageInvalidArtifactName
1✔
1119
        }
1✔
1120

1121
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1122
        collImg := database.Collection(CollectionImages)
5✔
1123

5✔
1124
        query := bson.M{
5✔
1125
                "$and": []bson.M{
5✔
1126
                        {
5✔
1127
                                StorageKeyImageName: artifactName,
5✔
1128
                        },
5✔
1129
                        {
5✔
1130
                                StorageKeyImageDeviceTypes: bson.M{
5✔
1131
                                        "$in": deviceTypesCompatible},
5✔
1132
                        },
5✔
1133
                },
5✔
1134
        }
5✔
1135

5✔
1136
        // do part of the job manually
5✔
1137
        // if candidate images have any extra 'depends' - guaranteed non-overlap
5✔
1138
        // otherwise it's a match
5✔
1139
        cur, err := collImg.Find(ctx, query)
5✔
1140
        if err != nil {
5✔
1141
                return false, err
×
1142
        }
×
1143

1144
        var images []model.Image
5✔
1145
        err = cur.All(ctx, &images)
5✔
1146
        if err != nil {
5✔
1147
                return false, err
×
1148
        }
×
1149

1150
        for _, i := range images {
6✔
1151
                // the artifact already has same name and overlapping dev type
1✔
1152
                // if there are no more depends than dev type - it's not unique
1✔
1153
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1154
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1155
                                return false, nil
1✔
1156
                        }
1✔
1157
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1158
                        return false, nil
×
1159
                }
×
1160
        }
1161

1162
        return true, nil
4✔
1163
}
1164

1165
// Delete image specified by ID
1166
// Noop on if not found.
1167
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
1✔
1168

1✔
1169
        if len(id) == 0 {
1✔
1170
                return ErrImagesStorageInvalidID
×
1171
        }
×
1172

1173
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1174
        collImg := database.Collection(CollectionImages)
1✔
1175

1✔
1176
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
1177
                if res.DeletedCount == 0 {
×
1178
                        return nil
×
1179
                }
×
1180
                return err
×
1181
        }
1182

1183
        return nil
1✔
1184
}
1185

1186
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
33✔
1187
        if filt != nil && filt.Sort != "" {
42✔
1188
                sortParts := strings.SplitN(filt.Sort, ":", 2)
9✔
1189
                if len(sortParts) == 2 && (sortParts[0] == "name" || sortParts[0] == "modified") {
18✔
1190
                        sortField := sortParts[0]
9✔
1191
                        sortOrder := 1
9✔
1192
                        if sortParts[1] == model.SortDirectionDescending {
15✔
1193
                                sortOrder = -1
6✔
1194
                        }
6✔
1195
                        return sortField, sortOrder
9✔
1196
                }
1197
        }
1198
        return "", 0
24✔
1199
}
1200

1201
// ListImages lists all images
1202
func (db *DataStoreMongo) ListImages(
1203
        ctx context.Context,
1204
        filt *model.ReleaseOrImageFilter,
1205
) ([]*model.Image, int, error) {
15✔
1206

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

15✔
1210
        filters := bson.M{}
15✔
1211
        if filt != nil {
25✔
1212
                if filt.Name != "" {
14✔
1213
                        filters[StorageKeyImageName] = bson.M{
4✔
1214
                                "$regex": primitive.Regex{
4✔
1215
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
4✔
1216
                                        Options: "i",
4✔
1217
                                },
4✔
1218
                        }
4✔
1219
                }
4✔
1220
                if filt.Description != "" {
12✔
1221
                        filters[StorageKeyImageDescription] = bson.M{
2✔
1222
                                "$regex": primitive.Regex{
2✔
1223
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
2✔
1224
                                        Options: "i",
2✔
1225
                                },
2✔
1226
                        }
2✔
1227
                }
2✔
1228
                if filt.DeviceType != "" {
11✔
1229
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1230
                                "$regex": primitive.Regex{
1✔
1231
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1232
                                        Options: "i",
1✔
1233
                                },
1✔
1234
                        }
1✔
1235
                }
1✔
1236

1237
        }
1238

1239
        projection := bson.M{
15✔
1240
                StorageKeyImageDependsIdx:  0,
15✔
1241
                StorageKeyImageProvidesIdx: 0,
15✔
1242
        }
15✔
1243
        findOptions := &mopts.FindOptions{}
15✔
1244
        findOptions.SetProjection(projection)
15✔
1245
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
16✔
1246
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1247
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1248
        }
1✔
1249

1250
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
15✔
1251
        if sortField == "" || sortField == "name" {
28✔
1252
                sortField = StorageKeyImageName
13✔
1253
        }
13✔
1254
        if sortOrder == 0 {
27✔
1255
                sortOrder = 1
12✔
1256
        }
12✔
1257
        findOptions.SetSort(bson.D{
15✔
1258
                {Key: sortField, Value: sortOrder},
15✔
1259
                {Key: "_id", Value: sortOrder},
15✔
1260
        })
15✔
1261

15✔
1262
        cursor, err := collImg.Find(ctx, filters, findOptions)
15✔
1263
        if err != nil {
15✔
1264
                return nil, 0, err
×
1265
        }
×
1266

1267
        // NOTE: cursor.All closes the cursor before returning
1268
        var images []*model.Image
15✔
1269
        if err := cursor.All(ctx, &images); err != nil {
15✔
1270
                if err == mongo.ErrNoDocuments {
×
1271
                        return nil, 0, nil
×
1272
                }
×
1273
                return nil, 0, err
×
1274
        }
1275

1276
        count, err := collImg.CountDocuments(ctx, filters)
15✔
1277
        if err != nil {
15✔
1278
                return nil, -1, ErrDevicesCountFailed
×
1279
        }
×
1280

1281
        return images, int(count), nil
15✔
1282
}
1283

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

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

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

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

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

1316
        return nil
6✔
1317
}
1318

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

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

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

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

1338
        return &depl, nil
4✔
1339
}
1340

1341
// device deployments
1342

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

27✔
1352
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
27✔
1353
                return err
×
1354
        }
×
1355

1356
        if incrementDeviceCount {
54✔
1357
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
27✔
1358
                if err != nil {
27✔
1359
                        return err
×
1360
                }
×
1361
        }
1362

1363
        return nil
27✔
1364
}
1365

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

39✔
1371
        if len(deployments) == 0 {
51✔
1372
                return nil
12✔
1373
        }
12✔
1374

1375
        deviceCountIncrements := make(map[string]int)
27✔
1376

27✔
1377
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
27✔
1378
        var list []interface{}
27✔
1379
        for _, deployment := range deployments {
93✔
1380

66✔
1381
                if deployment == nil {
67✔
1382
                        return ErrStorageInvalidDeviceDeployment
1✔
1383
                }
1✔
1384

1385
                if err := deployment.Validate(); err != nil {
67✔
1386
                        return errors.Wrap(err, "Validating device deployment")
2✔
1387
                }
2✔
1388

1389
                list = append(list, deployment)
63✔
1390
                deviceCountIncrements[deployment.DeploymentId]++
63✔
1391
        }
1392

1393
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
24✔
1394
        collDevs := database.Collection(CollectionDevices)
24✔
1395

24✔
1396
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
24✔
1397
                return err
×
1398
        }
×
1399

1400
        for deploymentID := range deviceCountIncrements {
52✔
1401
                err := db.IncrementDeploymentDeviceCount(
28✔
1402
                        ctx,
28✔
1403
                        deploymentID,
28✔
1404
                        deviceCountIncrements[deploymentID],
28✔
1405
                )
28✔
1406
                if err != nil {
28✔
1407
                        return err
×
1408
                }
×
1409
        }
1410

1411
        return nil
24✔
1412
}
1413

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

1✔
1418
        // Verify ID formatting
1✔
1419
        if len(imageID) == 0 {
1✔
1420
                return false, ErrStorageInvalidID
×
1421
        }
×
1422

1423
        query := bson.M{StorageKeyDeviceDeploymentAssignedImageId: imageID}
1✔
1424

1✔
1425
        if len(statuses) > 0 {
2✔
1426
                query[StorageKeyDeviceDeploymentStatus] = bson.M{
1✔
1427
                        "$in": statuses,
1✔
1428
                }
1✔
1429
        }
1✔
1430

1431
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1432
        collDevs := database.Collection(CollectionDevices)
1✔
1433

1✔
1434
        // if found at least one then image in active deployment
1✔
1435
        var tmp interface{}
1✔
1436
        if err := collDevs.FindOne(ctx, query).Decode(&tmp); err != nil {
2✔
1437
                if err == mongo.ErrNoDocuments {
2✔
1438
                        return false, nil
1✔
1439
                }
1✔
1440
                return false, err
×
1441
        }
1442

1443
        return true, nil
×
1444
}
1445

1446
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1447
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1448
        ctx context.Context,
1449
        deviceID string,
1450
) (*model.DeviceDeployment, error) {
6✔
1451

6✔
1452
        // Verify ID formatting
6✔
1453
        if len(deviceID) == 0 {
7✔
1454
                return nil, ErrStorageInvalidID
1✔
1455
        }
1✔
1456

1457
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1458
        collDevs := database.Collection(CollectionDevices)
5✔
1459

5✔
1460
        // Device should know only about deployments that are not finished
5✔
1461
        query := bson.D{
5✔
1462
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
5✔
1463
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1464
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1465
                        {Key: "$exists", Value: false},
5✔
1466
                }},
5✔
1467
        }
5✔
1468

5✔
1469
        // Find the oldest one by sorting the creation timestamp
5✔
1470
        // in ascending order.
5✔
1471
        findOptions := mopts.FindOne()
5✔
1472
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
5✔
1473

5✔
1474
        // Select only the oldest one that have not been finished yet.
5✔
1475
        deployment := new(model.DeviceDeployment)
5✔
1476
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1477
                Decode(deployment); err != nil {
8✔
1478
                if err == mongo.ErrNoDocuments {
5✔
1479
                        return nil, nil
2✔
1480
                }
2✔
1481
                return nil, err
1✔
1482
        }
1483

1484
        return deployment, nil
3✔
1485
}
1486

1487
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1488
// matching device id that has not finished yet.
1489
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1490
        ctx context.Context,
1491
        deviceID string,
1492
) (*model.DeviceDeployment, error) {
6✔
1493

6✔
1494
        // Verify ID formatting
6✔
1495
        if len(deviceID) == 0 {
7✔
1496
                return nil, ErrStorageInvalidID
1✔
1497
        }
1✔
1498

1499
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1500
        collDevs := database.Collection(CollectionDevices)
5✔
1501

5✔
1502
        query := bson.D{
5✔
1503
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
5✔
1504
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1505
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1506
                        {Key: "$exists", Value: false},
5✔
1507
                }},
5✔
1508
        }
5✔
1509

5✔
1510
        // Find the latest one by sorting by the creation timestamp
5✔
1511
        // in ascending order.
5✔
1512
        findOptions := mopts.FindOne()
5✔
1513
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
5✔
1514

5✔
1515
        // Select only the latest one that have not been finished yet.
5✔
1516
        var deployment *model.DeviceDeployment
5✔
1517
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1518
                Decode(&deployment); err != nil {
8✔
1519
                if err == mongo.ErrNoDocuments {
5✔
1520
                        return nil, nil
2✔
1521
                }
2✔
1522
                return nil, err
1✔
1523
        }
1524

1525
        return deployment, nil
3✔
1526
}
1527

1528
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1529
        ctx context.Context,
1530
        deviceID string,
1531
        deploymentID string,
1532
        ddState model.DeviceDeploymentState,
1533
) (model.DeviceDeploymentStatus, error) {
10✔
1534

10✔
1535
        // Verify ID formatting
10✔
1536
        if len(deviceID) == 0 ||
10✔
1537
                len(deploymentID) == 0 {
12✔
1538
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
2✔
1539
        }
2✔
1540

1541
        if err := ddState.Validate(); err != nil {
9✔
1542
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1543
        }
1✔
1544

1545
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1546
        collDevs := database.Collection(CollectionDevices)
7✔
1547

7✔
1548
        // Device should know only about deployments that are not finished
7✔
1549
        query := bson.D{
7✔
1550
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
1551
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
1552
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
1553
                        {Key: "$exists", Value: false},
7✔
1554
                }},
7✔
1555
        }
7✔
1556

7✔
1557
        // update status field
7✔
1558
        set := bson.M{
7✔
1559
                StorageKeyDeviceDeploymentStatus: ddState.Status,
7✔
1560
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
7✔
1561
        }
7✔
1562
        // and finish time if provided
7✔
1563
        if ddState.FinishTime != nil {
9✔
1564
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
2✔
1565
        }
2✔
1566

1567
        if len(ddState.SubState) > 0 {
8✔
1568
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
1✔
1569
        }
1✔
1570

1571
        update := bson.D{
7✔
1572
                {Key: "$set", Value: set},
7✔
1573
        }
7✔
1574

7✔
1575
        var old model.DeviceDeployment
7✔
1576

7✔
1577
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
7✔
1578
                Decode(&old); err != nil {
9✔
1579
                if err == mongo.ErrNoDocuments {
4✔
1580
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
2✔
1581
                }
2✔
1582
                return model.DeviceDeploymentStatusNull, err
×
1583

1584
        }
1585

1586
        return old.Status, nil
5✔
1587
}
1588

1589
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1590
        deviceID string, deploymentID string, log bool) error {
7✔
1591

7✔
1592
        // Verify ID formatting
7✔
1593
        if len(deviceID) == 0 ||
7✔
1594
                len(deploymentID) == 0 {
9✔
1595
                return ErrStorageInvalidID
2✔
1596
        }
2✔
1597

1598
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1599
        collDevs := database.Collection(CollectionDevices)
5✔
1600

5✔
1601
        selector := bson.D{
5✔
1602
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1603
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
5✔
1604
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1605
                        {Key: "$exists", Value: false},
5✔
1606
                }},
5✔
1607
        }
5✔
1608

5✔
1609
        update := bson.D{
5✔
1610
                {Key: "$set", Value: bson.M{
5✔
1611
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
5✔
1612
        }
5✔
1613

5✔
1614
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
5✔
1615
                return err
×
1616
        } else if res.MatchedCount == 0 {
7✔
1617
                return ErrStorageNotFound
2✔
1618
        }
2✔
1619

1620
        return nil
3✔
1621
}
1622

1623
// SaveDeviceDeploymentRequest saves device deployment request
1624
// with the device deployment object
1625
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1626
        ctx context.Context,
1627
        ID string,
1628
        request *model.DeploymentNextRequest,
1629
) error {
4✔
1630

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

4✔
1634
        res, err := collDevs.UpdateOne(
4✔
1635
                ctx,
4✔
1636
                bson.D{{Key: StorageKeyId, Value: ID}},
4✔
1637
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
4✔
1638
        )
4✔
1639
        if err != nil {
4✔
1640
                return err
×
1641
        } else if res.MatchedCount == 0 {
5✔
1642
                return ErrStorageNotFound
1✔
1643
        }
1✔
1644
        return nil
3✔
1645
}
1646

1647
// AssignArtifact assigns artifact to the device deployment
1648
func (db *DataStoreMongo) AssignArtifact(
1649
        ctx context.Context,
1650
        deviceID string,
1651
        deploymentID string,
1652
        artifact *model.Image,
1653
) error {
1✔
1654

1✔
1655
        // Verify ID formatting
1✔
1656
        if len(deviceID) == 0 ||
1✔
1657
                len(deploymentID) == 0 {
1✔
1658
                return ErrStorageInvalidID
×
1659
        }
×
1660

1661
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1662
        collDevs := database.Collection(CollectionDevices)
1✔
1663

1✔
1664
        selector := bson.D{
1✔
1665
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1666
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1667
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1668
                        {Key: "$exists", Value: false},
1✔
1669
                }},
1✔
1670
        }
1✔
1671

1✔
1672
        update := bson.D{
1✔
1673
                {Key: "$set", Value: bson.M{
1✔
1674
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1675
                }},
1✔
1676
        }
1✔
1677

1✔
1678
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1679
                return err
×
1680
        } else if res.MatchedCount == 0 {
1✔
1681
                return ErrStorageNotFound
×
1682
        }
×
1683

1684
        return nil
1✔
1685
}
1686

1687
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1688
        id string) (model.Stats, error) {
6✔
1689

6✔
1690
        if len(id) == 0 {
6✔
1691
                return nil, ErrStorageInvalidID
×
1692
        }
×
1693

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

6✔
1697
        match := bson.D{
6✔
1698
                {Key: "$match", Value: bson.M{
6✔
1699
                        StorageKeyDeviceDeploymentDeploymentID: id,
6✔
1700
                        StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1701
                                {Key: "$exists", Value: false},
6✔
1702
                        },
6✔
1703
                }},
6✔
1704
        }
6✔
1705
        group := bson.D{
6✔
1706
                {Key: "$group", Value: bson.D{
6✔
1707
                        {Key: "_id",
6✔
1708
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
6✔
1709
                        {Key: "count",
6✔
1710
                                Value: bson.M{"$sum": 1}}},
6✔
1711
                },
6✔
1712
        }
6✔
1713
        pipeline := []bson.D{
6✔
1714
                match,
6✔
1715
                group,
6✔
1716
        }
6✔
1717
        var results []struct {
6✔
1718
                Status model.DeviceDeploymentStatus `bson:"_id"`
6✔
1719
                Count  int
6✔
1720
        }
6✔
1721
        cursor, err := collDevs.Aggregate(ctx, pipeline)
6✔
1722
        if err != nil {
6✔
1723
                return nil, err
×
1724
        }
×
1725
        if err := cursor.All(ctx, &results); err != nil {
6✔
1726
                if err == mongo.ErrNoDocuments {
×
1727
                        return nil, nil
×
1728
                }
×
1729
                return nil, err
×
1730
        }
1731

1732
        raw := model.NewDeviceDeploymentStats()
6✔
1733
        for _, res := range results {
17✔
1734
                raw.Set(res.Status, res.Count)
11✔
1735
        }
11✔
1736
        return raw, nil
6✔
1737
}
1738

1739
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1740
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1741
        deploymentID string) ([]model.DeviceDeployment, error) {
6✔
1742

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

6✔
1747
        query := bson.M{
6✔
1748
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1749
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1750
                        {Key: "$exists", Value: false},
6✔
1751
                },
6✔
1752
        }
6✔
1753

6✔
1754
        cursor, err := collDevs.Find(ctx, query)
6✔
1755
        if err != nil {
6✔
1756
                return nil, err
×
1757
        }
×
1758

1759
        if err = cursor.All(ctx, &statuses); err != nil {
6✔
1760
                if err == mongo.ErrNoDocuments {
×
1761
                        return nil, nil
×
1762
                }
×
1763
                return nil, err
×
1764
        }
1765

1766
        return statuses, nil
6✔
1767
}
1768

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

15✔
1772
        statuses := []model.DeviceDeployment{}
15✔
1773
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
15✔
1774
        collDevs := database.Collection(CollectionDevices)
15✔
1775

15✔
1776
        query := bson.D{
15✔
1777
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
15✔
1778
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
15✔
1779
                        {Key: "$exists", Value: false},
15✔
1780
                }},
15✔
1781
        }
15✔
1782
        if q.Status != nil {
19✔
1783
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
5✔
1784
                        query = append(query, bson.E{
1✔
1785
                                Key: "status", Value: bson.D{{
1✔
1786
                                        Key:   "$gte",
1✔
1787
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1788
                                }, {
1✔
1789
                                        Key:   "$lte",
1✔
1790
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1791
                                }},
1✔
1792
                        })
1✔
1793
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
4✔
1794
                        query = append(query, bson.E{
×
1795
                                Key: "status", Value: bson.D{{
×
1796
                                        Key:   "$gte",
×
1797
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1798
                                }, {
×
1799
                                        Key:   "$lte",
×
1800
                                        Value: model.DeviceDeploymentStatusPending,
×
1801
                                }},
×
1802
                        })
×
1803
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
4✔
1804
                        query = append(query, bson.E{
1✔
1805
                                Key: "status", Value: bson.D{{
1✔
1806
                                        Key: "$in",
1✔
1807
                                        Value: []model.DeviceDeploymentStatus{
1✔
1808
                                                model.DeviceDeploymentStatusFailure,
1✔
1809
                                                model.DeviceDeploymentStatusAborted,
1✔
1810
                                                model.DeviceDeploymentStatusSuccess,
1✔
1811
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1812
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1813
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1814
                                        },
1✔
1815
                                }},
1✔
1816
                        })
1✔
1817
                } else {
3✔
1818
                        var status model.DeviceDeploymentStatus
2✔
1819
                        err := status.UnmarshalText([]byte(*q.Status))
2✔
1820
                        if err != nil {
3✔
1821
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1822
                        }
1✔
1823
                        query = append(query, bson.E{
1✔
1824
                                Key: "status", Value: status,
1✔
1825
                        })
1✔
1826
                }
1827
        }
1828

1829
        options := mopts.Find()
14✔
1830
        sortFieldQuery := bson.D{
14✔
1831
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
14✔
1832
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
14✔
1833
        }
14✔
1834
        options.SetSort(sortFieldQuery)
14✔
1835
        if q.Skip > 0 {
17✔
1836
                options.SetSkip(int64(q.Skip))
3✔
1837
        }
3✔
1838
        if q.Limit > 0 {
19✔
1839
                options.SetLimit(int64(q.Limit))
5✔
1840
        } else {
14✔
1841
                options.SetLimit(DefaultDocumentLimit)
9✔
1842
        }
9✔
1843

1844
        cursor, err := collDevs.Find(ctx, query, options)
14✔
1845
        if err != nil {
15✔
1846
                return nil, -1, err
1✔
1847
        }
1✔
1848

1849
        if err = cursor.All(ctx, &statuses); err != nil {
13✔
1850
                if err == mongo.ErrNoDocuments {
×
1851
                        return nil, -1, nil
×
1852
                }
×
1853
                return nil, -1, err
×
1854
        }
1855

1856
        count, err := collDevs.CountDocuments(ctx, query)
13✔
1857
        if err != nil {
13✔
1858
                return nil, -1, ErrDevicesCountFailed
×
1859
        }
×
1860

1861
        return statuses, int(count), nil
13✔
1862
}
1863

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

10✔
1867
        statuses := []model.DeviceDeployment{}
10✔
1868
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
10✔
1869
        collDevs := database.Collection(CollectionDevices)
10✔
1870

10✔
1871
        query := bson.D{}
10✔
1872
        if q.DeviceID != "" {
19✔
1873
                query = append(query, bson.E{
9✔
1874
                        Key:   StorageKeyDeviceDeploymentDeviceId,
9✔
1875
                        Value: q.DeviceID,
9✔
1876
                })
9✔
1877
        } else if len(q.IDs) > 0 {
11✔
1878
                query = append(query, bson.E{
1✔
1879
                        Key: StorageKeyId,
1✔
1880
                        Value: bson.D{{
1✔
1881
                                Key:   "$in",
1✔
1882
                                Value: q.IDs,
1✔
1883
                        }},
1✔
1884
                })
1✔
1885
        }
1✔
1886

1887
        if q.Status != nil {
18✔
1888
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
9✔
1889
                        query = append(query, bson.E{
1✔
1890
                                Key: "status", Value: bson.D{{
1✔
1891
                                        Key:   "$gte",
1✔
1892
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1893
                                }, {
1✔
1894
                                        Key:   "$lte",
1✔
1895
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1896
                                }},
1✔
1897
                        })
1✔
1898
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
9✔
1899
                        query = append(query, bson.E{
1✔
1900
                                Key: "status", Value: bson.D{{
1✔
1901
                                        Key:   "$gte",
1✔
1902
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1903
                                }, {
1✔
1904
                                        Key:   "$lte",
1✔
1905
                                        Value: model.DeviceDeploymentStatusPending,
1✔
1906
                                }},
1✔
1907
                        })
1✔
1908
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
8✔
1909
                        query = append(query, bson.E{
1✔
1910
                                Key: "status", Value: bson.D{{
1✔
1911
                                        Key: "$in",
1✔
1912
                                        Value: []model.DeviceDeploymentStatus{
1✔
1913
                                                model.DeviceDeploymentStatusFailure,
1✔
1914
                                                model.DeviceDeploymentStatusAborted,
1✔
1915
                                                model.DeviceDeploymentStatusSuccess,
1✔
1916
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1917
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1918
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1919
                                        },
1✔
1920
                                }},
1✔
1921
                        })
1✔
1922
                } else {
6✔
1923
                        var status model.DeviceDeploymentStatus
5✔
1924
                        err := status.UnmarshalText([]byte(*q.Status))
5✔
1925
                        if err != nil {
6✔
1926
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1927
                        }
1✔
1928
                        query = append(query, bson.E{
4✔
1929
                                Key: "status", Value: status,
4✔
1930
                        })
4✔
1931
                }
1932
        }
1933

1934
        options := mopts.Find()
9✔
1935
        sortFieldQuery := bson.D{
9✔
1936
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
9✔
1937
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
9✔
1938
        }
9✔
1939
        options.SetSort(sortFieldQuery)
9✔
1940
        if q.Skip > 0 {
10✔
1941
                options.SetSkip(int64(q.Skip))
1✔
1942
        }
1✔
1943
        if q.Limit > 0 {
18✔
1944
                options.SetLimit(int64(q.Limit))
9✔
1945
        } else {
9✔
1946
                options.SetLimit(DefaultDocumentLimit)
×
1947
        }
×
1948

1949
        cursor, err := collDevs.Find(ctx, query, options)
9✔
1950
        if err != nil {
9✔
1951
                return nil, -1, err
×
1952
        }
×
1953

1954
        if err = cursor.All(ctx, &statuses); err != nil {
9✔
1955
                if err == mongo.ErrNoDocuments {
×
1956
                        return nil, 0, nil
×
1957
                }
×
1958
                return nil, -1, err
×
1959
        }
1960

1961
        maxCount := maxCountDocuments
9✔
1962
        countOptions := &mopts.CountOptions{
9✔
1963
                Limit: &maxCount,
9✔
1964
        }
9✔
1965
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
9✔
1966
        if err != nil {
9✔
1967
                return nil, -1, ErrDevicesCountFailed
×
1968
        }
×
1969

1970
        return statuses, int(count), nil
9✔
1971
}
1972

1973
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1974
// `deviceID`, false otherwise. In case of errors returns false and an error
1975
// that occurred
1976
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1977
        deploymentID string, deviceID string) (bool, error) {
7✔
1978

7✔
1979
        var dep model.DeviceDeployment
7✔
1980
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1981
        collDevs := database.Collection(CollectionDevices)
7✔
1982

7✔
1983
        query := bson.D{
7✔
1984
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
1985
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
1986
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
1987
                        {Key: "$exists", Value: false},
7✔
1988
                }},
7✔
1989
        }
7✔
1990

7✔
1991
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
10✔
1992
                if err == mongo.ErrNoDocuments {
6✔
1993
                        return false, nil
3✔
1994
                } else {
3✔
1995
                        return false, err
×
1996
                }
×
1997
        }
1998

1999
        return true, nil
4✔
2000
}
2001

2002
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2003
        deploymentId string) error {
3✔
2004

3✔
2005
        if len(deploymentId) == 0 {
4✔
2006
                return ErrStorageInvalidID
1✔
2007
        }
1✔
2008

2009
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2010
        collDevs := database.Collection(CollectionDevices)
2✔
2011
        selector := bson.M{
2✔
2012
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
2✔
2013
                StorageKeyDeviceDeploymentActive:       true,
2✔
2014
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2015
                        {Key: "$exists", Value: false},
2✔
2016
                },
2✔
2017
        }
2✔
2018

2✔
2019
        update := bson.M{
2✔
2020
                "$set": bson.M{
2✔
2021
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2022
                        StorageKeyDeviceDeploymentActive: false,
2✔
2023
                },
2✔
2024
        }
2✔
2025

2✔
2026
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2027
                return err
×
2028
        }
×
2029

2030
        return nil
2✔
2031
}
2032

2033
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2034
        deviceID string) error {
2✔
2035
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2036
        collDevs := database.Collection(CollectionDevices)
2✔
2037
        selector := bson.M{
2✔
2038
                StorageKeyDeviceDeploymentDeviceId: deviceID,
2✔
2039
                StorageKeyDeviceDeploymentActive:   false,
2✔
2040
                StorageKeyDeviceDeploymentDeleted: bson.M{
2✔
2041
                        "$exists": false,
2✔
2042
                },
2✔
2043
        }
2✔
2044

2✔
2045
        now := time.Now()
2✔
2046
        update := bson.M{
2✔
2047
                "$set": bson.M{
2✔
2048
                        StorageKeyDeviceDeploymentDeleted: &now,
2✔
2049
                },
2✔
2050
        }
2✔
2051

2✔
2052
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2053
                return err
×
2054
        }
×
2055

2056
        database = db.client.Database(DatabaseName)
2✔
2057
        collDevs = database.Collection(CollectionDevicesLastStatus)
2✔
2058
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
2✔
2059

2✔
2060
        return err
2✔
2061
}
2062

2063
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2064
        deviceId string) error {
2✔
2065

2✔
2066
        if len(deviceId) == 0 {
3✔
2067
                return ErrStorageInvalidID
1✔
2068
        }
1✔
2069

2070
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2071
        collDevs := database.Collection(CollectionDevices)
1✔
2072
        selector := bson.M{
1✔
2073
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2074
                StorageKeyDeviceDeploymentActive:   true,
1✔
2075
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2076
                        {Key: "$exists", Value: false},
1✔
2077
                },
1✔
2078
        }
1✔
2079

1✔
2080
        update := bson.M{
1✔
2081
                "$set": bson.M{
1✔
2082
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2083
                        StorageKeyDeviceDeploymentActive: false,
1✔
2084
                },
1✔
2085
        }
1✔
2086

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

2091
        return nil
1✔
2092
}
2093

2094
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2095
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2096

1✔
2097
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2098
        collDevs := database.Collection(CollectionDevices)
1✔
2099

1✔
2100
        filter := bson.M{
1✔
2101
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2102
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2103
        }
1✔
2104
        if !includeDeleted {
2✔
2105
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2106
                        {Key: "$exists", Value: false},
1✔
2107
                }
1✔
2108
        }
1✔
2109

2110
        opts := &mopts.FindOneOptions{}
1✔
2111
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2112

1✔
2113
        var dd model.DeviceDeployment
1✔
2114
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2115
                if err == mongo.ErrNoDocuments {
2✔
2116
                        return nil, ErrStorageNotFound
1✔
2117
                }
1✔
2118
                return nil, err
×
2119
        }
2120

2121
        return &dd, nil
1✔
2122
}
2123

2124
func (db *DataStoreMongo) GetDeviceDeployments(
2125
        ctx context.Context,
2126
        skip int,
2127
        limit int,
2128
        deviceID string,
2129
        active *bool,
2130
        includeDeleted bool,
2131
) ([]model.DeviceDeployment, error) {
4✔
2132

4✔
2133
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2134
        collDevs := database.Collection(CollectionDevices)
4✔
2135

4✔
2136
        filter := bson.M{}
4✔
2137
        if !includeDeleted {
6✔
2138
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2139
                        {Key: "$exists", Value: false},
2✔
2140
                }
2✔
2141
        }
2✔
2142
        if deviceID != "" {
5✔
2143
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2144
        }
1✔
2145
        if active != nil {
5✔
2146
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2147
        }
1✔
2148

2149
        opts := &mopts.FindOptions{}
4✔
2150
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
4✔
2151
        if skip > 0 {
5✔
2152
                opts.SetSkip(int64(skip))
1✔
2153
        }
1✔
2154
        if limit > 0 {
5✔
2155
                opts.SetLimit(int64(limit))
1✔
2156
        }
1✔
2157

2158
        var deviceDeployments []model.DeviceDeployment
4✔
2159
        cursor, err := collDevs.Find(ctx, filter, opts)
4✔
2160
        if err != nil {
4✔
2161
                return nil, err
×
2162
        }
×
2163
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
4✔
2164
                return nil, err
×
2165
        }
×
2166

2167
        return deviceDeployments, nil
4✔
2168
}
2169

2170
// deployments
2171

2172
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2173
        indexes ...mongo.IndexModel) error {
350✔
2174
        ctx := context.Background()
350✔
2175
        dataBase := db.client.Database(dbName)
350✔
2176

350✔
2177
        coll := dataBase.Collection(collName)
350✔
2178
        idxView := coll.Indexes()
350✔
2179
        _, err := idxView.CreateMany(ctx, indexes)
350✔
2180
        return err
350✔
2181
}
350✔
2182

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

16✔
2186
        var idx bson.M
16✔
2187
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
16✔
2188
        collDpl := database.Collection(CollectionDeployments)
16✔
2189
        idxView := collDpl.Indexes()
16✔
2190

16✔
2191
        cursor, err := idxView.List(ctx)
16✔
2192
        if err != nil {
16✔
2193
                // check failed, assume indexing is not there
×
2194
                return false
×
2195
        }
×
2196

2197
        has := map[string]bool{}
16✔
2198
        for cursor.Next(ctx) {
46✔
2199
                if err = cursor.Decode(&idx); err != nil {
30✔
2200
                        continue
×
2201
                }
2202
                if _, ok := idx["weights"]; ok {
45✔
2203
                        // text index
15✔
2204
                        for k := range idx["weights"].(bson.M) {
45✔
2205
                                has[k] = true
30✔
2206
                        }
30✔
2207
                } else {
15✔
2208
                        for i := range idx["key"].(bson.M) {
30✔
2209
                                has[i] = true
15✔
2210
                        }
15✔
2211

2212
                }
2213
        }
2214
        if err != nil {
16✔
2215
                return false
×
2216
        }
×
2217

2218
        for _, key := range StorageIndexes.Keys.(bson.D) {
47✔
2219
                _, ok := has[key.Key]
31✔
2220
                if !ok {
32✔
2221
                        return false
1✔
2222
                }
1✔
2223
        }
2224

2225
        return true
15✔
2226
}
2227

2228
// Insert persists object
2229
func (db *DataStoreMongo) InsertDeployment(
2230
        ctx context.Context,
2231
        deployment *model.Deployment,
2232
) error {
210✔
2233

210✔
2234
        if deployment == nil {
211✔
2235
                return ErrDeploymentStorageInvalidDeployment
1✔
2236
        }
1✔
2237

2238
        if err := deployment.Validate(); err != nil {
211✔
2239
                return err
2✔
2240
        }
2✔
2241

2242
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
208✔
2243
        collDpl := database.Collection(CollectionDeployments)
208✔
2244

208✔
2245
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
209✔
2246
                return err
1✔
2247
        }
1✔
2248
        return nil
208✔
2249
}
2250

2251
// Delete removed entry by ID
2252
// Noop on ID not found
2253
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
4✔
2254

4✔
2255
        if len(id) == 0 {
5✔
2256
                return ErrStorageInvalidID
1✔
2257
        }
1✔
2258

2259
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2260
        collDpl := database.Collection(CollectionDeployments)
3✔
2261

3✔
2262
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
2263
                return err
×
2264
        }
×
2265

2266
        return nil
3✔
2267
}
2268

2269
func (db *DataStoreMongo) FindDeploymentByID(
2270
        ctx context.Context,
2271
        id string,
2272
) (*model.Deployment, error) {
10✔
2273

10✔
2274
        if len(id) == 0 {
11✔
2275
                return nil, ErrStorageInvalidID
1✔
2276
        }
1✔
2277

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

9✔
2281
        deployment := new(model.Deployment)
9✔
2282
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
9✔
2283
                Decode(deployment); err != nil {
12✔
2284
                if err == mongo.ErrNoDocuments {
6✔
2285
                        return nil, nil
3✔
2286
                }
3✔
2287
                return nil, err
×
2288
        }
2289

2290
        return deployment, nil
6✔
2291
}
2292

2293
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2294
        ctx context.Context,
2295
        ids ...string,
2296
) (deploymentStats []*model.DeploymentStats, err error) {
2✔
2297

2✔
2298
        if len(ids) == 0 {
2✔
2299
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2300
        }
×
2301

2302
        for _, id := range ids {
6✔
2303
                if len(id) == 0 {
4✔
2304
                        return nil, ErrStorageInvalidID
×
2305
                }
×
2306
        }
2307

2308
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2309
        collDpl := database.Collection(CollectionDeployments)
2✔
2310

2✔
2311
        query := bson.M{
2✔
2312
                "_id": bson.M{
2✔
2313
                        "$in": ids,
2✔
2314
                },
2✔
2315
        }
2✔
2316
        statsProjection := &mopts.FindOptions{
2✔
2317
                Projection: bson.M{"stats": 1},
2✔
2318
        }
2✔
2319

2✔
2320
        results, err := collDpl.Find(
2✔
2321
                ctx,
2✔
2322
                query,
2✔
2323
                statsProjection,
2✔
2324
        )
2✔
2325
        if err != nil {
2✔
2326
                return nil, err
×
2327
        }
×
2328

2329
        for results.Next(context.Background()) {
6✔
2330
                depl := new(model.DeploymentStats)
4✔
2331
                if err = results.Decode(&depl); err != nil {
4✔
2332
                        if err == mongo.ErrNoDocuments {
×
2333
                                return nil, nil
×
2334
                        }
×
2335
                        return nil, err
×
2336
                }
2337
                deploymentStats = append(deploymentStats, depl)
4✔
2338
        }
2339

2340
        return deploymentStats, nil
2✔
2341
}
2342

2343
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2344
        id string) (*model.Deployment, error) {
8✔
2345

8✔
2346
        if len(id) == 0 {
9✔
2347
                return nil, ErrStorageInvalidID
1✔
2348
        }
1✔
2349

2350
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2351
        collDpl := database.Collection(CollectionDeployments)
7✔
2352

7✔
2353
        var deployment *model.Deployment
7✔
2354
        filter := bson.D{
7✔
2355
                {Key: "_id", Value: id},
7✔
2356
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2357
        }
7✔
2358
        if err := collDpl.FindOne(ctx, filter).
7✔
2359
                Decode(&deployment); err != nil {
12✔
2360
                if err == mongo.ErrNoDocuments {
10✔
2361
                        return nil, nil
5✔
2362
                }
5✔
2363
                return nil, err
×
2364
        }
2365

2366
        return deployment, nil
3✔
2367
}
2368

2369
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2370
        ctx context.Context,
2371
        deploymentID string,
2372
        increment int,
2373
) error {
55✔
2374
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
55✔
2375
        collection := database.Collection(CollectionDeployments)
55✔
2376

55✔
2377
        filter := bson.M{
55✔
2378
                "_id": deploymentID,
55✔
2379
                StorageKeyDeploymentDeviceCount: bson.M{
55✔
2380
                        "$ne": nil,
55✔
2381
                },
55✔
2382
        }
55✔
2383

55✔
2384
        update := bson.M{
55✔
2385
                "$inc": bson.M{
55✔
2386
                        StorageKeyDeploymentDeviceCount: increment,
55✔
2387
                },
55✔
2388
        }
55✔
2389

55✔
2390
        _, err := collection.UpdateOne(ctx, filter, update)
55✔
2391
        return err
55✔
2392
}
55✔
2393

2394
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2395
        ctx context.Context,
2396
        deploymentID string,
2397
        count int,
2398
) error {
3✔
2399
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2400
        collection := database.Collection(CollectionDeployments)
3✔
2401

3✔
2402
        filter := bson.M{
3✔
2403
                "_id": deploymentID,
3✔
2404
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2405
                        "$eq": nil,
3✔
2406
                },
3✔
2407
        }
3✔
2408

3✔
2409
        update := bson.M{
3✔
2410
                "$set": bson.M{
3✔
2411
                        StorageKeyDeploymentDeviceCount: count,
3✔
2412
                },
3✔
2413
        }
3✔
2414

3✔
2415
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2416
        return err
3✔
2417
}
3✔
2418

2419
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2420
        id string) (int, error) {
3✔
2421

3✔
2422
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2423
        collDevs := database.Collection(CollectionDevices)
3✔
2424

3✔
2425
        filter := bson.M{
3✔
2426
                StorageKeyDeviceDeploymentDeploymentID: id,
3✔
2427
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
2428
                        {Key: "$exists", Value: false},
3✔
2429
                },
3✔
2430
        }
3✔
2431

3✔
2432
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
3✔
2433
        if err != nil {
3✔
2434
                return 0, err
×
2435
        }
×
2436

2437
        return int(deviceCount), nil
3✔
2438
}
2439

2440
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2441
        id string, stats model.Stats) error {
6✔
2442

6✔
2443
        if len(id) == 0 {
7✔
2444
                return ErrStorageInvalidID
1✔
2445
        }
1✔
2446

2447
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2448
        collDpl := database.Collection(CollectionDeployments)
5✔
2449

5✔
2450
        deployment, err := model.NewDeployment()
5✔
2451
        if err != nil {
5✔
2452
                return errors.Wrap(err, "failed to create deployment")
×
2453
        }
×
2454

2455
        deployment.Stats = stats
5✔
2456
        var update bson.M
5✔
2457
        if deployment.IsFinished() {
5✔
2458
                now := time.Now()
×
2459

×
2460
                update = bson.M{
×
2461
                        "$set": bson.M{
×
2462
                                StorageKeyDeploymentStats:    stats,
×
2463
                                StorageKeyDeploymentFinished: &now,
×
2464
                        },
×
2465
                }
×
2466
        } else {
5✔
2467
                update = bson.M{
5✔
2468
                        "$set": bson.M{
5✔
2469
                                StorageKeyDeploymentStats: stats,
5✔
2470
                        },
5✔
2471
                }
5✔
2472
        }
5✔
2473

2474
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
5✔
2475
        if res != nil && res.MatchedCount == 0 {
7✔
2476
                return ErrStorageInvalidID
2✔
2477
        }
2✔
2478
        return err
3✔
2479
}
2480

2481
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2482
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
8✔
2483

8✔
2484
        if len(id) == 0 {
9✔
2485
                return ErrStorageInvalidID
1✔
2486
        }
1✔
2487

2488
        if _, err := stateTo.MarshalText(); err != nil {
7✔
2489
                return ErrStorageInvalidInput
×
2490
        }
×
2491

2492
        // does not need any extra operations
2493
        // following query won't handle this case well and increase the state_to value
2494
        if stateFrom == stateTo {
8✔
2495
                return nil
1✔
2496
        }
1✔
2497

2498
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2499
        collDpl := database.Collection(CollectionDeployments)
6✔
2500

6✔
2501
        var update bson.M
6✔
2502

6✔
2503
        if stateFrom == model.DeviceDeploymentStatusNull {
8✔
2504
                // note dot notation on embedded document
2✔
2505
                update = bson.M{
2✔
2506
                        "$inc": bson.M{
2✔
2507
                                "stats." + stateTo.String(): 1,
2✔
2508
                        },
2✔
2509
                }
2✔
2510
        } else {
7✔
2511
                // note dot notation on embedded document
5✔
2512
                update = bson.M{
5✔
2513
                        "$inc": bson.M{
5✔
2514
                                "stats." + stateFrom.String(): -1,
5✔
2515
                                "stats." + stateTo.String():   1,
5✔
2516
                        },
5✔
2517
                }
5✔
2518
        }
5✔
2519

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

6✔
2522
        if res != nil && res.MatchedCount == 0 {
7✔
2523
                return ErrStorageInvalidID
1✔
2524
        }
1✔
2525

2526
        return err
5✔
2527
}
2528

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2680
// FindNewerActiveDeployments finds active deployments which were created
2681
// after createdAfter
2682
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2683
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
5✔
2684

5✔
2685
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2686
        c := database.Collection(CollectionDeployments)
5✔
2687

5✔
2688
        queryFilters := make([]bson.M, 0)
5✔
2689
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
5✔
2690
        queryFilters = append(queryFilters,
5✔
2691
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
5✔
2692
        findQuery := bson.M{}
5✔
2693
        findQuery["$and"] = queryFilters
5✔
2694

5✔
2695
        findOptions := &mopts.FindOptions{}
5✔
2696
        findOptions.SetSkip(int64(skip))
5✔
2697
        findOptions.SetLimit(int64(limit))
5✔
2698

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

5✔
2706
        var deployments []*model.Deployment
5✔
2707

5✔
2708
        if err = cursor.All(ctx, &deployments); err != nil {
5✔
2709
                return nil, errors.Wrap(err, "failed to get deployments")
×
2710
        }
×
2711

2712
        return deployments, nil
5✔
2713
}
2714

2715
// SetDeploymentStatus simply sets the status field
2716
// optionally sets 'finished time' if deployment is indeed finished
2717
func (db *DataStoreMongo) SetDeploymentStatus(
2718
        ctx context.Context,
2719
        id string,
2720
        status model.DeploymentStatus,
2721
        now time.Time,
2722
) error {
6✔
2723
        if len(id) == 0 {
6✔
2724
                return ErrStorageInvalidID
×
2725
        }
×
2726

2727
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2728
        collDpl := database.Collection(CollectionDeployments)
6✔
2729

6✔
2730
        var update bson.M
6✔
2731
        if status == model.DeploymentStatusFinished {
8✔
2732
                update = bson.M{
2✔
2733
                        "$set": bson.M{
2✔
2734
                                StorageKeyDeploymentActive:   false,
2✔
2735
                                StorageKeyDeploymentStatus:   status,
2✔
2736
                                StorageKeyDeploymentFinished: &now,
2✔
2737
                        },
2✔
2738
                }
2✔
2739
        } else {
7✔
2740
                update = bson.M{
5✔
2741
                        "$set": bson.M{
5✔
2742
                                StorageKeyDeploymentActive: true,
5✔
2743
                                StorageKeyDeploymentStatus: status,
5✔
2744
                        },
5✔
2745
                }
5✔
2746
        }
5✔
2747

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

6✔
2750
        if res != nil && res.MatchedCount == 0 {
7✔
2751
                return ErrStorageInvalidID
1✔
2752
        }
1✔
2753

2754
        return err
5✔
2755
}
2756

2757
// ExistUnfinishedByArtifactId checks if there is an active deployment that uses
2758
// given artifact
2759
func (db *DataStoreMongo) ExistUnfinishedByArtifactId(ctx context.Context,
2760
        id string) (bool, error) {
4✔
2761

4✔
2762
        if len(id) == 0 {
4✔
2763
                return false, ErrStorageInvalidID
×
2764
        }
×
2765

2766
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2767
        collDpl := database.Collection(CollectionDeployments)
4✔
2768

4✔
2769
        var tmp interface{}
4✔
2770
        query := bson.D{
4✔
2771
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2772
                {Key: StorageKeyDeploymentArtifacts, Value: id},
4✔
2773
        }
4✔
2774
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
7✔
2775
                if err == mongo.ErrNoDocuments {
6✔
2776
                        return false, nil
3✔
2777
                }
3✔
2778
                return false, err
×
2779
        }
2780

2781
        return true, nil
2✔
2782
}
2783

2784
// ExistUnfinishedByArtifactName checks if there is an active deployment that uses
2785
// given artifact
2786
func (db *DataStoreMongo) ExistUnfinishedByArtifactName(ctx context.Context,
2787
        artifactName string) (bool, error) {
4✔
2788

4✔
2789
        if len(artifactName) == 0 {
4✔
2790
                return false, ErrImagesStorageInvalidArtifactName
×
2791
        }
×
2792

2793
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2794
        collDpl := database.Collection(CollectionDeployments)
4✔
2795

4✔
2796
        var tmp interface{}
4✔
2797
        query := bson.D{
4✔
2798
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2799
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
4✔
2800
        }
4✔
2801

4✔
2802
        projection := bson.M{
4✔
2803
                "_id": 1,
4✔
2804
        }
4✔
2805
        findOptions := mopts.FindOne()
4✔
2806
        findOptions.SetProjection(projection)
4✔
2807

4✔
2808
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
7✔
2809
                if err == mongo.ErrNoDocuments {
6✔
2810
                        return false, nil
3✔
2811
                }
3✔
2812
                return false, err
×
2813
        }
2814

2815
        return true, nil
1✔
2816
}
2817

2818
// ExistByArtifactId check if there is any deployment that uses give artifact
2819
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2820
        id string) (bool, error) {
×
2821

×
2822
        if len(id) == 0 {
×
2823
                return false, ErrStorageInvalidID
×
2824
        }
×
2825

2826
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2827
        collDpl := database.Collection(CollectionDeployments)
×
2828

×
2829
        var tmp interface{}
×
2830
        query := bson.D{
×
2831
                {Key: StorageKeyDeploymentArtifacts, Value: id},
×
2832
        }
×
2833
        if err := collDpl.FindOne(ctx, query).Decode(&tmp); err != nil {
×
2834
                if err == mongo.ErrNoDocuments {
×
2835
                        return false, nil
×
2836
                }
×
2837
                return false, err
×
2838
        }
2839

2840
        return true, nil
×
2841
}
2842

2843
// Per-tenant storage settings
2844
func (db *DataStoreMongo) GetStorageSettings(ctx context.Context) (*model.StorageSettings, error) {
2✔
2845
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2846
        collection := database.Collection(CollectionStorageSettings)
2✔
2847

2✔
2848
        settings := new(model.StorageSettings)
2✔
2849
        // supposed that it's only one document in the collection
2✔
2850
        query := bson.M{
2✔
2851
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2852
        }
2✔
2853
        if err := collection.FindOne(ctx, query).Decode(settings); err != nil {
3✔
2854
                if err == mongo.ErrNoDocuments {
2✔
2855
                        return nil, nil
1✔
2856
                }
1✔
2857
                return nil, err
×
2858
        }
2859

2860
        return settings, nil
2✔
2861
}
2862

2863
func (db *DataStoreMongo) SetStorageSettings(
2864
        ctx context.Context,
2865
        storageSettings *model.StorageSettings,
2866
) error {
2✔
2867
        var err error
2✔
2868
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2869
        collection := database.Collection(CollectionStorageSettings)
2✔
2870

2✔
2871
        filter := bson.M{
2✔
2872
                "_id": StorageKeyStorageSettingsDefaultID,
2✔
2873
        }
2✔
2874
        if storageSettings != nil {
4✔
2875
                replaceOptions := mopts.Replace()
2✔
2876
                replaceOptions.SetUpsert(true)
2✔
2877
                _, err = collection.ReplaceOne(ctx, filter, storageSettings, replaceOptions)
2✔
2878
        } else {
3✔
2879
                _, err = collection.DeleteOne(ctx, filter)
1✔
2880
        }
1✔
2881

2882
        return err
2✔
2883
}
2884

2885
func (db *DataStoreMongo) UpdateDeploymentsWithArtifactName(
2886
        ctx context.Context,
2887
        artifactName string,
2888
        artifactIDs []string,
2889
) error {
1✔
2890
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2891
        collDpl := database.Collection(CollectionDeployments)
1✔
2892

1✔
2893
        query := bson.D{
1✔
2894
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2895
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2896
        }
1✔
2897
        update := bson.M{
1✔
2898
                "$set": bson.M{
1✔
2899
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
2900
                },
1✔
2901
        }
1✔
2902

1✔
2903
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
2904
        return err
1✔
2905
}
1✔
2906

2907
func (db *DataStoreMongo) GetTenantDbs() ([]string, error) {
×
2908
        return migrate.GetTenantDbs(context.Background(), db.client, mstore.IsTenantDb(DbName))
×
2909
}
×
2910

2911
func (db *DataStoreMongo) UpdateReleaseArtifactDescription(
2912
        ctx context.Context,
2913
        artifactToEdit *model.Image,
2914
        releaseName string,
NEW
2915
) error {
×
NEW
2916
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
NEW
2917
        collReleases := database.Collection(CollectionReleases)
×
NEW
2918

×
NEW
2919
        update := bson.M{
×
NEW
2920
                "$set": bson.M{
×
NEW
2921
                        StorageKeyReleaseArtifactsIndexDescription: artifactToEdit.ImageMeta.Description,
×
NEW
2922
                        StorageKeyReleaseArtifactsIndexModified:    artifactToEdit.Modified,
×
NEW
2923
                        StorageKeyReleaseModified:                  time.Now(),
×
NEW
2924
                },
×
NEW
2925
        }
×
NEW
2926
        _, err := collReleases.UpdateOne(
×
NEW
2927
                ctx,
×
NEW
2928
                bson.M{
×
NEW
2929
                        StorageKeyReleaseName:        releaseName,
×
NEW
2930
                        StorageKeyReleaseArtifactsId: artifactToEdit.Id,
×
NEW
2931
                },
×
NEW
2932
                update,
×
NEW
2933
        )
×
NEW
2934
        if err != nil {
×
NEW
2935
                return err
×
NEW
2936
        }
×
NEW
2937
        return nil
×
2938
}
2939

2940
func (db *DataStoreMongo) UpdateReleaseArtifacts(
2941
        ctx context.Context,
2942
        artifactToAdd *model.Image,
2943
        artifactToRemove *model.Image,
2944
        releaseName string,
2945
) error {
1✔
2946
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2947
        collReleases := database.Collection(CollectionReleases)
1✔
2948

1✔
2949
        opt := &mopts.UpdateOptions{}
1✔
2950
        update := bson.M{
1✔
2951
                "$set": bson.M{
1✔
2952
                        StorageKeyReleaseName:     releaseName,
1✔
2953
                        StorageKeyReleaseModified: time.Now(),
1✔
2954
                },
1✔
2955
        }
1✔
2956
        if artifactToRemove != nil {
2✔
2957
                update["$pull"] = bson.M{
1✔
2958
                        StorageKeyReleaseArtifacts: bson.M{StorageKeyId: artifactToRemove.Id},
1✔
2959
                }
1✔
2960
        }
1✔
2961
        if artifactToAdd != nil {
2✔
2962
                upsert := true
1✔
2963
                opt.Upsert = &upsert
1✔
2964
                update["$push"] = bson.M{StorageKeyReleaseArtifacts: artifactToAdd}
1✔
2965
        }
1✔
2966
        _, err := collReleases.UpdateOne(
1✔
2967
                ctx,
1✔
2968
                bson.M{StorageKeyReleaseName: releaseName},
1✔
2969
                update,
1✔
2970
                opt,
1✔
2971
        )
1✔
2972
        if err != nil {
1✔
NEW
2973
                return err
×
NEW
2974
        }
×
2975
        return nil
1✔
2976
}
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