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

mendersoftware / deployments / 1069331446

12 Nov 2023 09:28AM UTC coverage: 77.424% (-2.8%) from 80.241%
1069331446

Pull #961

gitlab-ci

kjaskiewiczz
feat: endpoint for bulk removal of releases by names

Changelog: Title
Ticket: MEN-6354

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

0 of 97 new or added lines in 5 files covered. (0.0%)

11 existing lines in 1 file now uncovered.

4009 of 5178 relevant lines covered (77.42%)

55.17 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

434
        StorageKeyDeviceDeploymentLogMessages = "messages"
435

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

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

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

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

477
        StorageKeyStorageReleaseUpdateTypes = "update_types"
478

479
        ArtifactDependsDeviceType = "device_type"
480
)
481

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

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

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

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

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

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

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

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

534
        return client, nil
1✔
535
}
536

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

819
        return limit, nil
2✔
820
}
821

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

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

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

829
//images
830

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

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

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

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

850
        return true, nil
×
851
}
852

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

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

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

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

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

877
        return true, nil
1✔
878
}
879

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

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

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

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

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

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

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

916
        return &image, nil
3✔
917
}
918

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

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

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

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

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

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

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

953
        return &image, nil
1✔
954
}
955

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

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

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

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

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

982
        return images, nil
1✔
983
}
984

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

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

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

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

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

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

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

1036
        return nil
62✔
1037
}
1038

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

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

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

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

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

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

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

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

1138
        return &image, nil
1✔
1139
}
1140

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

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

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

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

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

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

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

1194
        return true, nil
4✔
1195
}
1196

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

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

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

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

1215
        return nil
1✔
1216
}
1217

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

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

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

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

1273
        }
1274

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

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

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

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

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

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

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

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

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

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

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

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

1364
        return nil
6✔
1365
}
1366

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

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

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

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

1386
        return &depl, nil
4✔
1387
}
1388

1389
// device deployments
1390

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

27✔
1400
        if _, err := c.InsertOne(ctx, deviceDeployment); err != nil {
27✔
1401
                return err
×
1402
        }
×
1403

1404
        if incrementDeviceCount {
54✔
1405
                err := db.IncrementDeploymentDeviceCount(ctx, deviceDeployment.DeploymentId, 1)
27✔
1406
                if err != nil {
27✔
1407
                        return err
×
1408
                }
×
1409
        }
1410

1411
        return nil
27✔
1412
}
1413

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

39✔
1419
        if len(deployments) == 0 {
51✔
1420
                return nil
12✔
1421
        }
12✔
1422

1423
        deviceCountIncrements := make(map[string]int)
27✔
1424

27✔
1425
        // Writing to another interface list addresses golang gatcha interface{} == []interface{}
27✔
1426
        var list []interface{}
27✔
1427
        for _, deployment := range deployments {
93✔
1428

66✔
1429
                if deployment == nil {
67✔
1430
                        return ErrStorageInvalidDeviceDeployment
1✔
1431
                }
1✔
1432

1433
                if err := deployment.Validate(); err != nil {
67✔
1434
                        return errors.Wrap(err, "Validating device deployment")
2✔
1435
                }
2✔
1436

1437
                list = append(list, deployment)
63✔
1438
                deviceCountIncrements[deployment.DeploymentId]++
63✔
1439
        }
1440

1441
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
24✔
1442
        collDevs := database.Collection(CollectionDevices)
24✔
1443

24✔
1444
        if _, err := collDevs.InsertMany(ctx, list); err != nil {
24✔
1445
                return err
×
1446
        }
×
1447

1448
        for deploymentID := range deviceCountIncrements {
52✔
1449
                err := db.IncrementDeploymentDeviceCount(
28✔
1450
                        ctx,
28✔
1451
                        deploymentID,
28✔
1452
                        deviceCountIncrements[deploymentID],
28✔
1453
                )
28✔
1454
                if err != nil {
28✔
1455
                        return err
×
1456
                }
×
1457
        }
1458

1459
        return nil
24✔
1460
}
1461

1462
// FindOldestActiveDeviceDeployment finds the oldest deployment that has not finished yet.
1463
func (db *DataStoreMongo) FindOldestActiveDeviceDeployment(
1464
        ctx context.Context,
1465
        deviceID string,
1466
) (*model.DeviceDeployment, error) {
6✔
1467

6✔
1468
        // Verify ID formatting
6✔
1469
        if len(deviceID) == 0 {
7✔
1470
                return nil, ErrStorageInvalidID
1✔
1471
        }
1✔
1472

1473
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1474
        collDevs := database.Collection(CollectionDevices)
5✔
1475

5✔
1476
        // Device should know only about deployments that are not finished
5✔
1477
        query := bson.D{
5✔
1478
                {Key: StorageKeyDeviceDeploymentActive, Value: true},
5✔
1479
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1480
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1481
                        {Key: "$exists", Value: false},
5✔
1482
                }},
5✔
1483
        }
5✔
1484

5✔
1485
        // Find the oldest one by sorting the creation timestamp
5✔
1486
        // in ascending order.
5✔
1487
        findOptions := mopts.FindOne()
5✔
1488
        findOptions.SetSort(bson.D{{Key: "created", Value: 1}})
5✔
1489

5✔
1490
        // Select only the oldest one that have not been finished yet.
5✔
1491
        deployment := new(model.DeviceDeployment)
5✔
1492
        if err := collDevs.FindOne(ctx, query, findOptions).
5✔
1493
                Decode(deployment); err != nil {
8✔
1494
                if err == mongo.ErrNoDocuments {
5✔
1495
                        return nil, nil
2✔
1496
                }
2✔
1497
                return nil, err
1✔
1498
        }
1499

1500
        return deployment, nil
3✔
1501
}
1502

