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

mendersoftware / iot-manager / 1445709825

09 Sep 2024 11:23AM UTC coverage: 86.917% (-0.3%) from 87.172%
1445709825

Pull #303

gitlab-ci

alfrunes
ci: update gitlab runner

Moving to deprecated docker gitlab public runner, to a self-hosted runner

Ticket: SEC-1133
Changelog: none

Signed-off-by: Roberto Giovanardi <roberto.giovanardi@northern.tech>
(cherry picked from commit bb026f77c)
Pull Request #303: Cherry-pick MEN-7478 to 1.3.x (3.7.x)

42 of 54 new or added lines in 2 files covered. (77.78%)

128 existing lines in 10 files now uncovered.

3169 of 3646 relevant lines covered (86.92%)

9.75 hits per line

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

85.01
/store/mongo/datastore_mongo.go
1
// Copyright 2022 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

15
package mongo
16

17
import (
18
        "context"
19
        "crypto/tls"
20
        "fmt"
21
        "strings"
22
        "time"
23

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

30
        "github.com/mendersoftware/go-lib-micro/config"
31
        "github.com/mendersoftware/go-lib-micro/identity"
32
        mstore "github.com/mendersoftware/go-lib-micro/store/v2"
33

34
        dconfig "github.com/mendersoftware/iot-manager/config"
35
        "github.com/mendersoftware/iot-manager/model"
36
        "github.com/mendersoftware/iot-manager/store"
37
)
38

39
const (
40
        CollNameDevices      = "devices"
41
        CollNameIntegrations = "integrations"
42

43
        KeyID             = "_id"
44
        KeyIntegrationIDs = "integration_ids"
45
        KeyProvider       = "provider"
46
        KeyTenantID       = "tenant_id"
47
        KeyCredentials    = "credentials"
48

49
        ConnectTimeoutSeconds = 10
50
        defaultAutomigrate    = false
51
)
52

53
var (
54
        ErrFailedToGetIntegrations = errors.New("failed to get integrations")
55
        ErrFailedToGetDevice       = errors.New("failed to get device")
56
        ErrFailedToGetSettings     = errors.New("failed to get settings")
57
)
58

59
type Config struct {
60
        Automigrate *bool
61
        DbName      *string
62
}
63

64
func NewConfig() *Config {
76✔
65
        conf := new(Config)
76✔
66
        return conf.
76✔
67
                SetAutomigrate(defaultAutomigrate).
76✔
68
                SetDbName(DbName)
76✔
69
}
76✔
70

71
func (c *Config) SetAutomigrate(migrate bool) *Config {
116✔
72
        c.Automigrate = &migrate
116✔
73
        return c
116✔
74
}
116✔
75

76
func (c *Config) SetDbName(name string) *Config {
111✔
77
        c.DbName = &name
111✔
78
        return c
111✔
79
}
111✔
80

81
func mergeConfig(configs ...*Config) *Config {
39✔
82
        config := NewConfig()
39✔
83
        for _, c := range configs {
78✔
84
                if c == nil {
39✔
85
                        continue
×
86
                }
87
                if c.Automigrate != nil {
78✔
88
                        config.SetAutomigrate(*c.Automigrate)
39✔
89
                }
39✔
90
                if c.DbName != nil {
78✔
91
                        config.DbName = c.DbName
39✔
92
                }
39✔
93
        }
94
        return config
39✔
95
}
96

97
// SetupDataStore returns the mongo data store and optionally runs migrations
98
func SetupDataStore(conf *Config) (store.DataStore, error) {
2✔
99
        conf = mergeConfig(conf)
2✔
100
        ctx := context.Background()
2✔
101
        dbClient, err := NewClient(ctx, config.Config)
2✔
102
        if err != nil {
2✔
103
                return nil, errors.New(fmt.Sprintf("failed to connect to db: %v", err))
×
104
        }
×
105
        dataStore := NewDataStoreWithClient(dbClient, conf)
2✔
106

2✔
107
        return dataStore, dataStore.Migrate(ctx)
2✔
108
}
109

110
func (ds *DataStoreMongo) Migrate(ctx context.Context) error {
2✔
111
        return Migrate(ctx, *ds.DbName, DbVersion, ds.client, *ds.Automigrate)
2✔
112
}
2✔
113

