208 lines
4.6 KiB
Go
208 lines
4.6 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"runtime"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/cenkalti/backoff/v5"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/diskresizer"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/logginglevel"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/rpc"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/spice/vdagent"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/tart"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/version"
|
|
"github.com/cirruslabs/tart-guest-agent/internal/vsock"
|
|
"github.com/spf13/cobra"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"golang.org/x/sync/errgroup"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
var resizeDisk bool
|
|
var runVdagent bool
|
|
var runRPC bool
|
|
|
|
var runDaemon bool
|
|
var runAgent bool
|
|
|
|
var debug bool
|
|
|
|
const componentFailedTimeout = time.Second
|
|
|
|
func NewRootCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "tart-guest-agent",
|
|
Short: "Guest agent for Tart VMs",
|
|
Version: version.FullVersion,
|
|
SilenceUsage: true,
|
|
SilenceErrors: true,
|
|
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
|
|
if debug {
|
|
logginglevel.Level.SetLevel(zapcore.DebugLevel)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
RunE: run,
|
|
}
|
|
|
|
// Individual components
|
|
cmd.Flags().BoolVar(&resizeDisk, "resize-disk", false, "resize disk")
|
|
cmd.Flags().BoolVar(&runVdagent, "run-vdagent", false, "run vdagent")
|
|
cmd.Flags().BoolVar(&runRPC, "run-rpc", false, "run RPC service (currently required "+
|
|
"to support \"tart exec\" functionality)")
|
|
|
|
// Component groups
|
|
cmd.Flags().BoolVar(&runDaemon, "run-daemon", false, "identical to running the agent"+
|
|
"with \"--resize-disk\" command-line argument")
|
|
cmd.Flags().BoolVar(&runAgent, "run-agent", false, "identical to running the agent "+
|
|
"with \"--run-vdagent\" and \"--run-rpc\" command-line arguments")
|
|
|
|
cmd.Flags().BoolVar(&debug, "debug", false, "enable debug logging")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func run(cmd *cobra.Command, args []string) error {
|
|
// Component groups automatically enable certain individual components
|
|
if runDaemon {
|
|
resizeDisk = true
|
|
}
|
|
|
|
if runAgent {
|
|
runVdagent = true
|
|
runRPC = true
|
|
}
|
|
|
|
// Terminate to prevent disk corruption on macOS guests
|
|
// with disk layouts other than provided by Tart
|
|
if runtime.GOOS == "darwin" {
|
|
version, ok := tart.Version()
|
|
if !ok {
|
|
return unix.Kill(os.Getppid(), syscall.SIGTERM)
|
|
}
|
|
|
|
zap.S().Infof("running on Tart %s, proceeding...", version.String())
|
|
}
|
|
|
|
// Perform disk resizing
|
|
if resizeDisk {
|
|
zap.S().Info("attempting to resize disk...")
|
|
|
|
if err := diskresizer.Resize(); err != nil {
|
|
if errors.Is(err, diskresizer.ErrUnsupported) || errors.Is(err, diskresizer.ErrAlreadyResized) {
|
|
zap.S().Infof("skipping disk resizing: %v", err)
|
|
} else {
|
|
zap.S().Warnf("failed to resize disk: %v", err)
|
|
}
|
|
} else {
|
|
zap.S().Infof("successfully resized the disk")
|
|
}
|
|
}
|
|
|
|
group, ctx := errgroup.WithContext(cmd.Context())
|
|
|
|
if runVdagent {
|
|
group.Go(func() error {
|
|
exponentialBackoff := backoff.NewExponentialBackOff()
|
|
|
|
for {
|
|
if err := runVdagentOnce(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
select {
|
|
case <-time.After(exponentialBackoff.NextBackOff()):
|
|
continue
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
if runRPC {
|
|
group.Go(func() error {
|
|
for {
|
|
if err := runRPCOnce(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
select {
|
|
case <-time.After(componentFailedTimeout):
|
|
continue
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// When running in daemon or agent mode, wait indefinitely until terminated
|
|
if runDaemon || runAgent {
|
|
group.Go(func() error {
|
|
<-ctx.Done()
|
|
|
|
return ctx.Err()
|
|
})
|
|
}
|
|
|
|
return group.Wait()
|
|
}
|
|
|
|
func runVdagentOnce(ctx context.Context) error {
|
|
zap.S().Infof("initializing vdagent...")
|
|
|
|
vdAgent, err := vdagent.New()
|
|
if err != nil {
|
|
zap.S().Errorf("failed to initialize vdagent: %v", err)
|
|
|
|
return nil
|
|
}
|
|
defer vdAgent.Close()
|
|
|
|
zap.S().Infof("running vdagent...")
|
|
|
|
if err := vdAgent.Run(ctx); err != nil {
|
|
zap.S().Errorf("failed to run vdagent: %v", err)
|
|
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runRPCOnce(ctx context.Context) error {
|
|
zap.S().Infof("initializing RPC server...")
|
|
|
|
listener, err := vsock.Listen(8080)
|
|
if err != nil {
|
|
zap.S().Errorf("RPC server failed to listen on AF_VSOCK port 8080: %v", err)
|
|
|
|
return nil
|
|
}
|
|
defer listener.Close()
|
|
|
|
rpcServer, err := rpc.New(listener)
|
|
if err != nil {
|
|
zap.S().Errorf("failed to initialize RPC server: %v", err)
|
|
|
|
return nil
|
|
}
|
|
|
|
zap.S().Info("running RPC server on AF_VSOCK port 8080...")
|
|
|
|
if err := rpcServer.Run(ctx); err != nil {
|
|
zap.S().Errorf("failed to run RPC server: %v", err)
|
|
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|