diff --git a/go.mod b/go.mod index 0c100d9775..71473b38be 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,7 @@ require ( github.com/spf13/pflag v1.0.9 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.11.1 - github.com/superfly/fly-go v0.2.3 + github.com/superfly/fly-go v0.3.0 github.com/superfly/graphql v0.2.6 github.com/superfly/lfsc-go v0.1.1 github.com/superfly/macaroon v0.3.0 diff --git a/go.sum b/go.sum index 2cf888a295..f4a42d36ee 100644 --- a/go.sum +++ b/go.sum @@ -637,8 +637,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/superfly/fly-go v0.2.3 h1:sK9lLjKNB/HWYtDcXp4G8WH4BIPkZe/8/MPa7VN1FzY= -github.com/superfly/fly-go v0.2.3/go.mod h1:2gCFoNR3iUELADGTJtbBoviMa2jlh2vlPK3cKUajOp8= +github.com/superfly/fly-go v0.3.0 h1:opzCfB5GeDe2AaqZgtPzWps+fW1zAMhISWAyQrhnV+Y= +github.com/superfly/fly-go v0.3.0/go.mod h1:2gCFoNR3iUELADGTJtbBoviMa2jlh2vlPK3cKUajOp8= github.com/superfly/graphql v0.2.6 h1:zppbodNerWecoXEdjkhrqaNaSjGqobhXNlViHFuZzb4= github.com/superfly/graphql v0.2.6/go.mod h1:CVfDl31srm8HnJ9udwLu6hFNUW/P6GUM2dKcG1YQ8jc= github.com/superfly/lfsc-go v0.1.1 h1:dGjLgt81D09cG+aR9lJZIdmonjZSR5zYCi7s54+ZU2Q= diff --git a/internal/command/machine/run.go b/internal/command/machine/run.go index 861a5f5dec..915c3381cf 100644 --- a/internal/command/machine/run.go +++ b/internal/command/machine/run.go @@ -154,9 +154,13 @@ var sharedFlags = flag.Set{ Name: "rootfs-persist", Description: "Whether to persist the root filesystem across restarts. Options include 'never', 'always', and 'restart'.", }, - flag.Int{ + flag.String{ Name: "rootfs-size", - Description: "Root filesystem size in GB. Uses an overlayfs to allow the root filesystem to exceed its default size.", + Description: "Root filesystem size in GB. Accepts a plain number (in GB) or a human-readable size (e.g. 2gb, 5gb). Uses an overlayfs to allow the root filesystem to exceed its default size. Set to 0 to unset.", + }, + flag.String{ + Name: "rootfs-fs-size", + Description: "Root filesystem size in GB. Accepts a plain number (in GB) or a human-readable size (e.g. 2gb, 5gb). Sets the size of the filesystem itself, independent of the rootfs volume size. Set to 0 to unset.", }, flag.String{ Name: "swap-size", @@ -712,7 +716,7 @@ func determineMachineConfig( } // Root filesystem persistence and size - if flag.IsSpecified(ctx, "rootfs-persist") || flag.IsSpecified(ctx, "rootfs-size") { + if flag.IsSpecified(ctx, "rootfs-persist") || flag.IsSpecified(ctx, "rootfs-size") || flag.IsSpecified(ctx, "rootfs-fs-size") { if machineConf.Rootfs == nil { machineConf.Rootfs = &fly.MachineRootfs{} } @@ -731,11 +735,33 @@ func determineMachineConfig( } if flag.IsSpecified(ctx, "rootfs-size") { - size := flag.GetInt(ctx, "rootfs-size") - if size <= 0 { - return machineConf, fmt.Errorf("--rootfs-size must be greater than zero") + sizeGB, err := helpers.ParseSize(flag.GetString(ctx, "rootfs-size"), units.RAMInBytes, units.GiB) + if err != nil { + return machineConf, fmt.Errorf("invalid rootfs size: %w", err) + } + if sizeGB < 0 { + return machineConf, fmt.Errorf("--rootfs-size must not be negative") + } + machineConf.Rootfs.SizeGB = uint64(sizeGB) + } + + if flag.IsSpecified(ctx, "rootfs-fs-size") { + sizeGB, err := helpers.ParseSize(flag.GetString(ctx, "rootfs-fs-size"), units.RAMInBytes, units.GiB) + if err != nil { + return machineConf, fmt.Errorf("invalid rootfs fs size: %w", err) + } + if sizeGB < 0 { + return machineConf, fmt.Errorf("--rootfs-fs-size must not be negative") + } + machineConf.Rootfs.FsSizeGB = uint64(sizeGB) + } + + if machineConf.Rootfs.FsSizeGB > 0 { + if machineConf.Rootfs.SizeGB == 0 { + machineConf.Rootfs.SizeGB = machineConf.Rootfs.FsSizeGB + } else if machineConf.Rootfs.FsSizeGB > machineConf.Rootfs.SizeGB { + return machineConf, fmt.Errorf("--rootfs-fs-size must be smaller than or equal to --rootfs-size") } - machineConf.Rootfs.SizeGB = uint64(size) } } diff --git a/test/preflight/fly_machine_test.go b/test/preflight/fly_machine_test.go index 86d7732099..1c35ba3327 100644 --- a/test/preflight/fly_machine_test.go +++ b/test/preflight/fly_machine_test.go @@ -127,6 +127,53 @@ func TestFlyMachineRun_standbyFor(t *testing.T) { require.Equal(f, []string{s1.ID}, s2.Config.Standbys) } +// test --rootfs-size and --rootfs-fs-size flags +func TestFlyMachineRun_rootfsSize(t *testing.T) { + f := testlib.NewTestEnvFromEnv(t) + appName := f.CreateRandomAppMachines() + + // Run with --rootfs-size only + f.Fly("machine run -a %s nginx --rootfs-size 5 --region %s", appName, f.PrimaryRegion()) + ml := f.MachinesList(appName) + require.Equal(f, 1, len(ml)) + m := ml[0] + require.NotNil(f, m.Config.Rootfs) + require.Equal(f, uint64(5), m.Config.Rootfs.SizeGB) + require.Equal(f, uint64(0), m.Config.Rootfs.FsSizeGB) + + // Update with --rootfs-fs-size, must be <= rootfs-size + f.Fly("machine update -a %s %s --rootfs-fs-size 3 -y", appName, m.ID) + m = f.MachinesList(appName)[0] + require.NotNil(f, m.Config.Rootfs) + require.Equal(f, uint64(5), m.Config.Rootfs.SizeGB) + require.Equal(f, uint64(3), m.Config.Rootfs.FsSizeGB) + + // Update with human-readable size + f.Fly("machine update -a %s %s --rootfs-size 10gb -y", appName, m.ID) + m = f.MachinesList(appName)[0] + require.NotNil(f, m.Config.Rootfs) + require.Equal(f, uint64(10), m.Config.Rootfs.SizeGB) + + // Run with --rootfs-fs-size only, should default rootfs-size to match + f.Fly("machine run -a %s nginx --rootfs-fs-size 4 --region %s", appName, f.PrimaryRegion()) + ml = f.MachinesList(appName) + require.Equal(f, 2, len(ml)) + m = ml[1] + require.NotNil(f, m.Config.Rootfs) + require.Equal(f, uint64(4), m.Config.Rootfs.SizeGB) + require.Equal(f, uint64(4), m.Config.Rootfs.FsSizeGB) + + // Unset rootfs-fs-size by passing 0 + f.Fly("machine update -a %s %s --rootfs-fs-size 0 -y", appName, m.ID) + m = f.MachinesList(appName)[1] + require.NotNil(f, m.Config.Rootfs) + require.Equal(f, uint64(0), m.Config.Rootfs.FsSizeGB) + + // Error: rootfs-fs-size > rootfs-size + result := f.FlyAllowExitFailure("machine run -a %s nginx --rootfs-size 2 --rootfs-fs-size 5 --region %s", appName, f.PrimaryRegion()) + require.NotEqual(f, 0, result.ExitCode()) +} + // test --port (add, update, remove services and ports) func TestFlyMachineRun_port(t *testing.T) { f := testlib.NewTestEnvFromEnv(t)