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

mendersoftware / mender-artifact / 1279561887

06 May 2024 07:18AM UTC coverage: 73.657% (-3.8%) from 77.428%
1279561887

Pull #605

gitlab-ci

alfrunes
chore(make): Refactor `coverage` target and disable pkcs11 on non-linux

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #605: Allow using mender-artifact without `cgo`

4 of 6 new or added lines in 1 file covered. (66.67%)

271 existing lines in 13 files now uncovered.

5469 of 7425 relevant lines covered (73.66%)

77.7 hits per line

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

69.89
/artifact/metadata.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 artifact
16

17
import (
18
        "bytes"
19
        "encoding/json"
20
        "fmt"
21
        "io"
22
        "strings"
23

24
        "github.com/pkg/errors"
25
)
26

27
// WriteValidator is the interface that wraps the io.Writer interface and
28
// Validate method.
29
type WriteValidator interface {
30
        io.Writer
31
        Validate() error
32
}
33

34
// ErrValidatingData is an error returned by Validate() in case of
35
// invalid data.
36
var ErrValidatingData = errors.New("error validating data")
37

38
// Info contains the information about the format and the version
39
// of artifact archive.
40
type Info struct {
41
        Format  string `json:"format"`
42
        Version int    `json:"version"`
43
}
44

45
// Validate performs sanity checks on artifact info.
46
func (i Info) Validate() error {
15✔
47
        if len(i.Format) == 0 || i.Version == 0 {
25✔
48
                return errors.Wrap(ErrValidatingData, "Artifact Info needs a format type and a version")
10✔
49
        }
10✔
50
        return nil
5✔
51
}
52

53
func decode(p []byte, data WriteValidator) error {
10✔
54
        if len(p) == 0 {
12✔
55
                return nil
2✔
56
        }
2✔
57

58
        dec := json.NewDecoder(bytes.NewReader(p))
8✔
59
        dec.DisallowUnknownFields()
8✔
60
        err := dec.Decode(data)
8✔
61
        if err != nil {
8✔
UNCOV
62
                return err
×
UNCOV
63
        }
×
64
        return nil
8✔
65
}
66

67
func (i *Info) Write(p []byte) (n int, err error) {
2✔
68
        if err := decode(p, i); err != nil {
2✔
69
                return 0, err
×
70
        }
×
71
        return len(p), nil
2✔
72
}
73

74
// UpdateType provides information about the type of update.
75
// At the moment the only built-in type is "rootfs-image".
76
type UpdateType struct {
77
        Type *string `json:"type"`
78
}
79

80
// HeaderInfoer wraps headerInfo version 2 and 3,
81
// in order to supply the artifact reader with the information it needs.
82
type HeaderInfoer interface {
83
        Write(b []byte) (n int, err error)
84
        GetArtifactName() string
85
        GetCompatibleDevices() []string
86
        GetUpdates() []UpdateType
87
        GetArtifactDepends() *ArtifactDepends
88
        GetArtifactProvides() *ArtifactProvides
89
}
90

91
// HeaderInfo contains information of number and type of update files
92
// archived in Mender metadata archive.
93
type HeaderInfo struct {
94
        ArtifactName      string       `json:"artifact_name"`
95
        Updates           []UpdateType `json:"updates"`
96
        CompatibleDevices []string     `json:"device_types_compatible"`
97
}
98

UNCOV
99
func (h *HeaderInfo) UnmarshalJSON(b []byte) error {
×
UNCOV
100
        type Alias HeaderInfo
×
UNCOV
101
        buf := &Alias{}
×
UNCOV
102
        if err := json.Unmarshal(b, &buf); err != nil {
×
103
                return err
×
104
        }
×
UNCOV
105
        if len(buf.CompatibleDevices) == 0 {
×
106
                return ErrCompatibleDevices
×
107
        }
×
UNCOV
108
        h.ArtifactName = buf.ArtifactName
×
UNCOV
109
        h.Updates = buf.Updates
×
UNCOV
110
        h.CompatibleDevices = buf.CompatibleDevices
×
UNCOV
111
        return nil
×
112
}
113

114
func NewHeaderInfo(
115
        artifactName string,
116
        updates []UpdateType,
117
        compatibleDevices []string,
118
) *HeaderInfo {
2✔
119
        return &HeaderInfo{
2✔
120
                ArtifactName:      artifactName,
2✔
121
                Updates:           updates,
2✔
122
                CompatibleDevices: compatibleDevices,
2✔
123
        }
2✔
124
}
2✔
125