114
// NewClient returns a mongo client
115
func NewClient(ctx context.Context, c config.Reader) (*mongo.Client, error) {
2✔
116

2✔
117
        clientOptions := mopts.Client()
2✔
118
        mongoURL := c.GetString(dconfig.SettingMongo)
2✔
119
        if !strings.Contains(mongoURL, "://") {
2✔
120
                return nil, errors.Errorf("Invalid mongoURL %q: missing schema.",
×
121
                        mongoURL)
×
122
        }
×
123
        clientOptions.ApplyURI(mongoURL)
2✔
124

2✔
125
        username := c.GetString(dconfig.SettingDbUsername)
2✔
126
        if username != "" {
2✔
127
                credentials := mopts.Credential{
×
128
                        Username: c.GetString(dconfig.SettingDbUsername),
×
129
                }
×
130
                password := c.GetString(dconfig.SettingDbPassword)
×
131
                if password != "" {
×
132
                        credentials.Password = password
×
133
                        credentials.PasswordSet = true
×
134
                }
×
135
                clientOptions.SetAuth(credentials)
×
136
        }
137

138
        if c.GetBool(dconfig.SettingDbSSL) {
2✔
139
                tlsConfig := &tls.Config{}
×
140
                tlsConfig.InsecureSkipVerify = c.GetBool(dconfig.SettingDbSSLSkipVerify)
×
141
                clientOptions.SetTLSConfig(tlsConfig)
×
142
        }
×
143

144
        // Set 10s timeout
145
        if _, ok := ctx.Deadline(); !ok {
4✔
146
                var cancel context.CancelFunc
2✔
147
                ctx, cancel = context.WithTimeout(ctx, ConnectTimeoutSeconds*time.Second)
2✔
148
                defer cancel()
2✔
149
        }
2✔
150
        client, err := mongo.Connect(ctx, clientOptions)
2✔
151
        if err != nil {
2✔
152
                return nil, errors.Wrap(err, "Failed to connect to mongo server")
×
153
        }
×
154

155
        // Validate connection
156
        if err = client.Ping(ctx, nil); err != nil {
2✔
157
                return nil, errors.Wrap(err, "Error reaching mongo server")
×
158
        }
×
159

160
        return client, nil
2✔
161
}
162

163
// DataStoreMongo is the data storage service
164
type DataStoreMongo struct {
165
        // client holds the reference to the client used to communicate with the
166
        // mongodb server.
167
        client *mongo.Client
168

169
        *Config
170
}
171

172
// NewDataStoreWithClient initializes a DataStore object
173
func NewDataStoreWithClient(client *mongo.Client, conf ...*Config) *DataStoreMongo {
37✔
174
        return &DataStoreMongo{
37✔
175
                client: client,
37✔
176
                Config: mergeConfig(conf...),
37✔
177
        }
37✔
178
}
37✔
179

180
// Ping verifies the connection to the database
181
func (db *DataStoreMongo) Ping(ctx context.Context) error {
×
182
        res := db.client.Database(*db.DbName).RunCommand(ctx, bson.M{"ping": 1})
×
183
        return res.Err()
×
184
}
×
185

186
func (db *DataStoreMongo) Close() error {
2✔
187
        ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
2✔
188
        defer cancel()
2✔
189
        err := db.client.Disconnect(ctx)
2✔
190
        return err
2✔
191
}
2✔
192

193
func (db *DataStoreMongo) Collection(
194
        name string,
195
        opts ...*mopts.CollectionOptions,
196
) *mongo.Collection {
67✔
197
        return db.client.Database(*db.DbName).Collection(name, opts...)
67✔
198
}
67✔
199

