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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

40.48
/backend/services/deployments/model/image.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 model
16

17
import (
18
        "context"
19
        "io"
20
        "path"
21
        "time"
22

23
        validation "github.com/go-ozzo/ozzo-validation/v4"
24
        "github.com/go-ozzo/ozzo-validation/v4/is"
25
        "github.com/pkg/errors"
26
        "go.mongodb.org/mongo-driver/bson"
27
        "go.mongodb.org/mongo-driver/bson/bsontype"
28

29
        "github.com/mendersoftware/mender-server/pkg/identity"
30
        "github.com/mendersoftware/mender-server/pkg/mongo/doc"
31
)
32

33
const (
34
        ArtifactFileSuffix = ".mender"
35
)
36

37
var (
38
        StorageKeyImageProvidesIdxKey   = "meta_artifact.provides_idx.key"
39
        StorageKeyImageProvidesIdxValue = "meta_artifact.provides_idx.value"
40
)
41

42
type ProvidesIdx map[string]string
43

UNCOV
44
func ImagePathFromContext(ctx context.Context, id string) string {
×
UNCOV
45
        imgPath := id
×
UNCOV
46
        if idty := identity.FromContext(ctx); idty != nil {
×
UNCOV
47
                imgPath = path.Join(idty.Tenant, id)
×
UNCOV
48
        }
×
UNCOV
49
        return imgPath
×
50
}
51

52
// Information provided by the user
53
type ImageMeta struct {
54
        // Image description
55
        Description string `json:"description,omitempty" valid:"length(1|4096),optional"`
56
}
57

58
// Creates new, empty ImageMeta
59
func NewImageMeta() *ImageMeta {
1✔
60
        return &ImageMeta{}
1✔
61
}
1✔
62

63
// Validate checks structure according to valid tags.
64
func (s ImageMeta) Validate() error {
1✔
65
        return validation.ValidateStruct(&s,
1✔
66
                validation.Field(&s.Description, lengthLessThan4096),
1✔
67
        )
1✔
68
}
1✔
69

70
// Structure with artifact version information
71
type ArtifactInfo struct {
72
        // Mender artifact format - the only possible value is "mender"
73
        //Format string `json:"format" valid:"string,equal("mender"),required"`
74
        Format string `json:"format" valid:"required"`
75

76
        // Mender artifact format version
77
        //Version uint `json:"version" valid:"uint,equal(1),required"`
78
        Version uint `json:"version" valid:"required"`
79
}
80

81
func (ai ArtifactInfo) Validate() error {
1✔
82
        return validation.ValidateStruct(&ai,
1✔
83
                validation.Field(&ai.Format, validation.Required),
1✔
84
                validation.Field(&ai.Version, validation.In(uint(1), uint(2), uint(3))),
1✔
85
        )
1✔
86
}
1✔
87

88
// Information provided by the Mender Artifact header
89
type ArtifactMeta struct {
90
        // artifact_name from artifact file
91
        Name string `json:"name" bson:"name" valid:"length(1|4096),required"`
92

93
        // Compatible device types for the application
94
        //nolint:lll
95
        DeviceTypesCompatible []string `json:"device_types_compatible" bson:"device_types_compatible" valid:"length(1|4096),required"`
96

97
        // Artifact version info
98
        Info *ArtifactInfo `json:"info"`
99

100
        // Flag that indicates if artifact is signed or not
101
        Signed bool `json:"signed" bson:"signed"`
102

103
        // List of updates
104
        Updates []Update `json:"updates" valid:"-"`
105

106
        // Provides is a map of artifact_provides used
107
        // for checking artifact (version 3) dependencies.
108
        //nolint: lll
109
        Provides map[string]string `json:"artifact_provides,omitempty" bson:"provides,omitempty" valid:"-"`
110

111
        // ProvidesIdx is special representation of provides
112
        // which makes possible to index and query using provides.
113
        ProvidesIdx ProvidesIdx `json:"-" bson:"provides_idx,omitempty"`
114

115
        // Depends is a map[string]interface{} (JSON) of artifact_depends used
116
        // for checking/validate against artifact (version 3) provides.
117
        Depends map[string]interface{} `json:"artifact_depends,omitempty" bson:"depends" valid:"-"`
118

119
        // ClearsProvides is a list of strings (JSON) of clears_artifact_provides used
120
        // for clearing already-installed artifact (version 3) provides.
121
        //nolint:lll
122
        ClearsProvides []string `json:"clears_artifact_provides,omitempty" bson:"clears_provides,omitempty" valid:"-"`
123
}
124

