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

mendersoftware / mender-server / 1495380963

14 Oct 2024 03:35PM UTC coverage: 70.373% (-2.5%) from 72.904%
1495380963

Pull #101

gitlab-ci

mineralsfree
feat: tenant list added

Ticket: MEN-7568
Changelog: None

Signed-off-by: Mikita Pilinka <mikita.pilinka@northern.tech>
Pull Request #101: feat: tenant list added

4406 of 6391 branches covered (68.94%)

Branch coverage included in aggregate %.

88 of 183 new or added lines in 10 files covered. (48.09%)

2623 existing lines in 65 files now uncovered.

36673 of 51982 relevant lines covered (70.55%)

31.07 hits per line

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

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

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

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

31
        "github.com/mendersoftware/mender-server/pkg/config"
32
        "github.com/mendersoftware/mender-server/pkg/identity"
33
        "github.com/mendersoftware/mender-server/pkg/log"
34
        "github.com/mendersoftware/mender-server/pkg/mongo/migrate"
35
        mstore "github.com/mendersoftware/mender-server/pkg/store"
36

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

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

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

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

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

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

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

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

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

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

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

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

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

110
        _false         = false
111
        _true          = true
112
        StorageIndexes = mongo.IndexModel{
113
                // NOTE: Keys should be bson.D as element
114
                //       order matters!
115
                Keys: bson.D{
116
                        {Key: StorageKeyDeploymentName,
117
                                Value: "text"},
118
                        {Key: StorageKeyDeploymentArtifactName,
119
                                Value: "text"},
120
                },
121
                Options: &mopts.IndexOptions{
122
                        Background: &_false,
123
                        Name:       &IndexDeploymentArtifactName,
124
                },
125
        }
126
        StatusIndexes = mongo.IndexModel{
127
                Keys: bson.D{
128
                        {Key: StorageKeyDeviceDeploymentDeviceId,
129
                                Value: 1},
130
                        {Key: StorageKeyDeviceDeploymentStatus,
131
                                Value: 1},
132
                        {Key: StorageKeyDeploymentStatsCreated,
133
                                Value: 1},
134
                },
135
                Options: &mopts.IndexOptions{
136
                        Background: &_false,
137
                        Name:       &IndexDeploymentDeviceStatusesName,
138
                },
139
        }
140
        DeploymentStatusIndex = mongo.IndexModel{
141
                Keys: bson.D{
142
                        {Key: StorageKeyDeviceDeploymentStatus,
143
                                Value: 1},
144
                },
145
                Options: &mopts.IndexOptions{
146
                        Background: &_false,
147
                        Name:       &IndexDeploymentStatus,
148
                },
149
        }
150
        DeviceIDStatusIndexes = mongo.IndexModel{
151
                Keys: bson.D{
152
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
153
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
154
                },
155
                Options: &mopts.IndexOptions{
156
                        Background: &_false,
157
                        Name:       &IndexDeploymentDeviceIdStatusName,
158
                },
159
        }
160
        DeviceIDCreatedStatusIndex = mongo.IndexModel{
161
                Keys: bson.D{
162
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
163
                        {Key: StorageKeyDeploymentStatsCreated, Value: 1},
164
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
165
                },
166
                Options: &mopts.IndexOptions{
167
                        Background: &_false,
168
                        Name:       &IndexDeploymentDeviceCreatedStatusName,
169
                },
170
        }
171
        DeploymentIdIndexes = mongo.IndexModel{
172
                Keys: bson.D{
173
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
174
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
175
                },
176
                Options: &mopts.IndexOptions{
177
                        Background: &_false,
178
                        Name:       &IndexDeploymentDeviceDeploymentIdName,
179
                },
180
        }
181
        DeviceDeploymentIdStatus = mongo.IndexModel{
182
                Keys: bson.D{
183
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
184
                        {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
185
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
186
                },
187
                Options: mopts.Index().
188
                        SetName(IndexDeviceDeploymentStatusName),
189
        }
190
        DeploymentCreatedIndex = mongo.IndexModel{
191
                Keys: bson.D{
192
                        {Key: "created", Value: -1},
193
                },
194
                Options: &mopts.IndexOptions{
195
                        Background: &_false,
196
                        Name:       &IndexDeploymentCreatedName,
197
                },
198
        }
199
        DeploymentDeviceStatusFinishedIndex = mongo.IndexModel{
200
                Keys: bson.D{
201
                        {Key: "finished", Value: 1},
202
                },
203
                Options: &mopts.IndexOptions{
204
                        Background: &_false,
205
                        Name:       &IndexDeploymentDeviceStatusFinishedName,
206
                },
207
        }
208
        UniqueNameVersionIndex = mongo.IndexModel{
209
                Keys: bson.D{
210
                        {Key: StorageKeyImageName,
211
                                Value: 1},
212
                        {Key: StorageKeyImageDeviceTypes,
213
                                Value: 1},
214
                },
215
                Options: &mopts.IndexOptions{
216
                        Background: &_false,
217
                        Name:       &IndexUniqueNameAndDeviceTypeName,
218
                        Unique:     &_true,
219
                },
220
        }
221

222
        // 1.2.3
223
        IndexArtifactNameDepends = mongo.IndexModel{
224
                Keys: bson.D{
225
                        {Key: StorageKeyImageName,
226
                                Value: 1},
227
                        {Key: StorageKeyImageDependsIdx,
228
                                Value: 1},
229
                },
230
                Options: &mopts.IndexOptions{
231
                        Background: &_false,
232
                        Name:       &IndexArtifactNameDependsName,
233
                        Unique:     &_true,
234
                },
235
        }
236

237
        // Indexes 1.2.7
238
        IndexImageMetaDescription      = "image_meta_description"
239
        IndexImageMetaDescriptionModel = mongo.IndexModel{
240
                Keys: bson.D{
241
                        {Key: StorageKeyImageDescription, Value: 1},
242
                },
243
                Options: &mopts.IndexOptions{
244
                        Background: &_false,
245
                        Name:       &IndexImageMetaDescription,
246
                },
247
        }
248

249
        IndexImageMetaArtifactDeviceTypeCompatible      = "image_meta_artifact_device_type_compatible"
250
        IndexImageMetaArtifactDeviceTypeCompatibleModel = mongo.IndexModel{
251
                Keys: bson.D{
252
                        {Key: StorageKeyImageDeviceTypes, Value: 1},
253
                },
254
                Options: &mopts.IndexOptions{
255
                        Background: &_false,
256
                        Name:       &IndexImageMetaArtifactDeviceTypeCompatible,
257
                },
258
        }
259

260
        // Indexes 1.2.8
261
        IndexDeploymentsActiveCreated      = "active_created"
262
        IndexDeploymentsActiveCreatedModel = mongo.IndexModel{
263
                Keys: bson.D{
264
                        {Key: StorageKeyDeploymentCreated, Value: 1},
265
                },
266
                Options: &mopts.IndexOptions{
267
                        Background: &_false,
268
                        Name:       &IndexDeploymentsActiveCreated,
269
                        PartialFilterExpression: bson.M{
270
                                StorageKeyDeploymentActive: true,
271
                        },
272
                },
273
        }
274

275
        // Index 1.2.9
276
        IndexDeviceDeploymentsActiveCreated      = "active_deviceid_created"
277
        IndexDeviceDeploymentsActiveCreatedModel = mongo.IndexModel{
278
                Keys: bson.D{
279
                        {Key: StorageKeyDeviceDeploymentActive, Value: 1},
280
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
281
                        {Key: StorageKeyDeviceDeploymentCreated, Value: 1},
282
                },
283
                Options: mopts.Index().
284
                        SetName(IndexDeviceDeploymentsActiveCreated),
285
        }
286

287
        // Index 1.2.11
288
        IndexDeviceDeploymentsLogs      = "devices_logs"
289
        IndexDeviceDeploymentsLogsModel = mongo.IndexModel{
290
                Keys: bson.D{
291
                        {Key: StorageKeyDeviceDeploymentDeploymentID, Value: 1},
292
                        {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
293
                },
294
                Options: mopts.Index().
295
                        SetName(IndexDeviceDeploymentsLogs),
296
        }
297

298
        // 1.2.13
299
        IndexArtifactProvides = mongo.IndexModel{
300
                Keys: bson.D{
301
                        {Key: model.StorageKeyImageProvidesIdxKey,
302
                                Value: 1},
303
                        {Key: model.StorageKeyImageProvidesIdxValue,
304
                                Value: 1},
305
                },
306
                Options: &mopts.IndexOptions{
307
                        Background: &_false,
308
                        Sparse:     &_true,
309
                        Name:       &IndexArtifactProvidesName,
310
                },
311
        }
312
)
313

314
// Errors
315
var (
316
        ErrImagesStorageInvalidID           = errors.New("Invalid id")
317
        ErrImagesStorageInvalidArtifactName = errors.New("Invalid artifact name")
318
        ErrImagesStorageInvalidName         = errors.New("Invalid name")
319
        ErrImagesStorageInvalidDeviceType   = errors.New("Invalid device type")
320
        ErrImagesStorageInvalidImage        = errors.New("Invalid image")
321

322
        ErrStorageInvalidDeviceDeployment = errors.New("Invalid device deployment")
323

324
        ErrDeploymentStorageInvalidDeployment = errors.New("Invalid deployment")
325
        ErrStorageInvalidID                   = errors.New("Invalid id")
326
        ErrStorageNotFound                    = errors.New("Not found")
327
        ErrDeploymentStorageInvalidQuery      = errors.New("Invalid query")
328
        ErrDeploymentStorageCannotExecQuery   = errors.New("Cannot execute query")
329
        ErrStorageInvalidInput                = errors.New("invalid input")
330

331
        ErrLimitNotFound      = errors.New("limit not found")
332
        ErrDevicesCountFailed = errors.New("failed to count devices")
333
        ErrConflictingDepends = errors.New(
334
                "an artifact with the same name and depends already exists",
335
        )
336
        ErrConflictingDeployment = errors.New(
337
                "an active deployment with the same parameter already exists",
338
        )
339
)
340

341
// Database keys
342
const (
343
        // Need to be kept in sync with structure filed names
344
        StorageKeyId       = "_id"
345
        StorageKeyTenantId = "tenant_id"
346

347
        StorageKeyImageProvides    = "meta_artifact.provides"
348
        StorageKeyImageProvidesIdx = "meta_artifact.provides_idx"
349
        StorageKeyImageDepends     = "meta_artifact.depends"
350
        StorageKeyImageDependsIdx  = "meta_artifact.depends_idx"
351
        StorageKeyImageSize        = "size"
352
        StorageKeyImageDeviceTypes = "meta_artifact.device_types_compatible"
353
        StorageKeyImageName        = "meta_artifact.name"
354
        StorageKeyUpdateType       = "meta_artifact.updates.typeinfo.type"
355
        StorageKeyImageDescription = "meta.description"
356
        StorageKeyImageModified    = "modified"
357

358
        // releases
359
        StorageKeyReleaseName                      = "_id"
360
        StorageKeyReleaseModified                  = "modified"
361
        StorageKeyReleaseTags                      = "tags"
362
        StorageKeyReleaseNotes                     = "notes"
363
        StorageKeyReleaseArtifacts                 = "artifacts"
364
        StorageKeyReleaseArtifactsCount            = "artifacts_count"
365
        StorageKeyReleaseArtifactsIndexDescription = StorageKeyReleaseArtifacts + ".$." +
366
                StorageKeyImageDescription
367
        StorageKeyReleaseArtifactsDescription = StorageKeyReleaseArtifacts + "." +
368
                StorageKeyImageDescription
369
        StorageKeyReleaseArtifactsDeviceTypes = StorageKeyReleaseArtifacts + "." +
370
                StorageKeyImageDeviceTypes
371
        StorageKeyReleaseArtifactsUpdateTypes = StorageKeyReleaseArtifacts + "." +
372
                StorageKeyUpdateType
373
        StorageKeyReleaseArtifactsIndexModified = StorageKeyReleaseArtifacts + ".$." +
374
                StorageKeyImageModified
375
        StorageKeyReleaseArtifactsId = StorageKeyReleaseArtifacts + "." +
376
                StorageKeyId
377
        StorageKeyReleaseImageDependsIdx = StorageKeyReleaseArtifacts + "." +
378
                StorageKeyImageDependsIdx
379
        StorageKeyReleaseImageProvidesIdx = StorageKeyReleaseArtifacts + "." +
380
                StorageKeyImageProvidesIdx
381

382
        StorageKeyDeviceDeploymentLogMessages = "messages"
383

384
        StorageKeyDeviceDeploymentAssignedImage   = "image"
385
        StorageKeyDeviceDeploymentAssignedImageId = StorageKeyDeviceDeploymentAssignedImage +
386
                "." + StorageKeyId
387

388
        StorageKeyDeviceDeploymentActive         = "active"
389
        StorageKeyDeviceDeploymentCreated        = "created"
390
        StorageKeyDeviceDeploymentDeviceId       = "deviceid"
391
        StorageKeyDeviceDeploymentStatus         = "status"
392
        StorageKeyDeviceDeploymentStarted        = "started"
393
        StorageKeyDeviceDeploymentSubState       = "substate"
394
        StorageKeyDeviceDeploymentDeploymentID   = "deploymentid"
395
        StorageKeyDeviceDeploymentFinished       = "finished"
396
        StorageKeyDeviceDeploymentIsLogAvailable = "log"
397
        StorageKeyDeviceDeploymentArtifact       = "image"
398
        StorageKeyDeviceDeploymentRequest        = "request"
399
        StorageKeyDeviceDeploymentDeleted        = "deleted"
400

401
        StorageKeyDeploymentName                = "deploymentconstructor.name"
402
        StorageKeyDeploymentArtifactName        = "deploymentconstructor.artifactname"
403
        StorageKeyDeploymentConstructorChecksum = "deploymentconstructor_checksum"
404
        StorageKeyDeploymentStats               = "stats"
405
        StorageKeyDeploymentActive              = "active"
406
        StorageKeyDeploymentStatus              = "status"
407
        StorageKeyDeploymentCreated             = "created"
408
        StorageKeyDeploymentDeviceList          = "device_list"
409
        StorageKeyDeploymentStatsCreated        = "created"
410
        StorageKeyDeploymentFinished            = "finished"
411
        StorageKeyDeploymentArtifacts           = "artifacts"
412
        StorageKeyDeploymentDeviceCount         = "device_count"
413
        StorageKeyDeploymentMaxDevices          = "max_devices"
414
        StorageKeyDeploymentType                = "type"
415
        StorageKeyDeploymentTotalSize           = "statistics.total_size"
416

417
        StorageKeyStorageSettingsDefaultID      = "settings"
418
        StorageKeyStorageSettingsBucket         = "bucket"
419
        StorageKeyStorageSettingsRegion         = "region"
420
        StorageKeyStorageSettingsKey            = "key"
421
        StorageKeyStorageSettingsSecret         = "secret"
422
        StorageKeyStorageSettingsURI            = "uri"
423
        StorageKeyStorageSettingsExternalURI    = "external_uri"
424
        StorageKeyStorageSettingsToken          = "token"
425
        StorageKeyStorageSettingsForcePathStyle = "force_path_style"
426
        StorageKeyStorageSettingsUseAccelerate  = "use_accelerate"
427

428
        StorageKeyStorageReleaseUpdateTypes = "update_types"
429

430
        ArtifactDependsDeviceType = "device_type"
431
)
432

433
type DataStoreMongo struct {
434
        client *mongo.Client
435
}
436

437
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
1✔
438
        return &DataStoreMongo{
1✔
439
                client: client,
1✔
440
        }
1✔
441
}
1✔
442

