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

mendersoftware / mender-cli / 1762341830

10 Apr 2025 12:40PM UTC coverage: 31.802%. Remained the same
1762341830

Pull #276

gitlab-ci

alfrunes
chore: Remove deprecated library `io/ioutil`

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #276: chore: bump the golang-dependencies group with 2 updates

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

20 existing lines in 1 file now uncovered.

810 of 2547 relevant lines covered (31.8%)

1.68 hits per line

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

61.49
/client/deployments/client.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
package deployments
15

16
import (
17
        "archive/tar"
18
        "bytes"
19
        "encoding/json"
20
        "fmt"
21
        "io"
22
        "mime/multipart"
23
        "net/http"
24
        "net/http/httputil"
25
        "os"
26
        "strconv"
27
        "strings"
28
        "time"
29

30
        "github.com/cheggaaa/pb/v3"
31
        "github.com/pkg/errors"
32

33
        "github.com/mendersoftware/mender-artifact/areader"
34

35
        "github.com/mendersoftware/mender-cli/client"
36
        "github.com/mendersoftware/mender-cli/log"
37
)
38

39
const (
40
        httpErrorBoundary = 300
41
)
42

43
type artifactsList struct {
44
        artifacts []artifactData
45
}
46

47
type artifactData struct {
48
        ID                    string   `json:"id"`
49
        Description           string   `json:"description"`
50
        Name                  string   `json:"name"`
51
        DeviceTypesCompatible []string `json:"device_types_compatible"`
52
        Info                  struct {
53
                Format  string `json:"format"`
54
                Version int    `json:"version"`
55
        } `json:"info"`
56
        Signed  bool `json:"signed"`
57
        Updates []struct {
58
                TypeInfo struct {
59
                        Type string `json:"type"`
60
                } `json:"type_info"`
61
                Files []struct {
62
                        Name     string    `json:"name"`
63
                        Checksum string    `json:"checksum"`
64
                        Size     int       `json:"size"`
65
                        Date     time.Time `json:"date"`
66
                } `json:"files"`
67
                MetaData []interface{} `json:"meta_data"`
68
        } `json:"updates"`
69
        ArtifactProvides struct {
70
                ArtifactName string `json:"artifact_name"`
71
        } `json:"artifact_provides"`
72
        ArtifactDepends struct {
73
                DeviceType []string `json:"device_type"`
74
        } `json:"artifact_depends"`
75
        Size     int       `json:"size"`
76
        Modified time.Time `json:"modified"`
77
}
78

79
const (
80
        artifactUploadURL   = "/api/management/v1/deployments/artifacts"
81
        artifactsListURL    = artifactUploadURL
82
        artifactsDeleteURL  = artifactUploadURL
83
        directUploadURL     = "/api/management/v1/deployments/artifacts/directupload"
84
        transferCompleteURL = "/api/management/v1/deployments/artifacts/directupload/:id/complete"
85
        artifactURL         = "/api/management/v1/deployments/artifacts/:id"
86
        artifactDownloadURL = "/api/management/v1/deployments/artifacts/:id/download"
87
)
88

89
type Client struct {
90
        url                 string
91
        artifactUploadURL   string
92
        artifactURL         string
93
        artifactDownloadURL string
94
        artifactsListURL    string
95
        artifactDeleteURL   string
96
        directUploadURL     string
97
        client              *http.Client
98
}
99

100
type Link struct {
101
        Uri    string            `json:"uri"`
102
        Header map[string]string `json:"header,omitempty"`
103
}
104

105
type UploadLink struct {
106
        ArtifactID string `json:"id"`
107

108
        Link
109
}
110

111
func NewClient(url string, skipVerify bool) *Client {
8✔
112
        return &Client{
8✔
113
                url:                 url,
8✔
114
                artifactUploadURL:   client.JoinURL(url, artifactUploadURL),
8✔
115
                artifactURL:         client.JoinURL(url, artifactURL),
8✔
116
                artifactDownloadURL: client.JoinURL(url, artifactDownloadURL),
8✔
117
                artifactsListURL:    client.JoinURL(url, artifactsListURL),
8✔
118
                artifactDeleteURL:   client.JoinURL(url, artifactsDeleteURL),
8✔
119
                directUploadURL:     client.JoinURL(url, directUploadURL),
8✔
120
                client:              client.NewHttpClient(skipVerify),
8✔
121
        }
8✔
122
}
8✔
123