1503
// FindLatestInactiveDeviceDeployment finds the latest device deployment
1504
// matching device id that has not finished yet.
1505
func (db *DataStoreMongo) FindLatestInactiveDeviceDeployment(
1506
        ctx context.Context,
1507
        deviceID string,
1508
) (*model.DeviceDeployment, error) {
6✔
1509

6✔
1510
        // Verify ID formatting
6✔
1511
        if len(deviceID) == 0 {
7✔
1512
                return nil, ErrStorageInvalidID
1✔
1513
        }
1✔
1514

1515
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1516
        collDevs := database.Collection(CollectionDevices)
5✔
1517

5✔
1518
        query := bson.D{
5✔
1519
                {Key: StorageKeyDeviceDeploymentActive, Value: false},
5✔
1520
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1521
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1522
                        {Key: "$exists", Value: false},
5✔
1523
                }},
5✔
1524
        }
5✔
1525

5✔
1526
        // Find the latest one by sorting by the creation timestamp
5✔
1527
        // in ascending order.
5✔
1528
        findOptions := mopts.FindOne()
5✔
1529
        findOptions.SetSort(bson.D{{Key: "created", Value: -1}})
5✔
1530

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

1541
        return deployment, nil
3✔
1542
}
1543

1544
func (db *DataStoreMongo) UpdateDeviceDeploymentStatus(
1545
        ctx context.Context,
1546
        deviceID string,
1547
        deploymentID string,
1548
        ddState model.DeviceDeploymentState,
1549
) (model.DeviceDeploymentStatus, error) {
10✔
1550

10✔
1551
        // Verify ID formatting
10✔
1552
        if len(deviceID) == 0 ||
10✔
1553
                len(deploymentID) == 0 {
12✔
1554
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidID
2✔
1555
        }
2✔
1556

1557
        if err := ddState.Validate(); err != nil {
9✔
1558
                return model.DeviceDeploymentStatusNull, ErrStorageInvalidInput
1✔
1559
        }
1✔
1560

1561
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1562
        collDevs := database.Collection(CollectionDevices)
7✔
1563

7✔
1564
        // Device should know only about deployments that are not finished
7✔
1565
        query := bson.D{
7✔
1566
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
1567
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
1568
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
1569
                        {Key: "$exists", Value: false},
7✔
1570
                }},
7✔
1571
        }
7✔
1572

7✔
1573
        // update status field
7✔
1574
        set := bson.M{
7✔
1575
                StorageKeyDeviceDeploymentStatus: ddState.Status,
7✔
1576
                StorageKeyDeviceDeploymentActive: ddState.Status.Active(),
7✔
1577
        }
7✔
1578
        // and finish time if provided
7✔
1579
        if ddState.FinishTime != nil {
9✔
1580
                set[StorageKeyDeviceDeploymentFinished] = ddState.FinishTime
2✔
1581
        }
2✔
1582

1583
        if len(ddState.SubState) > 0 {
8✔
1584
                set[StorageKeyDeviceDeploymentSubState] = ddState.SubState
1✔
1585
        }
1✔
1586

1587
        update := bson.D{
7✔
1588
                {Key: "$set", Value: set},
7✔
1589
        }
7✔
1590

7✔
1591
        var old model.DeviceDeployment
7✔
1592

7✔
1593
        if err := collDevs.FindOneAndUpdate(ctx, query, update).
7✔
1594
                Decode(&old); err != nil {
9✔
1595
                if err == mongo.ErrNoDocuments {
4✔
1596
                        return model.DeviceDeploymentStatusNull, ErrStorageNotFound
2✔
1597
                }
2✔
1598
                return model.DeviceDeploymentStatusNull, err
×
1599

1600
        }
1601

1602
        return old.Status, nil
5✔
1603
}
1604

1605
func (db *DataStoreMongo) UpdateDeviceDeploymentLogAvailability(ctx context.Context,
1606
        deviceID string, deploymentID string, log bool) error {
7✔
1607

7✔
1608
        // Verify ID formatting
7✔
1609
        if len(deviceID) == 0 ||
7✔
1610
                len(deploymentID) == 0 {
9✔
1611
                return ErrStorageInvalidID
2✔
1612
        }
2✔
1613

1614
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
1615
        collDevs := database.Collection(CollectionDevices)
5✔
1616

5✔
1617
        selector := bson.D{
5✔
1618
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
5✔
1619
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
5✔
1620
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
5✔
1621
                        {Key: "$exists", Value: false},
5✔
1622
                }},
5✔
1623
        }
5✔
1624

5✔
1625
        update := bson.D{
5✔
1626
                {Key: "$set", Value: bson.M{
5✔
1627
                        StorageKeyDeviceDeploymentIsLogAvailable: log}},
5✔
1628
        }
5✔
1629

5✔
1630
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
5✔
1631
                return err
×
1632
        } else if res.MatchedCount == 0 {
7✔
1633
                return ErrStorageNotFound
2✔
1634
        }
2✔
1635

1636
        return nil
3✔
1637
}
1638

1639
// SaveDeviceDeploymentRequest saves device deployment request
1640
// with the device deployment object
1641
func (db *DataStoreMongo) SaveDeviceDeploymentRequest(
1642
        ctx context.Context,
1643
        ID string,
1644
        request *model.DeploymentNextRequest,
1645
) error {
4✔
1646

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

4✔
1650
        res, err := collDevs.UpdateOne(
4✔
1651
                ctx,
4✔
1652
                bson.D{{Key: StorageKeyId, Value: ID}},
4✔
1653
                bson.D{{Key: "$set", Value: bson.M{StorageKeyDeviceDeploymentRequest: request}}},
4✔
1654
        )
4✔
1655
        if err != nil {
4✔
1656
                return err
×
1657
        } else if res.MatchedCount == 0 {
5✔
1658
                return ErrStorageNotFound
1✔
1659
        }
1✔
1660
        return nil
3✔
1661
}
1662

1663
// AssignArtifact assigns artifact to the device deployment
1664
func (db *DataStoreMongo) AssignArtifact(
1665
        ctx context.Context,
1666
        deviceID string,
1667
        deploymentID string,
1668
        artifact *model.Image,
1669
) error {
1✔
1670

1✔
1671
        // Verify ID formatting
1✔
1672
        if len(deviceID) == 0 ||
1✔
1673
                len(deploymentID) == 0 {
1✔
1674
                return ErrStorageInvalidID
×
1675
        }
×
1676

1677
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
1678
        collDevs := database.Collection(CollectionDevices)
1✔
1679

1✔
1680
        selector := bson.D{
1✔
1681
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
1✔
1682
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
1✔
1683
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
1✔
1684
                        {Key: "$exists", Value: false},
1✔
1685
                }},