UNCOV
443
func NewMongoClient(ctx context.Context, c config.Reader) (*mongo.Client, error) {
×
UNCOV
444

×
UNCOV
445
        clientOptions := mopts.Client()
×
UNCOV
446
        mongoURL := c.GetString(dconfig.SettingMongo)
×
UNCOV
447
        if !strings.Contains(mongoURL, "://") {
×
448
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
449
                        mongoURL)
×
450
        }
×
UNCOV
451
        clientOptions.ApplyURI(mongoURL)
×
UNCOV
452

×
UNCOV
453
        username := c.GetString(dconfig.SettingDbUsername)
×
UNCOV
454
        if username != "" {
×
455
                credentials := mopts.Credential{
×
456
                        Username: c.GetString(dconfig.SettingDbUsername),
×
457
                }
×
458
                password := c.GetString(dconfig.SettingDbPassword)
×
459
                if password != "" {
×
460
                        credentials.Password = password
×
461
                        credentials.PasswordSet = true
×
462
                }
×
463
                clientOptions.SetAuth(credentials)
×
464
        }
465

UNCOV
466
        if c.GetBool(dconfig.SettingDbSSL) {
×
467
                tlsConfig := &tls.Config{}
×
468
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
469
                clientOptions.SetTLSConfig(tlsConfig)
×
470
        }
×
471

472
        // Set 10s timeout
UNCOV
473
        ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
×
UNCOV
474
        defer cancel()
×
UNCOV
475
        client, err := mongo.Connect(ctx, clientOptions)
×
UNCOV
476
        if err != nil {
×
477
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
478
        }
×
479

480
        // Validate connection
UNCOV
481
        if err = client.Ping(ctx, nil); err != nil {
×
482
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
483
        }
×
484

UNCOV
485
        return client, nil
×
486
}
487

488
func (db *DataStoreMongo) Ping(ctx context.Context) error {
1✔
489
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
1✔
490
        return res.Err()
1✔
491
}
1✔
492

493
func (db *DataStoreMongo) setCurrentDbVersion(
494
        ctx context.Context,
495
) error {
×
496
        versions, err := migrate.GetMigrationInfo(
×
497
                ctx, db.client, mstore.DbFromContext(ctx, DatabaseName))
×
498
        if err != nil {
×
499
                return errors.Wrap(err, "failed to list applied migrations")
×
500
        }
×
501
        var current migrate.Version
×
502
        if len(versions) > 0 {
×
503
                // sort applied migrations wrt. version
×
504
                sort.Slice(versions, func(i int, j int) bool {
×
505
                        return migrate.VersionIsLess(versions[i].Version, versions[j].Version)
×
506
                })
×
507
                current = versions[len(versions)-1].Version
×
508
        }
509
        if currentDbVersion == nil {
×
510
                currentDbVersion = map[string]*migrate.Version{}
×
511
        }
×
512
        currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] = &current
×
513
        return nil
×
514
}
515

516
func (db *DataStoreMongo) getCurrentDbVersion(
517
        ctx context.Context,
518
) (*migrate.Version, error) {
×
519
        if currentDbVersion == nil ||
×
520
                currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)] == nil {
×
521
                if err := db.setCurrentDbVersion(ctx); err != nil {
×
522
                        return nil, err
×
523
                }
×
524
        }
525
        return currentDbVersion[mstore.DbFromContext(ctx, DatabaseName)], nil
×
526
}
527

528
func (db *DataStoreMongo) GetReleases(
529
        ctx context.Context,
530
        filt *model.ReleaseOrImageFilter,
531
) ([]model.Release, int, error) {
×
532
        current, err := db.getCurrentDbVersion(ctx)
×
533
        if err != nil {
×
534
                return []model.Release{}, 0, err
×
535
        } else if current == nil {
×
536
                return []model.Release{}, 0, errors.New("couldn't get current database version")
×
537
        }
×
538
        target, err := migrate.NewVersion(DbVersion)
×
539
        if err != nil {
×
540
                return []model.Release{}, 0, errors.Wrap(err, "failed to get latest DB version")
×
541
        }
×
542
        if migrate.VersionIsLess(*current, *target) {
×
543
                return db.getReleases_1_2_14(ctx, filt)
×
544
        } else {
×
545
                return db.getReleases_1_2_15(ctx, filt)
×
546
        }
×
547
}
548

549
func (db *DataStoreMongo) getReleases_1_2_14(
550
        ctx context.Context,
551
        filt *model.ReleaseOrImageFilter,
552
) ([]model.Release, int, error) {
1✔
553
        l := log.FromContext(ctx)
1✔
554
        l.Infof("get releases method version 1.2.14")
1✔
555
        var pipe []bson.D
1✔
556

1✔
557
        pipe = []bson.D{}
1✔
558
        if filt != nil && filt.Name != "" {
2✔
559
                pipe = append(pipe, bson.D{
1✔
560
                        {Key: "$match", Value: bson.M{
1✔
561
                                StorageKeyImageName: bson.M{
1✔
562
                                        "$regex": primitive.Regex{
1✔
563
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
1✔
564
                                                Options: "i",
1✔
565
                                        },
1✔
566
                                },
1✔
567
                        }},
1✔
568
                })
1✔
569
        }
1✔
570

571
        pipe = append(pipe, bson.D{
1✔
572
                // Remove (possibly expensive) sub-documents from pipeline
1✔
573
                {
1✔
574
                        Key: "$project",
1✔
575
                        Value: bson.M{
1✔
576
                                StorageKeyImageDependsIdx:  0,
1✔
577
                                StorageKeyImageProvidesIdx: 0,
1✔
578
                        },
1✔
579
                },
1✔
580
        })
1✔
581

1✔
582
        pipe = append(pipe, bson.D{
1✔
583
                {Key: "$group", Value: bson.D{
1✔
584
                        {Key: "_id", Value: "$" + StorageKeyImageName},
1✔
585
                        {Key: "name", Value: bson.M{"$first": "$" + StorageKeyImageName}},
1✔
586
                        {Key: "artifacts", Value: bson.M{"$push": "$$ROOT"}},
1✔
587
                        {Key: "modified", Value: bson.M{"$max": "$modified"}},
1✔
588
                }},
1✔
589
        })
1✔
590

1✔
591
        if filt != nil && filt.Description != "" {
2✔
592
                pipe = append(pipe, bson.D{
1✔
593
                        {Key: "$match", Value: bson.M{
1✔
594
                                "artifacts." + StorageKeyImageDescription: bson.M{
1✔
595
                                        "$regex": primitive.Regex{
1✔
596
                                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
1✔
597
                                                Options: "i",
1✔
598
                                        },
1✔
599
                                },
1✔
600
                        }},
1✔
601
                })
1✔
602
        }
1✔
603
        if filt != nil && filt.DeviceType != "" {
1✔
604
                pipe = append(pipe, bson.D{
×
605
                        {Key: "$match", Value: bson.M{
×
606
                                "artifacts." + StorageKeyImageDeviceTypes: bson.M{
×
607
                                        "$regex": primitive.Regex{
×
608
                                                Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
×
609
                                                Options: "i",
×
610
                                        },
×
611
                                },
×
612
                        }},
×
613
                })
×
614
        }
×
615

616
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
1✔
617
        if sortField == "" {
2✔
618
                sortField = "name"
1✔
619
        }
1✔
620
        if sortOrder == 0 {
2✔
621
                sortOrder = 1
1✔
622
        }
1✔
623

624
        page := 1
1✔
625
        perPage := math.MaxInt64
1✔
626
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
2✔
627
                page = filt.Page
1✔
628
                perPage = filt.PerPage
1✔
629
        }