126
// Satisfy HeaderInfoer interface for the artifact reader.
127
func (hi *HeaderInfo) GetArtifactName() string {
2✔
128
        return hi.ArtifactName
2✔
129
}
2✔
130

131
// Satisfy HeaderInfoer interface for the artifact reader.
132
func (hi *HeaderInfo) GetCompatibleDevices() []string {
2✔
133
        return hi.CompatibleDevices
2✔
134
}
2✔
135

136
// Satisfy HeaderInfoer interface for the artifact reader.
UNCOV
137
func (hi *HeaderInfo) GetUpdates() []UpdateType {
×
UNCOV
138
        return hi.Updates
×
UNCOV
139
}
×
140

141
// Validate checks if header-info structure is correct.
142
func (hi HeaderInfo) Validate() error {
20✔
143
        missingArgs := []string{"Artifact validation failed with missing argument"}
20✔
144
        if len(hi.Updates) == 0 {
24✔
145
                missingArgs = append(missingArgs, "No Payloads added")
4✔
146
        }
4✔
147
        if len(hi.CompatibleDevices) == 0 {
30✔
148
                missingArgs = append(missingArgs, "No compatible devices listed")
10✔
149
        }
10✔
150
        if len(hi.ArtifactName) == 0 {
30✔
151
                missingArgs = append(missingArgs, "No artifact name")
10✔
152
        }
10✔
153
        for _, update := range hi.Updates {
42✔
154
                if update == (UpdateType{}) {
30✔
155
                        missingArgs = append(missingArgs, "Empty Payload")
8✔
156
                        break
8✔
157
                }
158
        }
159
        if len(missingArgs) > 1 {
36✔
160
                if len(missingArgs) > 2 {
24✔
161
                        missingArgs[0] = missingArgs[0] + "s" // Add plural.
8✔
162
                }
8✔
163
                missingArgs[0] = missingArgs[0] + ": "
16✔
164
                return errors.New(missingArgs[0] + strings.Join(missingArgs[1:], ", "))
16✔
165
        }
166
        return nil
4✔
167
}
168

UNCOV
169
func (hi *HeaderInfo) Write(p []byte) (n int, err error) {
×
UNCOV
170
        if err := decode(p, hi); err != nil {
×
171
                return 0, err
×
172
        }
×
UNCOV
173
        return len(p), nil
×
174
}
175

UNCOV
176
func (hi *HeaderInfo) GetArtifactDepends() *ArtifactDepends {
×
UNCOV
177
        return nil
×
UNCOV
178
}
×
179

UNCOV
180
func (hi *HeaderInfo) GetArtifactProvides() *ArtifactProvides {
×
UNCOV
181
        return nil
×
UNCOV
182
}
×
183

184
type HeaderInfoV3 struct {
185
        // For historical reasons, "payloads" are often referred to as "updates"
186
        // in the code, since this was the old name (and still is, in V2).
187
        // This is the reason why the struct field is still called
188
        // "Updates".
189
        Updates []UpdateType `json:"payloads"`
190
        // Has its own json marshaller tags.
191
        ArtifactProvides *ArtifactProvides `json:"artifact_provides"`
192
        // Has its own json marshaller tags.
193
        ArtifactDepends *ArtifactDepends `json:"artifact_depends"`
194
}
195

196
func NewHeaderInfoV3(updates []UpdateType,
197
        artifactProvides *ArtifactProvides, artifactDepends *ArtifactDepends) *HeaderInfoV3 {
3✔
198
        return &HeaderInfoV3{
3✔
199
                Updates:          updates,
3✔
200
                ArtifactProvides: artifactProvides,
3✔
201
                ArtifactDepends:  artifactDepends,
3✔
202
        }
3✔
203
}
3✔
204

205
// Satisfy HeaderInfoer interface for the artifact reader.
206
func (hi *HeaderInfoV3) GetArtifactName() string {
2✔
207
        if hi.ArtifactProvides == nil {
2✔
208
                return ""
×
209
        }
×
210
        return hi.ArtifactProvides.ArtifactName
2✔
211
}
212

