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

mendersoftware / reporting / 804740158

pending completion
804740158

Pull #112

gitlab-ci

Alf-Rune Siqveland
fix(mongo): Handle attempt to update a full map
Pull Request #112: fix(mongo): Handle attempt to update a full map

24 of 24 new or added lines in 1 file covered. (100.0%)

9 existing lines in 4 files now uncovered.

2751 of 3204 relevant lines covered (85.86%)

17.81 hits per line

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

94.35
/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

15
package mongo
16

17
import (
18
        "context"
19
        "crypto/tls"
20
        "fmt"
21
        "net/url"
22

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

28
        "github.com/mendersoftware/reporting/model"
29
)
30

31
const (
32
        collNameMapping   = "mapping"
33
        keyNameTenantID   = "tenant_id"
34
        indexNameTenantID = "tenant_id_ndx"
35
)
36

37
type MongoStoreConfig struct {
38
        // MongoURL holds the URL to the MongoDB server.
39
        MongoURL *url.URL
40
        // SSL Enables SSL for mongo connections
41
        SSL bool
42
        // SkipVerify controls whether a mongo client verifies the server's
43
        // certificate chain and host name.
44
        SSLSkipVerify bool
45
        // Username holds the user id credential for authenticating with the
46
        // MongoDB server.
47
        Username string
48
        // Password holds the password credential for authenticating with the
49
        // MongoDB server.
50
        Password string
51
        // DbName contains the name of the reporting database.
52
        DbName string
53
}
54

55
// newClient returns a mongo client
56
func newClient(ctx context.Context, config MongoStoreConfig) (*mongo.Client, error) {
2✔
57

2✔
58
        clientOptions := mopts.Client()
2✔
59
        if config.MongoURL == nil {
2✔
60
                return nil, errors.New("mongo: missing URL")
61
        }
62
        clientOptions.ApplyURI(config.MongoURL.String())
2✔
63

2✔
64
        if config.Username != "" {
2✔
65
                credentials := mopts.Credential{
66
                        Username: config.Username,
67
                }
68
                if config.Password != "" {
69
                        credentials.Password = config.Password
70
                        credentials.PasswordSet = true
71
                }
72
                clientOptions.SetAuth(credentials)
73
        }
74

75
        if config.SSL {
2✔
76
                tlsConfig := &tls.Config{}
77
                tlsConfig.InsecureSkipVerify = config.SSLSkipVerify
78
                clientOptions.SetTLSConfig(tlsConfig)
79
        }
80

81
        client, err := mongo.Connect(ctx, clientOptions)
2✔
82
        if err != nil {
2✔
83
                return nil, errors.Wrap(err, "mongo: failed to connect with server")
84
        }
85

86
        // Validate connection
87
        if err = client.Ping(ctx, nil); err != nil {
2✔
88
                return nil, errors.Wrap(err, "mongo: error reaching mongo server")
89
        }
90

91
        return client, nil
2✔
92
}
93

94
// MongoStore is the data storage service
95
type MongoStore struct {
96
        // client holds the reference to the client used to communicate with the
97
        // mongodb server.
98
        client *mongo.Client
99

100
        config MongoStoreConfig
101
}
102

103
// SetupDataStore returns the mongo data store and optionally runs migrations
104
func NewMongoStore(ctx context.Context, config MongoStoreConfig) (*MongoStore, error) {
2✔
105
        dbClient, err := newClient(ctx, config)
2✔
106
        if err != nil {
2✔
107
                return nil, err
108
        }
109
        return &MongoStore{
2✔
110
                client: dbClient,
2✔
111
                config: config,
2✔
112
        }, nil
2✔
113
}
114

115
func (db *MongoStore) Database(ctx context.Context, opt ...*mopts.DatabaseOptions) *mongo.Database {
116
        return db.client.Database(db.config.DbName, opt...)
117
}
118