200
func (db *DataStoreMongo) GetIntegrations(
201
        ctx context.Context,
202
        fltr model.IntegrationFilter,
203
) ([]model.Integration, error) {
15✔
204
        var (
15✔
205
                err      error
15✔
206
                tenantID string
15✔
207
                results  = []model.Integration{}
15✔
208
        )
15✔
209
        id := identity.FromContext(ctx)
15✔
210
        if id != nil {
29✔
211
                tenantID = id.Tenant
14✔
212
        }
14✔
213

214
        collIntegrations := db.Collection(CollNameIntegrations)
15✔
215
        findOpts := mopts.Find().
15✔
216
                SetSort(bson.D{{
15✔
217
                        Key:   KeyProvider,
15✔
218
                        Value: 1,
15✔
219
                }, {
15✔
220
                        Key:   KeyID,
15✔
221
                        Value: 1,
15✔
222
                }}).SetSkip(fltr.Skip)
15✔
223
        if fltr.Limit > 0 {
19✔
224
                findOpts.SetLimit(fltr.Limit)
4✔
225
        }
4✔
226

227
        fltrDoc := make(bson.D, 0, 3)
15✔
228
        fltrDoc = append(fltrDoc, bson.E{Key: KeyTenantID, Value: tenantID})
15✔
229
        if fltr.Provider != model.ProviderEmpty {
16✔
230
                fltrDoc = append(fltrDoc, bson.E{Key: KeyProvider, Value: fltr.Provider})
1✔
231
        }
1✔
232
        if fltr.IDs != nil {
18✔
233
                switch len(fltr.IDs) {
3✔
234
                case 0:
1✔
235
                        // Won't match anything, let's save the request
1✔
236
                        return results, nil
1✔
237
                case 1:
1✔
238
                        fltrDoc = append(fltrDoc, bson.E{Key: KeyID, Value: fltr.IDs[0]})
1✔
239

240
                default:
1✔
241
                        fltrDoc = append(fltrDoc, bson.E{Key: KeyID, Value: bson.D{{
1✔
242
                                Key: "$in", Value: fltr.IDs,
1✔
243
                        }}})
1✔
244
                }
245
        }
246

247
        cur, err := collIntegrations.Find(ctx,
14✔
248
                fltrDoc,
14✔
249
                findOpts,
14✔
250
        )
14✔
251
        if err != nil {
15✔
252
                return nil, errors.Wrap(err, "error executing integrations collection request")
1✔
253
        }
1✔
254
        if err = cur.All(ctx, &results); err != nil {
14✔
255
                return nil, errors.Wrap(err, "error retrieving integrations collection results")
1✔
256
        }
1✔
257

258
        return results, nil
12✔
259
}
260

261
func (db *DataStoreMongo) GetIntegrationById(
262
        ctx context.Context,
263
        integrationId uuid.UUID,
264
) (*model.Integration, error) {
3✔
265
        var integration = new(model.Integration)
3✔
266

3✔
267
        collIntegrations := db.Collection(CollNameIntegrations)
3✔
268
        tenantId := ""
3✔
269
        id := identity.FromContext(ctx)
3✔
270
        if id != nil {
5✔
271
                tenantId = id.Tenant
2✔
272
        }
2✔
273

274
        if err := collIntegrations.FindOne(ctx,
3✔
275
                bson.M{KeyTenantID: tenantId},
3✔
276
        ).Decode(&integration); err != nil {
5✔
277
                switch err {
2✔
278
                case mongo.ErrNoDocuments:
1✔
279
                        return nil, store.ErrObjectNotFound
1✔
280
                default:
1✔
281
                        return nil, errors.Wrap(err, ErrFailedToGetIntegrations.Error())
1✔
282
                }
283
        }
284
        return integration, nil
1✔
285
}
286

287
func (db *DataStoreMongo) CreateIntegration(
288
        ctx context.Context,
289
        integration model.Integration,
290
) (*model.Integration, error) {
8✔
291
        var tenantID string
8✔
292
        if id := identity.FromContext(ctx); id != nil {
14✔
293
                tenantID = id.Tenant
6✔
294
        }
6✔
295
        collIntegrations := db.Collection(CollNameIntegrations)
8✔
296

8✔
297
        // Force a single integration per tenant by utilizing unique '_id' index
8✔
298
        integration.ID = uuid.NewSHA1(uuid.NameSpaceOID, []byte(tenantID))
8✔
299

8✔
300
        _, err := collIntegrations.
8✔
301
                InsertOne(ctx, mstore.WithTenantID(ctx, integration))
8✔
302
        if err != nil {
9✔
303
                if isDuplicateKeyError(err) {
1✔
UNCOV
304
                        return nil, store.ErrObjectExists
×
UNCOV
305
                }
×
306
                return nil, errors.Wrapf(err, "failed to store integration %v", integration)
1✔
307
        }
308

309
        return &integration, err
7✔
310
}
311

312
func (db *DataStoreMongo) SetIntegrationCredentials(
313
        ctx context.Context,
314
        integrationId uuid.UUID,
315
        credentials model.Credentials,
316
) error {
2✔
317
        collIntegrations := db.client.Database(*db.DbName).Collection(CollNameIntegrations)
2✔
318

2✔
319
        fltr := bson.D{{
2✔
320
                Key:   KeyID,
2✔
321
                Value: integrationId,
2✔
322
        }}
2✔
323

2✔
324
        update := bson.M{
2✔
325
                "$set": bson.D{
2✔
326
                        {
2✔
327
                                Key:   KeyCredentials,
2✔
328
                                Value: credentials,
2✔
329
                        },
2✔
330
                },
2✔
331
        }
2✔
332

2✔
333
        result, err := collIntegrations.UpdateOne(ctx,
2✔
334
                mstore.WithTenantID(ctx, fltr),
2✔
335
                update,
2✔
336
        )
2✔
337
        if result.MatchedCount == 0 {
3✔
338
                return store.ErrObjectNotFound
1✔
339
        }
1✔
340

341
        return errors.Wrap(err, "mongo: failed to set integration credentials")
1✔
342
}
343