1✔
630
        pipe = append(pipe,
1✔
631
                bson.D{{Key: "$facet", Value: bson.D{
1✔
632
                        {Key: "results", Value: []bson.D{
1✔
633
                                {
1✔
634
                                        {Key: "$sort", Value: bson.D{
1✔
635
                                                {Key: sortField, Value: sortOrder},
1✔
636
                                                {Key: "_id", Value: 1},
1✔
637
                                        }},
1✔
638
                                },
1✔
639
                                {{Key: "$skip", Value: int64((page - 1) * perPage)}},
1✔
640
                                {{Key: "$limit", Value: int64(perPage)}},
1✔
641
                        }},
1✔
642
                        {Key: "count", Value: []bson.D{
1✔
643
                                {{Key: "$count", Value: "count"}},
1✔
644
                        }},
1✔
645
                }}},
1✔
646
        )
1✔
647

1✔
648
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
649
        collImg := database.Collection(CollectionImages)
1✔
650

1✔
651
        cursor, err := collImg.Aggregate(ctx, pipe)
1✔
652
        if err != nil {
1✔
653
                return []model.Release{}, 0, err
×
654
        }
×
655
        defer cursor.Close(ctx)
1✔
656

1✔
657
        result := struct {
1✔
658
                Results []model.Release       `bson:"results"`
1✔
659
                Count   []struct{ Count int } `bson:"count"`
1✔
660
        }{}
1✔
661
        if !cursor.Next(ctx) {
1✔
662
                return []model.Release{}, 0, nil
×
663
        } else if err = cursor.Decode(&result); err != nil {
1✔
664
                return []model.Release{}, 0, err
×
665
        } else if len(result.Count) == 0 {
2✔
666
                return []model.Release{}, 0, err
1✔
667
        }
1✔
668
        return result.Results, result.Count[0].Count, nil
1✔
669
}
670

671
func (db *DataStoreMongo) getReleases_1_2_15(
672
        ctx context.Context,
673
        filt *model.ReleaseOrImageFilter,
674
) ([]model.Release, int, error) {
1✔
675
        l := log.FromContext(ctx)
1✔
676
        l.Infof("get releases method version 1.2.15")
1✔
677

1✔
678
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
1✔
679
        if sortField == "" {
2✔
680
                sortField = "_id"
1✔
681
        } else if sortField == "name" {
3✔
682
                sortField = StorageKeyReleaseName
1✔
683
        }
1✔
684
        if sortOrder == 0 {
2✔
685
                sortOrder = 1
1✔
686
        }
1✔
687

688
        page := 1
1✔
689
        perPage := DefaultDocumentLimit
1✔
690
        if filt != nil {
2✔
691
                if filt.Page > 0 {
2✔
692
                        page = filt.Page
1✔
693
                }
1✔
694
                if filt.PerPage > 0 {
2✔
695
                        perPage = filt.PerPage
1✔
696
                }
1✔
697
        }
698

699
        opts := &mopts.FindOptions{}
1✔
700
        opts.SetSort(bson.D{{Key: sortField, Value: sortOrder}})
1✔
701
        opts.SetSkip(int64((page - 1) * perPage))
1✔
702
        opts.SetLimit(int64(perPage))
1✔
703
        projection := bson.M{
1✔
704
                StorageKeyReleaseImageDependsIdx:  0,
1✔
705
                StorageKeyReleaseImageProvidesIdx: 0,
1✔
706
        }
1✔
707
        opts.SetProjection(projection)
1✔
708

1✔
709
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
710
        collReleases := database.Collection(CollectionReleases)
1✔
711

1✔
712
        filter := bson.M{}
1✔
713
        if filt != nil {
2✔
714
                if filt.Name != "" {
2✔
715
                        filter[StorageKeyReleaseName] = bson.M{"$regex": primitive.Regex{
1✔
716
                                Pattern: regexp.QuoteMeta(filt.Name) + ".*",
1✔
717
                                Options: "i",
1✔
718
                        }}
1✔
719
                }
1✔
720
                if len(filt.Tags) > 0 {
2✔
721
                        filter[StorageKeyReleaseTags] = bson.M{"$in": filt.Tags}
1✔
722
                }
1✔
723
                if filt.Description != "" {
2✔
724
                        filter[StorageKeyReleaseArtifactsDescription] = bson.M{"$regex": primitive.Regex{
1✔
725
                                Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
1✔
726
                                Options: "i",
1✔
727
                        }}
1✔
728
                }
1✔
729
                if filt.DeviceType != "" {
2✔
730
                        filter[StorageKeyReleaseArtifactsDeviceTypes] = filt.DeviceType
1✔
731
                }
1✔
732
                if filt.UpdateType != "" {
2✔
733
                        filter[StorageKeyReleaseArtifactsUpdateTypes] = filt.UpdateType
1✔
734
                }
1✔
735
        }
736
        releases := []model.Release{}
1✔
737
        cursor, err := collReleases.Find(ctx, filter, opts)
1✔
738
        if err != nil {
1✔
739
                return []model.Release{}, 0, err
×
740
        }
×
741
        if err := cursor.All(ctx, &releases); err != nil {
1✔
742
                return []model.Release{}, 0, err
×
743
        }
×
744

745
        // TODO: can we return number of all documents in the collection
746
        // using EstimatedDocumentCount?
747
        count, err := collReleases.CountDocuments(ctx, filter)
1✔
748
        if err != nil {
1✔
749
                return []model.Release{}, 0, err
×
750
        }
×
751

752
        if count < 1 {
2✔
753
                return []model.Release{}, int(count), nil
1✔
754
        }
1✔
755
        return releases, int(count), nil
1✔
756
}
757

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

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

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

773
        return limit, nil
1✔
774
}
775

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

1✔
778
        dbname := mstore.DbNameForTenant(tenantId, DbName)
1✔
779

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

783
//images
784

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

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

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

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

804
        return true, nil
×
805
}
806

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

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

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

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

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

831
        return true, nil
1✔
832
}
833

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

1✔
838
        if len(name) == 0 {
2✔
839
                return nil, ErrImagesStorageInvalidArtifactName
1✔
840
        }
1✔
841

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

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

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

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

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

870
        return &image, nil
1✔
871
}
872

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

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

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

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

×
890
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
891
        collImg := database.Collection(CollectionImages)
×
892

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

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

907
        return &image, nil
×
908
}
909

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

×
914
        var images []*model.Image
×
915

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

920
        // equal to artifact name
921
        query := bson.M{
×
922
                StorageKeyImageName: name,
×
923
        }
×
924

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

936
        return images, nil
×
937
}
938

939
func newDependsConflictError(mgoErr mongo.WriteError) *model.ConflictError {
1✔
940
        var err error
1✔
941
        conflictErr := model.NewConflictError(ErrConflictingDepends)
1✔
942
        // Try to lookup the document that caused the index violation:
1✔
943
        if raw, ok := mgoErr.Raw.Lookup("keyValue").DocumentOK(); ok {
2✔
944
                if raw, ok = raw.Lookup(StorageKeyImageDependsIdx).DocumentOK(); ok {
2✔
945
                        var conflicts map[string]interface{}
1✔
946
                        err = bson.Unmarshal([]byte(raw), &conflicts)
1✔
947
                        if err == nil {
2✔
948
                                _ = conflictErr.WithMetadata(
1✔
949
                                        map[string]interface{}{
1✔
950
                                                "conflict": conflicts,
1✔
951
                                        },
1✔
952
                                )
1✔
953
                        }
1✔
954
                }
955
        }
956
        return conflictErr
1✔
957
}
958

959
// Insert persists object
960
func (db *DataStoreMongo) InsertImage(ctx context.Context, image *model.Image) error {
1✔
961

1✔
962
        if image == nil {
1✔
963
                return ErrImagesStorageInvalidImage
×
964
        }
×
965

966
        if err := image.Validate(); err != nil {
1✔
967
                return err
×
968
        }
×
969

970
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
971
        collImg := database.Collection(CollectionImages)
1✔
972

1✔
973
        // add special representation of artifact provides
1✔
974
        image.ArtifactMeta.ProvidesIdx = model.ProvidesIdx(image.ArtifactMeta.Provides)
1✔
975

1✔
976
        _, err := collImg.InsertOne(ctx, image)
1✔
977
        if err != nil {
2✔
978
                var wExc mongo.WriteException
1✔
979
                if errors.As(err, &wExc) {
2✔
980
                        for _, wErr := range wExc.WriteErrors {
2✔
981
                                if !mongo.IsDuplicateKeyError(wErr) {
1✔
982
                                        continue
×
983
                                }
984
                                return newDependsConflictError(wErr)
1✔
985
                        }
986
                }
987
                return err
×
988
        }
989

990
        return nil
1✔
991
}
992

993
func (db *DataStoreMongo) InsertUploadIntent(ctx context.Context, link *model.UploadLink) error {
1✔
994
        collUploads := db.client.
1✔
995
                Database(DatabaseName).
1✔
996
                Collection(CollectionUploadIntents)
1✔
997
        if idty := identity.FromContext(ctx); idty != nil {
1✔
998
                link.TenantID = idty.Tenant
×
999
        }
×
1000
        _, err := collUploads.InsertOne(ctx, link)
1✔
1001
        return err
1✔
1002
}
1003

1004
func (db *DataStoreMongo) UpdateUploadIntentStatus(
1005
        ctx context.Context,
1006
        id string,
1007
        from, to model.LinkStatus,
1008
) error {
1✔
1009
        collUploads := db.client.
1✔
1010
                Database(DatabaseName).
1✔
1011
                Collection(CollectionUploadIntents)
1✔
1012
        q := bson.D{
1✔
1013
                {Key: "_id", Value: id},
1✔
1014
                {Key: "status", Value: from},
1✔
1015
        }
1✔
1016
        if idty := identity.FromContext(ctx); idty != nil {
2✔
1017
                q = append(q, bson.E{
1✔
1018
                        Key:   StorageKeyTenantId,
1✔
1019
                        Value: idty.Tenant,
1✔
1020
                })
1✔
1021
        }
1✔
1022
        update := bson.D{{
1✔
1023
                Key: "updated_ts", Value: time.Now(),
1✔
1024
        }}
1✔
1025
        if from != to {
2✔
1026
                update = append(update, bson.E{
1✔
1027
                        Key: "status", Value: to,
1✔
1028
                })
1✔
1029
        }
1✔
1030
        res, err := collUploads.UpdateOne(ctx, q, bson.D{
1✔
1031
                {Key: "$set", Value: update},
1✔
1032
        })
1✔
1033
        if err != nil {
2✔
1034
                return err
1✔
1035
        } else if res.MatchedCount == 0 {
3✔
1036
                return store.ErrNotFound
1✔
1037
        }
1✔
1038
        return nil
1✔
1039
}
1040

