Pure code motion — banger.go 3508→240 LOC, same-package decomposition keeps all identifiers visible without export changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
240 lines
8.8 KiB
Go
240 lines
8.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"banger/internal/api"
|
|
"banger/internal/buildinfo"
|
|
"banger/internal/daemon"
|
|
"banger/internal/guest"
|
|
"banger/internal/paths"
|
|
"banger/internal/rpc"
|
|
"banger/internal/toolingplan"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
bangerdPathFunc = paths.BangerdPath
|
|
daemonExePath = func(pid int) string {
|
|
return filepath.Join("/proc", fmt.Sprintf("%d", pid), "exe")
|
|
}
|
|
doctorFunc = daemon.Doctor
|
|
sshExecFunc = func(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, args []string) error {
|
|
sshCmd := exec.CommandContext(ctx, "ssh", args...)
|
|
sshCmd.Stdout = stdout
|
|
sshCmd.Stderr = stderr
|
|
sshCmd.Stdin = stdin
|
|
return sshCmd.Run()
|
|
}
|
|
hostCommandOutputFunc = func(ctx context.Context, name string, args ...string) ([]byte, error) {
|
|
cmd := exec.CommandContext(ctx, name, args...)
|
|
output, err := cmd.CombinedOutput()
|
|
if err == nil {
|
|
return output, nil
|
|
}
|
|
command := strings.TrimSpace(strings.Join(append([]string{name}, args...), " "))
|
|
detail := strings.TrimSpace(string(output))
|
|
if detail == "" {
|
|
return output, fmt.Errorf("%s: %w", command, err)
|
|
}
|
|
return output, fmt.Errorf("%s: %w: %s", command, err, detail)
|
|
}
|
|
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
|
|
return rpc.Call[api.VMHealthResult](ctx, socketPath, "vm.health", api.VMRefParams{IDOrName: idOrName})
|
|
}
|
|
vmSSHFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMSSHResult, error) {
|
|
return rpc.Call[api.VMSSHResult](ctx, socketPath, "vm.ssh", api.VMRefParams{IDOrName: idOrName})
|
|
}
|
|
vmDeleteFunc = func(ctx context.Context, socketPath, idOrName string) error {
|
|
_, err := rpc.Call[api.VMShowResult](ctx, socketPath, "vm.delete", api.VMRefParams{IDOrName: idOrName})
|
|
return err
|
|
}
|
|
vmListFunc = func(ctx context.Context, socketPath string) (api.VMListResult, error) {
|
|
return rpc.Call[api.VMListResult](ctx, socketPath, "vm.list", api.Empty{})
|
|
}
|
|
daemonPingFunc = func(ctx context.Context, socketPath string) (api.PingResult, error) {
|
|
return rpc.Call[api.PingResult](ctx, socketPath, "ping", api.Empty{})
|
|
}
|
|
vmCreateBeginFunc = func(ctx context.Context, socketPath string, params api.VMCreateParams) (api.VMCreateBeginResult, error) {
|
|
return rpc.Call[api.VMCreateBeginResult](ctx, socketPath, "vm.create.begin", params)
|
|
}
|
|
vmCreateStatusFunc = func(ctx context.Context, socketPath, operationID string) (api.VMCreateStatusResult, error) {
|
|
return rpc.Call[api.VMCreateStatusResult](ctx, socketPath, "vm.create.status", api.VMCreateStatusParams{ID: operationID})
|
|
}
|
|
vmCreateCancelFunc = func(ctx context.Context, socketPath, operationID string) error {
|
|
_, err := rpc.Call[api.Empty](ctx, socketPath, "vm.create.cancel", api.VMCreateStatusParams{ID: operationID})
|
|
return err
|
|
}
|
|
vmPortsFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMPortsResult, error) {
|
|
return rpc.Call[api.VMPortsResult](ctx, socketPath, "vm.ports", api.VMRefParams{IDOrName: idOrName})
|
|
}
|
|
vmWorkspacePrepareFunc = func(ctx context.Context, socketPath string, params api.VMWorkspacePrepareParams) (api.VMWorkspacePrepareResult, error) {
|
|
return rpc.Call[api.VMWorkspacePrepareResult](ctx, socketPath, "vm.workspace.prepare", params)
|
|
}
|
|
vmWorkspaceExportFunc = func(ctx context.Context, socketPath string, params api.WorkspaceExportParams) (api.WorkspaceExportResult, error) {
|
|
return rpc.Call[api.WorkspaceExportResult](ctx, socketPath, "vm.workspace.export", params)
|
|
}
|
|
guestSessionStartFunc = func(ctx context.Context, socketPath string, params api.GuestSessionStartParams) (api.GuestSessionShowResult, error) {
|
|
return rpc.Call[api.GuestSessionShowResult](ctx, socketPath, "guest.session.start", params)
|
|
}
|
|
guestSessionGetFunc = func(ctx context.Context, socketPath string, params api.GuestSessionRefParams) (api.GuestSessionShowResult, error) {
|
|
return rpc.Call[api.GuestSessionShowResult](ctx, socketPath, "guest.session.get", params)
|
|
}
|
|
guestSessionListFunc = func(ctx context.Context, socketPath, idOrName string) (api.GuestSessionListResult, error) {
|
|
return rpc.Call[api.GuestSessionListResult](ctx, socketPath, "guest.session.list", api.VMRefParams{IDOrName: idOrName})
|
|
}
|
|
guestSessionStopFunc = func(ctx context.Context, socketPath string, params api.GuestSessionRefParams) (api.GuestSessionShowResult, error) {
|
|
return rpc.Call[api.GuestSessionShowResult](ctx, socketPath, "guest.session.stop", params)
|
|
}
|
|
guestSessionKillFunc = func(ctx context.Context, socketPath string, params api.GuestSessionRefParams) (api.GuestSessionShowResult, error) {
|
|
return rpc.Call[api.GuestSessionShowResult](ctx, socketPath, "guest.session.kill", params)
|
|
}
|
|
guestSessionLogsFunc = func(ctx context.Context, socketPath string, params api.GuestSessionLogsParams) (api.GuestSessionLogsResult, error) {
|
|
return rpc.Call[api.GuestSessionLogsResult](ctx, socketPath, "guest.session.logs", params)
|
|
}
|
|
guestSessionAttachBeginFunc = func(ctx context.Context, socketPath string, params api.GuestSessionAttachBeginParams) (api.GuestSessionAttachBeginResult, error) {
|
|
return rpc.Call[api.GuestSessionAttachBeginResult](ctx, socketPath, "guest.session.attach.begin", params)
|
|
}
|
|
guestSessionSendFunc = func(ctx context.Context, socketPath string, params api.GuestSessionSendParams) (api.GuestSessionSendResult, error) {
|
|
return rpc.Call[api.GuestSessionSendResult](ctx, socketPath, "guest.session.send", params)
|
|
}
|
|
guestWaitForSSHFunc = func(ctx context.Context, address, privateKeyPath string, interval time.Duration) error {
|
|
knownHosts, _ := bangerKnownHostsPath()
|
|
return guest.WaitForSSH(ctx, address, privateKeyPath, knownHosts, interval)
|
|
}
|
|
guestDialFunc = func(ctx context.Context, address, privateKeyPath string) (vmRunGuestClient, error) {
|
|
knownHosts, _ := bangerKnownHostsPath()
|
|
return guest.Dial(ctx, address, privateKeyPath, knownHosts)
|
|
}
|
|
buildVMRunToolingPlanFunc = toolingplan.Build
|
|
cwdFunc = os.Getwd
|
|
)
|
|
|
|
func NewBangerCommand() *cobra.Command {
|
|
root := &cobra.Command{
|
|
Use: "banger",
|
|
Short: "Manage development VMs and images",
|
|
SilenceUsage: true,
|
|
SilenceErrors: true,
|
|
RunE: helpNoArgs,
|
|
}
|
|
root.AddCommand(newDaemonCommand(), newDoctorCommand(), newImageCommand(), newInternalCommand(), newKernelCommand(), newVersionCommand(), newPSCommand(), newVMCommand())
|
|
return root
|
|
}
|
|
|
|
func newDoctorCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "doctor",
|
|
Short: "Check host and runtime readiness",
|
|
Args: noArgsUsage("usage: banger doctor"),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
report, err := doctorFunc(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := printDoctorReport(cmd.OutOrStdout(), report); err != nil {
|
|
return err
|
|
}
|
|
if report.HasFailures() {
|
|
return errors.New("doctor found failing checks")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func newVersionCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "version",
|
|
Short: "Show banger build information",
|
|
Args: noArgsUsage("usage: banger version"),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
_, err := fmt.Fprint(cmd.OutOrStdout(), formatBuildInfoBlock(buildinfo.Current()))
|
|
return err
|
|
},
|
|
}
|
|
}
|
|
|
|
func helpNoArgs(cmd *cobra.Command, args []string) error {
|
|
if len(args) != 0 {
|
|
return fmt.Errorf("unknown arguments: %s", strings.Join(args, " "))
|
|
}
|
|
return cmd.Help()
|
|
}
|
|
|
|
func noArgsUsage(usage string) cobra.PositionalArgs {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
if len(args) != 0 {
|
|
return errors.New(usage)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func exactArgsUsage(n int, usage string) cobra.PositionalArgs {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
if len(args) != n {
|
|
return errors.New(usage)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func minArgsUsage(n int, usage string) cobra.PositionalArgs {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
if len(args) < n {
|
|
return errors.New(usage)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func maxArgsUsage(n int, usage string) cobra.PositionalArgs {
|
|
return func(cmd *cobra.Command, args []string) error {
|
|
if len(args) > n {
|
|
return errors.New(usage)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func shellQuote(value string) string {
|
|
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
|
|
}
|
|
|
|
func absolutizeImageRegisterPaths(params *api.ImageRegisterParams) error {
|
|
return absolutizePaths(
|
|
¶ms.RootfsPath,
|
|
¶ms.WorkSeedPath,
|
|
¶ms.KernelPath,
|
|
¶ms.InitrdPath,
|
|
¶ms.ModulesDir,
|
|
)
|
|
}
|
|
|
|
func absolutizePaths(values ...*string) error {
|
|
var err error
|
|
for _, value := range values {
|
|
if *value == "" || filepath.IsAbs(*value) {
|
|
continue
|
|
}
|
|
*value, err = filepath.Abs(*value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func formatBuildInfoBlock(info buildinfo.Info) string {
|
|
return fmt.Sprintf("version: %s\ncommit: %s\nbuilt_at: %s\n", info.Version, info.Commit, info.BuiltAt)
|
|
}
|