344
func (db *DataStoreMongo) RemoveIntegration(ctx context.Context, integrationId uuid.UUID) error {
2✔
345
        collIntegrations := db.client.Database(*db.DbName).Collection(CollNameIntegrations)
2✔
346
        fltr := bson.D{{
2✔
347
                Key:   KeyID,
2✔
348
                Value: integrationId,
2✔
349
        }}
2✔
350
        res, err := collIntegrations.DeleteOne(ctx, mstore.WithTenantID(ctx, fltr))
2✔
351
        if err != nil {
2✔
UNCOV
352
                return err
×
353
        } else if res.DeletedCount == 0 {
3✔
354
                return store.ErrObjectNotFound
1✔
355
        }
1✔
356
        return nil
1✔
357
}
358

359
// DoDevicesExistByIntegrationID checks if there is at least one device connected
360
// with given integration ID
361
func (db *DataStoreMongo) DoDevicesExistByIntegrationID(
362
        ctx context.Context,
363
        integrationID uuid.UUID,
364
) (bool, error) {
2✔
365
        var (
2✔
366
                err error
2✔
367
        )
2✔
368
        collDevices := db.client.Database(*db.DbName).Collection(CollNameDevices)
2✔
369

2✔
370
        fltr := bson.D{
2✔
371
                {
2✔
372
                        Key: KeyIntegrationIDs, Value: integrationID,
2✔
373
                },
2✔
374
        }
2✔
375
        if err = collDevices.FindOne(ctx, mstore.WithTenantID(ctx, fltr)).Err(); err != nil {
3✔
376
                if err == mongo.ErrNoDocuments {
2✔
377
                        return false, nil
1✔
378
                } else {
1✔
UNCOV
379
                        return false, err
×
UNCOV
380
                }
×
381
        }
382
        return true, nil
1✔
383
}
384

385
func (db *DataStoreMongo) GetDeviceByIntegrationID(
386
        ctx context.Context,
387
        deviceID string,
388
        integrationID uuid.UUID,
389
) (*model.Device, error) {
4✔
390
        var device *model.Device
4✔
391

4✔
392
        collDevices := db.Collection(CollNameDevices)
4✔
393
        tenantId := ""
4✔
394
        id := identity.FromContext(ctx)
4✔
395
        if id != nil {
6✔
396
                tenantId = id.Tenant
2✔
397
        }
2✔
398

399
        filter := bson.D{{
4✔
400
                Key: KeyTenantID, Value: tenantId,
4✔
401
        }, {
4✔
402
                Key: KeyID, Value: deviceID,
4✔
403
        }, {
4✔
404
                Key: KeyIntegrationIDs, Value: integrationID,
4✔
405
        }}
4✔
406
        if err := collDevices.FindOne(ctx,
4✔
407
                filter,
4✔
408
        ).Decode(&device); err != nil {
6✔
409
                switch err {
2✔
410
                case mongo.ErrNoDocuments:
1✔
411
                        return nil, store.ErrObjectNotFound
1✔
412
                default:
1✔
413
                        return nil, errors.Wrap(err, ErrFailedToGetDevice.Error())
1✔
414
                }
415
        }
416
        return device, nil
2✔
417
}
418

419
func (db *DataStoreMongo) GetDevice(
420
        ctx context.Context,
421
        deviceID string,
422
) (*model.Device, error) {
10✔
423
        var (
10✔
424
                tenantID string
10✔
425
                result   *model.Device = new(model.Device)
10✔
426
        )
10✔
427
        if id := identity.FromContext(ctx); id != nil {
17✔
428
                tenantID = id.Tenant
7✔
429
        }
7✔
430
        filter := bson.D{{
10✔
431
                Key: KeyID, Value: deviceID,
10✔
432
        }, {
10✔
433
                Key: KeyTenantID, Value: tenantID,
10✔
434
        }}
10✔
435
        collDevices := db.Collection(CollNameDevices)
10✔
436

10✔
437
        err := collDevices.FindOne(ctx, filter).
10✔
438
                Decode(result)
10✔
439
        if err == mongo.ErrNoDocuments {
14✔
440
                return nil, store.ErrObjectNotFound
4✔
441
        }
4✔
442
        return result, err
6✔
443
}
444