213
// Satisfy HeaderInfoer interface for the artifact reader.
214
func (hi *HeaderInfoV3) GetCompatibleDevices() []string {
2✔
215
        if hi.ArtifactDepends == nil {
2✔
216
                return nil
×
217
        }
×
218
        return hi.ArtifactDepends.CompatibleDevices
2✔
219
}
220

221
// Satisfy HeaderInfoer interface for the artifact reader.
222
func (hi *HeaderInfoV3) GetUpdates() []UpdateType {
4✔
223
        return hi.Updates
4✔
224
}
4✔
225

226
// Validate validates the correctness of the header version3.
227
func (hi *HeaderInfoV3) Validate() error {
7✔
228
        missingArgs := []string{"Artifact validation failed with missing argument"}
7✔
229
        // Artifact must have an update with them,
7✔
230
        // because the signature of the update is stored in the metadata field.
7✔
231
        if len(hi.Updates) == 0 {
9✔
232
                missingArgs = append(missingArgs, "No Payloads added")
2✔
233
        }
2✔
234
        //////////////////////////////////
235
        // All required Artifact-provides:
236
        //////////////////////////////////
237
        /* Artifact-provides cannot be empty. */
238
        if hi.ArtifactProvides == nil {
9✔
239
                missingArgs = append(missingArgs, "Empty Artifact provides")
2✔
240
        } else {
7✔
241
                /* Artifact must have a name. */
5✔
242
                if len(hi.ArtifactProvides.ArtifactName) == 0 {
7✔
243
                        missingArgs = append(missingArgs, "Artifact name")
2✔
244
                }
2✔
245
                //
246
                /* Artifact need not have a group */
247
                //
248
        }
249
        ///////////////////////////////////////
250
        // Artifact-depends can be empty, thus:
251
        ///////////////////////////////////////
252
        /* Artifact must not depend on a name. */
253
        /* Artifact must not depend on a device. */
254
        /* Artifact must not depend on an device group. */
255
        /* Artifact must not depend on a update types supported. */
256
        /* Artifact must not depend on artifact versions supported. */
257
        if len(missingArgs) > 1 {
11✔
258
                if len(missingArgs) > 2 {
6✔
259
                        missingArgs[0] = missingArgs[0] + "s" // Add plural.
2✔
260
                }
2✔
261
                missingArgs[0] = missingArgs[0] + ": "
4✔
262
                return errors.New(missingArgs[0] + strings.Join(missingArgs[1:], ", "))
4✔
263
        }
264
        return nil
3✔
265
}
266

267
func (hi *HeaderInfoV3) Write(p []byte) (n int, err error) {
2✔
268
        if err := decode(p, hi); err != nil {
2✔
269
                return 0, err
×
270
        }
×
271
        return len(p), nil
2✔
272
}
273

274
type ArtifactDepends struct {
275
        ArtifactName      []string `json:"artifact_name,omitempty"`
276
        CompatibleDevices []string `json:"device_type,omitempty"`
277
        ArtifactGroup     []string `json:"artifact_group,omitempty"`
278
}
279

280
var ErrCompatibleDevices error = errors.New(
281
        "ArtifactDepends: Required field 'CompatibleDevices' not found",
282
)
283

284
func (a *ArtifactDepends) UnmarshalJSON(b []byte) error {
2✔
285
        type Alias ArtifactDepends // Same fields, no inherited UnmarshalJSON method
2✔
286
        buf := &Alias{}
2✔
287
        if err := json.Unmarshal(b, buf); err != nil {
2✔
288
                return err
×
289
        }
×
290
        if len(buf.CompatibleDevices) == 0 {
2✔
291
                return ErrCompatibleDevices
×
292
        }
×
293
        a.ArtifactName = buf.ArtifactName
2✔
294
        a.CompatibleDevices = buf.CompatibleDevices
2✔
295
        a.ArtifactGroup = buf.ArtifactGroup
2✔
296
        return nil
2✔
297
}
298

299
type ArtifactProvides struct {
300
        ArtifactName  string `json:"artifact_name"`
301
        ArtifactGroup string `json:"artifact_group,omitempty"`
302
}
303

304
// TypeInfo provides information of type of individual updates
305
// archived in artifacts archive.
306
type TypeInfo struct {
307
        Type string `json:"type"`
308
}
309

