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

mendersoftware / mender-artifact / 2115744134

23 Oct 2025 08:21AM UTC coverage: 75.909% (-0.4%) from 76.26%
2115744134

Pull #708

gitlab-ci

elkoniu
chore: Remove gobinarycoverage test

Ticket: QA-924
Signed-off-by: Paweł Poławski <pawel.polawski@northern.tech>
Pull Request #708: Remove gobinarycoverage test

6053 of 7974 relevant lines covered (75.91%)

140.22 hits per line

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

69.39
/cli/artifacts.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 cli
16

17
import (
18
        "context"
19
        "fmt"
20
        "io"
21
        "os"
22
        "path/filepath"
23

24
        "github.com/mendersoftware/mender-artifact/areader"
25
        "github.com/mendersoftware/mender-artifact/artifact"
26
        "github.com/mendersoftware/mender-artifact/artifact/azure"
27
        "github.com/mendersoftware/mender-artifact/artifact/gcp"
28
        "github.com/mendersoftware/mender-artifact/artifact/keyfactor"
29
        "github.com/mendersoftware/mender-artifact/artifact/vault"
30
        "github.com/mendersoftware/mender-artifact/awriter"
31
        "github.com/mendersoftware/mender-artifact/handlers"
32

33
        "github.com/pkg/errors"
34
        "github.com/urfave/cli"
35
)
36

37
type unpackedArtifact struct {
38
        origPath  string
39
        unpackDir string
40
        ar        *areader.Reader
41
        scripts   []string
42
        files     []string
43

44
        // Args needed to reconstruct the artifact
45
        writeArgs *awriter.WriteArtifactArgs
46
}
47

48
type writeUpdateStorer struct {
49
        // Dir to store files in
50
        dir string
51
        // Files that are stored. Will be filled in while storing
52
        names []string
53
}
54

55
func (w *writeUpdateStorer) Initialize(artifactHeaders,
56
        artifactAugmentedHeaders artifact.HeaderInfoer,
57
        payloadHeaders handlers.ArtifactUpdateHeaders) error {
110✔
58

110✔
59
        if artifactAugmentedHeaders != nil {
110✔
60
                return errors.New("Modifying augmented artifacts is not supported")
×
61
        }
×
62

63
        return nil
110✔
64
}
65

66
func (w *writeUpdateStorer) PrepareStoreUpdate() error {
110✔
67
        return nil
110✔
68
}
110✔
69