1✔
1686
        }
1✔
1687

1✔
1688
        update := bson.D{
1✔
1689
                {Key: "$set", Value: bson.M{
1✔
1690
                        StorageKeyDeviceDeploymentArtifact: artifact,
1✔
1691
                }},
1✔
1692
        }
1✔
1693

1✔
1694
        if res, err := collDevs.UpdateOne(ctx, selector, update); err != nil {
1✔
1695
                return err
×
1696
        } else if res.MatchedCount == 0 {
1✔
1697
                return ErrStorageNotFound
×
1698
        }
×
1699

1700
        return nil
1✔
1701
}
1702

1703
func (db *DataStoreMongo) AggregateDeviceDeploymentByStatus(ctx context.Context,
1704
        id string) (model.Stats, error) {
6✔
1705

6✔
1706
        if len(id) == 0 {
6✔
1707
                return nil, ErrStorageInvalidID
×
1708
        }
×
1709

1710
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
1711
        collDevs := database.Collection(CollectionDevices)
6✔
1712

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

1748
        raw := model.NewDeviceDeploymentStats()
6✔
1749
        for _, res := range results {
17✔
1750
                raw.Set(res.Status, res.Count)
11✔
1751
        }
11✔
1752
        return raw, nil
6✔
1753
}
1754

1755
// GetDeviceStatusesForDeployment retrieve device deployment statuses for a given deployment.
1756
func (db *DataStoreMongo) GetDeviceStatusesForDeployment(ctx context.Context,
1757
        deploymentID string) ([]model.DeviceDeployment, error) {
6✔
1758

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

6✔
1763
        query := bson.M{
6✔
1764
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
6✔
1765
                StorageKeyDeviceDeploymentDeleted: bson.D{
6✔
1766
                        {Key: "$exists", Value: false},
6✔
1767
                },
6✔
1768
        }
6✔
1769

6✔
1770
        cursor, err := collDevs.Find(ctx, query)
6✔
1771
        if err != nil {
6✔
1772
                return nil, err
×
1773
        }
×
1774

1775
        if err = cursor.All(ctx, &statuses); err != nil {
6✔
1776
                if err == mongo.ErrNoDocuments {
×
1777
                        return nil, nil
×
1778
                }
×
1779
                return nil, err
×
1780
        }
1781

1782
        return statuses, nil
6✔
1783
}
1784

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

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

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

1845
        options := mopts.Find()
14✔
1846
        sortFieldQuery := bson.D{
14✔
1847
                {Key: StorageKeyDeviceDeploymentStatus, Value: 1},
14✔
1848
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: 1},
14✔
1849
        }
14✔
1850
        options.SetSort(sortFieldQuery)
14✔
1851
        if q.Skip > 0 {
17✔
1852
                options.SetSkip(int64(q.Skip))
3✔
1853
        }
3✔
1854
        if q.Limit > 0 {
19✔
1855
                options.SetLimit(int64(q.Limit))
5✔
1856
        } else {
14✔
1857
                options.SetLimit(DefaultDocumentLimit)
9✔
1858
        }
9✔
1859

1860
        cursor, err := collDevs.Find(ctx, query, options)
14✔
1861
        if err != nil {
15✔
1862
                return nil, -1, err
1✔
1863
        }
1✔
1864

1865
        if err = cursor.All(ctx, &statuses); err != nil {
13✔
1866
                if err == mongo.ErrNoDocuments {
×
1867
                        return nil, -1, nil
×
1868
                }
×
1869
                return nil, -1, err
×
1870
        }
1871

1872
        count, err := collDevs.CountDocuments(ctx, query)
13✔
1873
        if err != nil {
13✔
1874
                return nil, -1, ErrDevicesCountFailed
×
1875
        }
×
1876

1877
        return statuses, int(count), nil
13✔
1878
}
1879

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

10✔
1883
        statuses := []model.DeviceDeployment{}
10✔
1884
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
10✔
1885
        collDevs := database.Collection(CollectionDevices)
10✔
1886

10✔
1887
        query := bson.D{}
10✔
1888
        if q.DeviceID != "" {
19✔
1889
                query = append(query, bson.E{
9✔
1890
                        Key:   StorageKeyDeviceDeploymentDeviceId,
9✔
1891
                        Value: q.DeviceID,
9✔
1892
                })
9✔
1893
        } else if len(q.IDs) > 0 {
11✔
1894
                query = append(query, bson.E{
1✔
1895
                        Key: StorageKeyId,
1✔
1896
                        Value: bson.D{{
1✔
1897
                                Key:   "$in",
1✔
1898
                                Value: q.IDs,
1✔
1899
                        }},
1✔
1900
                })
1✔
1901
        }
1✔
1902

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

1950
        options := mopts.Find()
9✔
1951
        sortFieldQuery := bson.D{
9✔
1952
                {Key: StorageKeyDeviceDeploymentCreated, Value: -1},
9✔
1953
                {Key: StorageKeyDeviceDeploymentStatus, Value: -1},
9✔
1954
        }
9✔
1955
        options.SetSort(sortFieldQuery)
9✔
1956
        if q.Skip > 0 {
10✔
1957
                options.SetSkip(int64(q.Skip))
1✔
1958
        }
1✔
1959
        if q.Limit > 0 {
18✔
1960
                options.SetLimit(int64(q.Limit))
9✔
1961
        } else {
9✔
1962
                options.SetLimit(DefaultDocumentLimit)
×
1963
        }
×
1964

1965
        cursor, err := collDevs.Find(ctx, query, options)
9✔
1966
        if err != nil {
9✔
1967
                return nil, -1, err
×
1968
        }
×
1969

1970
        if err = cursor.All(ctx, &statuses); err != nil {
9✔
1971
                if err == mongo.ErrNoDocuments {
×
1972
                        return nil, 0, nil
×
1973
                }
×
1974
                return nil, -1, err
×
1975
        }