124
func (c *Client) DirectDownloadLink(token string) (*UploadLink, error) {
1✔
125
        var link UploadLink
1✔
126

1✔
127
        body, err := client.DoPostRequest(token, c.directUploadURL, c.client, nil)
1✔
128
        if err != nil {
1✔
129
                return nil, err
×
130
        }
×
131

132
        err = json.Unmarshal(body, &link)
1✔
133
        if err != nil {
1✔
134
                return nil, err
×
135
        }
×
136

137
        return &link, nil
1✔
138
}
139

140
func (c *Client) ListArtifacts(token string, detailLevel int) error {
2✔
141
        if detailLevel > 3 || detailLevel < 0 {
2✔
142
                return fmt.Errorf("FAILURE: invalid artifact detail")
×
143
        }
×
144

145
        body, err := client.DoGetRequest(token, c.artifactsListURL, c.client)
2✔
146
        if err != nil {
2✔
147
                return err
×
148
        }
×
149

150
        var list artifactsList
2✔
151
        err = json.Unmarshal(body, &list.artifacts)
2✔
152
        if err != nil {
2✔
153
                return err
×
154
        }
×
155
        for _, v := range list.artifacts {
4✔
156
                listArtifact(v, detailLevel)
2✔
157
        }
2✔
158

159
        return nil
2✔
160
}
161

162
func listArtifact(a artifactData, detailLevel int) {
2✔
163
        fmt.Printf("ID: %s\n", a.ID)
2✔
164
        fmt.Printf("Name: %s\n", a.Name)
2✔
165
        if detailLevel >= 1 {
2✔
166
                fmt.Printf("Signed: %t\n", a.Signed)
×
167
                fmt.Printf("Modfied: %s\n", a.Modified)
×
168
                fmt.Printf("Size: %d\n", a.Size)
×
169
                fmt.Printf("Description: %s\n", a.Description)
×
170
                fmt.Println("Compatible device types:")
×
171
                for _, v := range a.DeviceTypesCompatible {
×
172
                        fmt.Printf("  %s\n", v)
×
173
                }
×
174
                fmt.Printf("Artifact format: %s\n", a.Info.Format)
×
175
                fmt.Printf("Format version: %d\n", a.Info.Version)
×
176
        }
177
        if detailLevel >= 2 {
2✔
178
                fmt.Printf("Artifact provides: %s\n", a.ArtifactProvides.ArtifactName)
×
179
                fmt.Println("Artifact depends:")
×
180
                for _, v := range a.ArtifactDepends.DeviceType {
×
181
                        fmt.Printf("  %s\n", v)
×
182
                }
×
183
                fmt.Println("Updates:")
×
184
                for _, v := range a.Updates {
×
185
                        fmt.Printf("  Type: %s\n", v.TypeInfo.Type)
×
186
                        fmt.Println("  Files:")
×
187
                        for _, f := range v.Files {
×
188
                                fmt.Printf("\tName: %s\n", f.Name)
×
189
                                fmt.Printf("\tChecksum: %s\n", f.Checksum)
×
190
                                fmt.Printf("\tSize: %d\n", f.Size)
×
191
                                fmt.Printf("\tDate: %s\n", f.Date)
×
192
                                if len(v.Files) > 1 {
×
193
                                        fmt.Println()
×
194
                                }
×
195
                        }
196
                        if detailLevel == 3 {
×
197
                                fmt.Printf("  MetaData: %v\n", v.MetaData)
×
198
                        }
×
199
                }
200
        }
201

202
        fmt.Println("--------------------------------------------------------------------------------")
2✔
203
}
204

205
// Type info structure
206
type ArtifactUpdateTypeInfo struct {
207
        Type *string `json:"type" valid:"required"`
208
}
209

210
// Update file structure
211
type UpdateFile struct {
212
        // Image name
213
        Name string `json:"name" valid:"required"`
214

215
        // Image file checksum
216
        Checksum string `json:"checksum" valid:"optional"`
217

218
        // Image size
219
        Size int64 `json:"size" valid:"optional"`
220

221
        // Date build
222
        Date *time.Time `json:"date" valid:"optional"`
223
}
224

