Skip to content

Commit 9586764

Browse files
committed
Support human-readable reclaim sizes
1 parent 657b489 commit 9586764

4 files changed

Lines changed: 66 additions & 6 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/kernel/hypeman-cli
33
go 1.25
44

55
require (
6+
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
67
github.com/charmbracelet/bubbles v0.21.0
78
github.com/charmbracelet/bubbletea v1.3.6
89
github.com/charmbracelet/lipgloss v1.1.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
66
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
77
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
88
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
9+
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
10+
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
911
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
1012
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
1113
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=

pkg/cmd/resourcecmd.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package cmd
33
import (
44
"context"
55
"fmt"
6+
"math"
67
"os"
78
"strings"
89

10+
"github.com/c2h5oh/datasize"
911
"github.com/kernel/hypeman-go"
1012
"github.com/kernel/hypeman-go/option"
1113
"github.com/tidwall/gjson"
@@ -39,10 +41,15 @@ Examples:
3941
var resourcesReclaimMemoryCmd = cli.Command{
4042
Name: "reclaim-memory",
4143
Usage: "Request guest memory reclaim from reclaim-eligible instances",
44+
Description: `Request guest memory reclaim across eligible instances.
45+
46+
Examples:
47+
hypeman resources reclaim-memory --reclaim-bytes 512MB --dry-run
48+
hypeman resources reclaim-memory --reclaim-bytes 1073741824 --hold-for 10m --reason "pack host before launch"`,
4249
Flags: []cli.Flag{
43-
&cli.Int64Flag{
50+
&cli.StringFlag{
4451
Name: "reclaim-bytes",
45-
Usage: "Total bytes of guest memory to reclaim across eligible VMs",
52+
Usage: `Total guest memory to reclaim (e.g., "512MB", "2GB", or "1048576" for raw bytes)`,
4653
Required: true,
4754
},
4855
&cli.BoolFlag{
@@ -93,9 +100,9 @@ func handleResources(ctx context.Context, cmd *cli.Command) error {
93100
func handleResourcesReclaimMemory(ctx context.Context, cmd *cli.Command) error {
94101
client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)
95102

96-
reclaimBytes := cmd.Int64("reclaim-bytes")
97-
if reclaimBytes <= 0 {
98-
return fmt.Errorf("reclaim-bytes must be greater than 0")
103+
reclaimBytes, err := parseReclaimBytes(cmd.String("reclaim-bytes"))
104+
if err != nil {
105+
return err
99106
}
100107

101108
request := hypeman.MemoryReclaimRequestParam{
@@ -122,7 +129,7 @@ func handleResourcesReclaimMemory(ctx context.Context, cmd *cli.Command) error {
122129

123130
var res []byte
124131
opts = append(opts, option.WithResponseBodyInto(&res))
125-
_, err := client.Resources.ReclaimMemory(ctx, params, opts...)
132+
_, err = client.Resources.ReclaimMemory(ctx, params, opts...)
126133
if err != nil {
127134
return err
128135
}
@@ -143,6 +150,25 @@ func handleResourcesReclaimMemory(ctx context.Context, cmd *cli.Command) error {
143150
return ShowJSON(os.Stdout, "resources reclaim-memory", obj, format, transform)
144151
}
145152

153+
func parseReclaimBytes(raw string) (int64, error) {
154+
if raw == "" {
155+
return 0, fmt.Errorf("reclaim-bytes is required")
156+
}
157+
158+
var size datasize.ByteSize
159+
if err := size.UnmarshalText([]byte(raw)); err != nil {
160+
return 0, fmt.Errorf("invalid reclaim-bytes %q: %w", raw, err)
161+
}
162+
if size == 0 {
163+
return 0, fmt.Errorf("reclaim-bytes must be greater than 0")
164+
}
165+
if size.Bytes() > math.MaxInt64 {
166+
return 0, fmt.Errorf("reclaim-bytes %q exceeds the maximum supported size", raw)
167+
}
168+
169+
return int64(size.Bytes()), nil
170+
}
171+
146172
func showResourcesTable(data []byte) error {
147173
obj := gjson.ParseBytes(data)
148174

pkg/cmd/resourcecmd_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,39 @@ import (
44
"testing"
55

66
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
78
)
89

10+
func TestParseReclaimBytes(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
input string
14+
expected int64
15+
wantErr string
16+
}{
17+
{name: "raw bytes", input: "1048576", expected: 1048576},
18+
{name: "megabytes", input: "512MB", expected: 512 * 1024 * 1024},
19+
{name: "gigabytes with space", input: "2 GB", expected: 2 * 1024 * 1024 * 1024},
20+
{name: "empty", input: "", wantErr: "reclaim-bytes is required"},
21+
{name: "zero", input: "0", wantErr: "reclaim-bytes must be greater than 0"},
22+
{name: "invalid", input: "nope", wantErr: "invalid reclaim-bytes \"nope\""},
23+
}
24+
25+
for _, tt := range tests {
26+
t.Run(tt.name, func(t *testing.T) {
27+
got, err := parseReclaimBytes(tt.input)
28+
if tt.wantErr != "" {
29+
require.Error(t, err)
30+
assert.Contains(t, err.Error(), tt.wantErr)
31+
return
32+
}
33+
34+
require.NoError(t, err)
35+
assert.Equal(t, tt.expected, got)
36+
})
37+
}
38+
}
39+
940
func TestFormatBytes(t *testing.T) {
1041
tests := []struct {
1142
bytes int64

0 commit comments

Comments
 (0)