125
// MarshalBSON transparently creates depends_idx field on bson.Marshal
126
func (am ArtifactMeta) MarshalBSON() ([]byte, error) {
×
127
        if err := am.Validate(); err != nil {
×
128
                return nil, err
×
129
        }
×
130
        dependsIdx, err := doc.UnwindMap(am.Depends)
×
131
        if err != nil {
×
132
                return nil, err
×
133
        }
×
134
        doc := doc.DocumentFromStruct(am, bson.E{
×
135
                Key: "depends_idx", Value: dependsIdx,
×
136
        })
×
137
        return bson.Marshal(doc)
×
138
}
139

140
// MarshalBSONValue transparently creates depends_idx field on bson.MarshalValue
141
// which is called if ArtifactMeta is marshaled as an embedded document.
UNCOV
142
func (am ArtifactMeta) MarshalBSONValue() (bsontype.Type, []byte, error) {
×
UNCOV
143
        if err := am.Validate(); err != nil {
×
144
                return bson.TypeNull, nil, err
×
145
        }
×
UNCOV
146
        dependsIdx, err := doc.UnwindMap(am.Depends)
×
UNCOV
147
        if err != nil {
×
148
                return bson.TypeNull, nil, err
×
149
        }
×
UNCOV
150
        doc := doc.DocumentFromStruct(am, bson.E{
×
UNCOV
151
                Key: "depends_idx", Value: dependsIdx,
×
UNCOV
152
        })
×
UNCOV
153
        return bson.MarshalValue(doc)
×
154
}
155

156
// Validate checks structure according to valid tags.
157
func (am *ArtifactMeta) Validate() error {
1✔
158
        if am.Depends == nil {
2✔
159
                am.Depends = make(map[string]interface{})
1✔
160
        }
1✔
161
        am.Depends["device_type"] = am.DeviceTypesCompatible
1✔
162

1✔
163
        return validation.ValidateStruct(am,
1✔
164
                validation.Field(&am.Name, validation.Required, lengthIn1To4096),
1✔
165
                validation.Field(&am.DeviceTypesCompatible,
1✔
166
                        validation.Required,
1✔
167
                        lengthIn0To200,
1✔
168
                        validation.Each(lengthIn1To4096),
1✔
169
                ),
1✔
170
                validation.Field(&am.Info),
1✔
171
        )
1✔
172
}
173

174
func NewArtifactMeta() *ArtifactMeta {
1✔
175
        return &ArtifactMeta{}
1✔
176
}
1✔
177

178
// Image YOCTO image with user application
179
type Image struct {
180
        // Image ID
181
        Id string `json:"id" bson:"_id" valid:"uuidv4,required"`
182

183
        // User provided field set
184
        *ImageMeta `bson:"meta"`
185

186
        // Field set provided with yocto image
187
        *ArtifactMeta `bson:"meta_artifact"`
188

189
        // Artifact total size
190
        Size int64 `json:"size" bson:"size" valid:"-"`
191

192
        // Last modification time, including image upload time
193
        Modified *time.Time `json:"modified" valid:"-"`
194
}
195

UNCOV
196
func (img Image) MarshalBSON() (b []byte, err error) {
×
UNCOV
197
        return bson.Marshal(doc.DocumentFromStruct(img))
×
UNCOV
198
}
×
199

UNCOV
200
func (img Image) MarshalBSONValue() (bsontype.Type, []byte, error) {
×
UNCOV
201
        return bson.MarshalValue(doc.DocumentFromStruct(img))
×
UNCOV
202
}
×
203

204
// Validate checks structure according to valid tags.
205
func (s Image) Validate() error {
1✔
206
        return validation.ValidateStruct(&s,
1✔
207
                validation.Field(&s.Id, validation.Required, is.UUID),
1✔
208
                validation.Field(&s.ImageMeta),
1✔
209
                validation.Field(&s.ArtifactMeta),
1✔
210
        )
1✔
211
}
1✔
212

