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

mendersoftware / mender-artifact / 2085792213

07 Oct 2025 02:02PM UTC coverage: 76.248% (+0.2%) from 76.088%
2085792213

Pull #754

gitlab-ci

lluiscampos
fixup! test: Add cli `write` tests for Artifact size limits
Pull Request #754: MEN-8567: Warn and optionally fail on large Artifact sizes

111 of 129 new or added lines in 3 files covered. (86.05%)

2 existing lines in 1 file now uncovered.

6093 of 7991 relevant lines covered (76.25%)

141.33 hits per line

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

91.4
/cli/size_limits.go
1
// Copyright 2025 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
        "fmt"
19
        "os"
20
        "regexp"
21
        "strconv"
22
        "strings"
23

24
        "github.com/pkg/errors"
25
        "github.com/urfave/cli"
26
)
27

28
// ParseSizeLimit parses size strings like "5MB", "1.5GB", "100"
29
// Supports: B, KB, MB, GB, TB (decimal, base 1000)
30
// Case-insensitive, allows decimals (1.5MB), bare numbers (5000000)
31
func ParseSizeLimit(sizeStr string) (int64, error) {
231✔
32
        if sizeStr == "" {
233✔
33
                return 0, errors.New("size string is empty")
2✔
34
        }
2✔
35

36
        // Regex: optional decimal number + optional unit
37
        // Examples: "5", "5MB", "1.5GB", "100kb"
38
        re := regexp.MustCompile(`^([0-9]+\.?[0-9]*)([a-zA-Z]*)$`)
229✔
39
        matches := re.FindStringSubmatch(strings.TrimSpace(sizeStr))
229✔
40

229✔
41
        if matches == nil {
245✔
42
                return 0, errors.Errorf("invalid size format: %s", sizeStr)
16✔
43
        }
16✔
44

45
        // Parse number part
46
        numStr := matches[1]
213✔
47
        num, err := strconv.ParseFloat(numStr, 64)
213✔
48
        if err != nil {
213✔
NEW
49
                return 0, errors.Wrapf(err, "invalid number: %s", numStr)
×
NEW
50
        }
×
51

52
        // Parse unit part (case-insensitive)
53
        unit := strings.ToUpper(matches[2])
213✔
54

213✔
55
        var multiplier int64
213✔
56
        switch unit {
213✔
57
        case "", "B":
18✔
58
                multiplier = 1
18✔
59
        case "KB":
20✔
60
                multiplier = 1000
20✔
61
        case "MB":
143✔
62
                multiplier = 1000 * 1000
143✔
63
        case "GB":
14✔
64
                multiplier = 1000 * 1000 * 1000
14✔
65
        case "TB":
8✔
66
                multiplier = 1000 * 1000 * 1000 * 1000
8✔
67
        default:
10✔
68
                return 0, errors.Errorf(
10✔
69
                        "unsupported unit: %s (supported: B, KB, MB, GB, TB)",
10✔
70
                        unit,
10✔
71
                )
10✔
72
        }
73

74
        result := int64(num * float64(multiplier))
203✔
75
        return result, nil
203✔
76
}
77

78
// FormatSize formats bytes into human-readable format
79
// Examples: 5000000 -> "5.0 MB", 1500000000 -> "1.5 GB"
80
func FormatSize(bytes int64) string {
58✔
81
        const (
58✔
82
                KB = 1000
58✔
83
                MB = 1000 * 1000
58✔
84
                GB = 1000 * 1000 * 1000
58✔
85
                TB = 1000 * 1000 * 1000 * 1000
58✔
86
        )
58✔
87

58✔
88
        switch {
58✔
89
        case bytes >= TB:
8✔
90
                return fmt.Sprintf("%.1f TB", float64(bytes)/float64(TB))
8✔
91
        case bytes >= GB:
12✔
92
                return fmt.Sprintf("%.1f GB", float64(bytes)/float64(GB))
12✔
93
        case bytes >= MB:
12✔
94
                return fmt.Sprintf("%.1f MB", float64(bytes)/float64(MB))
12✔
95
        case bytes >= KB:
18✔
96
                return fmt.Sprintf("%.1f KB", float64(bytes)/float64(KB))
18✔
97
        default:
8✔
98
                return fmt.Sprintf("%d B", bytes)
8✔
99
        }
100
}
101

102
// CheckArtifactSize checks the Artifact file size against configured limits
103
// Returns error if max limit exceeded (and deletes file)
104
// Logs warning if warn limit exceeded (keeps file)
105
func CheckArtifactSize(outputPath string, ctx *cli.Context) error {
137✔
106
        // Get file info
137✔
107
        fi, err := os.Stat(outputPath)
137✔
108
        if err != nil {
137✔
NEW
109
                return errors.Wrapf(err, "failed to stat Artifact file: %s", outputPath)
×
NEW
110
        }
×
111

112
        size := fi.Size()
137✔
113

137✔
114
        // Check hard limit (--max-payload-size)
137✔
115
        if maxSize := ctx.String("max-payload-size"); maxSize != "" {
151✔
116
                limit, err := ParseSizeLimit(maxSize)
14✔
117
                if err != nil {
16✔
118
                        return errors.Wrap(err, "invalid --max-payload-size")
2✔
119
                }
2✔
120

121
                if size > limit {
16✔
122
                        // Delete the artifact file
4✔
123
                        if rmErr := os.Remove(outputPath); rmErr != nil {
4✔
NEW
124
                                Log.Warnf("Failed to delete Artifact file: %v", rmErr)
×
NEW
125
                        }
×
126

127
                        return fmt.Errorf(
4✔
128
                                "Artifact size %s exceeds maximum allowed size %s "+
4✔
129
                                        "(--max-payload-size); file deleted",
4✔
130
                                FormatSize(size),
4✔
131
                                maxSize,
4✔
132
                        )
4✔
133
                }
134
        }
135

136
        // Check soft limit (--warn-payload-size)
137
        if warnSize := ctx.String("warn-payload-size"); warnSize != "" {
262✔
138
                limit, err := ParseSizeLimit(warnSize)
131✔
139
                if err != nil {
131✔
NEW
140
                        return errors.Wrap(err, "invalid --warn-payload-size")
×
NEW
141
                }
×
142

143
                if size > limit {
133✔
144
                        Log.Warnf(
2✔
145
                                "Artifact size %s exceeds specified limit %s",
2✔
146
                                FormatSize(size),
2✔
147
                                warnSize,
2✔
148
                        )
2✔
149
                }
2✔
150
        }
151

152
        return nil
131✔
153
}
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