1041
func (db *DataStoreMongo) FindUploadLinks(
1042
        ctx context.Context,
1043
        expiredAt time.Time,
1044
) (store.Iterator[model.UploadLink], error) {
1✔
1045
        collUploads := db.client.
1✔
1046
                Database(DatabaseName).
1✔
1047
                Collection(CollectionUploadIntents)
1✔
1048

1✔
1049
        q := bson.D{{
1✔
1050
                Key: "status",
1✔
1051
                Value: bson.D{{
1✔
1052
                        Key:   "$lt",
1✔
1053
                        Value: model.LinkStatusProcessedBit,
1✔
1054
                }},
1✔
1055
        }, {
1✔
1056
                Key: "expire",
1✔
1057
                Value: bson.D{{
1✔
1058
                        Key:   "$lt",
1✔
1059
                        Value: expiredAt,
1✔
1060
                }},
1✔
1061
        }}
1✔
1062
        cur, err := collUploads.Find(ctx, q)
1✔
1063
        return IteratorFromCursor[model.UploadLink](cur), err
1✔
1064
}
1✔
1065

1066
// FindImageByID search storage for image with ID, returns nil if not found
1067
func (db *DataStoreMongo) FindImageByID(ctx context.Context,
1068
        id string) (*model.Image, error) {
×
1069

×
1070
        if len(id) == 0 {
×
1071
                return nil, ErrImagesStorageInvalidID
×
1072
        }
×
1073

1074
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
1075
        collImg := database.Collection(CollectionImages)
×
1076
        projection := bson.M{
×
1077
                StorageKeyImageDependsIdx:  0,
×
1078
                StorageKeyImageProvidesIdx: 0,
×
1079
        }
×
1080
        findOptions := mopts.FindOne()
×
1081
        findOptions.SetProjection(projection)
×
1082

×
1083
        var image model.Image
×
1084
        if err := collImg.FindOne(ctx, bson.M{"_id": id}, findOptions).
×
1085
                Decode(&image); err != nil {
×
1086
                if err == mongo.ErrNoDocuments {
×
1087
                        return nil, nil
×
1088
                }
×
1089
                return nil, err
×
1090
        }
1091

1092
        return &image, nil
×
1093
}
1094

1095
// IsArtifactUnique checks if there is no artifact with the same artifactName
1096
// supporting one of the device types from deviceTypesCompatible list.
1097
// Returns true, nil if artifact is unique;
1098
// false, nil if artifact is not unique;
1099
// false, error in case of error.
1100
func (db *DataStoreMongo) IsArtifactUnique(ctx context.Context,
1101
        artifactName string, deviceTypesCompatible []string) (bool, error) {
1✔
1102

1✔
1103
        if len(artifactName) == 0 {
2✔
1104
                return false, ErrImagesStorageInvalidArtifactName
1✔
1105
        }
1✔
1106

1107
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1108
        collImg := database.Collection(CollectionImages)
1✔
1109

1✔
1110
        query := bson.M{
1✔
1111
                "$and": []bson.M{
1✔
1112
                        {
1✔
1113
                                StorageKeyImageName: artifactName,
1✔
1114
                        },
1✔
1115
                        {
1✔
1116
                                StorageKeyImageDeviceTypes: bson.M{
1✔
1117
                                        "$in": deviceTypesCompatible},
1✔
1118
                        },
1✔
1119
                },
1✔
1120
        }
1✔
1121

1✔
1122
        // do part of the job manually
1✔
1123
        // if candidate images have any extra 'depends' - guaranteed non-overlap
1✔
1124
        // otherwise it's a match
1✔
1125
        cur, err := collImg.Find(ctx, query)
1✔
1126
        if err != nil {
1✔
1127
                return false, err
×
1128
        }
×
1129

1130
        var images []model.Image
1✔
1131
        err = cur.All(ctx, &images)
1✔
1132
        if err != nil {
1✔
1133
                return false, err
×
1134
        }
×
1135

1136
        for _, i := range images {
2✔
1137
                // the artifact already has same name and overlapping dev type
1✔
1138
                // if there are no more depends than dev type - it's not unique
1✔
1139
                if len(i.ArtifactMeta.Depends) == 1 {
2✔
1140
                        if _, ok := i.ArtifactMeta.Depends["device_type"]; ok {
2✔
1141
                                return false, nil
1✔
1142
                        }
1✔
1143
                } else if len(i.ArtifactMeta.Depends) == 0 {
×
1144
                        return false, nil
×
1145
                }
×
1146
        }
1147

1148
        return true, nil
1✔
1149
}
1150

1151
// Delete image specified by ID
1152
// Noop on if not found.
1153
func (db *DataStoreMongo) DeleteImage(ctx context.Context, id string) error {
1✔
1154

1✔
1155
        if len(id) == 0 {
1✔
1156
                return ErrImagesStorageInvalidID
×
1157
        }
×
1158

1159
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1160
        collImg := database.Collection(CollectionImages)
1✔
1161

1✔
1162
        if res, err := collImg.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
1163
                if res.DeletedCount == 0 {
×
1164
                        return nil
×
1165
                }
×
1166
                return err
×
1167
        }
1168

1169
        return nil
1✔
1170
}
1171

1172
func getReleaseSortFieldAndOrder(filt *model.ReleaseOrImageFilter) (string, int) {
1✔
1173
        if filt != nil && filt.Sort != "" {
2✔
1174
                sortParts := strings.SplitN(filt.Sort, ":", 2)
1✔
1175
                if len(sortParts) == 2 &&
1✔
1176
                        (sortParts[0] == "name" ||
1✔
1177
                                sortParts[0] == "modified" ||
1✔
1178
                                sortParts[0] == "artifacts_count" ||
1✔
1179
                                sortParts[0] == "tags") {
2✔
1180
                        sortField := sortParts[0]
1✔
1181
                        sortOrder := 1
1✔
1182
                        if sortParts[1] == model.SortDirectionDescending {
2✔
1183
                                sortOrder = -1
1✔
1184
                        }
1✔
1185
                        return sortField, sortOrder
1✔
1186
                }
1187
        }
1188
        return "", 0
1✔
1189
}
1190

1191
// ListImages lists all images
1192
func (db *DataStoreMongo) ListImages(
1193
        ctx context.Context,
1194
        filt *model.ReleaseOrImageFilter,
1195
) ([]*model.Image, int, error) {
1✔
1196

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

1✔
1200
        filters := bson.M{}
1✔
1201
        if filt != nil {
2✔
1202
                if filt.Name != "" {
2✔
1203
                        filters[StorageKeyImageName] = bson.M{
1✔
1204
                                "$regex": primitive.Regex{
1✔
1205
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Name) + ".*",
1✔
1206
                                        Options: "i",
1✔
1207
                                },
1✔
1208
                        }
1✔
1209
                }
1✔
1210
                if filt.Description != "" {
2✔
1211
                        filters[StorageKeyImageDescription] = bson.M{
1✔
1212
                                "$regex": primitive.Regex{
1✔
1213
                                        Pattern: ".*" + regexp.QuoteMeta(filt.Description) + ".*",
1✔
1214
                                        Options: "i",
1✔
1215
                                },
1✔
1216
                        }
1✔
1217
                }
1✔
1218
                if filt.DeviceType != "" {
2✔
1219
                        filters[StorageKeyImageDeviceTypes] = bson.M{
1✔
1220
                                "$regex": primitive.Regex{
1✔
1221
                                        Pattern: ".*" + regexp.QuoteMeta(filt.DeviceType) + ".*",
1✔
1222
                                        Options: "i",
1✔
1223
                                },
1✔
1224
                        }
1✔
1225
                }
1✔
1226

1227
        }
1228

1229
        projection := bson.M{
1✔
1230
                StorageKeyImageDependsIdx:  0,
1✔
1231
                StorageKeyImageProvidesIdx: 0,
1✔
1232
        }
1✔
1233
        findOptions := &mopts.FindOptions{}
1✔
1234
        findOptions.SetProjection(projection)
1✔
1235
        if filt != nil && filt.Page > 0 && filt.PerPage > 0 {
2✔
1236
                findOptions.SetSkip(int64((filt.Page - 1) * filt.PerPage))
1✔
1237
                findOptions.SetLimit(int64(filt.PerPage))
1✔
1238
        }
1✔
1239

1240
        sortField, sortOrder := getReleaseSortFieldAndOrder(filt)
1✔
1241
        if sortField == "" || sortField == "name" {
2✔
1242
                sortField = StorageKeyImageName
1✔
1243
        }
1✔
1244
        if sortOrder == 0 {
2✔
1245
                sortOrder = 1
1✔
1246
        }
1✔
1247
        findOptions.SetSort(bson.D{
1✔
1248
                {Key: sortField, Value: sortOrder},
1✔
1249
                {Key: "_id", Value: sortOrder},
1✔
1250
        })
1✔
1251

1✔
1252
        cursor, err := collImg.Find(ctx, filters, findOptions)
1✔
1253
        if err != nil {
1✔
1254
                return nil, 0, err
×
1255
        }
×
1256

1257
        // NOTE: cursor.All closes the cursor before returning
1258
        var images []*model.Image
1✔
1259
        if err := cursor.All(ctx, &images); err != nil {
1✔
1260
                if err == mongo.ErrNoDocuments {
×
1261
                        return nil, 0, nil
×
1262
                }
×
1263
                return nil, 0, err
×
1264
        }
1265

1266
        count, err := collImg.CountDocuments(ctx, filters)
1✔
1267
        if err != nil {
1✔
1268
                return nil, -1, ErrDevicesCountFailed
×
1269
        }
×
1270

1271
        return images, int(count), nil
1✔
1272
}
1273

1274
func (db *DataStoreMongo) DeleteImagesByNames(ctx context.Context, names []string) error {
1✔
1275
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1276
        collDevs := database.Collection(CollectionImages)
1✔
1277
        query := bson.M{
1✔
1278
                StorageKeyImageName: bson.M{
1✔
1279
                        "$in": names,
1✔
1280
                },
1✔
1281
        }
1✔
1282
        _, err := collDevs.DeleteMany(ctx, query)
1✔
1283
        return err
1✔
1284
}
1✔
1285

1286
// device deployment log
1287
func (db *DataStoreMongo) SaveDeviceDeploymentLog(ctx context.Context,
1288
        log model.DeploymentLog) error {
1✔
1289

1✔
1290
        if err := log.Validate(); err != nil {
2✔
1291
                return err
1✔
1292
        }
1✔
1293

1294
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1295
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
1✔
1296

1✔
1297
        query := bson.D{
1✔
1298
                {Key: StorageKeyDeviceDeploymentDeviceId,
1✔
1299
                        Value: log.DeviceID},
1✔
1300
                {Key: StorageKeyDeviceDeploymentDeploymentID,
1✔
1301
                        Value: log.DeploymentID},
1✔
1302
        }
1✔
1303

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

1318
        return nil
1✔
1319
}
1320

1321
func (db *DataStoreMongo) GetDeviceDeploymentLog(ctx context.Context,
1322
        deviceID, deploymentID string) (*model.DeploymentLog, error) {
1✔
1323

1✔
1324
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1325
        collLogs := database.Collection(CollectionDeviceDeploymentLogs)
1✔
1326

1✔
1327
        query := bson.M{
1✔
1328
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
1329
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
1330
        }
1✔
1331

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

1340
        return &depl, nil
1✔
1341
}
1342