445
func (db *DataStoreMongo) DeleteDevice(ctx context.Context, deviceID string) error {
8✔
446
        var tenantID string
8✔
447
        if id := identity.FromContext(ctx); id != nil {
14✔
448
                tenantID = id.Tenant
6✔
449
        }
6✔
450
        collDevices := db.Collection(CollNameDevices)
8✔
451

8✔
452
        filter := bson.D{{
8✔
453
                Key: KeyID, Value: deviceID,
8✔
454
        }, {
8✔
455
                Key: KeyTenantID, Value: tenantID,
8✔
456
        }}
8✔
457

8✔
458
        res, err := collDevices.DeleteOne(ctx, filter)
8✔
459
        if err != nil {
9✔
460
                return err
1✔
461
        } else if res.DeletedCount == 0 {
11✔
462
                return store.ErrObjectNotFound
3✔
463
        }
3✔
464
        return nil
4✔
465
}
466

467
func (db *DataStoreMongo) RemoveDevicesFromIntegration(
468
        ctx context.Context,
469
        integrationID uuid.UUID,
UNCOV
470
) (int64, error) {
×
UNCOV
471
        var tenantID string
×
UNCOV
472
        if id := identity.FromContext(ctx); id != nil {
×
UNCOV
473
                tenantID = id.Tenant
×
UNCOV
474
        }
×
UNCOV
475
        filter := bson.D{{
×
476
                Key: KeyTenantID, Value: tenantID,
×
477
        }, {
×
478
                Key: KeyIntegrationIDs, Value: integrationID,
×
479
        }}
×
480
        update := bson.D{{
×
481
                Key: "$pull", Value: bson.D{{
×
482
                        Key: KeyIntegrationIDs, Value: integrationID,
×
483
                }},
×
484
        }}
×
485

×
486
        collDevices := db.Collection(CollNameDevices)
×
487

×
488
        res, err := collDevices.UpdateMany(ctx, filter, update)
×
489
        if res != nil {
×
490
                return res.ModifiedCount, err
×
491
        }
×
492
        return 0, errors.Wrap(err, "mongo: failed to remove device from integration")
×
493
}
494

495
func (db *DataStoreMongo) UpsertDeviceIntegrations(
496
        ctx context.Context,
497
        deviceID string,
498
        integrationIDs []uuid.UUID,
499
) (*model.Device, error) {
5✔
500
        var (
5✔
501
                tenantID string
5✔
502
                result   = new(model.Device)
5✔
503
        )
5✔
504
        if id := identity.FromContext(ctx); id != nil {
8✔
505
                tenantID = id.Tenant
3✔
506
        }
3✔
507
        if integrationIDs == nil {
7✔
508
                integrationIDs = []uuid.UUID{}
2✔
509
        }
2✔
510
        filter := bson.D{{
5✔
511
                Key: KeyID, Value: deviceID,
5✔
512
        }, {
5✔
513
                Key: KeyTenantID, Value: tenantID,
5✔
514
        }}
5✔
515
        update := bson.D{{
5✔
516
                Key: "$addToSet", Value: bson.D{{
5✔
517
                        Key: KeyIntegrationIDs, Value: bson.D{{
5✔
518
                                Key: "$each", Value: integrationIDs,
5✔
519
                        }},
5✔
520
                }},
5✔
521
        }}
5✔
522
        updateOpts := mopts.FindOneAndUpdate().
5✔
523
                SetUpsert(true).
5✔
524
                SetReturnDocument(mopts.After)
5✔
525
        collDevices := db.Collection(CollNameDevices)
5✔
526
        err := collDevices.FindOneAndUpdate(ctx, filter, update, updateOpts).
5✔
527
                Decode(result)
5✔
528
        return result, err
5✔
529
}
530

531
func (db *DataStoreMongo) GetAllDevices(ctx context.Context) (store.Iterator, error) {
1✔
532
        collDevs := db.Collection(CollNameDevices)
1✔
533

1✔
534
        return collDevs.Find(ctx,
1✔
535
                bson.D{},
1✔
536
                mopts.Find().
1✔
537
                        SetSort(bson.D{{Key: KeyTenantID, Value: 1}}),
1✔
538
        )
1✔
539

1✔
540
}
1✔
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