Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/finch/virtual_machine_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func newDiskVMCommand(creator command.NerdctlCmdCreator, logger flog.Logger) *co
diskCmd.AddCommand(
newVMDiskResizeCommand(creator, logger),
newVMDiskInfoCommand(creator, logger),
newVMDiskUsageCommand(creator, logger),
)

return diskCmd
Expand Down
98 changes: 98 additions & 0 deletions cmd/finch/virtual_machine_disk_usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have we looked into implementing equivalent of
https://docs.docker.com/reference/cli/docker/system/df/

I think this implementation will be appropriately set there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into the code base and this seems a lot more complex. I think it's probably a lot more maintainable to just use CLI commands.

// SPDX-License-Identifier: Apache-2.0

//go:build darwin

package main

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/flog"
)

func newVMDiskUsageCommand(creator command.NerdctlCmdCreator, logger flog.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "usage",
Short: "Display disk usage information from within the virtual machine",
Long: `Display disk usage information from within the virtual machine.
This command shows the actual disk usage as seen from inside the VM,
including used space, available space, and usage percentage.`,
RunE: newDiskUsageAction(creator, logger).runAdapter,
}
return cmd
}

type diskUsageAction struct {
creator command.NerdctlCmdCreator
logger flog.Logger
}

func newDiskUsageAction(creator command.NerdctlCmdCreator, logger flog.Logger) *diskUsageAction {
return &diskUsageAction{
creator: creator,
logger: logger,
}
}

func (dua *diskUsageAction) runAdapter(_ *cobra.Command, _ []string) error {
return dua.run()
}

func (dua *diskUsageAction) run() error {
// First check if the VM is running
statusCmd := dua.creator.CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName)
statusOutput, err := statusCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to check VM status: %w", err)
}

status := strings.TrimSpace(string(statusOutput))
if status != "Running" {
return fmt.Errorf("virtual machine is not running (status: %s)", status)
}

shellCmd := dua.creator.CreateWithoutStdio("shell", limaInstanceName, "df", "-h", "/mnt/lima-finch")
output, err := shellCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to get disk usage information: %w\n%s", err, output)
}

outputStr := strings.TrimSpace(string(output))
if len(outputStr) == 0 {
return fmt.Errorf("no disk usage information available")
}

// Parse and format the df output
lines := strings.Split(outputStr, "\n")
if len(lines) < 2 {
return fmt.Errorf("unexpected disk usage output format")
}

// Parse the data line (skip header)
fields := strings.Fields(lines[1])
if len(fields) < 6 {
return fmt.Errorf("insufficient disk usage data")
}

// df output format: Filesystem Size Used Avail Use% Mounted
filesystem := fields[0]
size := fields[1]
used := fields[2]
available := fields[3]
percentage := fields[4]
mountpoint := fields[5]

fmt.Printf("Filesystem: %s\n", filesystem)
fmt.Printf("Mountpoint: %s\n", mountpoint)
fmt.Printf("Total Size: %s\n", size)
fmt.Printf("Used: %s\n", used)
fmt.Printf("Available: %s\n", available)
fmt.Printf("Usage: %s\n", percentage)

return nil
}
140 changes: 140 additions & 0 deletions cmd/finch/virtual_machine_disk_usage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

//go:build darwin

package main

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"

"github.com/runfinch/finch/pkg/mocks"
)