310
// Validate validates corectness of TypeInfo.
311
func (ti TypeInfo) Validate() error {
6✔
312
        if len(ti.Type) == 0 {
10✔
313
                return errors.Wrap(ErrValidatingData, "TypeInfo requires a type")
4✔
314
        }
4✔
315
        return nil
2✔
316
}
317

318
func (ti *TypeInfo) Write(p []byte) (n int, err error) {
×
319
        if err := decode(p, ti); err != nil {
×
320
                return 0, err
×
321
        }
×
322
        return len(p), nil
×
323
}
324

325
type TypeInfoDepends map[string]interface{}
326

UNCOV
327
func (t TypeInfoDepends) Map() map[string]interface{} {
×
UNCOV
328
        return map[string]interface{}(t)
×
UNCOV
329
}
×
330

331
func NewTypeInfoDepends(m interface{}) (ti TypeInfoDepends, err error) {
12✔
332

12✔
333
        const errMsgInvalidTypeFmt = "Invalid TypeInfo depends type: %T"
12✔
334
        const errMsgInvalidTypeEntFmt = errMsgInvalidTypeFmt + ", with value %v"
12✔
335

12✔
336
        ti = make(map[string]interface{})
12✔
337
        switch val := m.(type) {
12✔
338
        case map[string]interface{}:
6✔
339
                for k, v := range val {
18✔
340
                        switch val := v.(type) {
12✔
341

342
                        case string, []string:
8✔
343
                                ti[k] = v
8✔
344

345
                        case []interface{}:
2✔
346
                                valStr := make([]string, len(val))
2✔
347
                                for i, entFace := range v.([]interface{}) {
6✔
348
                                        entStr, ok := entFace.(string)
4✔
349
                                        if !ok {
4✔
350
                                                return nil, fmt.Errorf(
×
351
                                                        errMsgInvalidTypeEntFmt,
×
352
                                                        v, v)
×
353
                                        }
×
354
                                        valStr[i] = entStr
4✔
355
                                }
356
                                ti[k] = valStr
2✔
357

358
                        default:
2✔
359
                                return nil, fmt.Errorf(
2✔
360
                                        errMsgInvalidTypeEntFmt,
2✔
361
                                        v, v)
2✔
362
                        }
363
                }
364
                return ti, nil
4✔
365
        case map[string]string:
2✔
366
                m := m.(map[string]string)
2✔
367
                for k, v := range m {
4✔
368
                        ti[k] = v
2✔
369
                }
2✔
370
                return ti, nil
2✔
371
        case map[string][]string:
2✔
372
                m := m.(map[string][]string)
2✔
373
                for k, v := range m {
4✔
374
                        ti[k] = v
2✔
375
                }
2✔
376
                return ti, nil
2✔
377
        default:
2✔
378
                return nil, fmt.Errorf(errMsgInvalidTypeFmt, m)
2✔
379
        }
380
}
381

382
// UnmarshalJSON attempts to deserialize the json stream into a 'map[string]interface{}',
383
// where each interface value is required to be either a string, or an array of strings
UNCOV
384
func (t *TypeInfoDepends) UnmarshalJSON(b []byte) error {
×
UNCOV
385
        m := make(map[string]interface{})
×
UNCOV
386
        err := json.Unmarshal(b, &m)
×
UNCOV
387
        if err != nil {
×
388
                return err
×
389
        }
×
UNCOV
390
        *t, err = NewTypeInfoDepends(m)
×
UNCOV
391
        return err
×
392
}
393

394
type TypeInfoProvides map[string]string
395

UNCOV
396
func (t TypeInfoProvides) Map() map[string]string {
×
UNCOV
397
        return t
×
UNCOV
398
}
×
399

400
func NewTypeInfoProvides(m interface{}) (ti TypeInfoProvides, err error) {
8✔
401

8✔
402
        const errMsgInvalidTypeFmt = "Invalid TypeInfo provides type: %T"
8✔
403
        const errMsgInvalidTypeEntFmt = errMsgInvalidTypeFmt + ", with value %v"
8✔
404

8✔
405
        ti = make(map[string]string)
8✔
406
        switch val := m.(type) {
8✔
407
        case map[string]interface{}:
4✔
408
                for k, v := range val {
9✔
409
                        switch val := v.(type) {
5✔
410
                        case string:
3✔
411
                                ti[k] = val
3✔
412
                                continue
3✔
413
                        default:
2✔
414
                                return nil, fmt.Errorf(errMsgInvalidTypeEntFmt,
2✔
415
                                        v, v)
2✔
416
                        }
417
                }
418
                return ti, nil
2✔
419
        case map[string]string:
2✔
420
                m := m.(map[string]string)
2✔
421
                for k, v := range m {
4✔
422
                        ti[k] = v
2✔
423
                }
2✔
424
                return ti, nil
2✔
425
        default:
2✔
426
                return nil, fmt.Errorf(errMsgInvalidTypeFmt, m)
2✔
427
        }
428
}
429

