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

mendersoftware / mender-artifact / 2092026745

10 Oct 2025 06:16AM UTC coverage: 76.263% (+0.2%) from 76.088%
2092026745

push

gitlab-ci

web-flow
Merge pull request #754 from lluiscampos/MEN-8567-warn-on-artifact-size

MEN-8567: Warn and optionally fail on large Artifact sizes

102 of 115 new or added lines in 3 files covered. (88.7%)

2 existing lines in 1 file now uncovered.

6082 of 7975 relevant lines covered (76.26%)

141.85 hits per line

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

90.7
/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
const (
29
        KB = 1000
30
        MB = 1000 * 1000
31
        GB = 1000 * 1000 * 1000
32
        TB = 1000 * 1000 * 1000 * 1000
33
)
34

35
// ParseSizeLimit parses size strings like "5MB", "1.5GB", "100"
36
// Supports: B, KB, MB, GB, TB (decimal, base 1000)
37
// Case-insensitive, allows decimals (1.5MB), bare numbers (5000000)
38
func ParseSizeLimit(sizeStr string) (int64, error) {
231✔
39
        if sizeStr == "" {
233✔
40
                return 0, errors.New("size string is empty")
2✔
41
        }
2✔
42

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

229✔
48
        if matches == nil {
245✔
49
                return 0, errors.Errorf("invalid size format: %s", sizeStr)
16✔
50
        }
16✔
51

52
        // Parse number part
53
        numStr := matches[1]
213✔
54
        num, err := strconv.ParseFloat(numStr, 64)
213✔
55
        if err != nil {
213✔
NEW
56
                return 0, errors.Wrapf(err, "invalid number: %s", numStr)
×
NEW
57
        }
×
58

59
        // Parse unit part (case-insensitive)
60
        unit := strings.ToUpper(matches[2])
213✔
61

213✔
62
        var multiplier int64
213✔
63
        switch unit {
213✔
64
        case "", "B":
18✔
65
                multiplier = 1
18✔
66
        case "KB":
20✔
67
                multiplier = KB
20✔
68
        case "MB":
143✔
69
                multiplier = MB
143✔
70
        case "GB":
14✔
71
                multiplier = GB
14✔
72
        case "TB":
8✔
73
                multiplier = TB
8✔
74
        default:
10✔
75
                return 0, errors.Errorf(
10✔
76
                        "unsupported unit: %s (supported: B, KB, MB, GB, TB)",
10✔
77
                        unit,
10✔
78
                )
10✔
79
        }
80

81
        result := int64(num * float64(multiplier))
203✔
82
        return result, nil
203✔
83
}
84

85
// FormatSize formats bytes into human-readable format
86
// Examples: 5000000 -> "5.0 MB", 1500000000 -> "1.5 GB"
87
func FormatSize(bytes int64) string {
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-artifact-size)
137✔
115
        if maxSize := ctx.String("max-artifact-size"); maxSize != "" {
151✔
116
                limit, err := ParseSizeLimit(maxSize)
14✔
117
                if err != nil {
16✔
118
                        return errors.Wrap(err, "invalid --max-artifact-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
                                        " file deleted",
4✔
130
                                FormatSize(size),
4✔
131
                                maxSize,
4✔
132
                        )
4✔
133
                }
134
        }
135

136
        // Check soft limit (--warn-artifact-size)
137
        if warnSize := ctx.String("warn-artifact-size"); warnSize != "" {
262✔
138
                limit, err := ParseSizeLimit(warnSize)
131✔
139
                if err != nil {
131✔
NEW
140
                        return errors.Wrap(err, "invalid --warn-artifact-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