1976

1977
        maxCount := maxCountDocuments
9✔
1978
        countOptions := &mopts.CountOptions{
9✔
1979
                Limit: &maxCount,
9✔
1980
        }
9✔
1981
        count, err := collDevs.CountDocuments(ctx, query, countOptions)
9✔
1982
        if err != nil {
9✔
1983
                return nil, -1, ErrDevicesCountFailed
×
1984
        }
×
1985

1986
        return statuses, int(count), nil
9✔
1987
}
1988

1989
// Returns true if deployment of ID `deploymentID` is assigned to device with ID
1990
// `deviceID`, false otherwise. In case of errors returns false and an error
1991
// that occurred
1992
func (db *DataStoreMongo) HasDeploymentForDevice(ctx context.Context,
1993
        deploymentID string, deviceID string) (bool, error) {
7✔
1994

7✔
1995
        var dep model.DeviceDeployment
7✔
1996
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
1997
        collDevs := database.Collection(CollectionDevices)
7✔
1998

7✔
1999
        query := bson.D{
7✔
2000
                {Key: StorageKeyDeviceDeploymentDeploymentID, Value: deploymentID},
7✔
2001
                {Key: StorageKeyDeviceDeploymentDeviceId, Value: deviceID},
7✔
2002
                {Key: StorageKeyDeviceDeploymentDeleted, Value: bson.D{
7✔
2003
                        {Key: "$exists", Value: false},
7✔
2004
                }},
7✔
2005
        }
7✔
2006

7✔
2007
        if err := collDevs.FindOne(ctx, query).Decode(&dep); err != nil {
10✔
2008
                if err == mongo.ErrNoDocuments {
6✔
2009
                        return false, nil
3✔
2010
                } else {
3✔
2011
                        return false, err
×
2012
                }
×
2013
        }
2014

2015
        return true, nil
4✔
2016
}
2017

2018
func (db *DataStoreMongo) AbortDeviceDeployments(ctx context.Context,
2019
        deploymentId string) error {
3✔
2020

3✔
2021
        if len(deploymentId) == 0 {
4✔
2022
                return ErrStorageInvalidID
1✔
2023
        }
1✔
2024

2025
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2026
        collDevs := database.Collection(CollectionDevices)
2✔
2027
        selector := bson.M{
2✔
2028
                StorageKeyDeviceDeploymentDeploymentID: deploymentId,
2✔
2029
                StorageKeyDeviceDeploymentActive:       true,
2✔
2030
                StorageKeyDeviceDeploymentDeleted: bson.D{
2✔
2031
                        {Key: "$exists", Value: false},
2✔
2032
                },
2✔
2033
        }
2✔
2034

2✔
2035
        update := bson.M{
2✔
2036
                "$set": bson.M{
2✔
2037
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusAborted,
2✔
2038
                        StorageKeyDeviceDeploymentActive: false,
2✔
2039
                },
2✔
2040
        }
2✔
2041

2✔
2042
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
2✔
2043
                return err
×
2044
        }
×
2045

2046
        return nil
2✔
2047
}
2048

2049
func (db *DataStoreMongo) DeleteDeviceDeploymentsHistory(ctx context.Context,
2050
        deviceID string) error {
2✔
2051
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
2✔
2052
        collDevs := database.Collection(CollectionDevices)
2✔
2053
        selector := bson.M{
2✔
2054
                StorageKeyDeviceDeploymentDeviceId: deviceID,
2✔
2055
                StorageKeyDeviceDeploymentActive:   false,
2✔
2056
                StorageKeyDeviceDeploymentDeleted: bson.M{
2✔
2057
                        "$exists": false,
2✔
2058
                },
2✔
2059
        }
2✔
2060

2✔
2061
        now := time.Now()
2✔
2062
        update := bson.M{
2✔
2063
                "$set": bson.M{
2✔
2064
                        StorageKeyDeviceDeploymentDeleted: &now,
2✔
2065
                },
2✔
2066
        }
2✔
2067

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

2072
        database = db.client.Database(DatabaseName)
2✔
2073
        collDevs = database.Collection(CollectionDevicesLastStatus)
2✔
2074
        _, err := collDevs.DeleteMany(ctx, bson.M{StorageKeyDeviceDeploymentDeviceId: deviceID})
2✔
2075

2✔
2076
        return err
2✔
2077
}
2078

2079
func (db *DataStoreMongo) DecommissionDeviceDeployments(ctx context.Context,
2080
        deviceId string) error {
2✔
2081

2✔
2082
        if len(deviceId) == 0 {
3✔
2083
                return ErrStorageInvalidID
1✔
2084
        }
1✔
2085

2086
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2087
        collDevs := database.Collection(CollectionDevices)
1✔
2088
        selector := bson.M{
1✔
2089
                StorageKeyDeviceDeploymentDeviceId: deviceId,
1✔
2090
                StorageKeyDeviceDeploymentActive:   true,
1✔
2091
                StorageKeyDeviceDeploymentDeleted: bson.D{
1✔
2092
                        {Key: "$exists", Value: false},
1✔
2093
                },
1✔
2094
        }
1✔
2095

1✔
2096
        update := bson.M{
1✔
2097
                "$set": bson.M{
1✔
2098
                        StorageKeyDeviceDeploymentStatus: model.DeviceDeploymentStatusDecommissioned,
1✔
2099
                        StorageKeyDeviceDeploymentActive: false,
1✔
2100
                },
1✔
2101
        }
1✔
2102

1✔
2103
        if _, err := collDevs.UpdateMany(ctx, selector, update); err != nil {
1✔
2104
                return err
×
2105
        }
×
2106

2107
        return nil
1✔
2108
}
2109