1343
// device deployments
1344

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

1✔
1354
        if deviceDeployment.Status != model.DeviceDeploymentStatusPending {
2✔
1355
                startedTime := time.Now().UTC()
1✔
1356
                deviceDeployment.Started = &startedTime
1✔
1357
        }
1✔
1358

1359
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
1✔
1360
                return err
×
1361
        }
×
1362

1363
        if incrementDeviceCount {
2✔
1364
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
1✔
1365
                if err != nil {
1✔
1366
                        return err
×
1367
                }
×
1368
        }
1369

1370
        return nil
1✔
1371
}
1372

1373
// InsertMany stores multiple device deployment objects.
1374
// TODO: Handle error cleanup, multi insert is not atomic, loop into two-phase commits
1375
func (db *DataStoreMongo) InsertMany(ctx context.Context,
1376
        deployments ...*model.DeviceDeployment) error {
1✔
1377

1✔
1378
        if len(deployments) == 0 {
2✔
1379
                return nil
1✔
1380
        }
1✔
1381

1382
        deviceCountIncrements := make(map[string]int)
1✔
1383

1✔
1384
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
1✔
1385
        var list []interface{}
1✔
1386
        for _, deployment := range deployments {
2✔
1387

1✔
1388
                if deployment == nil {
2✔
1389
                        return ErrStorageInvalidDeviceDeployment
1✔
1390
                }
1✔
1391

1392
                if err := deployment.Validate(); err != nil {
2✔
1393
                        return errors.Wrap(err, "Validating device deployment")
1✔
1394
                }
1✔
1395

1396
                list = append(list, deployment)
1✔
1397
                if deployment.Status != model.DeviceDeploymentStatusPending {
2✔
1398
                        startedTime := time.Now().UTC()
1✔
1399
                        deployment.Started = &startedTime
1✔
1400
                }
1✔
1401
                deviceCountIncrements[deployment.DeploymentId]++
1✔
1402
        }
1403

1404
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1405
        collDevs := database.Collection(CollectionDevices)
1✔
1406

1✔
1407
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
1✔
1408
                return err
×
1409
        }
×
1410

1411
        for deploymentID := range deviceCountIncrements {
2✔
1412
                err := db.IncrementDeploymentDeviceCount(
1✔
1413
                        ctx,
1✔
1414
                        deploymentID,
1✔
1415
                        deviceCountIncrements[deploymentID],
1✔
1416
                )
1✔
1417
                if err != nil {
1✔
1418
                        return err
×
1419
                }
×
1420
        }
1421

1422
        return nil
1✔
1423
}
1424

1425
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1426
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1427
        ctx context.Context,
1428
        deviceID string,
1429
) (*model.DeviceDeployment, error) {
1✔
1430

1✔
1431
        // Verify ID formatting
1✔
1432
        if len(deviceID) == 0 {
2✔
1433
                return nil, ErrStorageInvalidID
1✔
1434
        }
1✔
1435

1436
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1437
        collDevs := database.Collection(CollectionDevices)
1✔
1438

1✔
1439
        // Device should know only about deployments that are not finished
1✔
1440
        query := bson.D{
1✔
1441
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
1✔
1442
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1443
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1444
                        {Key: "$exists", Value: false},
1✔
1445
                }},
1✔
1446
        }
1✔
1447

1✔
1448
        // Find the oldest one by sorting the creation timestamp
1✔
1449
        // in ascending order.
1✔
1450
        findOptions := mopts.FindOne()
1✔
1451
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
1✔
1452

1✔
1453
        // Select only the oldest one that have not been finished yet.
1✔
1454
        deployment := new(model.DeviceDeployment)
1✔
1455
        if err := collDevs.FindOne(ctx, query, findOptions).
1✔
1456
                Decode(deployment); err != nil {
2✔
1457
                if err == mongo.ErrNoDocuments {
2✔
1458
                        return nil, nil
1✔
1459
                }
1✔
1460
                return nil, err
1✔
1461
        }
1462

1463
        return deployment, nil
1✔
1464
}
1465

1466
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1467
// matching device id that has not finished yet.
1468
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1469
        ctx context.Context,
1470
        deviceID string,
1471
) (*model.DeviceDeployment, error) {
1✔
1472

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

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

1✔
1481
        query := bson.D{
1✔
1482
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
1✔
1483
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1484
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1485
                        {Key: "$exists", Value: false},
1✔
1486
                }},
1✔
1487
        }
1✔
1488

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

1✔
1494
        // Select only the latest one that have not been finished yet.
1✔
1495
        var deployment *model.DeviceDeployment
1✔
1496
        if err := collDevs.FindOne(ctx, query, findOptions).
1✔
1497
                Decode(&deployment); err != nil {
2✔
1498
                if err == mongo.ErrNoDocuments {
2✔
1499
                        return nil, nil
1✔
1500
                }
1✔
1501
                return nil, err
1✔
1502
        }
1503

1504
        return deployment, nil
1✔
1505
}
1506

1507
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1508
        ctx context.Context,
1509
        deviceID string,
1510
        deploymentID string,
1511
        ddState model.DeviceDeploymentState,
1512
        currentStatus model.DeviceDeploymentStatus,
1513
) (model.DeviceDeploymentStatus, error) {
1✔
1514

1✔
1515
        // Verify ID formatting
1✔
1516
        if len(deviceID) == 0 ||
1✔
1517
                len(deploymentID) == 0 {
2✔
1518
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
1✔
1519
        }
1✔
1520

1521
        if err := ddState.Validate(); err != nil {
2✔
1522
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1523
        }
1✔
1524

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

1✔
1528
        // Device should know only about deployments that are not finished
1✔
1529
        query := bson.D{
1✔
1530
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1531
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1532
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1533
                        {Key: "$exists", Value: false},
1✔
1534
                }},
1✔
1535
        }
1✔
1536

1✔
1537
        // update status field
1✔
1538
        set := bson.M{
1✔
1539
                StorageKeyDeviceDeploymentStatus: ddState.Status,
1✔
1540
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
1✔
1541
        }
1✔
1542
        // and finish time if provided
1✔
1543
        if ddState.FinishTime != nil {
2✔
1544
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
1✔
1545
        }
1✔
1546

1547
        if len(ddState.SubState) > 0 {
2✔
1548
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
1✔
1549
        }
1✔
1550

1551
        if currentStatus == model.DeviceDeploymentStatusPending &&
1✔
1552
                ddState.Status != currentStatus {
2✔
1553
                startedTime := time.Now().UTC()
1✔
1554
                set[StorageKeyDeviceDeploymentStarted] = startedTime
1✔
1555
        }
1✔
1556

1557
        update := bson.D{
1✔
1558
                {Key: "$set", Value: set},
1✔
1559
        }
1✔
1560

1✔
1561
        var old model.DeviceDeployment
1✔
1562

1✔
1563
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
1✔
1564
                Decode(&old); err != nil {
2✔
1565
                if err == mongo.ErrNoDocuments {
2✔
1566
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
1✔
1567
                }
1✔
1568
                return model.DeviceDeploymentStatusNull, err
×
1569

1570
        }
1571

1572
        return old.Status, nil
1✔
1573
}
1574

1575
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1576
        deviceID string, deploymentID string, log bool) error {
1✔
1577

1✔
1578
        // Verify ID formatting
1✔
1579
        if len(deviceID) == 0 ||
1✔
1580
                len(deploymentID) == 0 {
2✔
1581
                return ErrStorageInvalidID
1✔
1582
        }
1✔
1583

1584
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1585
        collDevs := database.Collection(CollectionDevices)
1✔
1586

1✔
1587
        selector := bson.D{
1✔
1588
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1589
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1590
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1591
                        {Key: "$exists", Value: false},
1✔
1592
                }},
1✔
1593
        }
1✔
1594

1✔
1595
        update := bson.D{
1✔
1596
                {Key: "$set", Value: bson.M{
1✔
1597
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
1✔
1598
        }
1✔
1599

1✔
1600
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1601
                return err
×
1602
        } else if res.MatchedCount == 0 {
2✔
1603
                return ErrStorageNotFound
1✔
1604
        }
1✔
1605

1606
        return nil
1✔
1607
}
1608

1609
// SaveDeviceDeploymentRequest saves device deployment request
1610
// with the device deployment object
1611
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1612
        ctx context.Context,
1613
        ID string,
1614
        request *model.DeploymentNextRequest,
1615
) error {
1✔
1616

1✔
1617
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1618
        collDevs := database.Collection(CollectionDevices)
1✔
1619

1✔
1620
        res, err := collDevs.UpdateOne(
1✔
1621
                ctx,
1✔
1622
                bson.D{{Key: StorageKeyId, Value: ID}},
1✔
1623
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
1✔
1624
        )
1✔
1625
        if err != nil {
1✔
1626
                return err
×
1627
        } else if res.MatchedCount == 0 {
2✔
1628
                return ErrStorageNotFound
1✔
1629
        }
1✔
1630
        return nil
1✔
1631
}
1632

1633
// AssignArtifact assigns artifact to the device deployment
1634
func (db *DataStoreMongo) AssignArtifact(
1635
        ctx context.Context,
1636
        deviceID string,
1637
        deploymentID string,
1638
        artifact *model.Image,
1639
) error {
×
1640

×
1641
        // Verify ID formatting
×
1642
        if len(deviceID) == 0 ||
×
1643
                len(deploymentID) == 0 {
×
1644
                return ErrStorageInvalidID
×
1645
        }
×
1646

1647
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
1648
        collDevs := database.Collection(CollectionDevices)
×
1649

×
1650
        selector := bson.D{
×
1651
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
×
1652
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
×
1653
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
×
1654
                        {Key: "$exists", Value: false},
×
1655
                }},
×
1656
        }
×
1657

×
1658
        update := bson.D{
×
1659
                {Key: "$set", Value: bson.M{
×
1660
                        StorageKeyDeviceDeploymentArtifact: artifact,
×
1661
                }},
×
1662
        }
×
1663

×
1664
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
×
1665
                return err
×
1666
        } else if res.MatchedCount == 0 {
×
1667
                return ErrStorageNotFound
×
1668
        }
×
1669

1670
        return nil
×
1671
}
1672

1673
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1674
        id string) (model.Stats, error) {
1✔
1675

1✔
1676
        if len(id) == 0 {
1✔
1677
                return nil, ErrStorageInvalidID
×
1678
        }
×
1679

1680
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1681
        collDevs := database.Collection(CollectionDevices)
1✔
1682

1✔
1683
        match := bson.D{
1✔
1684
                {Key: "$match", Value: bson.M{
1✔
1685
                        StorageKeyDeviceDeploymentDeploymentID: id,
1✔
1686
                        StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
1687
                                {Key: "$exists", Value: false},
1✔
1688
                        },
1✔
1689
                }},
1✔
1690
        }
1✔
1691
        group := bson.D{
1✔
1692
                {Key: "$group", Value: bson.D{
1✔
1693
                        {Key: "_id",
1✔
1694
                                Value: "$" + StorageKeyDeviceDeploymentStatus},
1✔
1695
                        {Key: "count",
1✔
1696
                                Value: bson.M{"$sum": 1}}},
1✔
1697
                },
1✔
1698
        }
1✔
1699
        pipeline := []bson.D{
1✔
1700
                match,
1✔
1701
                group,
1✔
1702
        }
