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

mendersoftware / mender-artifact / 1327234555

11 Jun 2024 12:05PM UTC coverage: 76.862% (-0.4%) from 77.239%
1327234555

Pull #613

gitlab-ci

alfrunes
ci: Add missing Openssl dependencies to build jobs

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #613: chore(Dockerfile): Refactor dockerfile to Debian Slim with no deps

5707 of 7425 relevant lines covered (76.86%)

133.34 hits per line

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

69.72
/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
        "io/ioutil"
22
        "os"
23
        "path/filepath"
24

25
        "github.com/mendersoftware/mender-artifact/areader"
26
        "github.com/mendersoftware/mender-artifact/artifact"
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) {
228✔
96
        scr := artifact.Scripts{}
228✔
97
        for _, scriptArg := range scripts {
274✔
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 := ioutil.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
226✔
121
}
122

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

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

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

191
func unpackArtifact(name string) (ua *unpackedArtifact, err error) {
110✔
192
        ua = &unpackedArtifact{
110✔
193
                origPath: name,
110✔
194
        }
110✔
195

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

110✔
202
        aReader := areader.NewReader(f)
110✔
203
        ua.ar = aReader
110✔
204

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

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

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

110✔
240
        err = aReader.ReadArtifactHeaders()
110✔
241
        if err != nil {
110✔
242
                return nil, err
×
243
        }
×
244

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

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

261
        if err := aReader.ReadArtifactData(); err != nil {
110✔
262
                return nil, err
×
263
        }
×
264

265
        ua.files = updateStore.names
110✔
266

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

×
275
                return nil, errors.New("rootfs-image artifacts with more than one file not supported")
×
276
        }
×
277

278
        ua.writeArgs, err = reconstructArtifactWriteData(ua)
110✔
279
        return ua, err
110✔
280
}
281

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

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

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

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

333
                default:
×
334
                        err = errors.Errorf("unsupported artifact version: %d", info.Version)
×
335
                        return
×
336
                }
337

338
                var uDepends artifact.TypeInfoDepends
110✔
339
                var uProvides artifact.TypeInfoProvides
110✔
340

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

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

359
        return
110✔
360
}
361

362
func reconstructArtifactWriteData(ua *unpackedArtifact) (*awriter.WriteArtifactArgs, error) {
110✔
363
        info := ua.ar.GetInfo()
110✔
364
        inst := ua.ar.GetHandlers()
110✔
365

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

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

387
        scr, err := scripts(ua.scripts)
110✔
388
        if err != nil {
110✔
389
                return nil, err
×
390
        }
×
391

392
        name := ua.ar.GetArtifactName()
110✔
393

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

110✔
409
        return args, nil
110✔
410
}
411

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

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

437
        return aWriter.WriteArtifact(ua.writeArgs)
76✔
438
}
439

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

76✔
448
        if err = repack(comp, ua, tmp, key); err != nil {
76✔
449
                return err
×
450
        }
×
451

452
        tmp.Close()
76✔
453

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