2110
func (db *DataStoreMongo) GetDeviceDeployment(ctx context.Context, deploymentID string,
2111
        deviceID string, includeDeleted bool) (*model.DeviceDeployment, error) {
1✔
2112

1✔
2113
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
1✔
2114
        collDevs := database.Collection(CollectionDevices)
1✔
2115

1✔
2116
        filter := bson.M{
1✔
2117
                StorageKeyDeviceDeploymentDeploymentID: deploymentID,
1✔
2118
                StorageKeyDeviceDeploymentDeviceId:     deviceID,
1✔
2119
        }
1✔
2120
        if !includeDeleted {
2✔
2121
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
1✔
2122
                        {Key: "$exists", Value: false},
1✔
2123
                }
1✔
2124
        }
1✔
2125

2126
        opts := &mopts.FindOneOptions{}
1✔
2127
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
1✔
2128

1✔
2129
        var dd model.DeviceDeployment
1✔
2130
        if err := collDevs.FindOne(ctx, filter, opts).Decode(&dd); err != nil {
2✔
2131
                if err == mongo.ErrNoDocuments {
2✔
2132
                        return nil, ErrStorageNotFound
1✔
2133
                }
1✔
2134
                return nil, err
×
2135
        }
2136

2137
        return &dd, nil
1✔
2138
}
2139

2140
func (db *DataStoreMongo) GetDeviceDeployments(
2141
        ctx context.Context,
2142
        skip int,
2143
        limit int,
2144
        deviceID string,
2145
        active *bool,
2146
        includeDeleted bool,
2147
) ([]model.DeviceDeployment, error) {
4✔
2148

4✔
2149
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2150
        collDevs := database.Collection(CollectionDevices)
4✔
2151

4✔
2152
        filter := bson.M{}
4✔
2153
        if !includeDeleted {
6✔
2154
                filter[StorageKeyDeviceDeploymentDeleted] = bson.D{
2✔
2155
                        {Key: "$exists", Value: false},
2✔
2156
                }
2✔
2157
        }
2✔
2158
        if deviceID != "" {
5✔
2159
                filter[StorageKeyDeviceDeploymentDeviceId] = deviceID
1✔
2160
        }
1✔
2161
        if active != nil {
5✔
2162
                filter[StorageKeyDeviceDeploymentActive] = *active
1✔
2163
        }
1✔
2164

2165
        opts := &mopts.FindOptions{}
4✔
2166
        opts.SetSort(bson.D{{Key: "created", Value: -1}})
4✔
2167
        if skip > 0 {
5✔
2168
                opts.SetSkip(int64(skip))
1✔
2169
        }
1✔
2170
        if limit > 0 {
5✔
2171
                opts.SetLimit(int64(limit))
1✔
2172
        }
1✔
2173

2174
        var deviceDeployments []model.DeviceDeployment
4✔
2175
        cursor, err := collDevs.Find(ctx, filter, opts)
4✔
2176
        if err != nil {
4✔
2177
                return nil, err
×
2178
        }
×
2179
        if err := cursor.All(ctx, &deviceDeployments); err != nil {
4✔
2180
                return nil, err
×
2181
        }
×
2182

2183
        return deviceDeployments, nil
4✔
2184
}
2185

2186
// deployments
2187

2188
func (db *DataStoreMongo) EnsureIndexes(dbName string, collName string,
2189
        indexes ...mongo.IndexModel) error {
436✔
2190
        ctx := context.Background()
436✔
2191
        dataBase := db.client.Database(dbName)
436✔
2192

436✔
2193
        coll := dataBase.Collection(collName)
436✔
2194
        idxView := coll.Indexes()
436✔
2195
        _, err := idxView.CreateMany(ctx, indexes)
436✔
2196
        return err
436✔
2197
}
436✔
2198

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

16✔
2202
        var idx bson.M
16✔
2203
        database := client.Database(mstore.DbFromContext(ctx, DatabaseName))
16✔
2204
        collDpl := database.Collection(CollectionDeployments)
16✔
2205
        idxView := collDpl.Indexes()
16✔
2206

16✔
2207
        cursor, err := idxView.List(ctx)
16✔
2208
        if err != nil {
16✔
2209
                // check failed, assume indexing is not there
×
2210
                return false
×
2211
        }
×
2212

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

2228
                }
2229
        }
2230
        if err != nil {
16✔
2231
                return false
×
2232
        }
×
2233

2234
        for _, key := range StorageIndexes.Keys.(bson.D) {
47✔
2235
                _, ok := has[key.Key]
31✔
2236
                if !ok {
32✔
2237
                        return false
1✔
2238
                }
1✔
2239
        }
2240

2241
        return true
15✔
2242
}
2243

2244
// Insert persists object
2245
func (db *DataStoreMongo) InsertDeployment(
2246
        ctx context.Context,
2247
        deployment *model.Deployment,
2248
) error {
210✔
2249

210✔
2250
        if deployment == nil {
211✔
2251
                return ErrDeploymentStorageInvalidDeployment
1✔
2252
        }
1✔
2253

2254
        if err := deployment.Validate(); err != nil {
211✔
2255
                return err
2✔
2256
        }
2✔
2257

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

208✔
2261
        if _, err := collDpl.InsertOne(ctx, deployment); err != nil {
209✔
2262
                return err
1✔
2263
        }
1✔
2264
        return nil
208✔
2265
}
2266

2267
// Delete removed entry by ID
2268
// Noop on ID not found
2269
func (db *DataStoreMongo) DeleteDeployment(ctx context.Context, id string) error {
4✔
2270

4✔
2271
        if len(id) == 0 {
5✔
2272
                return ErrStorageInvalidID
1✔
2273
        }
1✔
2274

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

3✔
2278
        if _, err := collDpl.DeleteOne(ctx, bson.M{"_id": id}); err != nil {
3✔
2279
                return err
×
2280
        }
×
2281

2282
        return nil
3✔
2283
}
2284

2285
func (db *DataStoreMongo) FindDeploymentByID(
2286
        ctx context.Context,
2287
        id string,
2288
) (*model.Deployment, error) {
10✔
2289

10✔
2290
        if len(id) == 0 {
11✔
2291
                return nil, ErrStorageInvalidID
1✔
2292
        }
1✔
2293

2294
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
9✔
2295
        collDpl := database.Collection(CollectionDeployments)
9✔
2296

9✔
2297
        deployment := new(model.Deployment)
9✔
2298
        if err := collDpl.FindOne(ctx, bson.M{"_id": id}).
9✔
2299
                Decode(deployment); err != nil {
12✔
2300
                if err == mongo.ErrNoDocuments {
6✔
2301
                        return nil, nil
3✔
2302
                }
3✔
2303
                return nil, err
×
2304
        }
2305

2306
        return deployment, nil
6✔
2307
}
2308