1✔
1703
        var results []struct {
1✔
1704
                Status model.DeviceDeploymentStatus `bson:"_id"`
1✔
1705
                Count  int
1✔
1706
        }
1✔
1707
        cursor, err := collDevs.Aggregate(ctx, pipeline)
1✔
1708
        if err != nil {
1✔
1709
                return nil, err
×
1710
        }
×
1711
        if err := cursor.All(ctx, &results); err != nil {
1✔
1712
                if err == mongo.ErrNoDocuments {
×
1713
                        return nil, nil
×
1714
                }
×
1715
                return nil, err
×
1716
        }
1717

1718
        raw := model.NewDeviceDeploymentStats()
1✔
1719
        for _, res := range results {
2✔
1720
                raw.Set(res.Status, res.Count)
1✔
1721
        }
1✔
1722
        return raw, nil
1✔
1723
}
1724

1725
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1726
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1727
        deploymentID string) ([]model.DeviceDeployment, error) {
1✔
1728

1✔
1729
        statuses := []model.DeviceDeployment{}
1✔
1730
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1731
        collDevs := database.Collection(CollectionDevices)
1✔
1732

1✔
1733
        query := bson.M{
1✔
1734
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
1735
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
1736
                        {Key: "$exists", Value: false},
1✔
1737
                },
1✔
1738
        }
1✔
1739

1✔
1740
        cursor, err := collDevs.Find(ctx, query)
1✔
1741
        if err != nil {
1✔
1742
                return nil, err
×
1743
        }
×
1744

1745
        if err = cursor.All(ctx, &statuses); err != nil {
1✔
1746
                if err == mongo.ErrNoDocuments {
×
1747
                        return nil, nil
×
1748
                }
×
1749
                return nil, err
×
1750
        }
1751

1752
        return statuses, nil
1✔
1753
}
1754

1755
func (db *DataStoreMongo) GetDevicesListForDeployment(ctx context.Context,
1756
        q store.ListQuery) ([]model.DeviceDeployment, int, error) {
1✔
1757

1✔
1758
        statuses := []model.DeviceDeployment{}
1✔
1759
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1760
        collDevs := database.Collection(CollectionDevices)
1✔
1761

1✔
1762
        query := bson.D{
1✔
1763
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: q.DeploymentID},
1✔
1764
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1765
                        {Key: "$exists", Value: false},
1✔
1766
                }},
1✔
1767
        }
1✔
1768
        if q.Status != nil {
2✔
1769
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
2✔
1770
                        query = append(query, bson.E{
1✔
1771
                                Key: "status", Value: bson.D{{
1✔
1772
                                        Key:   "$gte",
1✔
1773
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1774
                                }, {
1✔
1775
                                        Key:   "$lte",
1✔
1776
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1777
                                }},
1✔
1778
                        })
1✔
1779
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
2✔
1780
                        query = append(query, bson.E{
×
1781
                                Key: "status", Value: bson.D{{
×
1782
                                        Key:   "$gte",
×
1783
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
×
1784
                                }, {
×
1785
                                        Key:   "$lte",
×
1786
                                        Value: model.DeviceDeploymentStatusPending,
×
1787
                                }},
×
1788
                        })
×
1789
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
2✔
1790
                        query = append(query, bson.E{
1✔
1791
                                Key: "status", Value: bson.D{{
1✔
1792
                                        Key: "$in",
1✔
1793
                                        Value: []model.DeviceDeploymentStatus{
1✔
1794
                                                model.DeviceDeploymentStatusFailure,
1✔
1795
                                                model.DeviceDeploymentStatusAborted,
1✔
1796
                                                model.DeviceDeploymentStatusSuccess,
1✔
1797
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1798
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1799
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1800
                                        },
1✔
1801
                                }},
1✔
1802
                        })
1✔
1803
                } else {
2✔
1804
                        var status model.DeviceDeploymentStatus
1✔
1805
                        err := status.UnmarshalText([]byte(*q.Status))
1✔
1806
                        if err != nil {
2✔
1807
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1808
                        }
1✔
1809
                        query = append(query, bson.E{
1✔
1810
                                Key: "status", Value: status,
1✔
1811
                        })
1✔
1812
                }
1813
        }
1814

1815
        options := mopts.Find()
1✔
1816
        sortFieldQuery := bson.D{
1✔
1817
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
1✔
1818
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
1✔
1819
        }
1✔
1820
        options.SetSort(sortFieldQuery)
1✔
1821
        if q.Skip > 0 {
2✔
1822
                options.SetSkip(int64(q.Skip))
1✔
1823
        }
1✔
1824
        if q.Limit > 0 {
2✔
1825
                options.SetLimit(int64(q.Limit))
1✔
1826
        } else {
2✔
1827
                options.SetLimit(DefaultDocumentLimit)
1✔
1828
        }
1✔
1829

1830
        cursor, err := collDevs.Find(ctx, query, options)
1✔
1831
        if err != nil {
2✔
1832
                return nil, -1, err
1✔
1833
        }
1✔
1834

1835
        if err = cursor.All(ctx, &statuses); err != nil {
1✔
1836
                if err == mongo.ErrNoDocuments {
×
1837
                        return nil, -1, nil
×
1838
                }
×
1839
                return nil, -1, err
×
1840
        }
1841

1842
        count, err := collDevs.CountDocuments(ctx, query)
1✔
1843
        if err != nil {
1✔
1844
                return nil, -1, ErrDevicesCountFailed
×
1845
        }
×
1846

1847
        return statuses, int(count), nil
1✔
1848
}
1849

1850
func (db *DataStoreMongo) GetDeviceDeploymentsForDevice(ctx context.Context,
1851
        q store.ListQueryDeviceDeployments) ([]model.DeviceDeployment, int, error) {
1✔
1852

1✔
1853
        statuses := []model.DeviceDeployment{}
1✔
1854
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1855
        collDevs := database.Collection(CollectionDevices)
1✔
1856

1✔
1857
        query := bson.D{}
1✔
1858
        if q.DeviceID != "" {
2✔
1859
                query = append(query, bson.E{
1✔
1860
                        Key:   StorageKeyDeviceDeploymentDeviceId,
1✔
1861
                        Value: q.DeviceID,
1✔
1862
                })
1✔
1863
        } else if len(q.IDs) > 0 {
3✔
1864
                query = append(query, bson.E{
1✔
1865
                        Key: StorageKeyId,
1✔
1866
                        Value: bson.D{{
1✔
1867
                                Key:   "$in",
1✔
1868
                                Value: q.IDs,
1✔
1869
                        }},
1✔
1870
                })
1✔
1871
        }
1✔
1872

1873
        if q.Status != nil {
2✔
1874
                if *q.Status == model.DeviceDeploymentStatusPauseStr {
2✔
1875
                        query = append(query, bson.E{
1✔
1876
                                Key: "status", Value: bson.D{{
1✔
1877
                                        Key:   "$gte",
1✔
1878
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1879
                                }, {
1✔
1880
                                        Key:   "$lte",
1✔
1881
                                        Value: model.DeviceDeploymentStatusPauseBeforeReboot,
1✔
1882
                                }},
1✔
1883
                        })
1✔
1884
                } else if *q.Status == model.DeviceDeploymentStatusActiveStr {
3✔
1885
                        query = append(query, bson.E{
1✔
1886
                                Key: "status", Value: bson.D{{
1✔
1887
                                        Key:   "$gte",
1✔
1888
                                        Value: model.DeviceDeploymentStatusPauseBeforeInstall,
1✔
1889
                                }, {
1✔
1890
                                        Key:   "$lte",
1✔
1891
                                        Value: model.DeviceDeploymentStatusPending,
1✔
1892
                                }},
1✔
1893
                        })
1✔
1894
                } else if *q.Status == model.DeviceDeploymentStatusFinishedStr {
3✔
1895
                        query = append(query, bson.E{
1✔
1896
                                Key: "status", Value: bson.D{{
1✔
1897
                                        Key: "$in",
1✔
1898
                                        Value: []model.DeviceDeploymentStatus{
1✔
1899
                                                model.DeviceDeploymentStatusFailure,
1✔
1900
                                                model.DeviceDeploymentStatusAborted,
1✔
1901
                                                model.DeviceDeploymentStatusSuccess,
1✔
1902
                                                model.DeviceDeploymentStatusNoArtifact,
1✔
1903
                                                model.DeviceDeploymentStatusAlreadyInst,
1✔
1904
                                                model.DeviceDeploymentStatusDecommissioned,
1✔
1905
                                        },
1✔
1906
                                }},
1✔
1907
                        })
1✔
1908
                } else {
2✔
1909
                        var status model.DeviceDeploymentStatus
1✔
1910
                        err := status.UnmarshalText([]byte(*q.Status))
1✔
1911
                        if err != nil {
2✔
1912
                                return nil, -1, errors.Wrap(err, "invalid status query")
1✔
1913
                        }
1✔
1914
                        query = append(query, bson.E{
1✔
1915
                                Key: "status", Value: status,
1✔
1916
                        })
1✔
1917
                }
1918
        }
1919

1920
        options := mopts.Find()
1✔
1921
        sortFieldQuery := bson.D{
1✔
1922
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
1✔
1923
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
1✔
1924
        }
1✔
1925
        options.SetSort(sortFieldQuery)
1✔
1926
        if q.Skip > 0 {
2✔
1927
                options.SetSkip(int64(q.Skip))
1✔
1928
        }
1✔
1929
        if q.Limit > 0 {
2✔
1930
                options.SetLimit(int64(q.Limit))
1✔
1931
        } else {
1✔
1932
                options.SetLimit(DefaultDocumentLimit)
×
1933
        }
×
1934

1935
        cursor, err := collDevs.Find(ctx, query, options)
1✔
1936
        if err != nil {
1✔
1937
                return nil, -1, err
×
1938
        }
×
1939

1940
        if err = cursor.All(ctx, &statuses); err != nil {
1✔
1941
                if err == mongo.ErrNoDocuments {
×
1942
                        return nil, 0, nil
×
1943
                }
×
1944
                return nil, -1, err
×
1945
        }
1946

1947
        maxCount := maxCountDocuments
1✔
1948
        countOptions := &mopts.CountOptions{
1✔
1949
                Limit: &maxCount,
1✔
1950
        }
1✔
1951
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
1✔
1952
        if err != nil {
1✔
1953
                return nil, -1, ErrDevicesCountFailed
×
1954
        }
×
1955

1956
        return statuses, int(count), nil
1✔
1957
}
1958

1959
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1960
// `deviceID`, false otherwise. In case of errors returns false and an error
1961
// that occurred
1962
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1963
        deploymentID string, deviceID string) (bool, error) {
1✔
1964

1✔
1965
        var dep model.DeviceDeployment
1✔
1966
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1967
        collDevs := database.Collection(CollectionDevices)
1✔
1968

1✔
1969
        query := bson.D{
1✔
1970
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1971
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1972
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1973
                        {Key: "$exists", Value: false},
1✔
1974
                }},
1✔
1975
        }
1✔
1976

1✔
1977
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
2✔
1978
                if err == mongo.ErrNoDocuments {
2✔
1979
                        return false, nil
1✔
1980
                } else {
1✔
1981
                        return false, err
×
1982
                }
×
1983
        }
1984

1985
        return true, nil
1✔
1986
}
1987