430
// UnmarshalJSON attempts to deserialize the json stream into a 'map[string]interface{}',
431
// where each interface value is required to be either a string, or an array of strings
432
func (t *TypeInfoProvides) UnmarshalJSON(b []byte) error {
2✔
433
        m := make(map[string]interface{})
2✔
434
        err := json.Unmarshal(b, &m)
2✔
435
        if err != nil {
2✔
436
                return err
×
437
        }
×
438
        *t, err = NewTypeInfoProvides(m)
2✔
439
        return err
2✔
440
}
441

442
// TypeInfoV3 provides information about the type of update contained within the
443
// headerstructure.
444
type TypeInfoV3 struct {
445
        // Rootfs/Delta (Required).
446
        Type *string `json:"type"`
447

448
        ArtifactDepends        TypeInfoDepends  `json:"artifact_depends,omitempty"`
449
        ArtifactProvides       TypeInfoProvides `json:"artifact_provides,omitempty"`
450
        ClearsArtifactProvides []string         `json:"clears_artifact_provides,omitempty"`
451
}
452

453
// Validate checks that the required `Type` field is set.
454
func (ti *TypeInfoV3) Validate() error {
4✔
455
        if ti.Type != nil && *ti.Type == "" {
6✔
456
                return errors.Wrap(ErrValidatingData, "TypeInfoV3: ")
2✔
457
        }
2✔
458
        return nil
2✔
459
}
460

461
// Write writes the underlying struct into a json data structure (bytestream).
462
func (ti *TypeInfoV3) Write(b []byte) (n int, err error) {
2✔
463
        if err := decode(b, ti); err != nil {
2✔
464
                return 0, err
×
465
        }
×
466
        return len(b), nil
2✔
467
}
468

UNCOV
469
func (hi *HeaderInfoV3) GetArtifactDepends() *ArtifactDepends {
×
UNCOV
470
        return hi.ArtifactDepends
×
UNCOV
471
}
×
472

UNCOV
473
func (hi *HeaderInfoV3) GetArtifactProvides() *ArtifactProvides {
×
UNCOV
474
        return hi.ArtifactProvides
×
UNCOV
475
}
×
476

477
// Metadata contains artifacts metadata information. The exact metadata fields
478
// are user-defined and are not specified. The only requirement is that those
479
// must be stored in a for of JSON.
480
// The fields which must exist are update-type dependent. In case of
481
// `rootfs-update` image type, there are no additional fields required.
482
type Metadata map[string]interface{}
483

484
// Validate check corecness of artifacts metadata. Since the exact format is
485
// not specified validation always succeeds.
486
func (m Metadata) Validate() error {
6✔
487
        return nil
6✔
488
}
6✔
489

490
func (m *Metadata) Write(p []byte) (n int, err error) {
6✔
491
        if err := decode(p, m); err != nil {
6✔
492
                return 0, err
×
493
        }
×
494
        return len(p), nil
6✔
495
}
496

497
func (m *Metadata) Map() map[string]interface{} {
×
498
        return map[string]interface{}(*m)
×
499
}
×
500

501
// Files represents the list of file names that make up the payload for given
502
// update.
503
type Files struct {
504
        FileList []string `json:"files"`
505
}
506

507
// Validate checks format of Files.
508
func (f Files) Validate() error {
12✔
509
        for _, f := range f.FileList {
24✔
510
                if len(f) == 0 {
16✔
511
                        return errors.Wrap(ErrValidatingData, "File in FileList requires a name")
4✔
512
                }
4✔
513
        }
514
        return nil
8✔
515
}
516

UNCOV
517
func (f *Files) Write(p []byte) (n int, err error) {
×
UNCOV
518
        if err := decode(p, f); err != nil {
×
UNCOV
519
                return 0, err
×
UNCOV
520
        }
×
UNCOV
521
        return len(p), nil
×
522
}
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