2309
func (db *DataStoreMongo) FindDeploymentStatsByIDs(
2310
        ctx context.Context,
2311
        ids ...string,
2312
) (deploymentStats []*model.DeploymentStats, err error) {
2✔
2313

2✔
2314
        if len(ids) == 0 {
2✔
2315
                return nil, errors.New("no IDs passed into the function. At least one is required")
×
2316
        }
×
2317

2318
        for _, id := range ids {
6✔
2319
                if len(id) == 0 {
4✔
2320
                        return nil, ErrStorageInvalidID
×
2321
                }
×
2322
        }
2323

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

2✔
2327
        query := bson.M{
2✔
2328
                "_id": bson.M{
2✔
2329
                        "$in": ids,
2✔
2330
                },
2✔
2331
        }
2✔
2332
        statsProjection := &mopts.FindOptions{
2✔
2333
                Projection: bson.M{"stats": 1},
2✔
2334
        }
2✔
2335

2✔
2336
        results, err := collDpl.Find(
2✔
2337
                ctx,
2✔
2338
                query,
2✔
2339
                statsProjection,
2✔
2340
        )
2✔
2341
        if err != nil {
2✔
2342
                return nil, err
×
2343
        }
×
2344

2345
        for results.Next(context.Background()) {
6✔
2346
                depl := new(model.DeploymentStats)
4✔
2347
                if err = results.Decode(&depl); err != nil {
4✔
2348
                        if err == mongo.ErrNoDocuments {
×
2349
                                return nil, nil
×
2350
                        }
×
2351
                        return nil, err
×
2352
                }
2353
                deploymentStats = append(deploymentStats, depl)
4✔
2354
        }
2355

2356
        return deploymentStats, nil
2✔
2357
}
2358

2359
func (db *DataStoreMongo) FindUnfinishedByID(ctx context.Context,
2360
        id string) (*model.Deployment, error) {
8✔
2361

8✔
2362
        if len(id) == 0 {
9✔
2363
                return nil, ErrStorageInvalidID
1✔
2364
        }
1✔
2365

2366
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
7✔
2367
        collDpl := database.Collection(CollectionDeployments)
7✔
2368

7✔
2369
        var deployment *model.Deployment
7✔
2370
        filter := bson.D{
7✔
2371
                {Key: "_id", Value: id},
7✔
2372
                {Key: StorageKeyDeploymentFinished, Value: nil},
7✔
2373
        }
7✔
2374
        if err := collDpl.FindOne(ctx, filter).
7✔
2375
                Decode(&deployment); err != nil {
12✔
2376
                if err == mongo.ErrNoDocuments {
10✔
2377
                        return nil, nil
5✔
2378
                }
5✔
2379
                return nil, err
×
2380
        }
2381

2382
        return deployment, nil
3✔
2383
}
2384

2385
func (db *DataStoreMongo) IncrementDeploymentDeviceCount(
2386
        ctx context.Context,
2387
        deploymentID string,
2388
        increment int,
2389
) error {
55✔
2390
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
55✔
2391
        collection := database.Collection(CollectionDeployments)
55✔
2392

55✔
2393
        filter := bson.M{
55✔
2394
                "_id": deploymentID,
55✔
2395
                StorageKeyDeploymentDeviceCount: bson.M{
55✔
2396
                        "$ne": nil,
55✔
2397
                },
55✔
2398
        }
55✔
2399

55✔
2400
        update := bson.M{
55✔
2401
                "$inc": bson.M{
55✔
2402
                        StorageKeyDeploymentDeviceCount: increment,
55✔
2403
                },
55✔
2404
        }
55✔
2405

55✔
2406
        _, err := collection.UpdateOne(ctx, filter, update)
55✔
2407
        return err
55✔
2408
}
55✔
2409

2410
func (db *DataStoreMongo) SetDeploymentDeviceCount(
2411
        ctx context.Context,
2412
        deploymentID string,
2413
        count int,
2414
) error {
3✔
2415
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2416
        collection := database.Collection(CollectionDeployments)
3✔
2417

3✔
2418
        filter := bson.M{
3✔
2419
                "_id": deploymentID,
3✔
2420
                StorageKeyDeploymentDeviceCount: bson.M{
3✔
2421
                        "$eq": nil,
3✔
2422
                },
3✔
2423
        }
3✔
2424

3✔
2425
        update := bson.M{
3✔
2426
                "$set": bson.M{
3✔
2427
                        StorageKeyDeploymentDeviceCount: count,
3✔
2428
                },
3✔
2429
        }
3✔
2430

3✔
2431
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2432
        return err
3✔
2433
}
3✔
2434

2435
func (db *DataStoreMongo) DeviceCountByDeployment(ctx context.Context,
2436
        id string) (int, error) {
3✔
2437

3✔
2438
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2439
        collDevs := database.Collection(CollectionDevices)
3✔
2440

3✔
2441
        filter := bson.M{
3✔
2442
                StorageKeyDeviceDeploymentDeploymentID: id,
3✔
2443
                StorageKeyDeviceDeploymentDeleted: bson.D{
3✔
2444
                        {Key: "$exists", Value: false},
3✔
2445
                },
3✔
2446
        }
3✔
2447

3✔
2448
        deviceCount, err := collDevs.CountDocuments(ctx, filter)
3✔
2449
        if err != nil {
3✔
2450
                return 0, err
×
2451
        }
×
2452

2453
        return int(deviceCount), nil
3✔
2454
}
2455