1988
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
1989
        deploymentId string) error {
1✔
1990

1✔
1991
        if len(deploymentId) == 0 {
2✔
1992
                return ErrStorageInvalidID
1✔
1993
        }
1✔
1994

1995
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1996
        collDevs := database.Collection(CollectionDevices)
1✔
1997
        selector := bson.M{
1✔
1998
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
1✔
1999
                StorageKeyDeviceDeploymentActive:       true,
1✔
2000
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2001
                        {Key: "$exists", Value: false},
1✔
2002
                },
1✔
2003
        }
1✔
2004

1✔
2005
        update := bson.M{
1✔
2006
                "$set": bson.M{
1✔
2007
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
1✔
2008
                        StorageKeyDeviceDeploymentActive: false,
1✔
2009
                },
1✔
2010
        }
1✔
2011

1✔
2012
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2013
                return err
×
2014
        }
×
2015

2016
        return nil
1✔
2017
}
2018

2019
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2020
        deviceID string) error {
1✔
2021
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2022
        collDevs := database.Collection(CollectionDevices)
1✔
2023
        selector := bson.M{
1✔
2024
                StorageKeyDeviceDeploymentDeviceId: deviceID,
1✔
2025
                StorageKeyDeviceDeploymentActive:   false,
1✔
2026
                StorageKeyDeviceDeploymentDeleted: bson.M{
1✔
2027
                        "$exists": false,
1✔
2028
                },
1✔
2029
        }
1✔
2030

1✔
2031
        now := time.Now()
1✔
2032
        update := bson.M{
1✔
2033
                "$set": bson.M{
1✔
2034
                        StorageKeyDeviceDeploymentDeleted: &now,
1✔
2035
                },
1✔
2036
        }
1✔
2037

1✔
2038
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2039
                return err
×
2040
        }
×
2041

2042
        database = db.client.Database(DatabaseName)
1✔
2043
        collDevs = database.Collection(CollectionDevicesLastStatus)
1✔
2044
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
1✔
2045

1✔
2046
        return err
1✔
2047
}
2048

2049
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2050
        deviceId string) error {
1✔
2051

1✔
2052
        if len(deviceId) == 0 {
2✔
2053
                return ErrStorageInvalidID
1✔
2054
        }
1✔
2055

2056
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2057
        collDevs := database.Collection(CollectionDevices)
1✔
2058
        selector := bson.M{
1✔
2059
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2060
                StorageKeyDeviceDeploymentActive:   true,
1✔
2061
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2062
                        {Key: "$exists", Value: false},
1✔
2063
                },
1✔
2064
        }
1✔
2065

1✔
2066
        update := bson.M{
1✔
2067
                "$set": bson.M{
1✔
2068
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2069
                        StorageKeyDeviceDeploymentActive: false,
1✔
2070
                },
1✔
2071
        }
1✔
2072

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

2077
        return nil
1✔
2078
}
2079

2080
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2081
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
×
2082

×
2083
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2084
        collDevs := database.Collection(CollectionDevices)
×
2085

×
2086
        filter := bson.M{
×
2087
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
×
2088
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
×
2089
        }
×
2090
        if !includeDeleted {
×
2091
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
×
2092
                        {Key: "$exists", Value: false},
×
2093
                }
×
2094
        }
×
2095

2096
        opts := &mopts.FindOneOptions{}
×
2097
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
×
2098

×
2099
        var dd model.DeviceDeployment
×
2100
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
×
2101
                if err == mongo.ErrNoDocuments {
×
2102
                        return nil, ErrStorageNotFound
×
2103
                }
×
2104
                return nil, err
×
2105
        }
2106

2107
        return &dd, nil
×
2108
}
2109

2110
func (db *DataStoreMongo) GetDeviceDeployments(
2111
        ctx context.Context,
2112
        skip int,
2113
        limit int,
2114
        deviceID string,
2115
        active *bool,
2116
        includeDeleted bool,
2117
) ([]model.DeviceDeployment, error) {
1✔
2118

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

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

2135
        opts := &mopts.FindOptions{}
1✔
2136
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2137
        if skip > 0 {
2✔
2138
                opts.SetSkip(int64(skip))
1✔
2139
        }
1✔
2140
        if limit > 0 {
2✔
2141
                opts.SetLimit(int64(limit))
1✔
2142
        }
1✔
2143

2144
        var deviceDeployments []model.DeviceDeployment
1✔
2145
        cursor, err := collDevs.Find(ctx, filter, opts)
1✔
2146
        if err != nil {
1✔
2147
                return nil, err
×
2148
        }
×
2149
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
1✔
2150
                return nil, err
×
2151
        }
×
2152

2153
        return deviceDeployments, nil
1✔
2154
}
2155

2156
// deployments
2157

2158
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2159
        indexes ...mongo.IndexModel) error {
1✔
2160
        ctx := context.Background()
1✔
2161
        dataBase := db.client.Database(dbName)
1✔
2162

1✔
2163
        coll := dataBase.Collection(collName)
1✔
2164
        idxView := coll.Indexes()
1✔
2165
        _, err := idxView.CreateMany(ctx, indexes)
1✔
2166
        return err
1✔
2167
}
1✔
2168

2169
// return true if required indexing was set up
2170
func (db *DataStoreMongo) hasIndexing(ctx context.Context, client *mongo.Client) bool {
1✔
2171

1✔
2172
        var idx bson.M
1✔
2173
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2174
        collDpl := database.Collection(CollectionDeployments)
1✔
2175
        idxView := collDpl.Indexes()
1✔
2176

1✔
2177
        cursor, err := idxView.List(ctx)
1✔
2178
        if err != nil {
1✔
2179
                // check failed, assume indexing is not there
×
2180
                return false
×
2181
        }
×
2182

2183
        has := map[string]bool{}
1✔
2184
        for cursor.Next(ctx) {
2✔
2185
                if err = cursor.Decode(&idx); err != nil {
1✔
2186
                        continue
×
2187
                }
2188
                if _, ok := idx["weights"]; ok {
2✔
2189
                        // text index
1✔
2190
                        for k := range idx["weights"].(bson.M) {
2✔
2191
                                has[k] = true
1✔
2192
                        }
1✔
2193
                } else {
1✔
2194
                        for i := range idx["key"].(bson.M) {
2✔
2195
                                has[i] = true
1✔
2196
                        }
1✔
2197

2198
                }
2199
        }
2200
        if err != nil {
1✔
2201
                return false
×
2202
        }
×
2203

2204
        for _, key := range StorageIndexes.Keys.(bson.D) {
2✔
2205
                _, ok := has[key.Key]
1✔
2206
                if !ok {
2✔
2207
                        return false
1✔
2208
                }
1✔
2209
        }
2210

2211
        return true
1✔
2212
}
2213

2214
// Insert persists object
2215
func (db *DataStoreMongo) InsertDeployment(
2216
        ctx context.Context,
2217
        deployment *model.Deployment,
2218
) error {
1✔
2219

1✔
2220
        if deployment == nil {
2✔
2221
                return ErrDeploymentStorageInvalidDeployment
1✔
2222
        }
1✔
2223

2224
        if err := deployment.Validate(); err != nil {
2✔
2225
                return err
1✔
2226
        }
1✔
2227

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

1✔
2231
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
2✔
2232
                if mongo.IsDuplicateKeyError(err) {
2✔
2233
                        return ErrConflictingDeployment
1✔
2234
                }
1✔
2235
                return err
×
2236
        }
2237
        return nil
1✔
2238
}
2239

2240
// Delete removed entry by ID
2241
// Noop on ID not found
2242
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
1✔
2243

1✔
2244
        if len(id) == 0 {
2✔
2245
                return ErrStorageInvalidID
1✔
2246
        }
1✔
2247

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

1✔
2251
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
1✔
2252
                return err
×
2253
        }
×
2254

2255
        return nil
1✔
2256
}
2257

2258
func (db *DataStoreMongo) FindDeploymentByID(
2259
        ctx context.Context,
2260
        id string,
2261
) (*model.Deployment, error) {
1✔
2262

1✔
2263
        if len(id) == 0 {
2✔
2264
                return nil, ErrStorageInvalidID
1✔
2265
        }
1✔
2266

2267
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2268
        collDpl := database.Collection(CollectionDeployments)
1✔
2269

1✔
2270
        deployment := new(model.Deployment)
1✔
2271
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
1✔
2272
                Decode(deployment); err != nil {
2✔
2273
                if err == mongo.ErrNoDocuments {
2✔
2274
                        return nil, nil
1✔
2275
                }
1✔
2276
                return nil, err
×
2277
        }
2278

2279
        return deployment, nil
1✔
2280
}
2281

2282
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2283
        ctx context.Context,
2284
        ids ...string,
2285
) (deploymentStats []*model.DeploymentStats, err error) {
1✔
2286

1✔
2287
        if len(ids) == 0 {
1✔
2288
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2289
        }
×
2290

2291
        for _, id := range ids {
2✔
2292
                if len(id) == 0 {
1✔
2293
                        return nil, ErrStorageInvalidID
×
2294
                }
×
2295
        }
2296

2297
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2298
        collDpl := database.Collection(CollectionDeployments)
1✔
2299

1✔
2300
        query := bson.M{
1✔
2301
                "_id": bson.M{
1✔
2302
                        "$in": ids,
1✔
2303
                },
1✔
2304
        }
1✔
2305
        statsProjection := &mopts.FindOptions{
1✔
2306
                Projection: bson.M{"stats": 1},
1✔
2307
        }
1✔
2308

1✔
2309
        results, err := collDpl.Find(
1✔
2310
                ctx,
1✔
2311
                query,
1✔
2312
                statsProjection,
1✔
2313
        )
1✔
2314
        if err != nil {
1✔
2315
                return nil, err
×
2316
        }
×
2317

2318
        for results.Next(context.Background()) {
2✔
2319
                depl := new(model.DeploymentStats)
1✔
2320
                if err = results.Decode(&depl); err != nil {
1✔
2321
                        if err == mongo.ErrNoDocuments {
×
2322
                                return nil, nil
×
2323
                        }
×
2324
                        return nil, err
×
2325
                }
2326
                deploymentStats = append(deploymentStats, depl)
1✔
2327
        }
2328

2329
        return deploymentStats, nil
1✔
2330
}
2331

2332
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2333
        id string) (*model.Deployment, error) {
1✔
2334

1✔
2335
        if len(id) == 0 {
2✔
2336
                return nil, ErrStorageInvalidID
1✔
2337
        }
1✔
2338

2339
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2340
        collDpl := database.Collection(CollectionDeployments)
1✔
2341

1✔
2342
        var deployment *model.Deployment
1✔
2343
        filter := bson.D{
1✔
2344
                {Key: "_id", Value: id},
1✔
2345
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2346
        }
1✔
2347
        if err := collDpl.FindOne(ctx, filter).
1✔
2348
                Decode(&deployment); err != nil {
2✔
2349
                if err == mongo.ErrNoDocuments {
2✔
2350
                        return nil, nil
1✔
2351
                }
1✔
2352
                return nil, err
×
2353
        }
2354

2355
        return deployment, nil
1✔
2356
}
2357

2358
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2359
        ctx context.Context,
2360
        deploymentID string,
2361
        increment int,