func TestDiskUsageAction_runAdapter(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
mockSvc func(*mocks.NerdctlCmdCreator, *mocks.Command, *gomock.Controller)
wantErr error
}{
{
name: "should return disk usage when VM is running",
mockSvc: func(creator *mocks.NerdctlCmdCreator, cmd *mocks.Command, ctrl *gomock.Controller) {
// Mock VM status check
statusCmd := mocks.NewCommand(ctrl)
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(statusCmd)
statusCmd.EXPECT().CombinedOutput().Return([]byte("Running"), nil)

// Mock disk usage command
creator.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "df", "-h", "/mnt/lima-finch").Return(cmd)
cmd.EXPECT().CombinedOutput().Return([]byte(`Filesystem Size Used Avail Use% Mounted on
/dev/vda1 20G 5.0G 14G 27% /mnt/lima-finch`), nil)
},
wantErr: nil,
},
{
name: "should return error when VM is not running",
mockSvc: func(creator *mocks.NerdctlCmdCreator, _ *mocks.Command, ctrl *gomock.Controller) {
statusCmd := mocks.NewCommand(ctrl)
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(statusCmd)
statusCmd.EXPECT().CombinedOutput().Return([]byte("Stopped"), nil)
},
wantErr: errors.New("virtual machine is not running (status: Stopped)"),
},
{
name: "should return error when status check fails",
mockSvc: func(creator *mocks.NerdctlCmdCreator, _ *mocks.Command, ctrl *gomock.Controller) {
// Mock VM status check failure
statusCmd := mocks.NewCommand(ctrl)
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(statusCmd)
statusCmd.EXPECT().CombinedOutput().Return(nil, errors.New("lima command failed"))
},
wantErr: errors.New("failed to check VM status: lima command failed"),
},
{
name: "should return error when disk usage command fails",
mockSvc: func(creator *mocks.NerdctlCmdCreator, cmd *mocks.Command, ctrl *gomock.Controller) {
// Mock VM status check
statusCmd := mocks.NewCommand(ctrl)
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(statusCmd)
statusCmd.EXPECT().CombinedOutput().Return([]byte("Running"), nil)

// Mock disk usage command failure
creator.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "df", "-h", "/mnt/lima-finch").Return(cmd)
cmd.EXPECT().CombinedOutput().Return([]byte("command failed"), errors.New("shell command failed"))
},
wantErr: errors.New("failed to get disk usage information: shell command failed\ncommand failed"),
},
{
name: "should return error when disk usage output is empty",
mockSvc: func(creator *mocks.NerdctlCmdCreator, cmd *mocks.Command, ctrl *gomock.Controller) {
// Mock VM status check
statusCmd := mocks.NewCommand(ctrl)
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(statusCmd)
statusCmd.EXPECT().CombinedOutput().Return([]byte("Running"), nil)

// Mock empty disk usage output
creator.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "df", "-h", "/mnt/lima-finch").Return(cmd)
cmd.EXPECT().CombinedOutput().Return([]byte(""), nil)
},
wantErr: errors.New("no disk usage information available"),
},
{
name: "should return error when disk usage output format is unexpected",
mockSvc: func(creator *mocks.NerdctlCmdCreator, cmd *mocks.Command, ctrl *gomock.Controller) {
// Mock VM status check
statusCmd := mocks.NewCommand(ctrl)
creator.EXPECT().CreateWithoutStdio("ls", "-f", "{{.Status}}", limaInstanceName).Return(statusCmd)
statusCmd.EXPECT().CombinedOutput().Return([]byte("Running"), nil)

// Mock malformed disk usage output
creator.EXPECT().CreateWithoutStdio("shell", limaInstanceName, "df", "-h", "/mnt/lima-finch").Return(cmd)
cmd.EXPECT().CombinedOutput().Return([]byte("invalid output"), nil)
},
wantErr: errors.New("unexpected disk usage output format"),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
creator := mocks.NewNerdctlCmdCreator(ctrl)
cmd := mocks.NewCommand(ctrl)
logger := mocks.NewLogger(ctrl)

tc.mockSvc(creator, cmd, ctrl)

action := newDiskUsageAction(creator, logger)
err := action.run()

if tc.wantErr != nil {
assert.EqualError(t, err, tc.wantErr.Error())
} else {
assert.NoError(t, err)
}
})
}
}

func TestNewVMDiskUsageCommand(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
creator := mocks.NewNerdctlCmdCreator(ctrl)
logger := mocks.NewLogger(ctrl)

cmd := newVMDiskUsageCommand(creator, logger)

assert.Equal(t, "usage", cmd.Use)
assert.Equal(t, "Display disk usage information from within the virtual machine", cmd.Short)
assert.NotEmpty(t, cmd.Long)
assert.NotNil(t, cmd.RunE)
}
Loading