225
// Update structure
226
type Update struct {
227
        TypeInfo ArtifactUpdateTypeInfo `json:"type_info" valid:"required"`
228
        Files    []UpdateFile           `json:"files"`
229
        MetaData interface{}            `json:"meta_data,omitempty" valid:"optional"`
230
}
231

232
type DirectUploadMetadata struct {
233
        Size    int64    `json:"size,omitempty" valid:"-"`
234
        Updates []Update `json:"updates" valid:"-"`
235
}
236

237
func readArtifactMetadata(path string, size int64) io.Reader {
1✔
238
        log.Verbf("reading artifact file...")
1✔
239
        r, err := os.Open(path)
1✔
240
        if err != nil {
1✔
241
                log.Verbf("failed to open artifact file: %s", err.Error())
×
242
                return nil
×
243
        }
×
244
        defer r.Close()
1✔
245
        ar := areader.NewReader(r)
1✔
246
        err = ar.ReadArtifact()
1✔
247
        if err != nil {
1✔
248
                log.Verbf("failed to read artifact file: %s", err.Error())
×
249
                return nil
×
250
        }
×
251
        handlers := ar.GetHandlers()
1✔
252
        directUploads := make([]Update, len(handlers))
1✔
253
        for i, p := range handlers {
2✔
254
                files := p.GetUpdateAllFiles()
1✔
255
                if len(files) < 1 {
1✔
256
                        log.Verbf("the artifact has no files information")
×
257
                        return nil
×
258
                }
×
259
                for _, f := range files {
2✔
260
                        directUploads[i].Files = append(directUploads[i].Files, UpdateFile{
1✔
261
                                Name:     f.Name,
1✔
262
                                Checksum: string(f.Checksum),
1✔
263
                                Size:     f.Size,
1✔
264
                                Date:     &f.Date,
1✔
265
                        })
1✔
266
                }
1✔
267
        }
268
        directMetadata := DirectUploadMetadata{
1✔
269
                Size:    size,
1✔
270
                Updates: directUploads,
1✔
271
        }
1✔
272
        data, err := json.Marshal(directMetadata)
1✔
273
        if err != nil {
1✔
274
                log.Verbf("failed to parse artifact metadata: %s", err.Error())
×
275
                return nil
×
276
        }
×
277
        log.Verbf("done reading artifact file.")
1✔
278
        return bytes.NewBuffer(data)
1✔
279
}
280

281
func (c *Client) DirectUpload(
282
        token, artifactPath, id, url string,
283
        headers map[string]string,
284
        noProgress bool,
285
) error {
1✔
286
        var bar *pb.ProgressBar
1✔
287

1✔
288
        artifact, err := os.Open(artifactPath)
1✔
289
        if err != nil {
1✔
290
                return errors.Wrap(err, "Cannot read artifact file")
×
291
        }
×
292
        defer artifact.Close()
1✔
293

1✔
294
        artifactStats, err := artifact.Stat()
1✔
295
        if err != nil {
1✔
296
                return errors.Wrap(err, "Cannot read artifact file stats")
×
297
        }
×
298
        if err = checkArtifactFormat(artifact); err != nil {
1✔
299
                return err
×
300
        }
×
301
        var req *http.Request
1✔
302
        if !noProgress {
2✔
303
                // create progress bar
1✔
304
                bar = pb.New64(artifactStats.Size()).
1✔
305
                        Set(pb.Bytes, true).
1✔
306
                        SetRefreshRate(time.Millisecond * 100)
1✔
307
                bar.Start()
1✔
308
                req, err = http.NewRequest(http.MethodPut, url, bar.NewProxyReader(artifact))
1✔
309
        } else {
1✔
310
                req, err = http.NewRequest(http.MethodPut, url, artifact)
×
311
        }
×
312
        if err != nil {
1✔
313
                return errors.Wrap(err, "Cannot create request")
×
314
        }
×
315
        req.Header.Set("Content-Type", "application/vnd.mender-artifact")
1✔
316
        req.ContentLength = artifactStats.Size()
1✔
317

1✔
318
        reqDump, _ := httputil.DumpRequest(req, false)
1✔
319
        log.Verbf("sending request: \n%v", string(reqDump))
1✔
320

1✔
321
        for k, h := range headers {
1✔
322
                req.Header.Set(k, h)
×
323
        }
×
324
        rsp, err := c.client.Do(req)
1✔
325
        if err != nil {
1✔
326
                return errors.Wrap(err, "POST /artifacts request failed")
×
327
        }
×
328
        defer rsp.Body.Close()
1✔
329

1✔
330
        rspDump, _ := httputil.DumpResponse(rsp, true)
1✔
331
        log.Verbf("response: \n%v\n", string(rspDump))
1✔
332

1✔
333
        if rsp.StatusCode >= httpErrorBoundary {
1✔
334
                return errors.New(
×
335
                        fmt.Sprintf("artifact upload to '%s' failed with status %d", req.Host, rsp.StatusCode),
×
336
                )
×
337
        } else {
1✔
338
                body := readArtifactMetadata(artifactPath, artifactStats.Size())
1✔
339
                _, err := client.DoPostRequest(
1✔
340
                        token,
1✔
341
                        client.JoinURL(
1✔
342
                                c.url,
1✔
343
                                strings.ReplaceAll(transferCompleteURL, ":id", id),
1✔
344
                        ),
1✔
345
                        c.client,
1✔
346
                        body,
1✔
347
                )
1✔
348
                if err != nil {
1✔
349
                        return errors.Wrap(err, "failed to notify on complete upload")
×
350
                }
×
351
        }
352

353
        return nil
1✔
354
}
355