2362
) error {
1✔
2363
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2364
        collection := database.Collection(CollectionDeployments)
1✔
2365

1✔
2366
        filter := bson.M{
1✔
2367
                "_id": deploymentID,
1✔
2368
                StorageKeyDeploymentDeviceCount: bson.M{
1✔
2369
                        "$ne": nil,
1✔
2370
                },
1✔
2371
        }
1✔
2372

1✔
2373
        update := bson.M{
1✔
2374
                "$inc": bson.M{
1✔
2375
                        StorageKeyDeploymentDeviceCount: increment,
1✔
2376
                },
1✔
2377
        }
1✔
2378

1✔
2379
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2380
        return err
1✔
2381
}
1✔
2382

2383
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2384
        ctx context.Context,
2385
        deploymentID string,
2386
        count int,
2387
) error {
1✔
2388
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2389
        collection := database.Collection(CollectionDeployments)
1✔
2390

1✔
2391
        filter := bson.M{
1✔
2392
                "_id": deploymentID,
1✔
2393
                StorageKeyDeploymentDeviceCount: bson.M{
1✔
2394
                        "$eq": nil,
1✔
2395
                },
1✔
2396
        }
1✔
2397

1✔
2398
        update := bson.M{
1✔
2399
                "$set": bson.M{
1✔
2400
                        StorageKeyDeploymentDeviceCount: count,
1✔
2401
                },
1✔
2402
        }
1✔
2403

1✔
2404
        _, err := collection.UpdateOne(ctx, filter, update)
1✔
2405
        return err
1✔
2406
}
1✔
2407

2408
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2409
        id string) (int, error) {
1✔
2410

1✔
2411
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2412
        collDevs := database.Collection(CollectionDevices)
1✔
2413

1✔
2414
        filter := bson.M{
1✔
2415
                StorageKeyDeviceDeploymentDeploymentID: id,
1✔
2416
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2417
                        {Key: "$exists", Value: false},
1✔
2418
                },
1✔
2419
        }
1✔
2420

1✔
2421
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
1✔
2422
        if err != nil {
1✔
2423
                return 0, err
×
2424
        }
×
2425

2426
        return int(deviceCount), nil
1✔
2427
}
2428

2429
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2430
        id string, stats model.Stats) error {
1✔
2431

1✔
2432
        if len(id) == 0 {
2✔
2433
                return ErrStorageInvalidID
1✔
2434
        }
1✔
2435

2436
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2437
        collDpl := database.Collection(CollectionDeployments)
1✔
2438

1✔
2439
        deployment, err := model.NewDeployment()
1✔
2440
        if err != nil {
1✔
2441
                return errors.Wrap(err, "failed to create deployment")
×
2442
        }
×
2443

2444
        deployment.Stats = stats
1✔
2445
        var update bson.M
1✔
2446
        if deployment.IsFinished() {
1✔
2447
                now := time.Now()
×
2448

×
2449
                update = bson.M{
×
2450
                        "$set": bson.M{
×
2451
                                StorageKeyDeploymentStats:    stats,
×
2452
                                StorageKeyDeploymentFinished: &now,
×
2453
                        },
×
2454
                }
×
2455
        } else {
1✔
2456
                update = bson.M{
1✔
2457
                        "$set": bson.M{
1✔
2458
                                StorageKeyDeploymentStats: stats,
1✔
2459
                        },
1✔
2460
                }
1✔
2461
        }
1✔
2462

2463
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
1✔
2464
        if res != nil && res.MatchedCount == 0 {
2✔
2465
                return ErrStorageInvalidID
1✔
2466
        }
1✔
2467
        return err
1✔
2468
}
2469

2470
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2471
        stateFrom, stateTo model.DeviceDeploymentStatus) (model.Stats, error) {
1✔
2472

1✔
2473
        if len(id) == 0 {
2✔
2474
                return nil, ErrStorageInvalidID
1✔
2475
        }
1✔
2476

2477
        if _, err := stateTo.MarshalText(); err != nil {
1✔
2478
                return nil, ErrStorageInvalidInput
×
2479
        }
×
2480

2481
        // does not need any extra operations
2482
        // following query won't handle this case well and increase the state_to value
2483
        if stateFrom == stateTo {
2✔
2484
                return nil, nil
1✔
2485
        }
1✔
2486

2487
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2488
        collDpl := database.Collection(CollectionDeployments)
1✔
2489

1✔
2490
        var update bson.M
1✔
2491

1✔
2492
        if stateFrom == model.DeviceDeploymentStatusNull {
2✔
2493
                // note dot notation on embedded document
1✔
2494
                update = bson.M{
1✔
2495
                        "$inc": bson.M{
1✔
2496
                                "stats." + stateTo.String(): 1,
1✔
2497
                        },
1✔
2498
                }
1✔
2499
        } else {
2✔
2500
                // note dot notation on embedded document
1✔
2501
                update = bson.M{
1✔
2502
                        "$inc": bson.M{
1✔
2503
                                "stats." + stateFrom.String(): -1,
1✔
2504
                                "stats." + stateTo.String():   1,
1✔
2505
                        },
1✔
2506
                }
1✔
2507
        }
1✔
2508

2509
        var res struct {
1✔
2510
                Stats model.Stats `bson:"stats"`
1✔
2511
        }
1✔
2512
        err := collDpl.FindOneAndUpdate(ctx,
1✔
2513
                bson.M{StorageKeyId: id},
1✔
2514
                update,
1✔
2515
                mopts.FindOneAndUpdate().
1✔
2516
                        SetReturnDocument(mopts.After).
1✔
2517
                        SetProjection(bson.M{
1✔
2518
                                StorageKeyDeploymentStats: 1,
1✔
2519
                        }),
1✔
2520
        ).Decode(&res)
1✔
2521

1✔
2522
        if errors.Is(err, mongo.ErrNoDocuments) {
2✔
2523
                return nil, ErrStorageInvalidID
1✔
2524
        }
1✔
2525

2526
        return res.Stats, err
1✔
2527
}
2528

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

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

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

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

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

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

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

1✔
2559
        // filter by IDs
1✔
2560
        if match.IDs != nil {
1✔
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 != "" {
2✔
2571
                // we must have indexing for text search
1✔
2572
                if !db.hasIndexing(ctx, db.client) {
2✔
2573
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
1✔
2574
                }
1✔
2575

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

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

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

2599
        // build deployment by type part of the query
2600
        if match.Type != "" {
2✔
2601
                if match.Type == model.DeploymentTypeConfiguration {
2✔
2602
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
1✔
2603
                } else if match.Type == model.DeploymentTypeSoftware {
1✔
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{}
1✔
2614
        if len(andq) != 0 {
2✔
2615
                // use search criteria if any
1✔
2616
                query = bson.M{
1✔
2617
                        "$and": andq,
1✔
2618
                }
1✔
2619
        }
1✔
2620

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

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

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

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

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

2682
// FindNewerActiveDeployments finds active deployments which were created
2683
// after createdAfter
2684
// Deprecated: No longer in use
2685
func (db *DataStoreMongo) FindNewerActiveDeployments(ctx context.Context,
2686
        createdAfter *time.Time, skip, limit int) ([]*model.Deployment, error) {
1✔
2687

1✔
2688
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2689
        c := database.Collection(CollectionDeployments)
1✔
2690

1✔
2691
        queryFilters := make([]bson.M, 0)
1✔
2692
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
1✔
2693
        queryFilters = append(queryFilters,
1✔
2694
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
1✔
2695
        findQuery := bson.M{}
1✔
2696
        findQuery["$and"] = queryFilters
1✔
2697

1✔
2698
        findOptions := &mopts.FindOptions{}
1✔
2699
        findOptions.SetSkip(int64(skip))
1✔
2700
        findOptions.SetLimit(int64(limit))
1✔
2701

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

1✔
2709
        var deployments []*model.Deployment
1✔
2710

1✔
2711
        if err = cursor.All(ctx, &deployments); err != nil {
1✔
2712
                return nil, errors.Wrap(err, "failed to get deployments")
×
2713
        }
×
2714

2715
        return deployments, nil
1✔
2716
}
2717

2718
// FindNewerActiveDeployment finds active deployments which were created
2719
// after createdAfter where deviceID is part of the device list.
2720
func (db *DataStoreMongo) FindNewerActiveDeployment(ctx context.Context,
2721
        createdAfter *time.Time, deviceID string) (*model.Deployment, error) {
×
2722

×
2723
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2724
        c := database.Collection(CollectionDeployments)
×
2725

×
2726
        findQuery := bson.D{
×
2727
                {Key: StorageKeyDeploymentActive, Value: true},
×
2728
                {Key: StorageKeyDeploymentCreated, Value: bson.M{"$gt": createdAfter}},
×
2729
                {Key: StorageKeyDeploymentDeviceList, Value: deviceID},
×
2730
        }
×
2731
        findOptions := mopts.FindOne().
×
2732
                SetSort(bson.D{{Key: StorageKeyDeploymentCreated, Value: 1}}).
×
2733
                SetProjection(bson.M{
×
2734
                        // Discard information we don't need
×
2735
                        StorageKeyDeploymentConstructorChecksum: 0,
×
2736
                        StorageKeyDeploymentDeviceList:          0,
×
2737
                })
×
2738

×
2739
        var deployment = new(model.Deployment)
×
2740
        err := c.FindOne(ctx, findQuery, findOptions).
×
2741
                Decode(deployment)
×
2742
        if err != nil {
×
2743
                if errors.Is(err, mongo.ErrNoDocuments) {
×
2744
                        return nil, nil
×
2745
                }
×
2746
                return nil, errors.Wrap(err, "failed to get deployments")
×
2747
        }
2748

2749
        return deployment, nil
×
2750
}
2751

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

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

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

2785
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
1✔
2786

1✔
2787
        if res != nil && res.MatchedCount == 0 {
2✔
2788
                return ErrStorageInvalidID
1✔
2789
        }
1✔
2790

2791
        return err
1✔
2792
}
2793

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

1✔
2799
        if len(id) == 0 {
1✔
2800
                return false, ErrStorageInvalidID
×
2801
        }
×
2802

2803
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2804
        collDpl := database.Collection(CollectionDeployments)
1✔
2805

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

2818
        return true, nil
1✔
2819
}
2820

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

1✔
2826
        if len(artifactName) == 0 {
1✔
2827
                return false, ErrImagesStorageInvalidArtifactName
×
2828
        }
×
2829

2830
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2831
        collDpl := database.Collection(CollectionDeployments)
1✔
2832

1✔
2833
        var tmp interface{}
1✔
2834
        query := bson.D{
1✔
2835
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2836
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2837
        }
1✔
2838

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

1✔
2845
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
2✔
2846
                if err == mongo.ErrNoDocuments {
2✔
2847
                        return false, nil
1✔
2848
                }
1✔
2849
                return false, err
×
2850
        }
2851

2852
        return true, nil
1✔
2853
}
2854

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

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

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

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

2877
        return true, nil
×
2878
}
2879

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

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

2897
        return settings, nil
1✔
2898
}
2899

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

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

2919
        return err
1✔
2920
}
2921

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

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

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

2944
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
2945
        ctx context.Context,
2946
        artifactNames []string,
2947
) ([]string, error) {
1✔
2948

1✔
2949
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2950
        collDpl := database.Collection(CollectionDeployments)
1✔
2951

1✔
2952
        query := bson.M{
1✔
2953
                StorageKeyDeploymentArtifactName: bson.M{
1✔
2954
                        "$in": artifactNames,
1✔
2955
                },
1✔
2956
        }
1✔
2957

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

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

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

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

2983
        return ids, nil
1✔
2984
}
2985

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