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

mendersoftware / mender-cli / 1779499080

22 Apr 2025 10:11AM UTC coverage: 1.737% (-30.1%) from 31.802%
1779499080

push

gitlab-ci

web-flow
Merge pull request #277 from alfrunes/MEN-7794

MEN-7794: Add support for pagination when listing devices

28 of 82 new or added lines in 4 files covered. (34.15%)

770 existing lines in 17 files now uncovered.

45 of 2590 relevant lines covered (1.74%)

0.04 hits per line

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

0.0
/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

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

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

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

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

UNCOV
137
        return &link, nil
×
138
}
139

UNCOV
140
func (c *Client) ListArtifacts(token string, detailLevel int) error {
×
UNCOV
141
        if detailLevel > 3 || detailLevel < 0 {
×
142
                return fmt.Errorf("FAILURE: invalid artifact detail")
×
143
        }
×
NEW
144
        log.Err("warning: use of deprecated API for listing artifacts")
×
UNCOV
145

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

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

UNCOV
160
        return nil
×
161
}
162

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

UNCOV
203
        fmt.Println("--------------------------------------------------------------------------------")
×
204
}
205

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

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

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

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

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

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

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

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

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

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

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

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

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

×
UNCOV
331
        rspDump, _ := httputil.DumpResponse(rsp, true)
×
UNCOV
332
        log.Verbf("response: \n%v\n", string(rspDump))
×
UNCOV
333

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

UNCOV
354
        return nil
×
355
}
356

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

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

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

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

×
UNCOV
379
        // create multipart writer
×
UNCOV
380
        writer := multipart.NewWriter(pW)
×
UNCOV
381

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

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

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

UNCOV
400
        go func() {
×
UNCOV
401
                var part io.Writer
×
UNCOV
402
                defer pW.Close()
×
UNCOV
403
                defer artifact.Close()
×
UNCOV
404

×
UNCOV
405
                _ = writer.WriteField("size", strconv.FormatInt(artifactStats.Size(), 10))
×
UNCOV
406
                _ = writer.WriteField("description", description)
×
UNCOV
407
                part, _ = writer.CreateFormFile("artifact", artifactStats.Name())
×
UNCOV
408

×
UNCOV
409
                if !noProgress {
×
UNCOV
410
                        part = bar.NewProxyWriter(part)
×
UNCOV
411
                }
×
412

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

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

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

×
UNCOV
432
        rspDump, _ := httputil.DumpResponse(rsp, true)
×
UNCOV
433
        log.Verbf("response: \n%v\n", string(rspDump))
×
UNCOV
434

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

UNCOV
448
        return nil
×
449
}
450

451
func (c *Client) DeleteArtifact(
452
        artifactID, token string,
UNCOV
453
) error {
×
UNCOV
454

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

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

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

×
UNCOV
470
        rspDump, _ := httputil.DumpResponse(rsp, true)
×
UNCOV
471
        log.Verbf("response: \n%v\n", string(rspDump))
×
UNCOV
472

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

UNCOV
487
        return nil
×
488
}
489

490
func (c *Client) DownloadArtifact(
491
        sourcePath, artifactID, token string, noProgress bool,
UNCOV
492
) error {
×
UNCOV
493

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

×
UNCOV
505
        if sourcePath != "" {
×
506
                sourcePath += "/"
×
507
        }
×
UNCOV
508
        sourcePath += artifact.Name + ".mender"
×
UNCOV
509

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

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

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

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

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

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

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

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

×
UNCOV
571
        rspDump, _ := httputil.DumpResponse(rsp, true)
×
UNCOV
572
        log.Verbf("response: \n%v\n", string(rspDump))
×
UNCOV
573

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

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

UNCOV
585
        return &artifact, nil
×
586
}
587

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

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

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

×
UNCOV
607
        rspDump, _ := httputil.DumpResponse(rsp, true)
×
UNCOV
608
        log.Verbf("response: \n%v\n", string(rspDump))
×
UNCOV
609

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

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

UNCOV
621
        return &link, nil
×
622
}
623

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

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

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

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