diff --git a/cmd/limactl/disk.go b/cmd/limactl/disk.go index cf95c0162cf..d3ce417053b 100644 --- a/cmd/limactl/disk.go +++ b/cmd/limactl/disk.go @@ -14,12 +14,12 @@ import ( contfs "github.com/containerd/continuity/fs" "github.com/docker/go-units" - "github.com/lima-vm/go-qcow2reader" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/lima-vm/go-qcow2reader" "github.com/lima-vm/lima/pkg/nativeimgutil" - "github.com/lima-vm/lima/pkg/qemu/imgutil" + "github.com/lima-vm/lima/pkg/qemuimgutil" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" ) @@ -116,7 +116,7 @@ func diskCreateAction(cmd *cobra.Command, args []string) error { if format == "raw" { err = nativeimgutil.CreateRawDisk(dataDisk, int(diskSize)) } else { - err = imgutil.CreateDisk(dataDisk, format, int(diskSize)) + err = qemuimgutil.CreateDisk(dataDisk, format, int(diskSize)) } if err != nil { rerr := os.RemoveAll(diskDir) @@ -413,7 +413,7 @@ func diskResizeAction(cmd *cobra.Command, args []string) error { if disk.Format == "raw" { err = nativeimgutil.ResizeRawDisk(dataDisk, int(diskSize)) } else { - err = imgutil.ResizeDisk(dataDisk, disk.Format, int(diskSize)) + err = qemuimgutil.ResizeDisk(dataDisk, disk.Format, int(diskSize)) } if err != nil { return fmt.Errorf("failed to resize disk %q: %w", diskName, err) diff --git a/cmd/limactl/main.go b/cmd/limactl/main.go index a006f0bb33f..2cbeba2e21f 100644 --- a/cmd/limactl/main.go +++ b/cmd/limactl/main.go @@ -16,8 +16,10 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/pkg/debugutil" + // _ "github.com/lima-vm/lima/pkg/driver/qemu" // register qemu driver for all platforms "github.com/lima-vm/lima/pkg/fsutil" "github.com/lima-vm/lima/pkg/osutil" + "github.com/lima-vm/lima/pkg/registry" "github.com/lima-vm/lima/pkg/store/dirnames" "github.com/lima-vm/lima/pkg/version" ) @@ -43,6 +45,8 @@ func main() { handleExitCoder(err) logrus.Fatal(err) } + + defer registry.DefaultRegistry.StopAllExternalDrivers() } func newApp() *cobra.Command { diff --git a/cmd/limactl/main_darwin.go b/cmd/limactl/main_darwin.go new file mode 100644 index 00000000000..b933ed98b85 --- /dev/null +++ b/cmd/limactl/main_darwin.go @@ -0,0 +1,9 @@ +//go:build darwin && !no_vz + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package main + +// Import vz driver to register it in the registry on darwin. +// import _ "github.com/lima-vm/lima/pkg/driver/vz" diff --git a/cmd/limactl/main_windows.go b/cmd/limactl/main_windows.go new file mode 100644 index 00000000000..871029c8a4f --- /dev/null +++ b/cmd/limactl/main_windows.go @@ -0,0 +1,9 @@ +//go:build windows && !no_wsl + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package main + +// Import wsl2 driver to register it in the registry on windows. +import _ "github.com/lima-vm/lima/pkg/driver/wsl2" diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go index ad0534847bc..43eb23a27c2 100644 --- a/cmd/limactl/start.go +++ b/cmd/limactl/start.go @@ -20,6 +20,7 @@ import ( "github.com/lima-vm/lima/pkg/limatmpl" "github.com/lima-vm/lima/pkg/limayaml" networks "github.com/lima-vm/lima/pkg/networks/reconcile" + "github.com/lima-vm/lima/pkg/registry" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/lima/pkg/templatestore" @@ -31,6 +32,7 @@ func registerCreateFlags(cmd *cobra.Command, commentPrefix string) { flags := cmd.Flags() flags.String("name", "", commentPrefix+"Override the instance name") flags.Bool("list-templates", false, commentPrefix+"List available templates and exit") + flags.Bool("list-drivers", false, commentPrefix+"List available drivers and exit") editflags.RegisterCreate(cmd, commentPrefix) } @@ -53,6 +55,7 @@ $ limactl create --set='.cpus = 2 | .memory = "2GiB"' To see the template list: $ limactl create --list-templates + To create an instance "default" from a local file: $ limactl create --name=default /usr/local/share/lima/templates/fedora.yaml @@ -391,6 +394,14 @@ func createStartActionCommon(cmd *cobra.Command, _ []string) (exit bool, err err } } return true, nil + } else if listDrivers, err := cmd.Flags().GetBool("list-drivers"); err != nil { + return true, err + } else if listDrivers { + w := cmd.OutOrStdout() + for _, d := range registry.DefaultRegistry.List() { + _, _ = fmt.Fprintln(w, d) + } + return true, nil } return false, nil } diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index 457265aedd7..8a91ad721a4 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -5,17 +5,13 @@ package driver import ( "context" - "errors" "net" "github.com/lima-vm/lima/pkg/store" ) -// Driver interface is used by hostagent for managing vm. -// -// This interface is extended by BaseDriver which provides default implementation. -// All other driver definition must extend BaseDriver. -type Driver interface { +// Lifecycle defines basic lifecycle operations. +type Lifecycle interface { // Validate returns error if the current driver isn't support for given config Validate() error @@ -35,120 +31,62 @@ type Driver interface { // The second argument may contain error occurred while starting driver Start(_ context.Context) (chan error, error) - // CanRunGUI returns bool to indicate if the hostagent need to run GUI synchronously - CanRunGUI() bool - - // RunGUI is for starting GUI synchronously by hostagent. This method should be wait and return only after vm terminates - // It returns error if there are any failures - RunGUI() error - // Stop will terminate the running vm instance. // It returns error if there are any errors during Stop Stop(_ context.Context) error - - // Register will add an instance to a registry. - // It returns error if there are any errors during Register - Register(_ context.Context) error - - // Unregister will perform any cleanup related to the vm instance. - // It returns error if there are any errors during Unregister - Unregister(_ context.Context) error - - ChangeDisplayPassword(_ context.Context, password string) error - - GetDisplayConnection(_ context.Context) (string, error) - - CreateSnapshot(_ context.Context, tag string) error - - ApplySnapshot(_ context.Context, tag string) error - - DeleteSnapshot(_ context.Context, tag string) error - - ListSnapshots(_ context.Context) (string, error) - - // ForwardGuestAgent returns if the guest agent sock needs forwarding by host agent. - ForwardGuestAgent() bool - - // GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh). - GuestAgentConn(_ context.Context) (net.Conn, error) -} - -type BaseDriver struct { - Instance *store.Instance - - SSHLocalPort int - VSockPort int - VirtioPort string -} - -var _ Driver = (*BaseDriver)(nil) - -func (d *BaseDriver) Validate() error { - return nil -} - -func (d *BaseDriver) Initialize(_ context.Context) error { - return nil -} - -func (d *BaseDriver) CreateDisk(_ context.Context) error { - return nil -} - -func (d *BaseDriver) Start(_ context.Context) (chan error, error) { - return nil, nil -} - -func (d *BaseDriver) CanRunGUI() bool { - return false -} - -func (d *BaseDriver) RunGUI() error { - return nil -} - -func (d *BaseDriver) Stop(_ context.Context) error { - return nil } -func (d *BaseDriver) Register(_ context.Context) error { - return nil -} +// GUI defines GUI-related operations. +type GUI interface { + // RunGUI is for starting GUI synchronously by hostagent. This method should be wait and return only after vm terminates + // It returns error if there are any failures + RunGUI() error -func (d *BaseDriver) Unregister(_ context.Context) error { - return nil + ChangeDisplayPassword(ctx context.Context, password string) error + GetDisplayConnection(ctx context.Context) (string, error) } -func (d *BaseDriver) ChangeDisplayPassword(_ context.Context, _ string) error { - return nil +// SnapshotManager defines operations for managing snapshots. +type SnapshotManager interface { + CreateSnapshot(ctx context.Context, tag string) error + ApplySnapshot(ctx context.Context, tag string) error + DeleteSnapshot(ctx context.Context, tag string) error + ListSnapshots(ctx context.Context) (string, error) } -func (d *BaseDriver) GetDisplayConnection(_ context.Context) (string, error) { - return "", nil +// Registration defines operations for registering and unregistering the driver instance. +type Registration interface { + Register(ctx context.Context) error + Unregister(ctx context.Context) error } -func (d *BaseDriver) CreateSnapshot(_ context.Context, _ string) error { - return errors.New("unimplemented") -} +// GuestAgent defines operations for the guest agent. +type GuestAgent interface { + // ForwardGuestAgent returns if the guest agent sock needs forwarding by host agent. + ForwardGuestAgent() bool -func (d *BaseDriver) ApplySnapshot(_ context.Context, _ string) error { - return errors.New("unimplemented") + // GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh). + GuestAgentConn(_ context.Context) (net.Conn, error) } -func (d *BaseDriver) DeleteSnapshot(_ context.Context, _ string) error { - return errors.New("unimplemented") -} +// Driver interface is used by hostagent for managing vm. +type Driver interface { + Lifecycle + GUI + SnapshotManager + Registration + GuestAgent -func (d *BaseDriver) ListSnapshots(_ context.Context) (string, error) { - return "", errors.New("unimplemented") -} + GetInfo() Info -func (d *BaseDriver) ForwardGuestAgent() bool { - // if driver is not providing, use host agent - return d.VSockPort == 0 && d.VirtioPort == "" + // SetConfig sets the configuration for the instance. + SetConfig(inst *store.Instance, sshLocalPort int) } -func (d *BaseDriver) GuestAgentConn(_ context.Context) (net.Conn, error) { - // use the unix socket forwarded by host agent - return nil, nil +type Info struct { + DriverName string `json:"driverName"` + CanRunGUI bool `json:"canRunGui,omitempty"` + VsockPort int `json:"vsockPort"` + VirtioPort string `json:"virtioPort"` + InstanceDir string `json:"instanceDir,omitempty"` } diff --git a/pkg/driver/external/client/client.go b/pkg/driver/external/client/client.go new file mode 100644 index 00000000000..675dfb6fb61 --- /dev/null +++ b/pkg/driver/external/client/client.go @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package client + +import ( + "context" + "net" + + pb "github.com/lima-vm/lima/pkg/driver/external" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type DriverClient struct { + socketPath string + Conn *grpc.ClientConn + DriverSvc pb.DriverClient + logger *logrus.Logger +} + +func NewDriverClient(socketPath string, logger *logrus.Logger) (*DriverClient, error) { + // pipeConn := newPipeConn(stdin, stdout) + opts := []grpc.DialOption{ + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(16 << 20)), + grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(16 << 20)), + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + } + + // conn, err := grpc.NewClient("pipe", opts...) + // if err != nil { + // logger.Errorf("failed to create gRPC driver client connection: %v", err) + // return nil, err + // } + // -> ERRO[2025-06-04T21:32:54+05:30] Failed to set config: rpc error: code = + // Unavailable desc = name resolver error: produced zero addresses + + conn, err := grpc.Dial("unix://"+socketPath, opts...) + if err != nil { + logger.Errorf("failed to dial gRPC driver client connection: %v", err) + return nil, err + } + + driverSvc := pb.NewDriverClient(conn) + + return &DriverClient{ + socketPath: socketPath, + Conn: conn, + DriverSvc: driverSvc, + logger: logger, + }, nil +} diff --git a/pkg/driver/external/client/methods.go b/pkg/driver/external/client/methods.go new file mode 100644 index 00000000000..3be9f4f059b --- /dev/null +++ b/pkg/driver/external/client/methods.go @@ -0,0 +1,314 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package client + +import ( + "context" + "encoding/json" + "errors" + "net" + "time" + + // "google.golang.org/protobuf/proto" + + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/lima-vm/lima/pkg/driver" + pb "github.com/lima-vm/lima/pkg/driver/external" + "github.com/lima-vm/lima/pkg/store" +) + +func (d *DriverClient) Validate() error { + d.logger.Debug("Validating driver for the given config") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + _, err := d.DriverSvc.Validate(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Validation failed: %v", err) + return err + } + + d.logger.Debug("Driver validated successfully") + return nil +} + +func (d *DriverClient) Initialize(ctx context.Context) error { + d.logger.Debug("Initializing driver instance") + + _, err := d.DriverSvc.Initialize(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Initialization failed: %v", err) + return err + } + + d.logger.Debug("Driver instance initialized successfully") + return nil +} + +func (d *DriverClient) CreateDisk(ctx context.Context) error { + d.logger.Debug("Creating disk for the instance") + + _, err := d.DriverSvc.CreateDisk(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Disk creation failed: %v", err) + return err + } + + d.logger.Debug("Disk created successfully") + return nil +} + +func (d *DriverClient) Start(ctx context.Context) (chan error, error) { + d.logger.Debug("Starting driver instance") + + stream, err := d.DriverSvc.Start(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to start driver instance: %v", err) + return nil, err + } + + errCh := make(chan error, 1) + go func() { + for { + errorStream, err := stream.Recv() + if err != nil { + d.logger.Errorf("Error receiving response from driver: %v", err) + return + } + d.logger.Debugf("Received response: %v", errorStream) + if !errorStream.Success { + errCh <- errors.New(errorStream.Error) + } else { + errCh <- nil + return + } + } + }() + + d.logger.Debug("Driver instance started successfully") + return errCh, nil +} + +func (d *DriverClient) Stop(ctx context.Context) error { + d.logger.Debug("Stopping driver instance") + + connCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + _, err := d.DriverSvc.Stop(connCtx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to stop driver instance: %v", err) + return err + } + + d.logger.Debug("Driver instance stopped successfully") + return nil +} + +func (d *DriverClient) RunGUI() error { + d.logger.Debug("Running GUI for the driver instance") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + _, err := d.DriverSvc.RunGUI(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to run GUI: %v", err) + return err + } + + d.logger.Debug("GUI started successfully") + return nil +} + +func (d *DriverClient) ChangeDisplayPassword(ctx context.Context, password string) error { + d.logger.Debug("Changing display password for the driver instance") + + _, err := d.DriverSvc.ChangeDisplayPassword(ctx, &pb.ChangeDisplayPasswordRequest{ + Password: password, + }) + if err != nil { + d.logger.Errorf("Failed to change display password: %v", err) + return err + } + + d.logger.Debug("Display password changed successfully") + return nil +} + +func (d *DriverClient) GetDisplayConnection(ctx context.Context) (string, error) { + d.logger.Debug("Getting display connection for the driver instance") + + resp, err := d.DriverSvc.GetDisplayConnection(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to get display connection: %v", err) + return "", err + } + + d.logger.Debugf("Display connection retrieved: %s", resp.Connection) + return resp.Connection, nil +} + +func (d *DriverClient) CreateSnapshot(ctx context.Context, tag string) error { + d.logger.Debugf("Creating snapshot with tag: %s", tag) + + _, err := d.DriverSvc.CreateSnapshot(ctx, &pb.CreateSnapshotRequest{ + Tag: tag, + }) + if err != nil { + d.logger.Errorf("Failed to create snapshot: %v", err) + return err + } + + d.logger.Debugf("Snapshot '%s' created successfully", tag) + return nil +} + +func (d *DriverClient) ApplySnapshot(ctx context.Context, tag string) error { + d.logger.Debugf("Applying snapshot with tag: %s", tag) + + _, err := d.DriverSvc.ApplySnapshot(ctx, &pb.ApplySnapshotRequest{ + Tag: tag, + }) + if err != nil { + d.logger.Errorf("Failed to apply snapshot: %v", err) + return err + } + + d.logger.Debugf("Snapshot '%s' applied successfully", tag) + return nil +} + +func (d *DriverClient) DeleteSnapshot(ctx context.Context, tag string) error { + d.logger.Debugf("Deleting snapshot with tag: %s", tag) + + _, err := d.DriverSvc.DeleteSnapshot(ctx, &pb.DeleteSnapshotRequest{ + Tag: tag, + }) + if err != nil { + d.logger.Errorf("Failed to delete snapshot: %v", err) + return err + } + + d.logger.Debugf("Snapshot '%s' deleted successfully", tag) + return nil +} + +func (d *DriverClient) ListSnapshots(ctx context.Context) (string, error) { + d.logger.Debug("Listing snapshots") + + resp, err := d.DriverSvc.ListSnapshots(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to list snapshots: %v", err) + return "", err + } + + d.logger.Debugf("Snapshots listed successfully: %s", resp.Snapshots) + return resp.Snapshots, nil +} + +func (d *DriverClient) Register(ctx context.Context) error { + d.logger.Debug("Registering driver instance") + + _, err := d.DriverSvc.Register(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to register driver instance: %v", err) + return err + } + + d.logger.Debug("Driver instance registered successfully") + return nil +} + +func (d *DriverClient) Unregister(ctx context.Context) error { + d.logger.Debug("Unregistering driver instance") + + _, err := d.DriverSvc.Unregister(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to unregister driver instance: %v", err) + return err + } + + d.logger.Debug("Driver instance unregistered successfully") + return nil +} + +func (d *DriverClient) ForwardGuestAgent() bool { + d.logger.Debug("Checking if guest agent needs to be forwarded") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + resp, err := d.DriverSvc.ForwardGuestAgent(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to check guest agent forwarding: %v", err) + return false + } + + return resp.ShouldForward +} + +func (d *DriverClient) GuestAgentConn(ctx context.Context) (net.Conn, error) { + d.logger.Info("Getting guest agent connection") + resp, err := d.DriverSvc.GuestAgentConn(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to get guest agent connection: %v", err) + return nil, err + } + + var nd net.Dialer + unixConn, err := nd.DialContext(ctx, "unix", resp.SocketPath) + if err != nil { + d.logger.Errorf("Failed to connect to guest agent socket %s: %v", resp.SocketPath, err) + return nil, err + } + return unixConn, nil +} + +func (d *DriverClient) GetInfo() driver.Info { + d.logger.Debug("Getting driver info") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + resp, err := d.DriverSvc.GetInfo(ctx, &emptypb.Empty{}) + if err != nil { + d.logger.Errorf("Failed to get driver info: %v", err) + return driver.Info{} + } + + var info driver.Info + if err := json.Unmarshal(resp.InfoJson, &info); err != nil { + d.logger.Errorf("Failed to unmarshal driver info: %v", err) + return driver.Info{} + } + + d.logger.Debugf("Driver info retrieved: %+v", info) + return info +} + +func (d *DriverClient) SetConfig(inst *store.Instance, sshLocalPort int) { + d.logger.Debugf("Setting config for instance %s with SSH local port %d", inst.Name, sshLocalPort) + + instJson, err := inst.MarshalJSON() + if err != nil { + d.logger.Errorf("Failed to marshal instance config: %v", err) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + _, err = d.DriverSvc.SetConfig(ctx, &pb.SetConfigRequest{ + InstanceConfigJson: instJson, + SshLocalPort: int64(sshLocalPort), + }) + if err != nil { + d.logger.Errorf("Failed to set config: %v", err) + return + } + + d.logger.Debugf("Config set successfully for instance %s", inst.Name) +} diff --git a/pkg/driver/external/client/pipe.go b/pkg/driver/external/client/pipe.go new file mode 100644 index 00000000000..aa30a70f86a --- /dev/null +++ b/pkg/driver/external/client/pipe.go @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package client + +// import ( +// "io" +// "net" +// "os" +// "time" +// ) + +// type PipeConn struct { +// Reader io.Reader +// Writer io.Writer +// Closer io.Closer +// } + +// func newPipeConn(writer io.WriteCloser, reader io.ReadCloser) net.Conn { +// return &PipeConn{ +// Reader: reader, +// Writer: writer, +// Closer: os.Stdout, +// } +// } + +// func (p *PipeConn) Read(b []byte) (n int, err error) { +// return p.Reader.Read(b) +// } + +// func (p *PipeConn) Write(b []byte) (n int, err error) { +// return p.Writer.Write(b) +// } + +// func (p *PipeConn) Close() error { +// return p.Closer.Close() +// } + +// func (p *PipeConn) LocalAddr() net.Addr { +// return pipeAddr{} +// } + +// func (p *PipeConn) RemoteAddr() net.Addr { +// return pipeAddr{} +// } + +// func (p *PipeConn) SetDeadline(t time.Time) error { +// return nil +// } + +// func (p *PipeConn) SetReadDeadline(t time.Time) error { +// return nil +// } + +// func (p *PipeConn) SetWriteDeadline(t time.Time) error { +// return nil +// } + +// type pipeAddr struct{} + +// func (pipeAddr) Network() string { return "pipe" } +// func (pipeAddr) String() string { return "pipe" } diff --git a/pkg/driver/external/driver.pb.go b/pkg/driver/external/driver.pb.go new file mode 100644 index 00000000000..8330f23523a --- /dev/null +++ b/pkg/driver/external/driver.pb.go @@ -0,0 +1,673 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: pkg/driver/external/driver.proto + +package external + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GuestAgentConnResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + SocketPath string `protobuf:"bytes,1,opt,name=socket_path,json=socketPath,proto3" json:"socket_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GuestAgentConnResponse) Reset() { + *x = GuestAgentConnResponse{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GuestAgentConnResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GuestAgentConnResponse) ProtoMessage() {} + +func (x *GuestAgentConnResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GuestAgentConnResponse.ProtoReflect.Descriptor instead. +func (*GuestAgentConnResponse) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{0} +} + +func (x *GuestAgentConnResponse) GetSocketPath() string { + if x != nil { + return x.SocketPath + } + return "" +} + +type InfoResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + InfoJson []byte `protobuf:"bytes,1,opt,name=info_json,json=infoJson,proto3" json:"info_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InfoResponse) Reset() { + *x = InfoResponse{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InfoResponse) ProtoMessage() {} + +func (x *InfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InfoResponse.ProtoReflect.Descriptor instead. +func (*InfoResponse) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{1} +} + +func (x *InfoResponse) GetInfoJson() []byte { + if x != nil { + return x.InfoJson + } + return nil +} + +type StartResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StartResponse) Reset() { + *x = StartResponse{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StartResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartResponse) ProtoMessage() {} + +func (x *StartResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartResponse.ProtoReflect.Descriptor instead. +func (*StartResponse) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{2} +} + +func (x *StartResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *StartResponse) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type SetConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + InstanceConfigJson []byte `protobuf:"bytes,1,opt,name=instance_config_json,json=instanceConfigJson,proto3" json:"instance_config_json,omitempty"` + SshLocalPort int64 `protobuf:"varint,3,opt,name=ssh_local_port,json=sshLocalPort,proto3" json:"ssh_local_port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetConfigRequest) Reset() { + *x = SetConfigRequest{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetConfigRequest) ProtoMessage() {} + +func (x *SetConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetConfigRequest.ProtoReflect.Descriptor instead. +func (*SetConfigRequest) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{3} +} + +func (x *SetConfigRequest) GetInstanceConfigJson() []byte { + if x != nil { + return x.InstanceConfigJson + } + return nil +} + +func (x *SetConfigRequest) GetSshLocalPort() int64 { + if x != nil { + return x.SshLocalPort + } + return 0 +} + +type ChangeDisplayPasswordRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChangeDisplayPasswordRequest) Reset() { + *x = ChangeDisplayPasswordRequest{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChangeDisplayPasswordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangeDisplayPasswordRequest) ProtoMessage() {} + +func (x *ChangeDisplayPasswordRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangeDisplayPasswordRequest.ProtoReflect.Descriptor instead. +func (*ChangeDisplayPasswordRequest) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{4} +} + +func (x *ChangeDisplayPasswordRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type GetDisplayConnectionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Connection string `protobuf:"bytes,1,opt,name=connection,proto3" json:"connection,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDisplayConnectionResponse) Reset() { + *x = GetDisplayConnectionResponse{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDisplayConnectionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDisplayConnectionResponse) ProtoMessage() {} + +func (x *GetDisplayConnectionResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDisplayConnectionResponse.ProtoReflect.Descriptor instead. +func (*GetDisplayConnectionResponse) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{5} +} + +func (x *GetDisplayConnectionResponse) GetConnection() string { + if x != nil { + return x.Connection + } + return "" +} + +type CreateSnapshotRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSnapshotRequest) Reset() { + *x = CreateSnapshotRequest{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSnapshotRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSnapshotRequest) ProtoMessage() {} + +func (x *CreateSnapshotRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSnapshotRequest.ProtoReflect.Descriptor instead. +func (*CreateSnapshotRequest) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateSnapshotRequest) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +type ApplySnapshotRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApplySnapshotRequest) Reset() { + *x = ApplySnapshotRequest{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplySnapshotRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplySnapshotRequest) ProtoMessage() {} + +func (x *ApplySnapshotRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplySnapshotRequest.ProtoReflect.Descriptor instead. +func (*ApplySnapshotRequest) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{7} +} + +func (x *ApplySnapshotRequest) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +type DeleteSnapshotRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSnapshotRequest) Reset() { + *x = DeleteSnapshotRequest{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSnapshotRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSnapshotRequest) ProtoMessage() {} + +func (x *DeleteSnapshotRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSnapshotRequest.ProtoReflect.Descriptor instead. +func (*DeleteSnapshotRequest) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteSnapshotRequest) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +type ListSnapshotsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Snapshots string `protobuf:"bytes,1,opt,name=snapshots,proto3" json:"snapshots,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSnapshotsResponse) Reset() { + *x = ListSnapshotsResponse{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSnapshotsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSnapshotsResponse) ProtoMessage() {} + +func (x *ListSnapshotsResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSnapshotsResponse.ProtoReflect.Descriptor instead. +func (*ListSnapshotsResponse) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{9} +} + +func (x *ListSnapshotsResponse) GetSnapshots() string { + if x != nil { + return x.Snapshots + } + return "" +} + +type ForwardGuestAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ShouldForward bool `protobuf:"varint,1,opt,name=should_forward,json=shouldForward,proto3" json:"should_forward,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ForwardGuestAgentResponse) Reset() { + *x = ForwardGuestAgentResponse{} + mi := &file_pkg_driver_external_driver_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForwardGuestAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForwardGuestAgentResponse) ProtoMessage() {} + +func (x *ForwardGuestAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_driver_external_driver_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForwardGuestAgentResponse.ProtoReflect.Descriptor instead. +func (*ForwardGuestAgentResponse) Descriptor() ([]byte, []int) { + return file_pkg_driver_external_driver_proto_rawDescGZIP(), []int{10} +} + +func (x *ForwardGuestAgentResponse) GetShouldForward() bool { + if x != nil { + return x.ShouldForward + } + return false +} + +var File_pkg_driver_external_driver_proto protoreflect.FileDescriptor + +const file_pkg_driver_external_driver_proto_rawDesc = "" + + "\n" + + " pkg/driver/external/driver.proto\x1a\x1bgoogle/protobuf/empty.proto\"9\n" + + "\x16GuestAgentConnResponse\x12\x1f\n" + + "\vsocket_path\x18\x01 \x01(\tR\n" + + "socketPath\"+\n" + + "\fInfoResponse\x12\x1b\n" + + "\tinfo_json\x18\x01 \x01(\fR\binfoJson\"?\n" + + "\rStartResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x14\n" + + "\x05error\x18\x02 \x01(\tR\x05error\"j\n" + + "\x10SetConfigRequest\x120\n" + + "\x14instance_config_json\x18\x01 \x01(\fR\x12instanceConfigJson\x12$\n" + + "\x0essh_local_port\x18\x03 \x01(\x03R\fsshLocalPort\":\n" + + "\x1cChangeDisplayPasswordRequest\x12\x1a\n" + + "\bpassword\x18\x01 \x01(\tR\bpassword\">\n" + + "\x1cGetDisplayConnectionResponse\x12\x1e\n" + + "\n" + + "connection\x18\x01 \x01(\tR\n" + + "connection\")\n" + + "\x15CreateSnapshotRequest\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\"(\n" + + "\x14ApplySnapshotRequest\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\")\n" + + "\x15DeleteSnapshotRequest\x12\x10\n" + + "\x03tag\x18\x01 \x01(\tR\x03tag\"5\n" + + "\x15ListSnapshotsResponse\x12\x1c\n" + + "\tsnapshots\x18\x01 \x01(\tR\tsnapshots\"B\n" + + "\x19ForwardGuestAgentResponse\x12%\n" + + "\x0eshould_forward\x18\x01 \x01(\bR\rshouldForward2\xf9\b\n" + + "\x06Driver\x12:\n" + + "\bValidate\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12<\n" + + "\n" + + "Initialize\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12<\n" + + "\n" + + "CreateDisk\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x121\n" + + "\x05Start\x12\x16.google.protobuf.Empty\x1a\x0e.StartResponse0\x01\x126\n" + + "\x04Stop\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x128\n" + + "\x06RunGUI\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12N\n" + + "\x15ChangeDisplayPassword\x12\x1d.ChangeDisplayPasswordRequest\x1a\x16.google.protobuf.Empty\x12M\n" + + "\x14GetDisplayConnection\x12\x16.google.protobuf.Empty\x1a\x1d.GetDisplayConnectionResponse\x12@\n" + + "\x0eCreateSnapshot\x12\x16.CreateSnapshotRequest\x1a\x16.google.protobuf.Empty\x12>\n" + + "\rApplySnapshot\x12\x15.ApplySnapshotRequest\x1a\x16.google.protobuf.Empty\x12@\n" + + "\x0eDeleteSnapshot\x12\x16.DeleteSnapshotRequest\x1a\x16.google.protobuf.Empty\x12?\n" + + "\rListSnapshots\x12\x16.google.protobuf.Empty\x1a\x16.ListSnapshotsResponse\x12:\n" + + "\bRegister\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12<\n" + + "\n" + + "Unregister\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12G\n" + + "\x11ForwardGuestAgent\x12\x16.google.protobuf.Empty\x1a\x1a.ForwardGuestAgentResponse\x12A\n" + + "\x0eGuestAgentConn\x12\x16.google.protobuf.Empty\x1a\x17.GuestAgentConnResponse\x126\n" + + "\tSetConfig\x12\x11.SetConfigRequest\x1a\x16.google.protobuf.Empty\x120\n" + + "\aGetInfo\x12\x16.google.protobuf.Empty\x1a\r.InfoResponseB-Z+github.com/lima-vm/lima/pkg/driver/externalb\x06proto3" + +var ( + file_pkg_driver_external_driver_proto_rawDescOnce sync.Once + file_pkg_driver_external_driver_proto_rawDescData []byte +) + +func file_pkg_driver_external_driver_proto_rawDescGZIP() []byte { + file_pkg_driver_external_driver_proto_rawDescOnce.Do(func() { + file_pkg_driver_external_driver_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_pkg_driver_external_driver_proto_rawDesc), len(file_pkg_driver_external_driver_proto_rawDesc))) + }) + return file_pkg_driver_external_driver_proto_rawDescData +} + +var file_pkg_driver_external_driver_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_pkg_driver_external_driver_proto_goTypes = []any{ + (*GuestAgentConnResponse)(nil), // 0: GuestAgentConnResponse + (*InfoResponse)(nil), // 1: InfoResponse + (*StartResponse)(nil), // 2: StartResponse + (*SetConfigRequest)(nil), // 3: SetConfigRequest + (*ChangeDisplayPasswordRequest)(nil), // 4: ChangeDisplayPasswordRequest + (*GetDisplayConnectionResponse)(nil), // 5: GetDisplayConnectionResponse + (*CreateSnapshotRequest)(nil), // 6: CreateSnapshotRequest + (*ApplySnapshotRequest)(nil), // 7: ApplySnapshotRequest + (*DeleteSnapshotRequest)(nil), // 8: DeleteSnapshotRequest + (*ListSnapshotsResponse)(nil), // 9: ListSnapshotsResponse + (*ForwardGuestAgentResponse)(nil), // 10: ForwardGuestAgentResponse + (*emptypb.Empty)(nil), // 11: google.protobuf.Empty +} +var file_pkg_driver_external_driver_proto_depIdxs = []int32{ + 11, // 0: Driver.Validate:input_type -> google.protobuf.Empty + 11, // 1: Driver.Initialize:input_type -> google.protobuf.Empty + 11, // 2: Driver.CreateDisk:input_type -> google.protobuf.Empty + 11, // 3: Driver.Start:input_type -> google.protobuf.Empty + 11, // 4: Driver.Stop:input_type -> google.protobuf.Empty + 11, // 5: Driver.RunGUI:input_type -> google.protobuf.Empty + 4, // 6: Driver.ChangeDisplayPassword:input_type -> ChangeDisplayPasswordRequest + 11, // 7: Driver.GetDisplayConnection:input_type -> google.protobuf.Empty + 6, // 8: Driver.CreateSnapshot:input_type -> CreateSnapshotRequest + 7, // 9: Driver.ApplySnapshot:input_type -> ApplySnapshotRequest + 8, // 10: Driver.DeleteSnapshot:input_type -> DeleteSnapshotRequest + 11, // 11: Driver.ListSnapshots:input_type -> google.protobuf.Empty + 11, // 12: Driver.Register:input_type -> google.protobuf.Empty + 11, // 13: Driver.Unregister:input_type -> google.protobuf.Empty + 11, // 14: Driver.ForwardGuestAgent:input_type -> google.protobuf.Empty + 11, // 15: Driver.GuestAgentConn:input_type -> google.protobuf.Empty + 3, // 16: Driver.SetConfig:input_type -> SetConfigRequest + 11, // 17: Driver.GetInfo:input_type -> google.protobuf.Empty + 11, // 18: Driver.Validate:output_type -> google.protobuf.Empty + 11, // 19: Driver.Initialize:output_type -> google.protobuf.Empty + 11, // 20: Driver.CreateDisk:output_type -> google.protobuf.Empty + 2, // 21: Driver.Start:output_type -> StartResponse + 11, // 22: Driver.Stop:output_type -> google.protobuf.Empty + 11, // 23: Driver.RunGUI:output_type -> google.protobuf.Empty + 11, // 24: Driver.ChangeDisplayPassword:output_type -> google.protobuf.Empty + 5, // 25: Driver.GetDisplayConnection:output_type -> GetDisplayConnectionResponse + 11, // 26: Driver.CreateSnapshot:output_type -> google.protobuf.Empty + 11, // 27: Driver.ApplySnapshot:output_type -> google.protobuf.Empty + 11, // 28: Driver.DeleteSnapshot:output_type -> google.protobuf.Empty + 9, // 29: Driver.ListSnapshots:output_type -> ListSnapshotsResponse + 11, // 30: Driver.Register:output_type -> google.protobuf.Empty + 11, // 31: Driver.Unregister:output_type -> google.protobuf.Empty + 10, // 32: Driver.ForwardGuestAgent:output_type -> ForwardGuestAgentResponse + 0, // 33: Driver.GuestAgentConn:output_type -> GuestAgentConnResponse + 11, // 34: Driver.SetConfig:output_type -> google.protobuf.Empty + 1, // 35: Driver.GetInfo:output_type -> InfoResponse + 18, // [18:36] is the sub-list for method output_type + 0, // [0:18] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_pkg_driver_external_driver_proto_init() } +func file_pkg_driver_external_driver_proto_init() { + if File_pkg_driver_external_driver_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_pkg_driver_external_driver_proto_rawDesc), len(file_pkg_driver_external_driver_proto_rawDesc)), + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_pkg_driver_external_driver_proto_goTypes, + DependencyIndexes: file_pkg_driver_external_driver_proto_depIdxs, + MessageInfos: file_pkg_driver_external_driver_proto_msgTypes, + }.Build() + File_pkg_driver_external_driver_proto = out.File + file_pkg_driver_external_driver_proto_goTypes = nil + file_pkg_driver_external_driver_proto_depIdxs = nil +} diff --git a/pkg/driver/external/driver.proto b/pkg/driver/external/driver.proto new file mode 100644 index 00000000000..5e8e8564193 --- /dev/null +++ b/pkg/driver/external/driver.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; + +option go_package = "github.com/lima-vm/lima/pkg/driver/external"; + +import "google/protobuf/empty.proto"; + +service Driver { + rpc Validate(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Initialize(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc CreateDisk(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Start(google.protobuf.Empty) returns (stream StartResponse); + rpc Stop(google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc RunGUI(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ChangeDisplayPassword(ChangeDisplayPasswordRequest) returns (google.protobuf.Empty); + rpc GetDisplayConnection(google.protobuf.Empty) returns (GetDisplayConnectionResponse); + + rpc CreateSnapshot(CreateSnapshotRequest) returns (google.protobuf.Empty); + rpc ApplySnapshot(ApplySnapshotRequest) returns (google.protobuf.Empty); + rpc DeleteSnapshot(DeleteSnapshotRequest) returns (google.protobuf.Empty); + rpc ListSnapshots(google.protobuf.Empty) returns (ListSnapshotsResponse); + + rpc Register(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Unregister(google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc ForwardGuestAgent(google.protobuf.Empty) returns (ForwardGuestAgentResponse); + rpc GuestAgentConn(google.protobuf.Empty) returns (GuestAgentConnResponse); + + rpc SetConfig(SetConfigRequest) returns (google.protobuf.Empty); + + rpc GetInfo(google.protobuf.Empty) returns (InfoResponse); +} + +message GuestAgentConnResponse { + string socket_path = 1; +} + +message InfoResponse{ + bytes info_json = 1; +} + +message StartResponse { + bool success = 1; + string error = 2; +} + +message SetConfigRequest { + bytes instance_config_json = 1; + int64 ssh_local_port = 3; +} + +message ChangeDisplayPasswordRequest { + string password = 1; +} + +message GetDisplayConnectionResponse { + string connection = 1; +} + +message CreateSnapshotRequest { + string tag = 1; +} + +message ApplySnapshotRequest { + string tag = 1; +} + +message DeleteSnapshotRequest { + string tag = 1; +} + +message ListSnapshotsResponse { + string snapshots = 1; +} + +message ForwardGuestAgentResponse { + bool should_forward = 1; +} \ No newline at end of file diff --git a/pkg/driver/external/driver_grpc.pb.go b/pkg/driver/external/driver_grpc.pb.go new file mode 100644 index 00000000000..4662a507045 --- /dev/null +++ b/pkg/driver/external/driver_grpc.pb.go @@ -0,0 +1,772 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: pkg/driver/external/driver.proto + +package external + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Driver_Validate_FullMethodName = "/Driver/Validate" + Driver_Initialize_FullMethodName = "/Driver/Initialize" + Driver_CreateDisk_FullMethodName = "/Driver/CreateDisk" + Driver_Start_FullMethodName = "/Driver/Start" + Driver_Stop_FullMethodName = "/Driver/Stop" + Driver_RunGUI_FullMethodName = "/Driver/RunGUI" + Driver_ChangeDisplayPassword_FullMethodName = "/Driver/ChangeDisplayPassword" + Driver_GetDisplayConnection_FullMethodName = "/Driver/GetDisplayConnection" + Driver_CreateSnapshot_FullMethodName = "/Driver/CreateSnapshot" + Driver_ApplySnapshot_FullMethodName = "/Driver/ApplySnapshot" + Driver_DeleteSnapshot_FullMethodName = "/Driver/DeleteSnapshot" + Driver_ListSnapshots_FullMethodName = "/Driver/ListSnapshots" + Driver_Register_FullMethodName = "/Driver/Register" + Driver_Unregister_FullMethodName = "/Driver/Unregister" + Driver_ForwardGuestAgent_FullMethodName = "/Driver/ForwardGuestAgent" + Driver_GuestAgentConn_FullMethodName = "/Driver/GuestAgentConn" + Driver_SetConfig_FullMethodName = "/Driver/SetConfig" + Driver_GetInfo_FullMethodName = "/Driver/GetInfo" +) + +// DriverClient is the client API for Driver service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DriverClient interface { + Validate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + Initialize(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + CreateDisk(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + Start(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StartResponse], error) + Stop(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + RunGUI(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + ChangeDisplayPassword(ctx context.Context, in *ChangeDisplayPasswordRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetDisplayConnection(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetDisplayConnectionResponse, error) + CreateSnapshot(ctx context.Context, in *CreateSnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + ApplySnapshot(ctx context.Context, in *ApplySnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + DeleteSnapshot(ctx context.Context, in *DeleteSnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + ListSnapshots(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListSnapshotsResponse, error) + Register(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + Unregister(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) + ForwardGuestAgent(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ForwardGuestAgentResponse, error) + GuestAgentConn(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GuestAgentConnResponse, error) + SetConfig(ctx context.Context, in *SetConfigRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + GetInfo(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InfoResponse, error) +} + +type driverClient struct { + cc grpc.ClientConnInterface +} + +func NewDriverClient(cc grpc.ClientConnInterface) DriverClient { + return &driverClient{cc} +} + +func (c *driverClient) Validate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_Validate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) Initialize(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_Initialize_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) CreateDisk(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_CreateDisk_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) Start(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StartResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &Driver_ServiceDesc.Streams[0], Driver_Start_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[emptypb.Empty, StartResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Driver_StartClient = grpc.ServerStreamingClient[StartResponse] + +func (c *driverClient) Stop(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_Stop_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) RunGUI(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_RunGUI_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) ChangeDisplayPassword(ctx context.Context, in *ChangeDisplayPasswordRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_ChangeDisplayPassword_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) GetDisplayConnection(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetDisplayConnectionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDisplayConnectionResponse) + err := c.cc.Invoke(ctx, Driver_GetDisplayConnection_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) CreateSnapshot(ctx context.Context, in *CreateSnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_CreateSnapshot_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) ApplySnapshot(ctx context.Context, in *ApplySnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_ApplySnapshot_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) DeleteSnapshot(ctx context.Context, in *DeleteSnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_DeleteSnapshot_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) ListSnapshots(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListSnapshotsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSnapshotsResponse) + err := c.cc.Invoke(ctx, Driver_ListSnapshots_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) Register(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_Register_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) Unregister(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_Unregister_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) ForwardGuestAgent(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ForwardGuestAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ForwardGuestAgentResponse) + err := c.cc.Invoke(ctx, Driver_ForwardGuestAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) GuestAgentConn(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GuestAgentConnResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GuestAgentConnResponse) + err := c.cc.Invoke(ctx, Driver_GuestAgentConn_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) SetConfig(ctx context.Context, in *SetConfigRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Driver_SetConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) GetInfo(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InfoResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(InfoResponse) + err := c.cc.Invoke(ctx, Driver_GetInfo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DriverServer is the server API for Driver service. +// All implementations must embed UnimplementedDriverServer +// for forward compatibility. +type DriverServer interface { + Validate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + Initialize(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + CreateDisk(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + Start(*emptypb.Empty, grpc.ServerStreamingServer[StartResponse]) error + Stop(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + RunGUI(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + ChangeDisplayPassword(context.Context, *ChangeDisplayPasswordRequest) (*emptypb.Empty, error) + GetDisplayConnection(context.Context, *emptypb.Empty) (*GetDisplayConnectionResponse, error) + CreateSnapshot(context.Context, *CreateSnapshotRequest) (*emptypb.Empty, error) + ApplySnapshot(context.Context, *ApplySnapshotRequest) (*emptypb.Empty, error) + DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*emptypb.Empty, error) + ListSnapshots(context.Context, *emptypb.Empty) (*ListSnapshotsResponse, error) + Register(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + Unregister(context.Context, *emptypb.Empty) (*emptypb.Empty, error) + ForwardGuestAgent(context.Context, *emptypb.Empty) (*ForwardGuestAgentResponse, error) + GuestAgentConn(context.Context, *emptypb.Empty) (*GuestAgentConnResponse, error) + SetConfig(context.Context, *SetConfigRequest) (*emptypb.Empty, error) + GetInfo(context.Context, *emptypb.Empty) (*InfoResponse, error) + mustEmbedUnimplementedDriverServer() +} + +// UnimplementedDriverServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedDriverServer struct{} + +func (UnimplementedDriverServer) Validate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Validate not implemented") +} +func (UnimplementedDriverServer) Initialize(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Initialize not implemented") +} +func (UnimplementedDriverServer) CreateDisk(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateDisk not implemented") +} +func (UnimplementedDriverServer) Start(*emptypb.Empty, grpc.ServerStreamingServer[StartResponse]) error { + return status.Errorf(codes.Unimplemented, "method Start not implemented") +} +func (UnimplementedDriverServer) Stop(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Stop not implemented") +} +func (UnimplementedDriverServer) RunGUI(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method RunGUI not implemented") +} +func (UnimplementedDriverServer) ChangeDisplayPassword(context.Context, *ChangeDisplayPasswordRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChangeDisplayPassword not implemented") +} +func (UnimplementedDriverServer) GetDisplayConnection(context.Context, *emptypb.Empty) (*GetDisplayConnectionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDisplayConnection not implemented") +} +func (UnimplementedDriverServer) CreateSnapshot(context.Context, *CreateSnapshotRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateSnapshot not implemented") +} +func (UnimplementedDriverServer) ApplySnapshot(context.Context, *ApplySnapshotRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApplySnapshot not implemented") +} +func (UnimplementedDriverServer) DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteSnapshot not implemented") +} +func (UnimplementedDriverServer) ListSnapshots(context.Context, *emptypb.Empty) (*ListSnapshotsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListSnapshots not implemented") +} +func (UnimplementedDriverServer) Register(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +} +func (UnimplementedDriverServer) Unregister(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Unregister not implemented") +} +func (UnimplementedDriverServer) ForwardGuestAgent(context.Context, *emptypb.Empty) (*ForwardGuestAgentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForwardGuestAgent not implemented") +} +func (UnimplementedDriverServer) GuestAgentConn(context.Context, *emptypb.Empty) (*GuestAgentConnResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GuestAgentConn not implemented") +} +func (UnimplementedDriverServer) SetConfig(context.Context, *SetConfigRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetConfig not implemented") +} +func (UnimplementedDriverServer) GetInfo(context.Context, *emptypb.Empty) (*InfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") +} +func (UnimplementedDriverServer) mustEmbedUnimplementedDriverServer() {} +func (UnimplementedDriverServer) testEmbeddedByValue() {} + +// UnsafeDriverServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DriverServer will +// result in compilation errors. +type UnsafeDriverServer interface { + mustEmbedUnimplementedDriverServer() +} + +func RegisterDriverServer(s grpc.ServiceRegistrar, srv DriverServer) { + // If the following call pancis, it indicates UnimplementedDriverServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Driver_ServiceDesc, srv) +} + +func _Driver_Validate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).Validate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_Validate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).Validate(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_Initialize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).Initialize(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_Initialize_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).Initialize(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_CreateDisk_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).CreateDisk(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_CreateDisk_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).CreateDisk(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_Start_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(DriverServer).Start(m, &grpc.GenericServerStream[emptypb.Empty, StartResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Driver_StartServer = grpc.ServerStreamingServer[StartResponse] + +func _Driver_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).Stop(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_Stop_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).Stop(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_RunGUI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).RunGUI(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_RunGUI_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).RunGUI(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_ChangeDisplayPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChangeDisplayPasswordRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).ChangeDisplayPassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_ChangeDisplayPassword_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).ChangeDisplayPassword(ctx, req.(*ChangeDisplayPasswordRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_GetDisplayConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).GetDisplayConnection(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_GetDisplayConnection_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).GetDisplayConnection(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_CreateSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateSnapshotRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).CreateSnapshot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_CreateSnapshot_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).CreateSnapshot(ctx, req.(*CreateSnapshotRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_ApplySnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplySnapshotRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).ApplySnapshot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_ApplySnapshot_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).ApplySnapshot(ctx, req.(*ApplySnapshotRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_DeleteSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteSnapshotRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).DeleteSnapshot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_DeleteSnapshot_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).DeleteSnapshot(ctx, req.(*DeleteSnapshotRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_ListSnapshots_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).ListSnapshots(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_ListSnapshots_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).ListSnapshots(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_Register_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).Register(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_Unregister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).Unregister(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_Unregister_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).Unregister(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_ForwardGuestAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).ForwardGuestAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_ForwardGuestAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).ForwardGuestAgent(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_GuestAgentConn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).GuestAgentConn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_GuestAgentConn_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).GuestAgentConn(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_SetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).SetConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_SetConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).SetConfig(ctx, req.(*SetConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).GetInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Driver_GetInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).GetInfo(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +// Driver_ServiceDesc is the grpc.ServiceDesc for Driver service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Driver_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "Driver", + HandlerType: (*DriverServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Validate", + Handler: _Driver_Validate_Handler, + }, + { + MethodName: "Initialize", + Handler: _Driver_Initialize_Handler, + }, + { + MethodName: "CreateDisk", + Handler: _Driver_CreateDisk_Handler, + }, + { + MethodName: "Stop", + Handler: _Driver_Stop_Handler, + }, + { + MethodName: "RunGUI", + Handler: _Driver_RunGUI_Handler, + }, + { + MethodName: "ChangeDisplayPassword", + Handler: _Driver_ChangeDisplayPassword_Handler, + }, + { + MethodName: "GetDisplayConnection", + Handler: _Driver_GetDisplayConnection_Handler, + }, + { + MethodName: "CreateSnapshot", + Handler: _Driver_CreateSnapshot_Handler, + }, + { + MethodName: "ApplySnapshot", + Handler: _Driver_ApplySnapshot_Handler, + }, + { + MethodName: "DeleteSnapshot", + Handler: _Driver_DeleteSnapshot_Handler, + }, + { + MethodName: "ListSnapshots", + Handler: _Driver_ListSnapshots_Handler, + }, + { + MethodName: "Register", + Handler: _Driver_Register_Handler, + }, + { + MethodName: "Unregister", + Handler: _Driver_Unregister_Handler, + }, + { + MethodName: "ForwardGuestAgent", + Handler: _Driver_ForwardGuestAgent_Handler, + }, + { + MethodName: "GuestAgentConn", + Handler: _Driver_GuestAgentConn_Handler, + }, + { + MethodName: "SetConfig", + Handler: _Driver_SetConfig_Handler, + }, + { + MethodName: "GetInfo", + Handler: _Driver_GetInfo_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Start", + Handler: _Driver_Start_Handler, + ServerStreams: true, + }, + }, + Metadata: "pkg/driver/external/driver.proto", +} diff --git a/pkg/driver/external/server/methods.go b/pkg/driver/external/server/methods.go new file mode 100644 index 00000000000..20ac27af23e --- /dev/null +++ b/pkg/driver/external/server/methods.go @@ -0,0 +1,266 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package server + +import ( + "context" + "encoding/json" + "net" + "os" + "path/filepath" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + // "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/lima-vm/lima/pkg/bicopy" + pb "github.com/lima-vm/lima/pkg/driver/external" + "github.com/lima-vm/lima/pkg/store" + "github.com/lima-vm/lima/pkg/store/filenames" +) + +func (s *DriverServer) Start(empty *emptypb.Empty, stream pb.Driver_StartServer) error { + s.logger.Debug("Received Start request") + errChan, err := s.driver.Start(stream.Context()) + if err != nil { + s.logger.Errorf("Start failed: %v", err) + return nil + } + + for { + select { + case err, ok := <-errChan: + if !ok { + s.logger.Debug("Start error channel closed") + if err := stream.Send(&pb.StartResponse{Success: true}); err != nil { + s.logger.Errorf("Failed to send success response: %v", err) + return status.Errorf(codes.Internal, "failed to send success response: %v", err) + } + return nil + } + if err != nil { + s.logger.Errorf("Error during Start: %v", err) + if err := stream.Send(&pb.StartResponse{Error: err.Error(), Success: false}); err != nil { + s.logger.Errorf("Failed to send error response: %v", err) + return status.Errorf(codes.Internal, "failed to send error response: %v", err) + } + } + case <-stream.Context().Done(): + s.logger.Debug("Stream context done, stopping Start") + return nil + } + } +} + +func (s *DriverServer) SetConfig(ctx context.Context, req *pb.SetConfigRequest) (*emptypb.Empty, error) { + s.logger.Debugf("Received SetConfig request") + var inst store.Instance + + if err := inst.UnmarshalJSON(req.InstanceConfigJson); err != nil { + s.logger.Errorf("Failed to unmarshal InstanceConfigJson: %v", err) + return &emptypb.Empty{}, err + } + + s.driver.SetConfig(&inst, int(req.SshLocalPort)) + + return &emptypb.Empty{}, nil +} + +func (s *DriverServer) GuestAgentConn(ctx context.Context, empty *emptypb.Empty) (*pb.GuestAgentConnResponse, error) { + s.logger.Debug("Received GuestAgentConn request") + conn, err := s.driver.GuestAgentConn(ctx) + if err != nil { + s.logger.Errorf("GuestAgentConn failed: %v", err) + return nil, err + } + + proxySocketPath := filepath.Join(s.driver.GetInfo().InstanceDir, filenames.ExternalDriverSock) + os.Remove(proxySocketPath) + + listener, err := net.Listen("unix", proxySocketPath) + if err != nil { + s.logger.Errorf("Failed to create proxy socket: %v", err) + return nil, err + } + + go func() { + defer listener.Close() + defer conn.Close() + + proxyConn, err := listener.Accept() + if err != nil { + s.logger.Errorf("Failed to accept proxy connection: %v", err) + return + } + + bicopy.Bicopy(conn, proxyConn, nil) + }() + + return &pb.GuestAgentConnResponse{SocketPath: proxySocketPath}, nil +} + +func (s *DriverServer) GetInfo(ctx context.Context, empty *emptypb.Empty) (*pb.InfoResponse, error) { + s.logger.Debug("Received GetInfo request") + info := s.driver.GetInfo() + + infoJson, err := json.Marshal(info) + if err != nil { + s.logger.Errorf("Failed to marshal driver info: %v", err) + return nil, status.Errorf(codes.Internal, "failed to marshal driver info: %v", err) + } + + return &pb.InfoResponse{ + InfoJson: infoJson, + }, nil +} + +func (s *DriverServer) Validate(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.logger.Debugf("Received Validate request") + err := s.driver.Validate() + if err != nil { + s.logger.Errorf("Validation failed: %v", err) + return empty, err + } + s.logger.Debug("Validation succeeded") + return empty, nil +} + +func (s *DriverServer) Initialize(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.logger.Debug("Received Initialize request") + err := s.driver.Initialize(ctx) + if err != nil { + s.logger.Errorf("Initialization failed: %v", err) + return empty, err + } + s.logger.Debug("Initialization succeeded") + return empty, nil +} + +func (s *DriverServer) CreateDisk(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.logger.Debug("Received CreateDisk request") + err := s.driver.CreateDisk(ctx) + if err != nil { + s.logger.Errorf("CreateDisk failed: %v", err) + return empty, err + } + s.logger.Debug("CreateDisk succeeded") + return empty, nil +} + +func (s *DriverServer) Stop(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.logger.Debug("Received Stop request") + err := s.driver.Stop(ctx) + if err != nil { + s.logger.Errorf("Stop failed: %v", err) + return empty, err + } + s.logger.Debug("Stop succeeded") + return empty, nil +} + +func (s *DriverServer) RunGUI(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.logger.Debug("Received RunGUI request") + err := s.driver.RunGUI() + if err != nil { + s.logger.Errorf("RunGUI failed: %v", err) + return empty, err + } + s.logger.Debug("RunGUI succeeded") + return empty, nil +} + +func (s *DriverServer) ChangeDisplayPassword(ctx context.Context, req *pb.ChangeDisplayPasswordRequest) (*emptypb.Empty, error) { + s.logger.Debug("Received ChangeDisplayPassword request") + err := s.driver.ChangeDisplayPassword(ctx, req.Password) + if err != nil { + s.logger.Errorf("ChangeDisplayPassword failed: %v", err) + return &emptypb.Empty{}, err + } + s.logger.Debug("ChangeDisplayPassword succeeded") + return &emptypb.Empty{}, nil +} + +func (s *DriverServer) GetDisplayConnection(ctx context.Context, empty *emptypb.Empty) (*pb.GetDisplayConnectionResponse, error) { + s.logger.Debug("Received GetDisplayConnection request") + conn, err := s.driver.GetDisplayConnection(ctx) + if err != nil { + s.logger.Errorf("GetDisplayConnection failed: %v", err) + return nil, err + } + s.logger.Debug("GetDisplayConnection succeeded") + return &pb.GetDisplayConnectionResponse{Connection: conn}, nil +} + +func (s *DriverServer) CreateSnapshot(ctx context.Context, req *pb.CreateSnapshotRequest) (*emptypb.Empty, error) { + s.logger.Debugf("Received CreateSnapshot request with tag: %s", req.Tag) + err := s.driver.CreateSnapshot(ctx, req.Tag) + if err != nil { + s.logger.Errorf("CreateSnapshot failed: %v", err) + return &emptypb.Empty{}, err + } + s.logger.Debug("CreateSnapshot succeeded") + return &emptypb.Empty{}, nil +} + +func (s *DriverServer) ApplySnapshot(ctx context.Context, req *pb.ApplySnapshotRequest) (*emptypb.Empty, error) { + s.logger.Debugf("Received ApplySnapshot request with tag: %s", req.Tag) + err := s.driver.ApplySnapshot(ctx, req.Tag) + if err != nil { + s.logger.Errorf("ApplySnapshot failed: %v", err) + return &emptypb.Empty{}, err + } + s.logger.Debug("ApplySnapshot succeeded") + return &emptypb.Empty{}, nil +} + +func (s *DriverServer) DeleteSnapshot(ctx context.Context, req *pb.DeleteSnapshotRequest) (*emptypb.Empty, error) { + s.logger.Debugf("Received DeleteSnapshot request with tag: %s", req.Tag) + err := s.driver.DeleteSnapshot(ctx, req.Tag) + if err != nil { + s.logger.Errorf("DeleteSnapshot failed: %v", err) + return &emptypb.Empty{}, err + } + s.logger.Debug("DeleteSnapshot succeeded") + return &emptypb.Empty{}, nil +} + +func (s *DriverServer) ListSnapshots(ctx context.Context, empty *emptypb.Empty) (*pb.ListSnapshotsResponse, error) { + s.logger.Debug("Received ListSnapshots request") + snapshots, err := s.driver.ListSnapshots(ctx) + if err != nil { + s.logger.Errorf("ListSnapshots failed: %v", err) + return nil, err + } + s.logger.Debug("ListSnapshots succeeded") + return &pb.ListSnapshotsResponse{Snapshots: snapshots}, nil +} + +func (s *DriverServer) Register(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.logger.Debug("Received Register request") + err := s.driver.Register(ctx) + if err != nil { + s.logger.Errorf("Register failed: %v", err) + return empty, err + } + s.logger.Debug("Register succeeded") + return empty, nil +} + +func (s *DriverServer) Unregister(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.logger.Debug("Received Unregister request") + err := s.driver.Unregister(ctx) + if err != nil { + s.logger.Errorf("Unregister failed: %v", err) + return empty, err + } + s.logger.Debug("Unregister succeeded") + return empty, nil +} + +func (s *DriverServer) ForwardGuestAgent(ctx context.Context, empty *emptypb.Empty) (*pb.ForwardGuestAgentResponse, error) { + s.logger.Debug("Received ForwardGuestAgent request") + return &pb.ForwardGuestAgentResponse{ShouldForward: s.driver.ForwardGuestAgent()}, nil +} diff --git a/pkg/driver/external/server/pipe.go b/pkg/driver/external/server/pipe.go new file mode 100644 index 00000000000..d3c567121ec --- /dev/null +++ b/pkg/driver/external/server/pipe.go @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package server + +// import ( +// "io" +// "net" +// "sync" +// "time" +// ) + +// type PipeConn struct { +// Reader io.Reader +// Writer io.Writer +// Closer io.Closer +// } + +// func (p *PipeConn) Read(b []byte) (n int, err error) { +// return p.Reader.Read(b) +// } + +// func (p *PipeConn) Write(b []byte) (n int, err error) { +// return p.Writer.Write(b) +// } + +// func (p *PipeConn) Close() error { +// return p.Closer.Close() +// } + +// func (p *PipeConn) LocalAddr() net.Addr { +// return pipeAddr{} +// } + +// func (p *PipeConn) RemoteAddr() net.Addr { +// return pipeAddr{} +// } + +// func (p *PipeConn) SetDeadline(t time.Time) error { +// return nil +// } + +// func (p *PipeConn) SetReadDeadline(t time.Time) error { +// return nil +// } + +// func (p *PipeConn) SetWriteDeadline(t time.Time) error { +// return nil +// } + +// type pipeAddr struct{} + +// func (pipeAddr) Network() string { return "pipe" } +// func (pipeAddr) String() string { return "pipe" } + +// type PipeListener struct { +// conn net.Conn +// connSent bool +// mu sync.Mutex +// closed bool +// } + +// func NewPipeListener(conn net.Conn) *PipeListener { +// return &PipeListener{ +// conn: conn, +// connSent: false, +// closed: false, +// } +// } + +// func (l *PipeListener) Accept() (net.Conn, error) { +// l.mu.Lock() +// defer l.mu.Unlock() + +// if l.closed { +// return nil, net.ErrClosed +// } + +// if l.connSent { +// select {} +// } + +// l.connSent = true +// return l.conn, nil +// } + +// func (l *PipeListener) Close() error { +// l.mu.Lock() +// defer l.mu.Unlock() + +// if !l.closed { +// l.closed = true +// } +// return nil +// } + +// func (l *PipeListener) Addr() net.Addr { +// return pipeAddr{} +// } diff --git a/pkg/driver/external/server/server.go b/pkg/driver/external/server/server.go new file mode 100644 index 00000000000..39dbd21f0de --- /dev/null +++ b/pkg/driver/external/server/server.go @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package server + +import ( + "context" + "fmt" + "net" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + "google.golang.org/grpc/keepalive" + + "github.com/lima-vm/lima/pkg/bicopy" + "github.com/lima-vm/lima/pkg/driver" + pb "github.com/lima-vm/lima/pkg/driver/external" +) + +type DriverServer struct { + pb.UnimplementedDriverServer + driver driver.Driver + logger *logrus.Logger +} + +func Serve(driver driver.Driver) { + logger := logrus.New() + logger.SetLevel(logrus.DebugLevel) + // pipeConn := &PipeConn{ + // Reader: os.Stdin, + // Writer: os.Stdout, + // Closer: os.Stdout, + // } + + // listener := NewPipeListener(pipeConn) + + socketPath := filepath.Join(os.TempDir(), fmt.Sprintf("lima-driver-%s-%d.sock", driver.GetInfo().DriverName, os.Getpid())) + + defer func() { + if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { + logger.Warnf("Failed to remove socket file: %v", err) + } + }() + + if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { + logger.Fatalf("Failed to remove existing socket file: %v", err) + } + + listener, err := net.Listen("unix", socketPath) + if err != nil { + logger.Fatalf("Failed to listen on Unix socket: %v", err) + } + defer listener.Close() + + fmt.Println(socketPath) + + kaProps := keepalive.ServerParameters{ + Time: 10 * time.Second, + Timeout: 20 * time.Second, + } + + kaPolicy := keepalive.EnforcementPolicy{ + MinTime: 10 * time.Second, + PermitWithoutStream: true, + } + + server := grpc.NewServer( + grpc.KeepaliveParams(kaProps), + grpc.KeepaliveEnforcementPolicy(kaPolicy), + ) + + pb.RegisterDriverServer(server, &DriverServer{ + driver: driver, + logger: logger, + }) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-sigs + logger.Info("Received shutdown signal, stopping server...") + server.GracefulStop() + os.Exit(0) + }() + + logger.Infof("Starting external driver server for %s", driver.GetInfo().DriverName) + logger.Infof("Server starting on Unix socket: %s", socketPath) + if err := server.Serve(listener); err != nil { + logger.Fatalf("Failed to serve: %v", err) + } +} + +func HandleProxyConnection(ctx context.Context, conn net.Conn, unixSocketPath string) { + logrus.Infof("Handling proxy connection from %s", conn.LocalAddr()) + + var d net.Dialer + unixConn, err := d.DialContext(ctx, "unix", unixSocketPath) + if err != nil { + logrus.Errorf("Failed to connect to unix socket %s: %v", unixSocketPath, err) + return + } + + logrus.Infof("Successfully established proxy tunnel: %s <--> %s", conn.LocalAddr(), unixSocketPath) + + go bicopy.Bicopy(unixConn, conn, nil) + + logrus.Infof("Proxy session ended for %s", conn.LocalAddr()) +} diff --git a/pkg/driver/qemu/cmd/lima-driver-qemu/main.go b/pkg/driver/qemu/cmd/lima-driver-qemu/main.go new file mode 100644 index 00000000000..1d78c86e072 --- /dev/null +++ b/pkg/driver/qemu/cmd/lima-driver-qemu/main.go @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "github.com/lima-vm/lima/pkg/driver/external/server" + "github.com/lima-vm/lima/pkg/driver/qemu" +) + +// To be used as an external driver for Lima. +func main() { + driver := qemu.New() + server.Serve(driver) +} diff --git a/pkg/qemu/entitlementutil/entitlementutil.go b/pkg/driver/qemu/entitlementutil/entitlementutil.go similarity index 100% rename from pkg/qemu/entitlementutil/entitlementutil.go rename to pkg/driver/qemu/entitlementutil/entitlementutil.go diff --git a/pkg/qemu/qemu.go b/pkg/driver/qemu/qemu.go similarity index 99% rename from pkg/qemu/qemu.go rename to pkg/driver/qemu/qemu.go index ca775b2d7bc..1ee8c79d886 100644 --- a/pkg/qemu/qemu.go +++ b/pkg/driver/qemu/qemu.go @@ -33,7 +33,7 @@ import ( "github.com/lima-vm/lima/pkg/networks" "github.com/lima-vm/lima/pkg/networks/usernet" "github.com/lima-vm/lima/pkg/osutil" - "github.com/lima-vm/lima/pkg/qemu/imgutil" + "github.com/lima-vm/lima/pkg/qemuimgutil" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" ) @@ -137,11 +137,11 @@ func EnsureDisk(ctx context.Context, cfg Config) error { if err != nil { return err } - baseDiskInfo, err := imgutil.GetInfo(baseDisk) + baseDiskInfo, err := qemuimgutil.GetInfo(baseDisk) if err != nil { return fmt.Errorf("failed to get the information of base disk %q: %w", baseDisk, err) } - if err = imgutil.AcceptableAsBasedisk(baseDiskInfo); err != nil { + if err = qemuimgutil.AcceptableAsBasedisk(baseDiskInfo); err != nil { return fmt.Errorf("file %q is not acceptable as the base disk: %w", baseDisk, err) } if baseDiskInfo.Format == "" { @@ -691,11 +691,11 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er if diskSize, _ := units.RAMInBytes(*cfg.LimaYAML.Disk); diskSize > 0 { args = append(args, "-drive", fmt.Sprintf("file=%s,if=virtio,discard=on", diffDisk)) } else if !isBaseDiskCDROM { - baseDiskInfo, err := imgutil.GetInfo(baseDisk) + baseDiskInfo, err := qemuimgutil.GetInfo(baseDisk) if err != nil { return "", nil, fmt.Errorf("failed to get the information of %q: %w", baseDisk, err) } - if err = imgutil.AcceptableAsBasedisk(baseDiskInfo); err != nil { + if err = qemuimgutil.AcceptableAsBasedisk(baseDiskInfo); err != nil { return "", nil, fmt.Errorf("file %q is not acceptable as the base disk: %w", baseDisk, err) } if baseDiskInfo.Format == "" { diff --git a/pkg/qemu/qemu_driver.go b/pkg/driver/qemu/qemu_driver.go similarity index 90% rename from pkg/qemu/qemu_driver.go rename to pkg/driver/qemu/qemu_driver.go index e28b23429d5..8ed74654acc 100644 --- a/pkg/qemu/qemu_driver.go +++ b/pkg/driver/qemu/qemu_driver.go @@ -27,36 +27,48 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/pkg/driver" + "github.com/lima-vm/lima/pkg/driver/qemu/entitlementutil" "github.com/lima-vm/lima/pkg/executil" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/networks/usernet" "github.com/lima-vm/lima/pkg/osutil" - "github.com/lima-vm/lima/pkg/qemu/entitlementutil" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" ) type LimaQemuDriver struct { - *driver.BaseDriver + Instance *store.Instance + SSHLocalPort int + VSockPort int + VirtioPort string + qCmd *exec.Cmd qWaitCh chan error vhostCmds []*exec.Cmd } -func New(driver *driver.BaseDriver) *LimaQemuDriver { - driver.VSockPort = 0 - driver.VirtioPort = filenames.VirtioPort +var _ driver.Driver = (*LimaQemuDriver)(nil) + +func New() *LimaQemuDriver { // virtserialport doesn't seem to work reliably: https://github.com/lima-vm/lima/issues/2064 // but on Windows default Unix socket forwarding is not available + var virtioPort string + virtioPort = filenames.VirtioPort if runtime.GOOS != "windows" { - driver.VirtioPort = "" + virtioPort = "" } return &LimaQemuDriver{ - BaseDriver: driver, + VSockPort: 0, + VirtioPort: virtioPort, } } +func (l *LimaQemuDriver) SetConfig(inst *store.Instance, sshLocalPort int) { + l.Instance = inst + l.SSHLocalPort = sshLocalPort +} + func (l *LimaQemuDriver) Validate() error { if runtime.GOOS == "darwin" { if err := l.checkBinarySignature(); err != nil { @@ -212,7 +224,7 @@ func (l *LimaQemuDriver) Start(ctx context.Context) (chan error, error) { go func() { if usernetIndex := limayaml.FirstUsernetIndex(l.Instance.Config); usernetIndex != -1 { client := usernet.NewClientByName(l.Instance.Config.Networks[usernetIndex].Lima) - err := client.ConfigureDriver(ctx, l.BaseDriver) + err := client.ConfigureDriver(ctx, l.Instance, l.SSHLocalPort) if err != nil { l.qWaitCh <- err } @@ -260,11 +272,11 @@ func (l *LimaQemuDriver) checkBinarySignature() error { } // The codesign --xml option is only available on macOS Monterey and later if !macOSProductVersion.LessThan(*semver.New("12.0.0")) { - qExe, _, err := Exe(l.BaseDriver.Instance.Arch) + qExe, _, err := Exe(l.Instance.Arch) if err != nil { - return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", l.BaseDriver.Instance.Arch, err) + return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", l.Instance.Arch, err) } - if accel := Accel(l.BaseDriver.Instance.Arch); accel == "hvf" { + if accel := Accel(l.Instance.Arch); accel == "hvf" { entitlementutil.AskToSignIfNotSignedProperly(qExe) } } @@ -498,3 +510,36 @@ func (a *qArgTemplateApplier) applyTemplate(qArg string) (string, error) { } return b.String(), nil } + +func (l *LimaQemuDriver) GetInfo() driver.Info { + var info driver.Info + if l.Instance != nil && l.Instance.Dir != "" { + info.InstanceDir = l.Instance.Dir + } + info.DriverName = "qemu" + info.CanRunGUI = false + info.VirtioPort = l.VirtioPort + info.VsockPort = l.VSockPort + return info +} + +func (l *LimaQemuDriver) Initialize(_ context.Context) error { + return nil +} + +func (l *LimaQemuDriver) RunGUI() error { + return nil +} + +func (l *LimaQemuDriver) Register(_ context.Context) error { + return nil +} + +func (l *LimaQemuDriver) Unregister(_ context.Context) error { + return nil +} + +func (l *LimaQemuDriver) ForwardGuestAgent() bool { + // if driver is not providing, use host agent + return l.VSockPort == 0 && l.VirtioPort == "" +} diff --git a/pkg/qemu/qemu_test.go b/pkg/driver/qemu/qemu_test.go similarity index 100% rename from pkg/qemu/qemu_test.go rename to pkg/driver/qemu/qemu_test.go diff --git a/pkg/driver/qemu/register.go b/pkg/driver/qemu/register.go new file mode 100644 index 00000000000..58558d4198a --- /dev/null +++ b/pkg/driver/qemu/register.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package qemu + +import "github.com/lima-vm/lima/pkg/registry" + +func init() { + registry.Register(New()) +} diff --git a/pkg/driver/vz/cmd/lima-driver-vz/main.go b/pkg/driver/vz/cmd/lima-driver-vz/main.go new file mode 100644 index 00000000000..267df019d6b --- /dev/null +++ b/pkg/driver/vz/cmd/lima-driver-vz/main.go @@ -0,0 +1,17 @@ +//go:build (darwin && amd64) || (darwin && arm64) + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "github.com/lima-vm/lima/pkg/driver/external/server" + "github.com/lima-vm/lima/pkg/driver/vz" +) + +// To be used as an external driver for Lima. +func main() { + driver := vz.New() + server.Serve(driver) +} diff --git a/pkg/vz/disk.go b/pkg/driver/vz/disk.go similarity index 69% rename from pkg/vz/disk.go rename to pkg/driver/vz/disk.go index 3fff17c48fd..02e042ac44a 100644 --- a/pkg/vz/disk.go +++ b/pkg/driver/vz/disk.go @@ -11,36 +11,35 @@ import ( "path/filepath" "github.com/docker/go-units" - - "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/fileutils" "github.com/lima-vm/lima/pkg/iso9660util" "github.com/lima-vm/lima/pkg/nativeimgutil" + "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" ) -func EnsureDisk(ctx context.Context, driver *driver.BaseDriver) error { - diffDisk := filepath.Join(driver.Instance.Dir, filenames.DiffDisk) +func EnsureDisk(ctx context.Context, inst *store.Instance) error { + diffDisk := filepath.Join(inst.Dir, filenames.DiffDisk) if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) { // disk is already ensured return err } - baseDisk := filepath.Join(driver.Instance.Dir, filenames.BaseDisk) - kernel := filepath.Join(driver.Instance.Dir, filenames.Kernel) - kernelCmdline := filepath.Join(driver.Instance.Dir, filenames.KernelCmdline) - initrd := filepath.Join(driver.Instance.Dir, filenames.Initrd) + baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk) + kernel := filepath.Join(inst.Dir, filenames.Kernel) + kernelCmdline := filepath.Join(inst.Dir, filenames.KernelCmdline) + initrd := filepath.Join(inst.Dir, filenames.Initrd) if _, err := os.Stat(baseDisk); errors.Is(err, os.ErrNotExist) { var ensuredBaseDisk bool - errs := make([]error, len(driver.Instance.Config.Images)) - for i, f := range driver.Instance.Config.Images { - if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *driver.Instance.Config.Arch); err != nil { + errs := make([]error, len(inst.Config.Images)) + for i, f := range inst.Config.Images { + if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *inst.Config.Arch); err != nil { errs[i] = err continue } if f.Kernel != nil { // ensure decompress kernel because vz expects it to be decompressed - if _, err := fileutils.DownloadFile(ctx, kernel, f.Kernel.File, true, "the kernel", *driver.Instance.Config.Arch); err != nil { + if _, err := fileutils.DownloadFile(ctx, kernel, f.Kernel.File, true, "the kernel", *inst.Config.Arch); err != nil { errs[i] = err continue } @@ -52,7 +51,7 @@ func EnsureDisk(ctx context.Context, driver *driver.BaseDriver) error { } } if f.Initrd != nil { - if _, err := fileutils.DownloadFile(ctx, initrd, *f.Initrd, false, "the initrd", *driver.Instance.Config.Arch); err != nil { + if _, err := fileutils.DownloadFile(ctx, initrd, *f.Initrd, false, "the initrd", *inst.Config.Arch); err != nil { errs[i] = err continue } @@ -64,7 +63,7 @@ func EnsureDisk(ctx context.Context, driver *driver.BaseDriver) error { return fileutils.Errors(errs) } } - diskSize, _ := units.RAMInBytes(*driver.Instance.Config.Disk) + diskSize, _ := units.RAMInBytes(*inst.Config.Disk) if diskSize == 0 { return nil } diff --git a/pkg/vz/errors_darwin.go b/pkg/driver/vz/errors_darwin.go similarity index 60% rename from pkg/vz/errors_darwin.go rename to pkg/driver/vz/errors_darwin.go index 2bc832cf98b..aa180c70255 100644 --- a/pkg/vz/errors_darwin.go +++ b/pkg/driver/vz/errors_darwin.go @@ -8,4 +8,7 @@ package vz import "errors" //nolint:revive,staticcheck // false positives with proper nouns -var errRosettaUnsupported = errors.New("Rosetta is unsupported on non-ARM64 hosts") +var ( + errRosettaUnsupported = errors.New("Rosetta is unsupported on non-ARM64 hosts") + errUnimplemented = errors.New("unimplemented") +) diff --git a/pkg/vz/network_darwin.go b/pkg/driver/vz/network_darwin.go similarity index 100% rename from pkg/vz/network_darwin.go rename to pkg/driver/vz/network_darwin.go diff --git a/pkg/vz/network_darwin_test.go b/pkg/driver/vz/network_darwin_test.go similarity index 100% rename from pkg/vz/network_darwin_test.go rename to pkg/driver/vz/network_darwin_test.go diff --git a/pkg/driver/vz/register.go b/pkg/driver/vz/register.go new file mode 100644 index 00000000000..750be4b37c2 --- /dev/null +++ b/pkg/driver/vz/register.go @@ -0,0 +1,12 @@ +//go:build darwin && !no_vz + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package vz + +import "github.com/lima-vm/lima/pkg/registry" + +func init() { + registry.Register(New()) +} diff --git a/pkg/vz/rosetta_directory_share.go b/pkg/driver/vz/rosetta_directory_share.go similarity index 100% rename from pkg/vz/rosetta_directory_share.go rename to pkg/driver/vz/rosetta_directory_share.go diff --git a/pkg/vz/rosetta_directory_share_arm64.go b/pkg/driver/vz/rosetta_directory_share_arm64.go similarity index 100% rename from pkg/vz/rosetta_directory_share_arm64.go rename to pkg/driver/vz/rosetta_directory_share_arm64.go diff --git a/pkg/vz/vm_darwin.go b/pkg/driver/vz/vm_darwin.go similarity index 80% rename from pkg/vz/vm_darwin.go rename to pkg/driver/vz/vm_darwin.go index 1f21cf448b0..5312702d394 100644 --- a/pkg/vz/vm_darwin.go +++ b/pkg/driver/vz/vm_darwin.go @@ -24,7 +24,6 @@ import ( "github.com/lima-vm/go-qcow2reader/image/raw" "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/iso9660util" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/nativeimgutil" @@ -51,13 +50,13 @@ type virtualMachineWrapper struct { // Hold all *os.File created via socketpair() so that they won't get garbage collected. f.FD() gets invalid if f gets garbage collected. var vmNetworkFiles = make([]*os.File, 1) -func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWrapper, chan error, error) { - usernetClient, err := startUsernet(ctx, driver) +func startVM(ctx context.Context, inst *store.Instance, sshLocalPort int) (*virtualMachineWrapper, chan error, error) { + usernetClient, err := startUsernet(ctx, inst) if err != nil { return nil, nil, err } - machine, err := createVM(driver) + machine, err := createVM(inst) if err != nil { return nil, nil, err } @@ -95,7 +94,7 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra case newState := <-machine.StateChangedNotify(): switch newState { case vz.VirtualMachineStateRunning: - pidFile := filepath.Join(driver.Instance.Dir, filenames.PIDFile(*driver.Instance.Config.VMType)) + pidFile := filepath.Join(inst.Dir, filenames.PIDFile(*inst.Config.VMType)) if _, err := os.Stat(pidFile); !errors.Is(err, os.ErrNotExist) { logrus.Errorf("pidfile %q already exists", pidFile) errCh <- err @@ -107,7 +106,7 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra filesToRemove[pidFile] = struct{}{} logrus.Info("[VZ] - vm state change: running") - err := usernetClient.ConfigureDriver(ctx, driver) + err := usernetClient.ConfigureDriver(ctx, inst, sshLocalPort) if err != nil { errCh <- err } @@ -116,7 +115,7 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra wrapper.mu.Lock() wrapper.stopped = true wrapper.mu.Unlock() - _ = usernetClient.UnExposeSSH(driver.SSHLocalPort) + _ = usernetClient.UnExposeSSH(inst.SSHLocalPort) errCh <- errors.New("vz driver state stopped") default: logrus.Debugf("[VZ] - vm state change: %q", newState) @@ -128,17 +127,17 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra return wrapper, errCh, err } -func startUsernet(ctx context.Context, driver *driver.BaseDriver) (*usernet.Client, error) { - if firstUsernetIndex := limayaml.FirstUsernetIndex(driver.Instance.Config); firstUsernetIndex != -1 { - nwName := driver.Instance.Config.Networks[firstUsernetIndex].Lima +func startUsernet(ctx context.Context, inst *store.Instance) (*usernet.Client, error) { + if firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config); firstUsernetIndex != -1 { + nwName := inst.Config.Networks[firstUsernetIndex].Lima return usernet.NewClientByName(nwName), nil } // Start a in-process gvisor-tap-vsock - endpointSock, err := usernet.SockWithDirectory(driver.Instance.Dir, "", usernet.EndpointSock) + endpointSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.EndpointSock) if err != nil { return nil, err } - vzSock, err := usernet.SockWithDirectory(driver.Instance.Dir, "", usernet.FDSock) + vzSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock) if err != nil { return nil, err } @@ -150,7 +149,7 @@ func startUsernet(ctx context.Context, driver *driver.BaseDriver) (*usernet.Clie FdSocket: vzSock, Async: true, DefaultLeases: map[string]string{ - networks.SlirpIPAddress: limayaml.MACAddress(driver.Instance.Dir), + networks.SlirpIPAddress: limayaml.MACAddress(inst.Dir), }, Subnet: networks.SlirpNetwork, }) @@ -161,41 +160,41 @@ func startUsernet(ctx context.Context, driver *driver.BaseDriver) (*usernet.Clie return usernet.NewClient(endpointSock, subnetIP), err } -func createVM(driver *driver.BaseDriver) (*vz.VirtualMachine, error) { - vmConfig, err := createInitialConfig(driver) +func createVM(inst *store.Instance) (*vz.VirtualMachine, error) { + vmConfig, err := createInitialConfig(inst) if err != nil { return nil, err } - if err = attachPlatformConfig(driver, vmConfig); err != nil { + if err = attachPlatformConfig(inst, vmConfig); err != nil { return nil, err } - if err = attachSerialPort(driver, vmConfig); err != nil { + if err = attachSerialPort(inst, vmConfig); err != nil { return nil, err } - if err = attachNetwork(driver, vmConfig); err != nil { + if err = attachNetwork(inst, vmConfig); err != nil { return nil, err } - if err = attachDisks(driver, vmConfig); err != nil { + if err = attachDisks(inst, vmConfig); err != nil { return nil, err } - if err = attachDisplay(driver, vmConfig); err != nil { + if err = attachDisplay(inst, vmConfig); err != nil { return nil, err } - if err = attachFolderMounts(driver, vmConfig); err != nil { + if err = attachFolderMounts(inst, vmConfig); err != nil { return nil, err } - if err = attachAudio(driver, vmConfig); err != nil { + if err = attachAudio(inst, vmConfig); err != nil { return nil, err } - if err = attachOtherDevices(driver, vmConfig); err != nil { + if err = attachOtherDevices(inst, vmConfig); err != nil { return nil, err } @@ -207,20 +206,20 @@ func createVM(driver *driver.BaseDriver) (*vz.VirtualMachine, error) { return vz.NewVirtualMachine(vmConfig) } -func createInitialConfig(driver *driver.BaseDriver) (*vz.VirtualMachineConfiguration, error) { - bootLoader, err := bootLoader(driver) +func createInitialConfig(inst *store.Instance) (*vz.VirtualMachineConfiguration, error) { + bootLoader, err := bootLoader(inst) if err != nil { return nil, err } - bytes, err := units.RAMInBytes(*driver.Instance.Config.Memory) + bytes, err := units.RAMInBytes(*inst.Config.Memory) if err != nil { return nil, err } vmConfig, err := vz.NewVirtualMachineConfiguration( bootLoader, - uint(*driver.Instance.Config.CPUs), + uint(*inst.Config.CPUs), uint64(bytes), ) if err != nil { @@ -229,8 +228,8 @@ func createInitialConfig(driver *driver.BaseDriver) (*vz.VirtualMachineConfigura return vmConfig, nil } -func attachPlatformConfig(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error { - machineIdentifier, err := getMachineIdentifier(driver) +func attachPlatformConfig(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { + machineIdentifier, err := getMachineIdentifier(inst) if err != nil { return err } @@ -241,7 +240,7 @@ func attachPlatformConfig(driver *driver.BaseDriver, vmConfig *vz.VirtualMachine } // nested virt - if *driver.Instance.Config.NestedVirtualization { + if *inst.Config.NestedVirtualization { macOSProductVersion, err := osutil.ProductVersion() if err != nil { return fmt.Errorf("failed to get macOS product version: %w", err) @@ -264,8 +263,8 @@ func attachPlatformConfig(driver *driver.BaseDriver, vmConfig *vz.VirtualMachine return nil } -func attachSerialPort(driver *driver.BaseDriver, config *vz.VirtualMachineConfiguration) error { - path := filepath.Join(driver.Instance.Dir, filenames.SerialVirtioLog) +func attachSerialPort(inst *store.Instance, config *vz.VirtualMachineConfiguration) error { + path := filepath.Join(inst.Dir, filenames.SerialVirtioLog) serialPortAttachment, err := vz.NewFileSerialPortAttachment(path, false) if err != nil { return err @@ -302,14 +301,14 @@ func newVirtioNetworkDeviceConfiguration(attachment vz.NetworkDeviceAttachment, return networkConfig, nil } -func attachNetwork(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error { +func attachNetwork(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { var configurations []*vz.VirtioNetworkDeviceConfiguration - // Configure default usernetwork with limayaml.MACAddress(driver.Instance.Dir) for eth0 interface - firstUsernetIndex := limayaml.FirstUsernetIndex(driver.Instance.Config) + // Configure default usernetwork with limayaml.MACAddress(inst.Dir) for eth0 interface + firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config) if firstUsernetIndex == -1 { // slirp network using gvisor netstack - vzSock, err := usernet.SockWithDirectory(driver.Instance.Dir, "", usernet.FDSock) + vzSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock) if err != nil { return err } @@ -317,13 +316,13 @@ func attachNetwork(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu if err != nil { return err } - networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(driver.Instance.Dir)) + networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(inst.Dir)) if err != nil { return err } configurations = append(configurations, networkConfig) } else { - vzSock, err := usernet.Sock(driver.Instance.Config.Networks[firstUsernetIndex].Lima, usernet.FDSock) + vzSock, err := usernet.Sock(inst.Config.Networks[firstUsernetIndex].Lima, usernet.FDSock) if err != nil { return err } @@ -331,14 +330,14 @@ func attachNetwork(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu if err != nil { return err } - networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(driver.Instance.Dir)) + networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(inst.Dir)) if err != nil { return err } configurations = append(configurations, networkConfig) } - for i, nw := range driver.Instance.Networks { + for i, nw := range inst.Networks { if nw.VZNAT != nil && *nw.VZNAT { attachment, err := vz.NewNATNetworkDeviceAttachment() if err != nil { @@ -434,10 +433,10 @@ func validateDiskFormat(diskPath string) error { return nil } -func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error { - baseDiskPath := filepath.Join(driver.Instance.Dir, filenames.BaseDisk) - diffDiskPath := filepath.Join(driver.Instance.Dir, filenames.DiffDisk) - ciDataPath := filepath.Join(driver.Instance.Dir, filenames.CIDataISO) +func attachDisks(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { + baseDiskPath := filepath.Join(inst.Dir, filenames.BaseDisk) + diffDiskPath := filepath.Join(inst.Dir, filenames.DiffDisk) + ciDataPath := filepath.Join(inst.Dir, filenames.CIDataISO) isBaseDiskCDROM, err := iso9660util.IsISO9660(baseDiskPath) if err != nil { return err @@ -471,7 +470,7 @@ func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigura } configurations = append(configurations, diffDisk) - for _, d := range driver.Instance.Config.AdditionalDisks { + for _, d := range inst.Config.AdditionalDisks { diskName := d.Name disk, err := store.InspectDisk(diskName) if err != nil { @@ -482,7 +481,7 @@ func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigura return fmt.Errorf("failed to run attach disk %q, in use by instance %q", diskName, disk.Instance) } logrus.Infof("Mounting disk %q on %q", diskName, disk.MountPoint) - err = disk.Lock(driver.Instance.Dir) + err = disk.Lock(inst.Dir) if err != nil { return fmt.Errorf("failed to run lock disk %q: %w", diskName, err) } @@ -520,8 +519,8 @@ func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigura return nil } -func attachDisplay(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error { - switch *driver.Instance.Config.Video.Display { +func attachDisplay(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { + switch *inst.Config.Video.Display { case "vz", "default": graphicsDeviceConfiguration, err := vz.NewVirtioGraphicsDeviceConfiguration() if err != nil { @@ -540,14 +539,14 @@ func attachDisplay(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu case "none": return nil default: - return fmt.Errorf("unexpected video display %q", *driver.Instance.Config.Video.Display) + return fmt.Errorf("unexpected video display %q", *inst.Config.Video.Display) } } -func attachFolderMounts(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error { +func attachFolderMounts(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { var mounts []vz.DirectorySharingDeviceConfiguration - if *driver.Instance.Config.MountType == limayaml.VIRTIOFS { - for i, mount := range driver.Instance.Config.Mounts { + if *inst.Config.MountType == limayaml.VIRTIOFS { + for i, mount := range inst.Config.Mounts { if _, err := os.Stat(mount.Location); errors.Is(err, os.ErrNotExist) { err := os.MkdirAll(mount.Location, 0o750) if err != nil { @@ -574,7 +573,7 @@ func attachFolderMounts(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineCo } } - if *driver.Instance.Config.Rosetta.Enabled { + if *inst.Config.Rosetta.Enabled { logrus.Info("Setting up Rosetta share") directorySharingDeviceConfig, err := createRosettaDirectoryShareConfiguration() if err != nil { @@ -590,8 +589,8 @@ func attachFolderMounts(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineCo return nil } -func attachAudio(driver *driver.BaseDriver, config *vz.VirtualMachineConfiguration) error { - switch *driver.Instance.Config.Audio.Device { +func attachAudio(inst *store.Instance, config *vz.VirtualMachineConfiguration) error { + switch *inst.Config.Audio.Device { case "vz", "default": outputStream, err := vz.NewVirtioSoundDeviceHostOutputStreamConfiguration() if err != nil { @@ -609,11 +608,11 @@ func attachAudio(driver *driver.BaseDriver, config *vz.VirtualMachineConfigurati case "", "none": return nil default: - return fmt.Errorf("unexpected audio device %q", *driver.Instance.Config.Audio.Device) + return fmt.Errorf("unexpected audio device %q", *inst.Config.Audio.Device) } } -func attachOtherDevices(_ *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error { +func attachOtherDevices(_ *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { entropyConfig, err := vz.NewVirtioEntropyDeviceConfiguration() if err != nil { return err @@ -687,8 +686,8 @@ func attachOtherDevices(_ *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu return nil } -func getMachineIdentifier(driver *driver.BaseDriver) (*vz.GenericMachineIdentifier, error) { - identifier := filepath.Join(driver.Instance.Dir, filenames.VzIdentifier) +func getMachineIdentifier(inst *store.Instance) (*vz.GenericMachineIdentifier, error) { + identifier := filepath.Join(inst.Dir, filenames.VzIdentifier) if _, err := os.Stat(identifier); os.IsNotExist(err) { machineIdentifier, err := vz.NewGenericMachineIdentifier() if err != nil { @@ -703,15 +702,15 @@ func getMachineIdentifier(driver *driver.BaseDriver) (*vz.GenericMachineIdentifi return vz.NewGenericMachineIdentifierWithDataPath(identifier) } -func bootLoader(driver *driver.BaseDriver) (vz.BootLoader, error) { - linuxBootLoader, err := linuxBootLoader(driver) +func bootLoader(inst *store.Instance) (vz.BootLoader, error) { + linuxBootLoader, err := linuxBootLoader(inst) if linuxBootLoader != nil { return linuxBootLoader, nil } else if !errors.Is(err, os.ErrNotExist) { return nil, err } - efiVariableStore, err := getEFI(driver) + efiVariableStore, err := getEFI(inst) if err != nil { return nil, err } @@ -719,10 +718,10 @@ func bootLoader(driver *driver.BaseDriver) (vz.BootLoader, error) { return vz.NewEFIBootLoader(vz.WithEFIVariableStore(efiVariableStore)) } -func linuxBootLoader(driver *driver.BaseDriver) (*vz.LinuxBootLoader, error) { - kernel := filepath.Join(driver.Instance.Dir, filenames.Kernel) - kernelCmdline := filepath.Join(driver.Instance.Dir, filenames.KernelCmdline) - initrd := filepath.Join(driver.Instance.Dir, filenames.Initrd) +func linuxBootLoader(inst *store.Instance) (*vz.LinuxBootLoader, error) { + kernel := filepath.Join(inst.Dir, filenames.Kernel) + kernelCmdline := filepath.Join(inst.Dir, filenames.KernelCmdline) + initrd := filepath.Join(inst.Dir, filenames.Initrd) if _, err := os.Stat(kernel); err != nil { if errors.Is(err, os.ErrNotExist) { logrus.Debugf("Kernel file %q not found", kernel) @@ -744,8 +743,8 @@ func linuxBootLoader(driver *driver.BaseDriver) (*vz.LinuxBootLoader, error) { return vz.NewLinuxBootLoader(kernel, opt...) } -func getEFI(driver *driver.BaseDriver) (*vz.EFIVariableStore, error) { - efi := filepath.Join(driver.Instance.Dir, filenames.VzEfi) +func getEFI(inst *store.Instance) (*vz.EFIVariableStore, error) { + efi := filepath.Join(inst.Dir, filenames.VzEfi) if _, err := os.Stat(efi); os.IsNotExist(err) { return vz.NewEFIVariableStore(efi, vz.WithCreatingEFIVariableStore()) } diff --git a/pkg/vz/vz_driver_darwin.go b/pkg/driver/vz/vz_driver_darwin.go similarity index 77% rename from pkg/vz/vz_driver_darwin.go rename to pkg/driver/vz/vz_driver_darwin.go index 812eaeb6a42..560d7a0d70f 100644 --- a/pkg/vz/vz_driver_darwin.go +++ b/pkg/driver/vz/vz_driver_darwin.go @@ -22,6 +22,7 @@ import ( "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/osutil" "github.com/lima-vm/lima/pkg/reflectutil" + "github.com/lima-vm/lima/pkg/store" ) var knownYamlProperties = []string{ @@ -68,19 +69,29 @@ var knownYamlProperties = []string{ const Enabled = true type LimaVzDriver struct { - *driver.BaseDriver + Instance *store.Instance + + SSHLocalPort int + VSockPort int + VirtioPort string machine *virtualMachineWrapper } -func New(driver *driver.BaseDriver) *LimaVzDriver { - driver.VSockPort = 2222 - driver.VirtioPort = "" +var _ driver.Driver = (*LimaVzDriver)(nil) + +func New() *LimaVzDriver { return &LimaVzDriver{ - BaseDriver: driver, + VSockPort: 2222, + VirtioPort: "", } } +func (l *LimaVzDriver) SetConfig(inst *store.Instance, sshLocalPort int) { + l.Instance = inst + l.SSHLocalPort = sshLocalPort +} + func (l *LimaVzDriver) Validate() error { macOSProductVersion, err := osutil.ProductVersion() if err != nil { @@ -167,17 +178,17 @@ func (l *LimaVzDriver) Validate() error { } func (l *LimaVzDriver) Initialize(_ context.Context) error { - _, err := getMachineIdentifier(l.BaseDriver) + _, err := getMachineIdentifier(l.Instance) return err } func (l *LimaVzDriver) CreateDisk(ctx context.Context) error { - return EnsureDisk(ctx, l.BaseDriver) + return EnsureDisk(ctx, l.Instance) } func (l *LimaVzDriver) Start(ctx context.Context) (chan error, error) { logrus.Infof("Starting VZ (hint: to watch the boot progress, see %q)", filepath.Join(l.Instance.Dir, "serial*.log")) - vm, errCh, err := startVM(ctx, l.BaseDriver) + vm, errCh, err := startVM(ctx, l.Instance, l.SSHLocalPort) if err != nil { if errors.Is(err, vz.ErrUnsupportedOSVersion) { return nil, fmt.Errorf("vz driver requires macOS 13 or higher to run: %w", err) @@ -189,7 +200,7 @@ func (l *LimaVzDriver) Start(ctx context.Context) (chan error, error) { return errCh, nil } -func (l *LimaVzDriver) CanRunGUI() bool { +func (l *LimaVzDriver) canRunGUI() bool { switch *l.Instance.Config.Video.Display { case "vz", "default": return true @@ -199,7 +210,7 @@ func (l *LimaVzDriver) CanRunGUI() bool { } func (l *LimaVzDriver) RunGUI() error { - if l.CanRunGUI() { + if l.canRunGUI() { return l.machine.StartGraphicApplication(1920, 1200) } //nolint:revive // error-strings @@ -245,3 +256,55 @@ func (l *LimaVzDriver) GuestAgentConn(_ context.Context) (net.Conn, error) { } return nil, errors.New("unable to connect to guest agent via vsock port 2222") } + +func (l *LimaVzDriver) GetInfo() driver.Info { + var info driver.Info + if l.Instance != nil && l.Instance.Config != nil { + info.CanRunGUI = l.canRunGUI() + } + + info.DriverName = "vz" + info.VsockPort = l.VSockPort + info.VirtioPort = l.VirtioPort + if l.Instance != nil && l.Instance.Dir != "" { + info.InstanceDir = l.Instance.Dir + } + return info +} + +func (l *LimaVzDriver) Register(_ context.Context) error { + return nil +} + +func (l *LimaVzDriver) Unregister(_ context.Context) error { + return nil +} + +func (l *LimaVzDriver) ChangeDisplayPassword(_ context.Context, _ string) error { + return nil +} + +func (l *LimaVzDriver) GetDisplayConnection(_ context.Context) (string, error) { + return "", nil +} + +func (l *LimaVzDriver) CreateSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaVzDriver) ApplySnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaVzDriver) DeleteSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaVzDriver) ListSnapshots(_ context.Context) (string, error) { + return "", errUnimplemented +} + +func (l *LimaVzDriver) ForwardGuestAgent() bool { + // If driver is not providing, use host agent + return l.VSockPort == 0 && l.VirtioPort == "" +} diff --git a/pkg/driver/wsl2/errors_windows.go b/pkg/driver/wsl2/errors_windows.go new file mode 100644 index 00000000000..2c615ca5458 --- /dev/null +++ b/pkg/driver/wsl2/errors_windows.go @@ -0,0 +1,11 @@ +//go:build windows && !no_wsl + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package wsl2 + +import "errors" + +//nolint:revive,staticcheck // false positives with proper nouns +var errUnimplemented = errors.New("unimplemented") diff --git a/pkg/wsl2/fs.go b/pkg/driver/wsl2/fs.go similarity index 65% rename from pkg/wsl2/fs.go rename to pkg/driver/wsl2/fs.go index 8a3060ac924..53823a7fb06 100644 --- a/pkg/wsl2/fs.go +++ b/pkg/driver/wsl2/fs.go @@ -11,19 +11,19 @@ import ( "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/fileutils" + "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" ) // EnsureFs downloads the root fs. -func EnsureFs(ctx context.Context, driver *driver.BaseDriver) error { - baseDisk := filepath.Join(driver.Instance.Dir, filenames.BaseDisk) +func EnsureFs(ctx context.Context, inst *store.Instance) error { + baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk) if _, err := os.Stat(baseDisk); errors.Is(err, os.ErrNotExist) { var ensuredBaseDisk bool - errs := make([]error, len(driver.Instance.Config.Images)) - for i, f := range driver.Instance.Config.Images { - if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *driver.Instance.Config.Arch); err != nil { + errs := make([]error, len(inst.Config.Images)) + for i, f := range inst.Config.Images { + if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *inst.Config.Arch); err != nil { errs[i] = err continue } diff --git a/pkg/wsl2/lima-init.TEMPLATE b/pkg/driver/wsl2/lima-init.TEMPLATE similarity index 100% rename from pkg/wsl2/lima-init.TEMPLATE rename to pkg/driver/wsl2/lima-init.TEMPLATE diff --git a/pkg/driver/wsl2/register.go b/pkg/driver/wsl2/register.go new file mode 100644 index 00000000000..ccbc516d75e --- /dev/null +++ b/pkg/driver/wsl2/register.go @@ -0,0 +1,12 @@ +//go:build windows && !no_wsl + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package wsl2 + +import "github.com/lima-vm/lima/pkg/registry" + +func init() { + registry.Register(New()) +} diff --git a/pkg/wsl2/vm_windows.go b/pkg/driver/wsl2/vm_windows.go similarity index 100% rename from pkg/wsl2/vm_windows.go rename to pkg/driver/wsl2/vm_windows.go diff --git a/pkg/wsl2/wsl_driver_windows.go b/pkg/driver/wsl2/wsl_driver_windows.go similarity index 74% rename from pkg/wsl2/wsl_driver_windows.go rename to pkg/driver/wsl2/wsl_driver_windows.go index 479aaf0ffd7..acbc9c7cb7e 100644 --- a/pkg/wsl2/wsl_driver_windows.go +++ b/pkg/driver/wsl2/wsl_driver_windows.go @@ -47,21 +47,32 @@ var knownYamlProperties = []string{ const Enabled = true type LimaWslDriver struct { - *driver.BaseDriver + Instance *store.Instance + + SSHLocalPort int + VSockPort int + VirtioPort string } -func New(driver *driver.BaseDriver) *LimaWslDriver { +var _ driver.Driver = (*LimaWslDriver)(nil) + +func New() *LimaWslDriver { port, err := freeport.VSock() if err != nil { logrus.WithError(err).Error("failed to get free VSock port") } - driver.VSockPort = port - driver.VirtioPort = "" + return &LimaWslDriver{ - BaseDriver: driver, + VSockPort: port, + VirtioPort: "", } } +func (l *LimaWslDriver) SetConfig(inst *store.Instance, sshLocalPort int) { + l.Instance = inst + l.SSHLocalPort = sshLocalPort +} + func (l *LimaWslDriver) Validate() error { if *l.Instance.Config.MountType != limayaml.WSLMount { return fmt.Errorf("field `mountType` must be %q for WSL2 driver, got %q", limayaml.WSLMount, *l.Instance.Config.MountType) @@ -123,10 +134,10 @@ func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) { distroName := "lima-" + l.Instance.Name if status == store.StatusUninitialized { - if err := EnsureFs(ctx, l.BaseDriver); err != nil { + if err := EnsureFs(ctx, l.Instance); err != nil { return nil, err } - if err := initVM(ctx, l.BaseDriver.Instance.Dir, distroName); err != nil { + if err := initVM(ctx, l.Instance.Dir, distroName); err != nil { return nil, err } } @@ -139,8 +150,8 @@ func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) { if err := provisionVM( ctx, - l.BaseDriver.Instance.Dir, - l.BaseDriver.Instance.Name, + l.Instance.Dir, + l.Instance.Name, distroName, errCh, ); err != nil { @@ -154,7 +165,7 @@ func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) { // Requires WSLg, which requires specific version of WSL2 to be installed. // TODO: Add check and add support for WSLg (instead of VNC) to hostagent. -func (l *LimaWslDriver) CanRunGUI() bool { +func (l *LimaWslDriver) canRunGUI() bool { // return *l.InstConfig.Video.Display == "wsl" return false } @@ -202,3 +213,56 @@ func (l *LimaWslDriver) GuestAgentConn(ctx context.Context) (net.Conn, error) { } return winio.Dial(ctx, sockAddr) } + +func (l *LimaWslDriver) GetInfo() driver.Info { + var info driver.Info + if l.Instance != nil && l.Instance.Dir != "" { + info.InstanceDir = l.Instance.Dir + } + info.DriverName = "wsl" + info.CanRunGUI = l.canRunGUI() + info.VirtioPort = l.VirtioPort + info.VsockPort = l.VSockPort + return info +} + +func (l *LimaWslDriver) Initialize(_ context.Context) error { + return nil +} + +func (l *LimaWslDriver) CreateDisk(_ context.Context) error { + return nil +} + +func (l *LimaWslDriver) Register(_ context.Context) error { + return nil +} + +func (l *LimaWslDriver) ChangeDisplayPassword(_ context.Context, _ string) error { + return nil +} + +func (l *LimaWslDriver) GetDisplayConnection(_ context.Context) (string, error) { + return "", nil +} + +func (l *LimaWslDriver) CreateSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaWslDriver) ApplySnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaWslDriver) DeleteSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaWslDriver) ListSnapshots(_ context.Context) (string, error) { + return "", errUnimplemented +} + +func (l *LimaWslDriver) ForwardGuestAgent() bool { + // If driver is not providing, use host agent + return l.VSockPort == 0 && l.VirtioPort == "" +} diff --git a/pkg/driverutil/driverutil.go b/pkg/driverutil/driverutil.go deleted file mode 100644 index eb27833e7ad..00000000000 --- a/pkg/driverutil/driverutil.go +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: Copyright The Lima Authors -// SPDX-License-Identifier: Apache-2.0 - -package driverutil - -import ( - "github.com/lima-vm/lima/pkg/limayaml" - "github.com/lima-vm/lima/pkg/vz" - "github.com/lima-vm/lima/pkg/wsl2" -) - -// Drivers returns the available drivers. -func Drivers() []string { - drivers := []string{limayaml.QEMU} - if vz.Enabled { - drivers = append(drivers, limayaml.VZ) - } - if wsl2.Enabled { - drivers = append(drivers, limayaml.WSL2) - } - return drivers -} diff --git a/pkg/driverutil/instance.go b/pkg/driverutil/instance.go index d7c443ff5d2..ac7b1fffd91 100644 --- a/pkg/driverutil/instance.go +++ b/pkg/driverutil/instance.go @@ -4,20 +4,22 @@ package driverutil import ( + "fmt" + "github.com/lima-vm/lima/pkg/driver" - "github.com/lima-vm/lima/pkg/limayaml" - "github.com/lima-vm/lima/pkg/qemu" - "github.com/lima-vm/lima/pkg/vz" - "github.com/lima-vm/lima/pkg/wsl2" + "github.com/lima-vm/lima/pkg/registry" + "github.com/lima-vm/lima/pkg/store" ) -func CreateTargetDriverInstance(base *driver.BaseDriver) driver.Driver { - limaDriver := base.Instance.Config.VMType - if *limaDriver == limayaml.VZ { - return vz.New(base) - } - if *limaDriver == limayaml.WSL2 { - return wsl2.New(base) +// CreateTargetDriverInstance creates the appropriate driver for an instance. +func CreateTargetDriverInstance(inst *store.Instance, sshLocalPort int) (driver.Driver, error) { + limaDriver := inst.Config.VMType + driver, exists := registry.DefaultRegistry.Get(string(*limaDriver), inst.Name) + if !exists { + return nil, fmt.Errorf("unknown or unsupported VM type: %s", *limaDriver) } - return qemu.New(base) + + driver.SetConfig(inst, sshLocalPort) + + return driver, nil } diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go index a6e293e11a2..f2d7b37e915 100644 --- a/pkg/hostagent/hostagent.go +++ b/pkg/hostagent/hostagent.go @@ -131,13 +131,13 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt } } - baseDriver := driver.BaseDriver{ - Instance: inst, - SSHLocalPort: sshLocalPort, + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, sshLocalPort) + if err != nil { + return nil, fmt.Errorf("failed to create driver instance: %w", err) } - limaDriver := driverutil.CreateTargetDriverInstance(&baseDriver) - vSockPort := baseDriver.VSockPort - virtioPort := baseDriver.VirtioPort + + vSockPort := limaDriver.GetInfo().VsockPort + virtioPort := limaDriver.GetInfo().VirtioPort if err := cidata.GenerateCloudConfig(inst.Dir, instName, inst.Config); err != nil { return nil, err @@ -364,7 +364,7 @@ func (a *HostAgent) Run(ctx context.Context) error { logrus.Infof("VNC Password: `%s`", vncpwdfile) } - if a.driver.CanRunGUI() { + if a.driver.GetInfo().CanRunGUI { go func() { err = a.startRoutinesAndWait(ctx, errCh) if err != nil { @@ -382,7 +382,7 @@ func (a *HostAgent) startRoutinesAndWait(ctx context.Context, errCh <-chan error } stBooting := stBase a.emitEvent(ctx, events.Event{Status: stBooting}) - ctxHA, cancelHA := context.WithCancel(ctx) + ctxHA, cancelHA := context.WithCancelCause(ctx) go func() { stRunning := stBase if haErr := a.startHostAgentRoutines(ctxHA); haErr != nil { @@ -396,7 +396,7 @@ func (a *HostAgent) startRoutinesAndWait(ctx context.Context, errCh <-chan error select { case driverErr := <-errCh: logrus.Infof("Driver stopped due to error: %q", driverErr) - cancelHA() + cancelHA(driverErr) if closeErr := a.close(); closeErr != nil { logrus.WithError(closeErr).Warn("an error during shutting down the host agent") } @@ -404,7 +404,7 @@ func (a *HostAgent) startRoutinesAndWait(ctx context.Context, errCh <-chan error return err case sig := <-a.signalCh: logrus.Infof("Received %s, shutting down the host agent", osutil.SignalName(sig)) - cancelHA() + cancelHA(nil) // no error, just a signal if closeErr := a.close(); closeErr != nil { logrus.WithError(closeErr).Warn("an error during shutting down the host agent") } diff --git a/pkg/instance/create.go b/pkg/instance/create.go index b9088785976..6af427110a8 100644 --- a/pkg/instance/create.go +++ b/pkg/instance/create.go @@ -11,7 +11,6 @@ import ( "path/filepath" "github.com/lima-vm/lima/pkg/cidata" - "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/driverutil" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/osutil" @@ -76,9 +75,10 @@ func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenY return nil, err } - limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{ - Instance: inst, - }) + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, 0) + if err != nil { + return nil, fmt.Errorf("failed to create driver instance: %w", err) + } if err := limaDriver.Register(ctx); err != nil { return nil, err diff --git a/pkg/instance/delete.go b/pkg/instance/delete.go index c4078d48a11..9ad4d10b878 100644 --- a/pkg/instance/delete.go +++ b/pkg/instance/delete.go @@ -9,7 +9,6 @@ import ( "fmt" "os" - "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/driverutil" "github.com/lima-vm/lima/pkg/store" ) @@ -37,9 +36,10 @@ func Delete(ctx context.Context, inst *store.Instance, force bool) error { } func unregister(ctx context.Context, inst *store.Instance) error { - limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{ - Instance: inst, - }) + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, 0) + if err != nil { + return fmt.Errorf("failed to create driver instance: %w", err) + } return limaDriver.Unregister(ctx) } diff --git a/pkg/instance/start.go b/pkg/instance/start.go index 1df00c785a3..c143bee33db 100644 --- a/pkg/instance/start.go +++ b/pkg/instance/start.go @@ -30,7 +30,7 @@ import ( "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/nativeimgutil" "github.com/lima-vm/lima/pkg/osutil" - "github.com/lima-vm/lima/pkg/qemu/imgutil" + "github.com/lima-vm/lima/pkg/qemuimgutil" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/lima/pkg/usrlocalsharelima" @@ -91,9 +91,10 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) { return nil, err } } - limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{ - Instance: inst, - }) + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, 0) + if err != nil { + return nil, fmt.Errorf("failed to create driver instance: %w", err) + } if err := limaDriver.Validate(); err != nil { return nil, err @@ -105,7 +106,7 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) { // Check if the instance has been created (the base disk already exists) baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk) - _, err := os.Stat(baseDisk) + _, err = os.Stat(baseDisk) created := err == nil if err := limaDriver.CreateDisk(ctx); err != nil { @@ -189,7 +190,7 @@ func Start(ctx context.Context, inst *store.Instance, limactl string, launchHost "hostagent", "--pidfile", haPIDPath, "--socket", haSockPath) - if prepared.Driver.CanRunGUI() { + if prepared.Driver.GetInfo().CanRunGUI { args = append(args, "--run-gui") } if prepared.GuestAgent != "" { @@ -430,7 +431,7 @@ func prepareDiffDisk(inst *store.Instance) error { if format == "raw" { err = nativeimgutil.ResizeRawDisk(diffDisk, int(inst.Disk)) } else { - err = imgutil.ResizeDisk(diffDisk, format, int(inst.Disk)) + err = qemuimgutil.ResizeDisk(diffDisk, format, int(inst.Disk)) } if err != nil { diff --git a/pkg/limainfo/limainfo.go b/pkg/limainfo/limainfo.go index 5311cca2805..0bd1876b6b4 100644 --- a/pkg/limainfo/limainfo.go +++ b/pkg/limainfo/limainfo.go @@ -9,8 +9,8 @@ import ( "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/pkg/driverutil" "github.com/lima-vm/lima/pkg/limayaml" + "github.com/lima-vm/lima/pkg/registry" "github.com/lima-vm/lima/pkg/store/dirnames" "github.com/lima-vm/lima/pkg/templatestore" "github.com/lima-vm/lima/pkg/usrlocalsharelima" @@ -45,7 +45,7 @@ func New() (*LimaInfo, error) { info := &LimaInfo{ Version: version.Version, DefaultTemplate: y, - VMTypes: driverutil.Drivers(), + VMTypes: registry.DefaultRegistry.List(), GuestAgents: make(map[limayaml.Arch]GuestAgent), } info.Templates, err = templatestore.Templates() diff --git a/pkg/networks/usernet/client.go b/pkg/networks/usernet/client.go index 2296f103ed1..4ed790321e0 100644 --- a/pkg/networks/usernet/client.go +++ b/pkg/networks/usernet/client.go @@ -17,10 +17,10 @@ import ( gvproxyclient "github.com/containers/gvisor-tap-vsock/pkg/client" "github.com/containers/gvisor-tap-vsock/pkg/types" - "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/httpclientutil" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/networks/usernet/dnshosts" + "github.com/lima-vm/lima/pkg/store" ) type Client struct { @@ -32,18 +32,21 @@ type Client struct { subnet net.IP } -func (c *Client) ConfigureDriver(ctx context.Context, driver *driver.BaseDriver) error { - macAddress := limayaml.MACAddress(driver.Instance.Dir) +func (c *Client) ConfigureDriver(ctx context.Context, inst *store.Instance, sshLocalPort int) error { + macAddress := limayaml.MACAddress(inst.Dir) ipAddress, err := c.ResolveIPAddress(ctx, macAddress) if err != nil { return err } - err = c.ResolveAndForwardSSH(ipAddress, driver.SSHLocalPort) + err = c.ResolveAndForwardSSH(ipAddress, sshLocalPort) if err != nil { return err } - hosts := driver.Instance.Config.HostResolver.Hosts - hosts[fmt.Sprintf("%s.internal", driver.Instance.Hostname)] = ipAddress + hosts := inst.Config.HostResolver.Hosts + if hosts == nil { + hosts = make(map[string]string) + } + hosts[fmt.Sprintf("%s.internal", inst.Hostname)] = ipAddress err = c.AddDNSHosts(hosts) return err } diff --git a/pkg/qemu/imgutil/imgutil.go b/pkg/qemuimgutil/qemuimgutil.go similarity index 99% rename from pkg/qemu/imgutil/imgutil.go rename to pkg/qemuimgutil/qemuimgutil.go index 18512bf0bf7..fd383f41236 100644 --- a/pkg/qemu/imgutil/imgutil.go +++ b/pkg/qemuimgutil/qemuimgutil.go @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright The Lima Authors // SPDX-License-Identifier: Apache-2.0 -package imgutil +package qemuimgutil import ( "bytes" diff --git a/pkg/qemu/imgutil/imgutil_test.go b/pkg/qemuimgutil/qemuimgutil_test.go similarity index 99% rename from pkg/qemu/imgutil/imgutil_test.go rename to pkg/qemuimgutil/qemuimgutil_test.go index bb630f48162..a2dcbfc83f0 100644 --- a/pkg/qemu/imgutil/imgutil_test.go +++ b/pkg/qemuimgutil/qemuimgutil_test.go @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright The Lima Authors // SPDX-License-Identifier: Apache-2.0 -package imgutil +package qemuimgutil import ( "testing" diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go new file mode 100644 index 00000000000..8400c0a32aa --- /dev/null +++ b/pkg/registry/registry.go @@ -0,0 +1,320 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package registry + +import ( + "bufio" + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/lima-vm/lima/pkg/driver" + "github.com/lima-vm/lima/pkg/driver/external/client" + "github.com/lima-vm/lima/pkg/store" + "github.com/lima-vm/lima/pkg/store/filenames" + "github.com/lima-vm/lima/pkg/usrlocalsharelima" + "github.com/sirupsen/logrus" +) + +type ExternalDriver struct { + Name string + InstanceName string + Command *exec.Cmd + SocketPath string + Client *client.DriverClient // Client is the gRPC client for the external driver + Path string + ctx context.Context + logger *logrus.Logger + cancelFunc context.CancelFunc +} + +type Registry struct { + internalDrivers map[string]driver.Driver + externalDrivers map[string]*ExternalDriver + mu sync.RWMutex +} + +func NewRegistry() *Registry { + return &Registry{ + internalDrivers: make(map[string]driver.Driver), + externalDrivers: make(map[string]*ExternalDriver), + } +} + +func (e *ExternalDriver) Start(instName string) error { + e.logger.Infof("Starting external driver at %s", e.Path) + if instName == "" { + return fmt.Errorf("instance name cannot be empty") + } + e.InstanceName = instName + + ctx, cancel := context.WithCancel(context.Background()) + cmd := exec.CommandContext(ctx, e.Path) + + stdout, err := cmd.StdoutPipe() + if err != nil { + cancel() + return fmt.Errorf("failed to create stdout pipe for external driver: %w", err) + } + + instanceDir, err := store.InstanceDir(e.InstanceName) + if err != nil { + cancel() + return fmt.Errorf("failed to determine instance directory: %w", err) + } + logPath := filepath.Join(instanceDir, filenames.ExternalDriverStderrLog) + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + cancel() + return fmt.Errorf("failed to open external driver log file: %w", err) + } + + // Redirect stderr to the log file + cmd.Stderr = logFile + + if err := cmd.Start(); err != nil { + cancel() + return fmt.Errorf("failed to start external driver: %w", err) + } + + driverLogger := e.logger.WithField("driver", e.Name) + + time.Sleep(time.Millisecond * 100) + + scanner := bufio.NewScanner(stdout) + var socketPath string + if scanner.Scan() { + socketPath = strings.TrimSpace(scanner.Text()) + } else { + cancel() + cmd.Process.Kill() + return fmt.Errorf("failed to read socket path from driver") + } + e.SocketPath = socketPath + + driverClient, err := client.NewDriverClient(e.SocketPath, e.logger) + if err != nil { + cancel() + cmd.Process.Kill() + return fmt.Errorf("failed to create driver client: %w", err) + } + + e.Command = cmd + e.Client = driverClient + e.ctx = ctx + e.cancelFunc = cancel + + driverLogger.Infof("External driver %s started successfully", e.Name) + return nil +} + +func (e *ExternalDriver) cleanup() { + if e.cancelFunc != nil { + e.cancelFunc() + } + if err := os.Remove(e.SocketPath); err != nil && !os.IsNotExist(err) { + e.logger.Warnf("Failed to remove socket file: %v", err) + } + + e.Command = nil + e.Client = nil + e.ctx = nil + e.cancelFunc = nil +} + +func (e *ExternalDriver) Stop() error { + if e.Command == nil || e.Command.Process == nil { + return fmt.Errorf("external driver %s is not running", e.Name) + } + + e.logger.Infof("Stopping external driver %s", e.Name) + e.cleanup() + + e.logger.Infof("External driver %s stopped successfully", e.Name) + return nil +} + +func (r *Registry) StopAllExternalDrivers() { + r.mu.Lock() + defer r.mu.Unlock() + + for name, driver := range r.externalDrivers { + // Only try to stop if the driver is actually running + if driver.Command != nil && driver.Command.Process != nil { + if err := driver.Stop(); err != nil { + logrus.Errorf("Failed to stop external driver %s: %v", name, err) + } else { + logrus.Infof("External driver %s stopped successfully", name) + } + } + // Always remove from registry + delete(r.externalDrivers, name) + } +} + +func (r *Registry) List() []string { + r.mu.RLock() + defer r.mu.RUnlock() + + var names []string + for name := range r.internalDrivers { + names = append(names, name) + } + + for name := range r.externalDrivers { + names = append(names, name+" (external)") + } + return names +} + +func (r *Registry) Get(name, instName string) (driver.Driver, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + + driver, exists := r.internalDrivers[name] + if !exists { + externalDriver, exists := r.externalDrivers[name] + if exists { + externalDriver.logger.Debugf("Using external driver %q", name) + if externalDriver.Client == nil || externalDriver.Command == nil || externalDriver.Command.Process == nil { + logrus.Infof("Starting new instance of external driver %q", name) + if err := externalDriver.Start(instName); err != nil { + externalDriver.logger.Errorf("Failed to start external driver %q: %v", name, err) + return nil, false + } + } else { + logrus.Infof("Reusing existing external driver %q instance", name) + r.externalDrivers[name].InstanceName = instName + } + + return externalDriver.Client, true + } + } + return driver, exists +} + +func (r *Registry) RegisterDriver(name, path string) { + r.mu.Lock() + defer r.mu.Unlock() + + if _, exists := DefaultRegistry.externalDrivers[name]; exists { + logrus.Debugf("Driver %q is already registered, skipping", name) + return + } + + DefaultRegistry.externalDrivers[name] = &ExternalDriver{ + Name: name, + Path: path, + logger: logrus.New(), + } +} + +func (r *Registry) DiscoverDrivers() error { + limaShareDir, err := usrlocalsharelima.Dir() + if err != nil { + return fmt.Errorf("failed to determine Lima share directory: %w", err) + } + stdDriverDir := filepath.Join(filepath.Dir(limaShareDir), "libexec", "lima", "drivers") + + if _, err := os.Stat(stdDriverDir); err == nil { + if err := r.discoverDriversInDir(stdDriverDir); err != nil { + logrus.Warnf("Error discovering drivers in %s: %v", stdDriverDir, err) + } + } + + if driverPaths := os.Getenv("LIMA_DRIVERS_PATH"); driverPaths != "" { + paths := filepath.SplitList(driverPaths) + for _, path := range paths { + if path == "" { + continue + } + + info, err := os.Stat(path) + if err != nil { + logrus.Warnf("Error accessing driver path %s: %v", path, err) + continue + } + + if info.IsDir() { + if err := r.discoverDriversInDir(path); err != nil { + logrus.Warnf("Error discovering drivers in %s: %v", path, err) + } + } else if isExecutable(info.Mode()) { + r.registerDriverFile(path) + } + } + } + + return nil +} + +func (r *Registry) discoverDriversInDir(dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + return fmt.Errorf("failed to read driver directory %s: %w", dir, err) + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + info, err := entry.Info() + if err != nil { + logrus.Warnf("Failed to get info for %s: %v", entry.Name(), err) + continue + } + + if !isExecutable(info.Mode()) { + continue + } + + driverPath := filepath.Join(dir, entry.Name()) + r.registerDriverFile(driverPath) + } + + return nil +} + +func (r *Registry) registerDriverFile(path string) { + base := filepath.Base(path) + if !strings.HasPrefix(base, "lima-driver-") { + fmt.Printf("Skipping %s: does not start with 'lima-driver-'\n", base) + return + } + + name := strings.TrimPrefix(base, "lima-driver-") + name = strings.TrimSuffix(name, filepath.Ext(name)) + + r.RegisterDriver(name, path) +} + +func isExecutable(mode os.FileMode) bool { + return mode&0111 != 0 +} + +var DefaultRegistry *Registry + +func init() { + DefaultRegistry = NewRegistry() + if err := DefaultRegistry.DiscoverDrivers(); err != nil { + logrus.Warnf("Error discovering drivers: %v", err) + } +} + +func Register(driver driver.Driver) { + if DefaultRegistry != nil { + name := driver.GetInfo().DriverName + if _, exists := DefaultRegistry.internalDrivers[name]; exists { + return + } + + DefaultRegistry.internalDrivers[name] = driver + } +} diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go index 520a41d92a4..b41579e273a 100644 --- a/pkg/snapshot/snapshot.go +++ b/pkg/snapshot/snapshot.go @@ -5,36 +5,42 @@ package snapshot import ( "context" + "fmt" - "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/driverutil" "github.com/lima-vm/lima/pkg/store" ) func Del(ctx context.Context, inst *store.Instance, tag string) error { - limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{ - Instance: inst, - }) + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, 0) + if err != nil { + return fmt.Errorf("failed to create driver instance: %w", err) + } + return limaDriver.DeleteSnapshot(ctx, tag) } func Save(ctx context.Context, inst *store.Instance, tag string) error { - limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{ - Instance: inst, - }) + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, 0) + if err != nil { + return fmt.Errorf("failed to create driver instance: %w", err) + } return limaDriver.CreateSnapshot(ctx, tag) } func Load(ctx context.Context, inst *store.Instance, tag string) error { - limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{ - Instance: inst, - }) + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, 0) + if err != nil { + return fmt.Errorf("failed to create driver instance: %w", err) + } return limaDriver.ApplySnapshot(ctx, tag) } func List(ctx context.Context, inst *store.Instance) (string, error) { - limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{ - Instance: inst, - }) + limaDriver, err := driverutil.CreateTargetDriverInstance(inst, 0) + if err != nil { + return "", fmt.Errorf("failed to create driver instance: %w", err) + } + return limaDriver.ListSnapshots(ctx) } diff --git a/pkg/store/filenames/filenames.go b/pkg/store/filenames/filenames.go index cfb825fc0ee..0bfec19995e 100644 --- a/pkg/store/filenames/filenames.go +++ b/pkg/store/filenames/filenames.go @@ -31,39 +31,41 @@ const ( // Filenames that may appear under an instance directory const ( - LimaYAML = "lima.yaml" - LimaVersion = "lima-version" // Lima version used to create instance - CIDataISO = "cidata.iso" - CIDataISODir = "cidata" - CloudConfig = "cloud-config.yaml" - BaseDisk = "basedisk" - DiffDisk = "diffdisk" - Kernel = "kernel" - KernelCmdline = "kernel.cmdline" - Initrd = "initrd" - QMPSock = "qmp.sock" - SerialLog = "serial.log" // default serial (ttyS0, but ttyAMA0 on qemu-system-{arm,aarch64}) - SerialSock = "serial.sock" - SerialPCILog = "serialp.log" // pci serial (ttyS0 on qemu-system-{arm,aarch64}) - SerialPCISock = "serialp.sock" - SerialVirtioLog = "serialv.log" // virtio serial - SerialVirtioSock = "serialv.sock" - SSHSock = "ssh.sock" - SSHConfig = "ssh.config" - VhostSock = "virtiofsd-%d.sock" - VNCDisplayFile = "vncdisplay" - VNCPasswordFile = "vncpassword" - GuestAgentSock = "ga.sock" - VirtioPort = "io.lima-vm.guest_agent.0" - HostAgentPID = "ha.pid" - HostAgentSock = "ha.sock" - HostAgentStdoutLog = "ha.stdout.log" - HostAgentStderrLog = "ha.stderr.log" - VzIdentifier = "vz-identifier" - VzEfi = "vz-efi" // efi variable store - QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created - QemuEfiFullFD = "qemu-efi-full.fd" // concatenated efi vars and code; not always created - AnsibleInventoryYAML = "ansible-inventory.yaml" + LimaYAML = "lima.yaml" + LimaVersion = "lima-version" // Lima version used to create instance + CIDataISO = "cidata.iso" + CIDataISODir = "cidata" + CloudConfig = "cloud-config.yaml" + BaseDisk = "basedisk" + DiffDisk = "diffdisk" + Kernel = "kernel" + KernelCmdline = "kernel.cmdline" + Initrd = "initrd" + QMPSock = "qmp.sock" + SerialLog = "serial.log" // default serial (ttyS0, but ttyAMA0 on qemu-system-{arm,aarch64}) + SerialSock = "serial.sock" + SerialPCILog = "serialp.log" // pci serial (ttyS0 on qemu-system-{arm,aarch64}) + SerialPCISock = "serialp.sock" + SerialVirtioLog = "serialv.log" // virtio serial + SerialVirtioSock = "serialv.sock" + SSHSock = "ssh.sock" + SSHConfig = "ssh.config" + VhostSock = "virtiofsd-%d.sock" + VNCDisplayFile = "vncdisplay" + VNCPasswordFile = "vncpassword" + GuestAgentSock = "ga.sock" + VirtioPort = "io.lima-vm.guest_agent.0" + HostAgentPID = "ha.pid" + HostAgentSock = "ha.sock" + HostAgentStdoutLog = "ha.stdout.log" + HostAgentStderrLog = "ha.stderr.log" + ExternalDriverStderrLog = "driver.stderr.log" + ExternalDriverSock = "driver.sock" + VzIdentifier = "vz-identifier" + VzEfi = "vz-efi" // efi variable store + QemuEfiCodeFD = "qemu-efi-code.fd" // efi code; not always created + QemuEfiFullFD = "qemu-efi-full.fd" // concatenated efi vars and code; not always created + AnsibleInventoryYAML = "ansible-inventory.yaml" // SocketDir is the default location for forwarded sockets with a relative paths in HostSocket. SocketDir = "sock" diff --git a/pkg/store/instance.go b/pkg/store/instance.go index 19b4b2270dc..5031b1c145c 100644 --- a/pkg/store/instance.go +++ b/pkg/store/instance.go @@ -5,6 +5,7 @@ package store import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -438,3 +439,38 @@ func (inst *Instance) Unprotect() error { inst.Protected = false return nil } + +func (inst *Instance) MarshalJSON() ([]byte, error) { + type Alias Instance + errorsAsStrings := make([]string, len(inst.Errors)) + for i, err := range inst.Errors { + if err != nil { + errorsAsStrings[i] = err.Error() + } + } + return json.Marshal(&struct { + *Alias + Errors []string `json:"errors,omitempty"` + }{ + Alias: (*Alias)(inst), + Errors: errorsAsStrings, + }) +} + +func (inst *Instance) UnmarshalJSON(data []byte) error { + type Alias Instance + aux := &struct { + *Alias + Errors []string `json:"errors,omitempty"` + }{ + Alias: (*Alias)(inst), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + inst.Errors = nil + for _, msg := range aux.Errors { + inst.Errors = append(inst.Errors, errors.New(msg)) + } + return nil +} diff --git a/pkg/vz/vz_driver_others.go b/pkg/vz/vz_driver_others.go deleted file mode 100644 index 66265628a49..00000000000 --- a/pkg/vz/vz_driver_others.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build !darwin || no_vz - -// SPDX-FileCopyrightText: Copyright The Lima Authors -// SPDX-License-Identifier: Apache-2.0 - -package vz - -import ( - "context" - "errors" - - "github.com/lima-vm/lima/pkg/driver" -) - -var ErrUnsupported = errors.New("vm driver 'vz' needs macOS 13 or later (Hint: try recompiling Lima if you are seeing this error on macOS 13)") - -const Enabled = false - -type LimaVzDriver struct { - *driver.BaseDriver -} - -func New(driver *driver.BaseDriver) *LimaVzDriver { - return &LimaVzDriver{ - BaseDriver: driver, - } -} - -func (l *LimaVzDriver) Validate() error { - return ErrUnsupported -} - -func (l *LimaVzDriver) CreateDisk(_ context.Context) error { - return ErrUnsupported -} - -func (l *LimaVzDriver) Start(_ context.Context) (chan error, error) { - return nil, ErrUnsupported -} - -func (l *LimaVzDriver) Stop(_ context.Context) error { - return ErrUnsupported -} diff --git a/pkg/wsl2/wsl_driver_others.go b/pkg/wsl2/wsl_driver_others.go deleted file mode 100644 index 4ad86d02768..00000000000 --- a/pkg/wsl2/wsl_driver_others.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build !windows || no_wsl - -// SPDX-FileCopyrightText: Copyright The Lima Authors -// SPDX-License-Identifier: Apache-2.0 - -package wsl2 - -import ( - "context" - "errors" - - "github.com/lima-vm/lima/pkg/driver" -) - -var ErrUnsupported = errors.New("vm driver 'wsl2' requires Windows 10 build 19041 or later (Hint: try recompiling Lima if you are seeing this error on Windows 10+)") - -const Enabled = false - -type LimaWslDriver struct { - *driver.BaseDriver -} - -func New(driver *driver.BaseDriver) *LimaWslDriver { - return &LimaWslDriver{ - BaseDriver: driver, - } -} - -func (l *LimaWslDriver) Validate() error { - return ErrUnsupported -} - -func (l *LimaWslDriver) CreateDisk(_ context.Context) error { - return ErrUnsupported -} - -func (l *LimaWslDriver) Start(_ context.Context) (chan error, error) { - return nil, ErrUnsupported -} - -func (l *LimaWslDriver) Stop(_ context.Context) error { - return ErrUnsupported -}