213
// NewImage creates new software image object.
214
func NewImage(
215
        id string,
216
        metaConstructor *ImageMeta,
217
        metaArtifactConstructor *ArtifactMeta,
218
        artifactSize int64) *Image {
1✔
219

1✔
220
        now := time.Now()
1✔
221

1✔
222
        return &Image{
1✔
223
                ImageMeta:    metaConstructor,
1✔
224
                ArtifactMeta: metaArtifactConstructor,
1✔
225
                Modified:     &now,
1✔
226
                Id:           id,
1✔
227
                Size:         artifactSize,
1✔
228
        }
1✔
229
}
1✔
230

231
// SetModified set last modification time for the image.
232
func (s *Image) SetModified(time time.Time) {
×
233
        s.Modified = &time
×
234
}
×
235

236
type ReadCounter interface {
237
        io.Reader
238
        // Count returns the number of bytes read.
239
        Count() int64
240
}
241

242
// MultipartUploadMsg is a structure with fields extracted from the multipart/form-data form
243
// send in the artifact upload request
244
type MultipartUploadMsg struct {
245
        // user metadata constructor
246
        MetaConstructor *ImageMeta
247
        // ArtifactID contains the artifact ID
248
        ArtifactID string
249
        // reader pointing to the beginning of the artifact data
250
        ArtifactReader io.Reader
251
}
252

253
// MultipartGenerateImageMsg is a structure with fields extracted from the multipart/form-data
254
// form sent in the artifact generation request
255
type MultipartGenerateImageMsg struct {
256
        Name                  string    `json:"name"`
257
        Description           string    `json:"description"`
258
        DeviceTypesCompatible []string  `json:"device_types_compatible"`
259
        Type                  string    `json:"type"`
260
        Args                  string    `json:"args"`
261
        ArtifactID            string    `json:"artifact_id"`
262
        GetArtifactURI        string    `json:"get_artifact_uri"`
263
        DeleteArtifactURI     string    `json:"delete_artifact_uri"`
264
        TenantID              string    `json:"tenant_id"`
265
        Token                 string    `json:"token"`
266
        FileReader            io.Reader `json:"-"`
267
}
268

UNCOV
269
func (msg MultipartGenerateImageMsg) Validate() error {
×
UNCOV
270
        if err := validation.ValidateStruct(&msg,
×
UNCOV
271
                validation.Field(&msg.Name, validation.Required),
×
UNCOV
272
                validation.Field(&msg.DeviceTypesCompatible, validation.Required),
×
UNCOV
273
                validation.Field(&msg.Type, validation.Required),
×
UNCOV
274
        ); err != nil {
×
275
                return err
×
276
        }
×
277
        // Somehow FileReader is not covered by "required" rule.
UNCOV
278
        if msg.FileReader == nil {
×
279
                return errors.New("missing 'file' section")
×
280
        }
×
UNCOV
281
        return nil
×
282
}
283

284
type provideInternal struct {
285
        Key   string
286
        Value string
287
}
288

UNCOV
289
func (p ProvidesIdx) MarshalBSONValue() (bsontype.Type, []byte, error) {
×
UNCOV
290
        attrs := make([]provideInternal, len(p))
×
UNCOV
291
        i := 0
×
UNCOV
292
        for k, v := range p {
×
UNCOV
293
                attrs[i].Key = k
×
UNCOV
294
                attrs[i].Value = v
×
UNCOV
295
                i++
×
UNCOV
296
        }
×
UNCOV
297
        return bson.MarshalValue(attrs)
×
298
}
299

UNCOV
300
func (p *ProvidesIdx) UnmarshalBSONValue(t bsontype.Type, b []byte) error {
×
UNCOV
301
        raw := bson.Raw(b)
×
UNCOV
302
        elems, err := raw.Elements()
×
UNCOV
303
        if err != nil {
×
304
                return err
×
305
        }
×
UNCOV
306
        *p = make(ProvidesIdx, len(elems))
×
UNCOV
307
        var tmp provideInternal
×
UNCOV
308
        for _, elem := range elems {
×
UNCOV
309
                err = elem.Value().Unmarshal(&tmp)
×
UNCOV
310
                if err != nil {
×
311
                        return err
×
312
                }
×
UNCOV
313
                (*p)[tmp.Key] = tmp.Value
×
314
        }
315

UNCOV
316
        return nil
×
317
}
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