356
func (c *Client) UploadArtifact(
357
        description, artifactPath, token string,
358
        noProgress bool,
359
) error {
3✔
360
        var bar *pb.ProgressBar
3✔
361

3✔
362
        artifact, err := os.Open(artifactPath)
3✔
363
        if err != nil {
3✔
364
                return errors.Wrap(err, "Cannot read artifact file")
×
365
        }
×
366

367
        artifactStats, err := artifact.Stat()
3✔
368
        if err != nil {
3✔
369
                return errors.Wrap(err, "Cannot read artifact file stats")
×
370
        }
×
371

372
        if err = checkArtifactFormat(artifact); err != nil {
3✔
373
                return err
×
374
        }
×
375
        // create pipe
376
        pR, pW := io.Pipe()
3✔
377

3✔
378
        // create multipart writer
3✔
379
        writer := multipart.NewWriter(pW)
3✔
380

3✔
381
        req, err := http.NewRequest(http.MethodPost, c.artifactUploadURL, pR)
3✔
382
        if err != nil {
3✔
383
                return errors.Wrap(err, "Cannot create request")
×
384
        }
×
385
        req.Header.Set("Content-Type", writer.FormDataContentType())
3✔
386
        req.Header.Set("Authorization", "Bearer "+string(token))
3✔
387

3✔
388
        reqDump, _ := httputil.DumpRequest(req, false)
3✔
389
        log.Verbf("sending request: \n%v", string(reqDump))
3✔
390

3✔
391
        if !noProgress {
6✔
392
                // create progress bar
3✔
393
                bar = pb.New64(artifactStats.Size()).
3✔
394
                        Set(pb.Bytes, true).
3✔
395
                        SetRefreshRate(time.Millisecond * 100)
3✔
396
                bar.Start()
3✔
397
        }
3✔
398

399
        go func() {
6✔
400
                var part io.Writer
3✔
401
                defer pW.Close()
3✔
402
                defer artifact.Close()
3✔
403

3✔
404
                _ = writer.WriteField("size", strconv.FormatInt(artifactStats.Size(), 10))
3✔
405
                _ = writer.WriteField("description", description)
3✔
406
                part, _ = writer.CreateFormFile("artifact", artifactStats.Name())
3✔
407

3✔
408
                if !noProgress {
6✔
409
                        part = bar.NewProxyWriter(part)
3✔
410
                }
3✔
411

412
                if _, err := io.Copy(part, artifact); err != nil {
3✔
413
                        writer.Close()
×
414
                        _ = pR.CloseWithError(err)
×
415
                        return
×
416
                }
×
417

418
                writer.Close()
3✔
419
                if !noProgress {
6✔
420
                        log.Info("Processing uploaded file. This may take around one minute.\n")
3✔
421
                }
3✔
422
        }()
423

424
        rsp, err := c.client.Do(req)
3✔
425
        if err != nil {
3✔
426
                return errors.Wrap(err, "POST /artifacts request failed")
×
427
        }
×
428
        defer rsp.Body.Close()
3✔
429
        pR.Close()
3✔
430

3✔
431
        rspDump, _ := httputil.DumpResponse(rsp, true)
3✔
432
        log.Verbf("response: \n%v\n", string(rspDump))
3✔
433

3✔
434
        if rsp.StatusCode != http.StatusCreated {
3✔
435
                if rsp.StatusCode == http.StatusUnauthorized {
×
436
                        log.Verbf("artifact upload to '%s' failed with status %d", req.Host, rsp.StatusCode)
×
437
                        return errors.New("Unauthorized. Please Login first")
×
438
                } else if rsp.StatusCode == http.StatusConflict {
×
439
                        log.Verbf("artifact upload to '%s' failed with status %d", req.Host, rsp.StatusCode)
×
440
                        return errors.New("Artifact with same name or depends exists already")
×
441
                }
×
442
                return errors.New(
×
443
                        fmt.Sprintf("artifact upload to '%s' failed with status %d", req.Host, rsp.StatusCode),
×
444
                )
×
445
        }
446

447
        return nil
3✔
448
}
449