70
func (w *writeUpdateStorer) StoreUpdate(r io.Reader, info os.FileInfo) error {
120✔
71
        fullpath := filepath.Join(w.dir, info.Name())
120✔
72
        w.names = append(w.names, fullpath)
120✔
73
        fd, err := os.OpenFile(fullpath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
120✔
74
        if err != nil {
120✔
75
                return err
×
76
        }
×
77
        _, err = io.Copy(fd, r)
120✔
78
        return err
120✔
79
}
80

81
func (w *writeUpdateStorer) FinishStoreUpdate() error {
110✔
82
        return nil
110✔
83
}
110✔
84

85
func (w *writeUpdateStorer) NewUpdateStorer(
86
        updateType *string,
87
        payloadNum int,
88
) (handlers.UpdateStorer, error) {
110✔
89
        if payloadNum != 0 {
110✔
90
                return nil, errors.New("More than one payload or update file is not supported")
×
91
        }
×
92
        return w, nil
110✔
93
}
94

95
func scripts(scripts []string) (*artifact.Scripts, error) {
248✔
96
        scr := artifact.Scripts{}
248✔
97
        for _, scriptArg := range scripts {
294✔
98
                statInfo, err := os.Stat(scriptArg)
46✔
99
                if err != nil {
46✔
100
                        return nil, errors.Wrapf(err, "can not stat script file: %s", scriptArg)
×
101
                }
×
102

103
                // Read either a directory, or add the script file directly.
104
                if statInfo.IsDir() {
48✔
105
                        fileList, err := os.ReadDir(scriptArg)
2✔
106
                        if err != nil {
2✔
107
                                return nil, errors.Wrapf(err, "can not list directory contents of: %s", scriptArg)
×
108
                        }
×
109
                        for _, nameInfo := range fileList {
6✔
110
                                if err := scr.Add(filepath.Join(scriptArg, nameInfo.Name())); err != nil {
4✔
111
                                        return nil, err
×
112
                                }
×
113
                        }
114
                } else {
44✔
115
                        if err := scr.Add(scriptArg); err != nil {
46✔
116
                                return nil, err
2✔
117
                        }
2✔
118
                }
119
        }
120
        return &scr, nil
246✔
121
}
122

123
type SigningKey interface {
124
        artifact.Signer
125
        artifact.Verifier
126
}
127

128
func getKey(c *cli.Context) (SigningKey, error) {
472✔
129
        var chosenOptions []string
472✔
130
        possibleOptions := []string{
472✔
131
                "key",
472✔
132
                "gcp-kms-key",
472✔
133
                "vault-transit-key",
472✔
134
                "key-pkcs11",
472✔
135
                "keyfactor-signserver-worker",
472✔
136
                "azure-key",
472✔
137
        }
472✔
138
        for _, optName := range possibleOptions {
3,304✔
139
                if c.String(optName) == "" {
5,608✔
140
                        continue
2,776✔
141
                }
142
                chosenOptions = append(chosenOptions, optName)
56✔
143
        }
144
        if len(chosenOptions) == 0 {
888✔
145
                return nil, nil
416✔
146
        } else if len(chosenOptions) > 1 {
472✔
147
                return nil, fmt.Errorf("too many signing keys given: %v", chosenOptions)
×
148
        }
×
149
        switch chosenOption := chosenOptions[0]; chosenOption {
56✔
150
        case "key":
56✔
151
                key, err := os.ReadFile(c.String("key"))
56✔
152
                if err != nil {
64✔
153
                        return nil, errors.Wrap(err, "Error reading key file")
8✔
154
                }
8✔
155

156
                // The "key" flag can either be public or private depending on the
157
                // command name. Explicitly map each command's name to which one it
158
                // should be, so we return the correct key type.
159
                publicKeyCommands := map[string]bool{
48✔
160
                        "validate": true,
48✔
161
                        "read":     true,
48✔
162
                }
48✔
163
                privateKeyCommands := map[string]bool{
48✔
164
                        "rootfs-image":       true,
48✔
165
                        "module-image":       true,
48✔
166
                        "bootstrap-artifact": true,
48✔
167
                        "sign":               true,
48✔
168
                        "modify":             true,
48✔
169
                        "copy":               true,
48✔
170
                }
48✔
171
                if publicKeyCommands[c.Command.Name] {
64✔
172
                        return artifact.NewPKIVerifier(key)
16✔
173
                }
16✔
174
                if privateKeyCommands[c.Command.Name] {
64✔
175
                        return artifact.NewPKISigner(key)
32✔
176
                }
32✔
177
                return nil, fmt.Errorf("unsupported command %q with %q flag, "+
×
178
                        "please add command to allowlist", c.Command.Name, "key")
×
179
        case "gcp-kms-key":
×
180
                return gcp.NewKMSSigner(context.TODO(), c.String("gcp-kms-key"))
×
181
        case "vault-transit-key":
×
182
                return vault.NewVaultSigner(c.String("vault-transit-key"))
×
183
        case "key-pkcs11":
×
184
                return artifact.NewPKCS11Signer(c.String("key-pkcs11"))
×
185
        case "keyfactor-signserver-worker":
×
186
                return keyfactor.NewSignServerSigner(c.String("keyfactor-signserver-worker"))
×
187
        case "azure-key":
×
188
                return azure.NewKeyVaultSigner(c.String("azure-key"))
×
189
        default:
×
190
                return nil, fmt.Errorf("unsupported signing key type %q", chosenOption)
×
191
        }
192
}
193

194
func unpackArtifact(name string) (ua *unpackedArtifact, err error) {
110✔
195
        ua = &unpackedArtifact{
110✔
196
                origPath: name,
110✔
197
        }
110✔
198

110✔
199
        f, err := os.Open(name)
110✔
200
        if err != nil {
110✔
201
                return nil, errors.Wrapf(err, "Can not open: %s", name)
×
202
        }
×
203
        defer f.Close()
110✔
204

110✔
205
        aReader := areader.NewReader(f)
110✔
206
        ua.ar = aReader
110✔
207

110✔
208
        tmpdir, err := os.MkdirTemp("", "mender-artifact")
110✔
209
        if err != nil {
110✔
210
                return nil, err
×
211
        }
×
212
        ua.unpackDir = tmpdir
110✔
213
        defer func() {
220✔
214
                if err != nil {
110✔
215
                        os.RemoveAll(tmpdir)
×
216
                }
×
217
        }()
218

219
        sDir := filepath.Join(tmpdir, "scripts")
110✔
220
        err = os.Mkdir(sDir, 0755)
110✔
221
        if err != nil {
110✔
222
                return nil, err
×
223
        }
×
224
        storeScripts := func(r io.Reader, info os.FileInfo) error {
122✔
225
                sLocation := filepath.Join(sDir, info.Name())
12✔
226
                ua.scripts = append(ua.scripts, sLocation)
12✔
227
                f, fileErr := os.OpenFile(sLocation, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0755)
12✔
228
                if fileErr != nil {
12✔
229
                        return errors.Wrapf(fileErr,
×
230
                                "can not create script file: %v", sLocation)
×
231
                }
×
232
                defer f.Close()
12✔
233

12✔
234
                _, err = io.Copy(f, r)
12✔
235
                if err != nil {
12✔
236
                        return errors.Wrapf(err,
×
237
                                "can not write script file: %v", sLocation)
×
238
                }
×
239
                return nil
12✔
240
        }
241
        aReader.ScriptsReadCallback = storeScripts
110✔
242

110✔
243
        err = aReader.ReadArtifactHeaders()
110✔
244
        if err != nil {
110✔
245
                return nil, err
×
246
        }
×
247

248
        fDir := filepath.Join(tmpdir, "files")
110✔
249
        err = os.Mkdir(fDir, 0755)
110✔
250
        if err != nil {
110✔
251
                return nil, err
×
252
        }
×
253

254
        updateStore := &writeUpdateStorer{
110✔
255
                dir: fDir,
110✔
256
        }
110✔
257
        inst := aReader.GetHandlers()
110✔
258
        if len(inst) == 1 {
220✔
259
                inst[0].SetUpdateStorerProducer(updateStore)
110✔
260
        } else if len(inst) > 1 {
110✔
261
                return nil, errors.New("More than one payload not supported")
×
262
        }
×
263

264
        if err := aReader.ReadArtifactData(); err != nil {
110✔
265
                return nil, err
×
266
        }
×
267

268
        ua.files = updateStore.names
110✔
269

110✔
270
        updType := inst[0].GetUpdateType()
110✔
271
        if updType == nil {
110✔
272
                return nil, errors.New("nil update type is not allowed")
×
273
        }
×
274
        if len(inst) > 0 &&
110✔
275
                *inst[0].GetUpdateType() == "rootfs-image" &&
110✔
276
                len(ua.files) != 1 {
110✔
277

×
278
                return nil, errors.New("rootfs-image artifacts with more than one file not supported")
×
279
        }
×
280

281
        ua.writeArgs, err = reconstructArtifactWriteData(ua)
110✔
282
        return ua, err
110✔
283
}
284

285
func reconstructPayloadWriteData(
286
        info *artifact.Info,
287
        inst map[int]handlers.Installer,
288
) (upd *awriter.Updates,
289
        typeInfoV3 *artifact.TypeInfoV3,
290
        augTypeInfoV3 *artifact.TypeInfoV3,
291
        metaData map[string]interface{},
292
        augMetaData map[string]interface{},
293
        err error) {
110✔
294

110✔
295
        if len(inst) > 1 {
110✔
296
                err = errors.New("More than one payload not supported")
×
297
                return
×
298
        } else if len(inst) == 1 {
220✔
299
                var updateType *string
110✔
300
                upd = &awriter.Updates{}
110✔
301

110✔
302
                switch info.Version {
110✔
303
                case 1:
×
304
                        err = errors.New("Mender-Artifact version 1 no longer supported")
×
305
                        return
×
306
                case 2:
2✔
307
                        updateType = inst[0].GetUpdateType()
2✔
308
                        if updateType == nil {
2✔
309
                                err = errors.New("nil update type is not allowed")
×
310
                                return
×
311
                        }
×
312
                        upd.Updates = []handlers.Composer{handlers.NewRootfsV2(*updateType)}
2✔
313
                case 3:
108✔
314
                        // Even rootfs images will be written using ModuleImage, which
108✔
315
                        // is a superset
108✔
316
                        var updType *string
108✔
317
                        updType = inst[0].GetUpdateOriginalType()
108✔
318
                        if *updType != "" {
108✔
319
                                // If augmented artifact.
×
320
                                upd.Augments = []handlers.Composer{handlers.NewModuleImage(*updType)}
×
321
                                augTypeInfoV3 = &artifact.TypeInfoV3{
×
322
                                        Type:             updType,
×
323
                                        ArtifactDepends:  inst[0].GetUpdateOriginalDepends(),
×
324
                                        ArtifactProvides: inst[0].GetUpdateOriginalProvides(),
×
325
                                }
×
326
                                augMetaData = inst[0].GetUpdateOriginalMetaData()
×
327
                        }
×
328

329
                        updateType = inst[0].GetUpdateType()
108✔
330
                        if updateType == nil {
108✔
331
                                err = errors.New("nil update type is not allowed")
×
332
                                return
×
333
                        }
×
334
                        upd.Updates = []handlers.Composer{handlers.NewModuleImage(*updateType)}
108✔
335

336
                default:
×
337
                        err = errors.Errorf("unsupported artifact version: %d", info.Version)
×
338
                        return
×
339
                }
340

341
                var uDepends artifact.TypeInfoDepends
110✔
342
                var uProvides artifact.TypeInfoProvides
110✔
343

110✔
344
                if uDepends, err = inst[0].GetUpdateDepends(); err != nil {
110✔
345
                        return
×
346
                }
×
347
                if uProvides, err = inst[0].GetUpdateProvides(); err != nil {
110✔
348
                        return
×
349
                }
×
350
                typeInfoV3 = &artifact.TypeInfoV3{
110✔
351
                        Type:                   updateType,
110✔
352
                        ArtifactDepends:        uDepends,
110✔
353
                        ArtifactProvides:       uProvides,
110✔
354
                        ClearsArtifactProvides: inst[0].GetUpdateOriginalClearsProvides(),
110✔
355
                }
110✔
356

110✔
357
                if metaData, err = inst[0].GetUpdateMetaData(); err != nil {
110✔
358
                        return
×
359
                }
×
360
        }
361

362
        return
110✔
363
}
364

365
func reconstructArtifactWriteData(ua *unpackedArtifact) (*awriter.WriteArtifactArgs, error) {
110✔
366
        info := ua.ar.GetInfo()
110✔
367
        inst := ua.ar.GetHandlers()
110✔
368

110✔
369
        upd, typeInfoV3, augTypeInfoV3, metaData, augMetaData, err := reconstructPayloadWriteData(
110✔
370
                &info,
110✔
371
                inst,
110✔
372
        )
110✔
373
        if err != nil {
110✔
374
                return nil, err
×
375
        }
×
376

377
        if len(inst) == 1 {
220✔
378
                dataFiles := make([]*handlers.DataFile, 0, len(ua.files))
110✔
379
                for _, file := range ua.files {
230✔
380
                        dataFiles = append(dataFiles, &handlers.DataFile{Name: file})
120✔
381
                }
120✔
382
                err := upd.Updates[0].SetUpdateFiles(dataFiles)
110✔
383
                if err != nil {
110✔
384
                        return nil, errors.Wrap(err, "Cannot assign payload files")
×
385
                }
×
386
        } else if len(inst) > 1 {
×
387
                return nil, errors.New("Multiple payloads not supported")
×
388
        }
×
389

390
        scr, err := scripts(ua.scripts)
110✔
391
        if err != nil {
110✔
392
                return nil, err
×
393
        }
×
394

395
        name := ua.ar.GetArtifactName()
110✔
396

110✔
397
        args := &awriter.WriteArtifactArgs{
110✔
398
                Format:            info.Format,
110✔
399
                Version:           info.Version,
110✔
400
                Devices:           ua.ar.GetCompatibleDevices(),
110✔
401
                Name:              name,
110✔
402
                Updates:           upd,
110✔
403
                Scripts:           scr,
110✔
404
                Provides:          ua.ar.GetArtifactProvides(),
110✔
405
                Depends:           ua.ar.GetArtifactDepends(),
110✔
406
                TypeInfoV3:        typeInfoV3,
110✔
407
                MetaData:          metaData,
110✔
408
                AugmentTypeInfoV3: augTypeInfoV3,
110✔
409
                AugmentMetaData:   augMetaData,
110✔
410
        }
110✔
411

110✔
412
        return args, nil
110✔
413
}
414

415
func repack(comp artifact.Compressor, ua *unpackedArtifact, to io.Writer, key SigningKey) error {
76✔
416
        aWriter := awriter.NewWriter(to, comp)
76✔
417
        if key != nil {
80✔
418
                aWriter = awriter.NewWriterSigned(to, comp, key)
4✔
419
        }
4✔
420

421
        // for rootfs-images: Update rootfs-image.checksum provide if there is one.
422
        _, hasChecksumProvide := ua.writeArgs.TypeInfoV3.ArtifactProvides["rootfs-image.checksum"]
76✔
423
        // for rootfs-images: Update legacy rootfs_image_checksum provide if there is one.
76✔
424
        _, hasLegacyChecksumProvide := ua.writeArgs.TypeInfoV3.ArtifactProvides["rootfs_image_checksum"]
76✔
425
        if *ua.writeArgs.TypeInfoV3.Type == "rootfs-image" && (hasChecksumProvide ||
76✔
426
                hasLegacyChecksumProvide) {
126✔
427
                if len(ua.files) != 1 {
50✔
428
                        return errors.New("Only rootfs-image Artifacts with one file are supported")
×
429
                }
×
430
                err := writeRootfsImageChecksum(
50✔
431
                        ua.files[0],
50✔
432
                        ua.writeArgs.TypeInfoV3,
50✔
433
                        hasLegacyChecksumProvide,
50✔
434
                )
50✔
435
                if err != nil {
50✔
436
                        return err
×
437
                }
×
438
        }
439

440
        return aWriter.WriteArtifact(ua.writeArgs)
76✔
441
}
442

443
func repackArtifact(comp artifact.Compressor, key SigningKey, ua *unpackedArtifact) error {
76✔
444
        tmp, err := os.CreateTemp(filepath.Dir(ua.origPath), "mender-artifact")
76✔
445
        if err != nil {
76✔
446
                return err
×
447
        }
×
448
        defer os.Remove(tmp.Name())
76✔
449
        defer tmp.Close()
76✔
450

76✔
451
        if err = repack(comp, ua, tmp, key); err != nil {
76✔
452
                return err
×
453
        }
×
454

455
        tmp.Close()
76✔
456

76✔
457
        return os.Rename(tmp.Name(), ua.origPath)
76✔
458
}
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