2456
func (db *DataStoreMongo) UpdateStats(ctx context.Context,
2457
        id string, stats model.Stats) error {
6✔
2458

6✔
2459
        if len(id) == 0 {
7✔
2460
                return ErrStorageInvalidID
1✔
2461
        }
1✔
2462

2463
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2464
        collDpl := database.Collection(CollectionDeployments)
5✔
2465

5✔
2466
        deployment, err := model.NewDeployment()
5✔
2467
        if err != nil {
5✔
2468
                return errors.Wrap(err, "failed to create deployment")
×
2469
        }
×
2470

2471
        deployment.Stats = stats
5✔
2472
        var update bson.M
5✔
2473
        if deployment.IsFinished() {
5✔
2474
                now := time.Now()
×
2475

×
2476
                update = bson.M{
×
2477
                        "$set": bson.M{
×
2478
                                StorageKeyDeploymentStats:    stats,
×
2479
                                StorageKeyDeploymentFinished: &now,
×
2480
                        },
×
2481
                }
×
2482
        } else {
5✔
2483
                update = bson.M{
5✔
2484
                        "$set": bson.M{
5✔
2485
                                StorageKeyDeploymentStats: stats,
5✔
2486
                        },
5✔
2487
                }
5✔
2488
        }
5✔
2489

2490
        res, err := collDpl.UpdateOne(ctx, bson.M{"_id": id}, update)
5✔
2491
        if res != nil && res.MatchedCount == 0 {
7✔
2492
                return ErrStorageInvalidID
2✔
2493
        }
2✔
2494
        return err
3✔
2495
}
2496

2497
func (db *DataStoreMongo) UpdateStatsInc(ctx context.Context, id string,
2498
        stateFrom, stateTo model.DeviceDeploymentStatus) error {
8✔
2499

8✔
2500
        if len(id) == 0 {
9✔
2501
                return ErrStorageInvalidID
1✔
2502
        }
1✔
2503

2504
        if _, err := stateTo.MarshalText(); err != nil {
7✔
2505
                return ErrStorageInvalidInput
×
2506
        }
×
2507

2508
        // does not need any extra operations
2509
        // following query won't handle this case well and increase the state_to value
2510
        if stateFrom == stateTo {
8✔
2511
                return nil
1✔
2512
        }
1✔
2513

2514
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2515
        collDpl := database.Collection(CollectionDeployments)
6✔
2516

6✔
2517
        var update bson.M
6✔
2518

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

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

6✔
2538
        if res != nil && res.MatchedCount == 0 {
7✔
2539
                return ErrStorageInvalidID
1✔
2540
        }
1✔
2541

2542
        return err
5✔
2543
}
2544

2545
func (db *DataStoreMongo) IncrementDeploymentTotalSize(
2546
        ctx context.Context,
2547
        deploymentID string,
2548
        increment int64,
2549
) error {
3✔
2550
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
3✔
2551
        collection := database.Collection(CollectionDeployments)
3✔
2552

3✔
2553
        filter := bson.M{
3✔
2554
                "_id": deploymentID,
3✔
2555
        }
3✔
2556

3✔
2557
        update := bson.M{
3✔
2558
                "$inc": bson.M{
3✔
2559
                        StorageKeyDeploymentTotalSize: increment,
3✔
2560
                },
3✔
2561
        }
3✔
2562

3✔
2563
        _, err := collection.UpdateOne(ctx, filter, update)
3✔
2564
        return err
3✔
2565
}
3✔
2566

2567
func (db *DataStoreMongo) Find(ctx context.Context,
2568
        match model.Query) ([]*model.Deployment, int64, error) {
36✔
2569

36✔
2570
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
36✔
2571
        collDpl := database.Collection(CollectionDeployments)
36✔
2572

36✔
2573
        andq := []bson.M{}
36✔
2574

36✔
2575
        // filter by IDs
36✔
2576
        if match.IDs != nil {
36✔
2577
                tq := bson.M{
×
2578
                        "_id": bson.M{
×
2579
                                "$in": match.IDs,
×
2580
                        },
×
2581
                }
×
2582
                andq = append(andq, tq)
×
2583
        }
×
2584

2585
        // build deployment by name part of the query
2586
        if match.SearchText != "" {
52✔
2587
                // we must have indexing for text search
16✔
2588
                if !db.hasIndexing(ctx, db.client) {
17✔
2589
                        return nil, 0, ErrDeploymentStorageCannotExecQuery
1✔
2590
                }
1✔
2591

2592
                tq := bson.M{
15✔
2593
                        "$text": bson.M{
15✔
2594
                                "$search": "\"" + match.SearchText + "\"",
15✔
2595
                        },
15✔
2596
                }
15✔
2597

15✔
2598
                andq = append(andq, tq)
15✔
2599
        }
2600

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

2615
        // build deployment by type part of the query
2616
        if match.Type != "" {
37✔
2617
                if match.Type == model.DeploymentTypeConfiguration {
4✔
2618
                        andq = append(andq, bson.M{StorageKeyDeploymentType: match.Type})
2✔
2619
                } else if match.Type == model.DeploymentTypeSoftware {
2✔
2620
                        andq = append(andq, bson.M{
×
2621
                                "$or": []bson.M{
×
2622
                                        {StorageKeyDeploymentType: match.Type},
×
2623
                                        {StorageKeyDeploymentType: ""},
×
2624
                                },
×
2625
                        })
×
2626
                }
×
2627
        }
2628

2629
        query := bson.M{}
35✔
2630
        if len(andq) != 0 {
58✔
2631
                // use search criteria if any
23✔
2632
                query = bson.M{
23✔
2633
                        "$and": andq,
23✔
2634
                }
23✔
2635
        }
23✔
2636

2637
        if match.CreatedAfter != nil && match.CreatedBefore != nil {
35✔
2638
                query["created"] = bson.M{
×
2639
                        "$gte": match.CreatedAfter,
×
2640
                        "$lte": match.CreatedBefore,
×
2641
                }
×
2642
        } else if match.CreatedAfter != nil {
35✔
2643
                query["created"] = bson.M{
×
2644
                        "$gte": match.CreatedAfter,
×
2645
                }
×
2646
        } else if match.CreatedBefore != nil {
35✔
2647
                query["created"] = bson.M{
×
2648
                        "$lte": match.CreatedBefore,
×
2649
                }
×
2650
        }
×
2651

2652
        options := db.findOptions(match)
35✔
2653

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

2677
        return deployments, count, nil
35✔
2678
}
2679

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

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