450
func (c *Client) DeleteArtifact(
451
        artifactID, token string,
452
) error {
1✔
453

1✔
454
        req, err := http.NewRequest(http.MethodDelete, c.artifactDeleteURL+"/"+artifactID, nil)
1✔
455
        if err != nil {
1✔
456
                return errors.Wrap(err, "Cannot create request")
×
457
        }
×
458
        req.Header.Set("Authorization", "Bearer "+string(token))
1✔
459

1✔
460
        reqDump, _ := httputil.DumpRequest(req, false)
1✔
461
        log.Verbf("sending request: \n%v", string(reqDump))
1✔
462

1✔
463
        rsp, err := c.client.Do(req)
1✔
464
        if err != nil {
1✔
465
                return errors.Wrap(err, "DELETE /artifacts request failed")
×
466
        }
×
467
        defer rsp.Body.Close()
1✔
468

1✔
469
        rspDump, _ := httputil.DumpResponse(rsp, true)
1✔
470
        log.Verbf("response: \n%v\n", string(rspDump))
1✔
471

1✔
472
        if rsp.StatusCode != http.StatusNoContent {
1✔
NEW
473
                body, err := io.ReadAll(rsp.Body)
×
474
                if err != nil {
×
475
                        return errors.Wrap(err, "can't read request body")
×
476
                }
×
477
                if rsp.StatusCode == http.StatusUnauthorized {
×
478
                        log.Verbf("artifact delete failed with status %d, reason: %s", rsp.StatusCode, body)
×
479
                        return errors.New("Unauthorized. Please Login first")
×
480
                }
×
481
                return errors.New(
×
482
                        fmt.Sprintf("artifact upload failed with status %d, reason: %s", rsp.StatusCode, body),
×
483
                )
×
484
        }
485

486
        return nil
1✔
487
}
488