119
// Ping verifies the connection to the database
120
func (db *MongoStore) Ping(ctx context.Context) error {
121
        res := db.client.
122
                Database(db.config.DbName).
123
                RunCommand(ctx, bson.M{"ping": 1})
124
        return res.Err()
125
}
126

127
// Close disconnects the client
128
func (db *MongoStore) Close(ctx context.Context) error {
2✔
129
        err := db.client.Disconnect(ctx)
2✔
130
        return err
2✔
131
}
2✔
132

133
//nolint:unused
134
func (db *MongoStore) DropDatabase(ctx context.Context) error {
135
        err := db.client.
136
                Database(db.config.DbName).
137
                Drop(ctx)
138
        return err
139
}
140

141
// GetMapping returns the mapping
142
func (db *MongoStore) GetMapping(ctx context.Context, tenantID string) (*model.Mapping, error) {
3✔
143
        query := bson.M{
3✔
144
                keyNameTenantID: tenantID,
3✔
145
        }
3✔
146
        res := db.client.
3✔
147
                Database(db.config.DbName).
3✔
148
                Collection(collNameMapping).
3✔
149
                FindOne(ctx, query)
3✔
150

3✔
151
        mapping := &model.Mapping{}
3✔
152
        err := res.Decode(mapping)
3✔
153
        if err == mongo.ErrNoDocuments {
5✔
154
                mapping = &model.Mapping{
2✔
155
                        TenantID:  tenantID,
2✔
156
                        Inventory: []string{},
2✔
157
                }
2✔
158
        } else if err != nil {
3✔
159
                return nil, errors.Wrap(err, "failed to update and get the mapping")
160
        }
161
        return mapping, nil
3✔
162
}
163

164
// UpdateAndGetMapping updates the mapping and returns it
165
func (db *MongoStore) UpdateAndGetMapping(ctx context.Context, tenantID string,
166
        inventory []string) (*model.Mapping, error) {
3✔
167
        inventoryLastField := fmt.Sprintf("inventory.%d", model.MaxMappingInventoryAttributes-1)
3✔
168
        query := bson.M{
3✔
169
                keyNameTenantID: tenantID,
3✔
170
                inventoryLastField: bson.M{
3✔
171
                        "$exists": false,
3✔
172
                },
3✔
173
        }
3✔
174
        update := bson.M{
3✔
175
                "$addToSet": bson.M{
3✔
176
                        "inventory": bson.M{
3✔
177
                                "$each": inventory,
3✔
178
                        },
3✔
179
                },
3✔
180
        }
3✔
181
        projection := bson.M{
3✔
182
                "tenant_id": 1,
3✔
183
                "inventory": bson.M{
3✔
184
                        "$slice": model.MaxMappingInventoryAttributes,
3✔
185
                },
3✔
186
        }
3✔
187
        opts := mopts.FindOneAndUpdate().
3✔
188
                SetReturnDocument(mopts.After).
3✔
189
                SetUpsert(true).
3✔
190
                SetProjection(projection)
3✔
191
        mapping := &model.Mapping{}
3✔
192
        err := db.client.
3✔
193
                Database(db.config.DbName).
3✔
194
                Collection(collNameMapping).
3✔
195
                FindOneAndUpdate(ctx, query, update, opts).
3✔
196
                Decode(mapping)
3✔
197

3✔
198
        if mongo.IsDuplicateKeyError(err) {
3✔
199
                // Tenant attribute quota already full
200
                err = db.client.
201
                        Database(db.config.DbName).
202
                        Collection(collNameMapping).
203
                        FindOne(ctx, bson.D{{Key: "tenant_id", Value: tenantID}},
204
                                mopts.FindOne().SetProjection(projection),
205
                        ).
206
                        Decode(mapping)
207
        }
208
        if err != nil {
3✔
UNCOV
209
                return nil, errors.Wrap(err, "failed to update and get the mapping")
UNCOV
210
        }
211
        return mapping, nil
3✔
212
}
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