5✔
2703
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
5✔
2704
        c := database.Collection(CollectionDeployments)
5✔
2705

5✔
2706
        queryFilters := make([]bson.M, 0)
5✔
2707
        queryFilters = append(queryFilters, bson.M{StorageKeyDeploymentActive: true})
5✔
2708
        queryFilters = append(queryFilters,
5✔
2709
                bson.M{StorageKeyDeploymentCreated: bson.M{"$gt": createdAfter}})
5✔
2710
        findQuery := bson.M{}
5✔
2711
        findQuery["$and"] = queryFilters
5✔
2712

5✔
2713
        findOptions := &mopts.FindOptions{}
5✔
2714
        findOptions.SetSkip(int64(skip))
5✔
2715
        findOptions.SetLimit(int64(limit))
5✔
2716

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

5✔
2724
        var deployments []*model.Deployment
5✔
2725

5✔
2726
        if err = cursor.All(ctx, &deployments); err != nil {
5✔
2727
                return nil, errors.Wrap(err, "failed to get deployments")
×
2728
        }
×
2729

2730
        return deployments, nil
5✔
2731
}
2732

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

2745
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
6✔
2746
        collDpl := database.Collection(CollectionDeployments)
6✔
2747

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

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

6✔
2768
        if res != nil && res.MatchedCount == 0 {
7✔
2769
                return ErrStorageInvalidID
1✔
2770
        }
1✔
2771

2772
        return err
5✔
2773
}
2774

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

4✔
2780
        if len(id) == 0 {
4✔
2781
                return false, ErrStorageInvalidID
×
2782
        }
×
2783

2784
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2785
        collDpl := database.Collection(CollectionDeployments)
4✔
2786

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

2799
        return true, nil
2✔
2800
}
2801

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

4✔
2807
        if len(artifactName) == 0 {
4✔
2808
                return false, ErrImagesStorageInvalidArtifactName
×
2809
        }
×
2810

2811
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
4✔
2812
        collDpl := database.Collection(CollectionDeployments)
4✔
2813

4✔
2814
        var tmp interface{}
4✔
2815
        query := bson.D{
4✔
2816
                {Key: StorageKeyDeploymentFinished, Value: nil},
4✔
2817
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
4✔
2818
        }
4✔
2819

4✔
2820
        projection := bson.M{
4✔
2821
                "_id": 1,
4✔
2822
        }
4✔
2823
        findOptions := mopts.FindOne()
4✔
2824
        findOptions.SetProjection(projection)
4✔
2825

4✔
2826
        if err := collDpl.FindOne(ctx, query, findOptions).Decode(&tmp); err != nil {
7✔
2827
                if err == mongo.ErrNoDocuments {
6✔
2828
                        return false, nil
3✔
2829
                }
3✔
2830
                return false, err
×
2831
        }
2832

2833
        return true, nil
1✔
2834
}
2835

2836
// ExistByArtifactId check if there is any deployment that uses give artifact
2837
func (db *DataStoreMongo) ExistByArtifactId(ctx context.Context,
2838
        id string) (bool, error) {
×
2839

×
2840
        if len(id) == 0 {
×
2841
                return false, ErrStorageInvalidID
×
2842
        }
×
2843

2844
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
2845
        collDpl := database.Collection(CollectionDeployments)
×
2846

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

2858
        return true, nil
×
2859
}
2860

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

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

2878
        return settings, nil
2✔
2879
}
2880

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

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

2900
        return err
2✔
2901
}
2902

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

1✔
2911
        query := bson.D{
1✔
2912
                {Key: StorageKeyDeploymentFinished, Value: nil},
1✔
2913
                {Key: StorageKeyDeploymentArtifactName, Value: artifactName},
1✔
2914
        }
1✔
2915
        update := bson.M{
1✔
2916
                "$set": bson.M{
1✔
2917
                        StorageKeyDeploymentArtifacts: artifactIDs,
1✔
2918
                },
1✔
2919
        }
1✔
2920

1✔
2921
        _, err := collDpl.UpdateMany(ctx, query, update)
1✔
2922
        return err
1✔
2923
}
1✔
2924

2925
func (db *DataStoreMongo) GetDeploymentIDsByArtifactNames(
2926
        ctx context.Context,
2927
        artifactNames []string,
NEW
2928
) ([]string, error) {
×
NEW
2929

×
NEW
2930
        database := db.client.Database(mstore.DbFromContext(ctx, DatabaseName))
×
NEW
2931
        collDpl := database.Collection(CollectionDeployments)
×
NEW
2932

×
NEW
2933
        query := bson.M{
×
NEW
2934
                StorageKeyDeploymentArtifactName: bson.M{
×
NEW
2935
                        "$in": artifactNames,
×
NEW
2936
                },
×
NEW
2937
        }
×
NEW
2938

×
NEW
2939
        projection := bson.M{
×
NEW
2940
                "_id": 1,
×
NEW
2941
        }
×
NEW
2942
        findOptions := mopts.Find()
×
NEW
2943
        findOptions.SetProjection(projection)
×
NEW
2944

×
NEW
2945
        cursor, err := collDpl.Find(ctx, query, findOptions)
×
NEW
2946
        if err != nil {
×
NEW
2947
                return []string{}, err
×
NEW
2948
        }
×
NEW
2949
        defer cursor.Close(ctx)
×
NEW
2950

×
NEW
2951
        var deployments []*model.Deployment
×
NEW
2952
        if err = cursor.All(ctx, &deployments); err != nil {
×
NEW
2953
                if err == mongo.ErrNoDocuments {
×
NEW
2954
                        return []string{}, nil
×
NEW
2955
                }
×
NEW
2956
                return []string{}, err
×
2957
        }
2958

NEW
2959
        ids := make([]string, len(deployments))
×
NEW
2960
        for i, d := range deployments {
×
NEW
2961
                ids[i] = d.Id
×
NEW
2962
        }
×
2963

NEW
2964
        return ids, nil
×
2965
}
2966

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