489
func (c *Client) DownloadArtifact(
490
        sourcePath, artifactID, token string, noProgress bool,
491
) error {
1✔
492

1✔
493
        link, err := c.getLink(artifactID, token)
1✔
494
        if err != nil {
1✔
495
                return errors.Wrap(err, "Cannot get artifact link")
×
496
        }
×
497
        artifact, err := c.getArtifact(artifactID, token)
1✔
498
        if err != nil {
1✔
499
                return errors.Wrap(err, "Cannot get artifact details")
×
500
        }
×
501
        log.Verbf("link: \n%v\n", link.Uri)
1✔
502
        log.Verbf("artifact: \n%v\n", artifact.Size)
1✔
503

1✔
504
        if sourcePath != "" {
1✔
505
                sourcePath += "/"
×
506
        }
×
507
        sourcePath += artifact.Name + ".mender"
1✔
508

1✔
509
        req, err := http.NewRequest(http.MethodGet, link.Uri, nil)
1✔
510
        if err != nil {
1✔
511
                return errors.Wrap(err, "Cannot create request")
×
512
        }
×
513

514
        reqDump, _ := httputil.DumpRequest(req, false)
1✔
515
        log.Verbf("sending request: \n%v", string(reqDump))
1✔
516
        resp, err := c.client.Do(req)
1✔
517
        if err != nil {
1✔
518
                return errors.Wrap(err, "GET /artifacts request failed")
×
519
        }
×
520
        defer resp.Body.Close()
1✔
521

1✔
522
        switch resp.StatusCode {
1✔
523
        case http.StatusOK:
1✔
524
                return c.downloadFile(artifact.Size, sourcePath, resp, noProgress)
1✔
525
        case http.StatusBadRequest:
×
526
                return errors.New("Bad request\n")
×
527
        case http.StatusForbidden:
×
528
                return errors.New("Forbidden")
×
529
        case http.StatusNotFound:
×
530
                return errors.New("File not found on the device\n")
×
531
        case http.StatusConflict:
×
532
                return errors.New("The device is not connected\n")
×
533
        case http.StatusInternalServerError:
×
534
                return errors.New("Internal server error\n")
×
535
        default:
×
536
                return errors.New("Error: Received unexpected response code: " +
×
537
                        strconv.Itoa(resp.StatusCode))
×
538
        }
539
}
540

541
type DownloadLink struct {
542
        Uri    string    `json:"uri"`
543
        Expire time.Time `json:"expire"`
544
}
545

546
type Artifact struct {
547
        Size int64  `json:"size"`
548
        Name string `json:"name"`
549
}
550

551
func (c *Client) getArtifact(
552
        artifactID, token string,
553
) (*Artifact, error) {
1✔
554
        req, err := http.NewRequest(http.MethodGet,
1✔
555
                strings.ReplaceAll(c.artifactURL, ":id", artifactID), nil)
1✔
556
        if err != nil {
1✔
557
                return nil, errors.Wrap(err, "Cannot create request")
×
558
        }
×
559
        req.Header.Set("Authorization", "Bearer "+string(token))
1✔
560

1✔
561
        reqDump, _ := httputil.DumpRequest(req, false)
1✔
562
        log.Verbf("sending request: \n%v", string(reqDump))
1✔
563

1✔
564
        rsp, err := c.client.Do(req)
1✔
565
        if err != nil {
1✔
566
                return nil, errors.Wrap(err, "GET /artifacts request failed")
×
567
        }
×
568
        defer rsp.Body.Close()
1✔
569

1✔
570
        rspDump, _ := httputil.DumpResponse(rsp, true)
1✔
571
        log.Verbf("response: \n%v\n", string(rspDump))
1✔
572

1✔
573
        body, err := io.ReadAll(rsp.Body)
1✔
574
        if err != nil {
1✔
575
                return nil, errors.Wrap(err, "GET /artifacts request failed")
×
576
        }
×
577

578
        var artifact Artifact
1✔
579
        err = json.Unmarshal(body, &artifact)
1✔
580
        if err != nil {
1✔
581
                return nil, errors.Wrap(err, "GET /artifacts request failed")
×
582
        }
×
583

584
        return &artifact, nil
1✔
585
}
586

587
func (c *Client) getLink(
588
        artifactID, token string,
589
) (*DownloadLink, error) {
1✔
590
        req, err := http.NewRequest(http.MethodGet,
1✔
591
                strings.ReplaceAll(c.artifactDownloadURL, ":id", artifactID), nil)
1✔
592
        if err != nil {
1✔
593
                return nil, errors.Wrap(err, "Cannot create request")
×
594
        }
×
595
        req.Header.Set("Authorization", "Bearer "+string(token))
1✔
596

1✔
597
        reqDump, _ := httputil.DumpRequest(req, false)
1✔
598
        log.Verbf("sending request: \n%v", string(reqDump))
1✔
599

1✔
600
        rsp, err := c.client.Do(req)
1✔
601
        if err != nil {
1✔
602
                return nil, errors.Wrap(err, "GET /artifacts request failed")
×
603
        }
×
604
        defer rsp.Body.Close()
1✔
605

1✔
606
        rspDump, _ := httputil.DumpResponse(rsp, true)
1✔
607
        log.Verbf("response: \n%v\n", string(rspDump))
1✔
608

1✔
609
        body, err := io.ReadAll(rsp.Body)
1✔
610
        if err != nil {
1✔
611
                return nil, errors.Wrap(err, "GET /artifacts request failed")
×
612
        }
×
613

614
        var link DownloadLink
1✔
615
        err = json.Unmarshal(body, &link)
1✔
616
        if err != nil {
1✔
617
                return nil, errors.Wrap(err, "GET /artifacts request failed")
×
618
        }
×
619

620
        return &link, nil
1✔
621
}
622

623
func (c *Client) downloadFile(size int64, localFileName string, resp *http.Response,
624
        noProgress bool) error {
1✔
625
        path := resp.Header.Get("X-MEN-FILE-PATH")
1✔
626
        uid := resp.Header.Get("X-MEN-FILE-UID")
1✔
627
        gid := resp.Header.Get("X-MEN-FILE-GID")
1✔
628
        var n int64
1✔
629

1✔
630
        file, err := os.Create(localFileName)
1✔
631
        if err != nil {
1✔
632
                return errors.Wrap(err, "Cannot create file")
×
633
        }
×
634
        defer file.Close()
1✔
635
        if err != nil {
1✔
636
                log.Errf("Failed to create the file %s locally\n", path)
×
637
                return err
×
638
        }
×
639

640
        if resp.Header.Get("Content-Type") != "application/vnd.mender-artifact" {
1✔
641
                return fmt.Errorf("Unexpected Content-Type header: %s", resp.Header.Get("Content-Type"))
×
642
        }
×
643
        if err != nil {
1✔
644
                log.Err("downloadFile: Failed to parse the Content-Type header")
×
645
                return err
×
646
        }
×
647
        i, _ := strconv.Atoi(resp.Header.Get("Content-Length"))
1✔
648
        sourceSize := int64(i)
1✔
649
        source := resp.Body
1✔
650

1✔
651
        if !noProgress {
2✔
652
                var bar *pb.ProgressBar = pb.New64(sourceSize).
1✔
653
                        Set(pb.Bytes, true).
1✔
654
                        SetRefreshRate(time.Millisecond * 100)
1✔
655
                bar.Start()
1✔
656
                // create proxy reader
1✔
657
                reader := bar.NewProxyReader(source)
1✔
658
                n, err = io.Copy(file, reader)
1✔
659
        } else {
1✔
660
                n, err = io.Copy(file, source)
×
661
        }
×
662
        log.Verbf("wrote: %d\n", n)
1✔
663
        if err != nil {
1✔
664
                return err
×
665
        }
×
666
        if n != size {
1✔
667
                return errors.New(
×
668
                        "The downloaded file does not match the expected length in 'X-MEN-FILE-SIZE'",
×
669
                )
×
670
        }
×
671
        // Set the proper permissions and {G,U}ID's if present
672
        if uid != "" && gid != "" {
1✔
673
                uidi, err := strconv.Atoi(uid)
×
674
                if err != nil {
×
675
                        return err
×
676
                }
×
677
                gidi, err := strconv.Atoi(gid)
×
678
                if err != nil {
×
679
                        return err
×
680
                }
×
681
                err = os.Chown(file.Name(), uidi, gidi)
×
682
                if err != nil {
×
683
                        return err
×
684
                }
×
685
        }
686
        return nil
1✔
687
}
688
func checkArtifactFormat(artifact *os.File) error {
4✔
689
        tr := tar.NewReader(artifact)
4✔
690
        versionH, err := tr.Next()
4✔
691
        if err != nil {
4✔
692
                return errors.Wrap(err, "error parsing artifact")
×
693
        } else if versionH.Name != "version" {
4✔
694
                return errors.New("Invalid artifact format")
×
695
        }
×
696
        v := struct {
4✔
697
                Format string `json:"format"`
4✔
698
        }{}
4✔
699
        err = json.NewDecoder(tr).Decode(&v)
4✔
700
        if err != nil || v.Format != "mender" {
4✔
701
                return errors.New("Invalid artifact format")
×
702
        }
×
703
        _, err = artifact.Seek(0, io.SeekStart)
4✔
704
        if err != nil || v.Format != "mender" {
4✔
705
                return err
×
706
        }
×
707
        return nil
4✔
708
}
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