driver: return structured snapshot metadata

Signed-off-by: olamilekan000 <odukoyaonline@gmail.com>
This commit is contained in:
olamilekan000 committed 2026-10-05 09:47:26 +01:00
1 parent 2eceab6c93
commit 5b5c0aeb28
19 files changed
+537 -187

No files matched your search

+109 -31
View File
@@ -4,13 +4,17 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"strings"
"io"
"text/tabwriter"
"time"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/lima-vm/lima/v2/pkg/driver"
"github.com/lima-vm/lima/v2/pkg/snapshot"
"github.com/lima-vm/lima/v2/pkg/store"
)
@@ -26,10 +30,10 @@ func newSnapshotCommand() *cobra.Command {
$ limactl snapshot create default --tag snap1
Apply (restore) a snapshot:
$ limactl snapshot apply default --tag snap1
$ limactl snapshot apply default --id 1
Delete a snapshot:
$ limactl snapshot delete default --tag snap1
$ limactl snapshot delete default --id 1
`,
PersistentPreRun: func(*cobra.Command, []string) {
logrus.Warn("`limactl snapshot` is experimental")
@@ -88,13 +92,15 @@ func newSnapshotDeleteCommand() *cobra.Command {
Aliases: []string{"del"},
Short: "Delete (del) a snapshot",
Example: ` Delete a snapshot:
$ limactl snapshot delete default --tag snap1
$ limactl snapshot delete default --id 1
`,
Args: cobra.MinimumNArgs(1),
RunE: snapshotDeleteAction,
ValidArgsFunction: snapshotBashComplete,
}
deleteCmd.Flags().String("id", "", "ID of the snapshot")
deleteCmd.Flags().String("tag", "", "Name of the snapshot")
deleteCmd.MarkFlagsMutuallyExclusive("id", "tag")
return deleteCmd
}
@@ -108,16 +114,29 @@ func snapshotDeleteAction(cmd *cobra.Command, args []string) error {
return err
}
tag, err := cmd.Flags().GetString("tag")
id, err := cmd.Flags().GetString("id")
if err != nil {
return err
}
if tag == "" {
return errors.New("expected tag")
if id == "" {
tag, err := cmd.Flags().GetString("tag")
if err != nil {
return err
}
if tag == "" {
return errors.New("expected ID or tag")
}
snapshots, err := snapshot.List(ctx, inst)
if err != nil {
return err
}
id, err = findSnapshotIDByTag(snapshots, tag)
if err != nil {
return err
}
}
return snapshot.Del(ctx, inst, tag)
return snapshot.Del(ctx, inst, id)
}
func newSnapshotApplyCommand() *cobra.Command {
@@ -126,13 +145,15 @@ func newSnapshotApplyCommand() *cobra.Command {
Aliases: []string{"load"},
Short: "Apply (load) a snapshot",
Example: ` Apply (restore) a snapshot:
$ limactl snapshot apply default --tag snap1
$ limactl snapshot apply default --id 1
`,
Args: cobra.MinimumNArgs(1),
RunE: snapshotApplyAction,
ValidArgsFunction: snapshotBashComplete,
}
applyCmd.Flags().String("id", "", "ID of the snapshot")
applyCmd.Flags().String("tag", "", "Name of the snapshot")
applyCmd.MarkFlagsMutuallyExclusive("id", "tag")
return applyCmd
}
@@ -146,16 +167,46 @@ func snapshotApplyAction(cmd *cobra.Command, args []string) error {
return err
}
tag, err := cmd.Flags().GetString("tag")
id, err := cmd.Flags().GetString("id")
if err != nil {
return err
}
if tag == "" {
return errors.New("expected tag")
if id == "" {
tag, err := cmd.Flags().GetString("tag")
if err != nil {
return err
}
if tag == "" {
return errors.New("expected ID or tag")
}
snapshots, err := snapshot.List(ctx, inst)
if err != nil {
return err
}
id, err = findSnapshotIDByTag(snapshots, tag)
if err != nil {
return err
}
}
return snapshot.Load(ctx, inst, tag)
return snapshot.Load(ctx, inst, id)
}
func findSnapshotIDByTag(snapshots []driver.Snapshot, tag string) (string, error) {
var id string
for _, snapshot := range snapshots {
if snapshot.Tag != tag {
continue
}
if id != "" {
return "", fmt.Errorf("snapshot tag %q is not unique", tag)
}
id = snapshot.ID
}
if id == "" {
return "", fmt.Errorf("snapshot tag %q not found", tag)
}
return id, nil
}
func newSnapshotListCommand() *cobra.Command {
@@ -168,12 +219,17 @@ func newSnapshotListCommand() *cobra.Command {
List only snapshot tags:
$ limactl snapshot list default --quiet
List snapshots in JSON format:
$ limactl snapshot list default --json
`,
Args: cobra.MinimumNArgs(1),
RunE: snapshotListAction,
ValidArgsFunction: snapshotBashComplete,
}
listCmd.Flags().BoolP("quiet", "q", false, "Only show tags")
listCmd.Flags().Bool("json", false, "JSONify output")
listCmd.MarkFlagsMutuallyExclusive("quiet", "json")
return listCmd
}
@@ -191,29 +247,51 @@ func snapshotListAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
out, err := snapshot.List(ctx, inst)
jsonFormat, err := cmd.Flags().GetBool("json")
if err != nil {
return err
}
if quiet {
for i, line := range strings.Split(out, "\n") {
// "ID", "TAG", "VM SIZE", "DATE", "VM CLOCK", "ICOUNT"
fields := strings.Fields(line)
if i == 0 && len(fields) > 1 && fields[1] != "TAG" {
// make sure that output matches the expected
return fmt.Errorf("unknown header: %s", line)
snapshots, err := snapshot.List(ctx, inst)
if err != nil {
return err
}
return printSnapshots(cmd.OutOrStdout(), snapshots, quiet, jsonFormat)
}
func printSnapshots(w io.Writer, snapshots []driver.Snapshot, quiet, jsonFormat bool) error {
if jsonFormat {
encoder := json.NewEncoder(w)
for _, snapshot := range snapshots {
if err := encoder.Encode(snapshot); err != nil {
return err
}
if i == 0 || line == "" {
// skip header and empty line after using split
continue
}
tag := fields[1]
fmt.Fprintf(cmd.OutOrStdout(), "%s\n", tag)
}
return nil
}
fmt.Fprint(cmd.OutOrStdout(), out)
return nil
if quiet {
for _, snapshot := range snapshots {
if _, err := fmt.Fprintln(w, snapshot.Tag); err != nil {
return err
}
}
return nil
}
tw := tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)
if _, err := fmt.Fprintln(tw, "ID\tTAG\tCREATED"); err != nil {
return err
}
for _, snapshot := range snapshots {
createdAt := "-"
if snapshot.CreatedAt != nil {
createdAt = snapshot.CreatedAt.Format(time.RFC3339)
}
if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\n", snapshot.ID, snapshot.Tag, createdAt); err != nil {
return err
}
}
return tw.Flush()
}
func snapshotBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
+73
View File
@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0
package main
import (
"bytes"
"testing"
"time"
"gotest.tools/v3/assert"
"github.com/lima-vm/lima/v2/pkg/driver"
)
func TestPrintSnapshots(t *testing.T) {
createdAt := time.Date(2026, time.September, 16, 20, 50, 52, 0, time.UTC)
snapshots := []driver.Snapshot{
{ID: "1", Tag: "initial", CreatedAt: &createdAt},
{ID: "2", Tag: "before package install"},
}
tests := []struct {
name string
quiet bool
jsonFormat bool
want string
}{
{
name: "table",
want: "ID TAG CREATED\n" +
"1 initial 2026-09-16T20:50:52Z\n" +
"2 before package install -\n",
},
{
name: "quiet",
quiet: true,
want: "initial\nbefore package install\n",
},
{
name: "json",
jsonFormat: true,
want: "{\"id\":\"1\",\"tag\":\"initial\",\"createdAt\":\"2026-09-16T20:50:52Z\"}\n" +
"{\"id\":\"2\",\"tag\":\"before package install\"}\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got bytes.Buffer
assert.NilError(t, printSnapshots(&got, snapshots, tt.quiet, tt.jsonFormat))
assert.Equal(t, got.String(), tt.want)
})
}
}
func TestFindSnapshotIDByTag(t *testing.T) {
snapshots := []driver.Snapshot{
{ID: "1", Tag: "initial"},
{ID: "2", Tag: "duplicate"},
{ID: "3", Tag: "duplicate"},
}
id, err := findSnapshotIDByTag(snapshots, "initial")
assert.NilError(t, err)
assert.Equal(t, id, "1")
_, err = findSnapshotIDByTag(snapshots, "duplicate")
assert.ErrorContains(t, err, `snapshot tag "duplicate" is not unique`)
_, err = findSnapshotIDByTag(snapshots, "missing")
assert.ErrorContains(t, err, `snapshot tag "missing" not found`)
}
+14 -3
View File
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"net"
"time"
"github.com/lima-vm/lima/v2/pkg/hostagent/events"
"github.com/lima-vm/lima/v2/pkg/limatype"
@@ -73,12 +74,22 @@ type GUI interface {
DisplayConnection(ctx context.Context) (string, error)
}
// Snapshot describes a VM snapshot.
type Snapshot struct {
// ID is the primary driver-specific identifier and is unique when available.
ID string `json:"id,omitempty"`
// Tag is the human-readable snapshot name and is not necessarily unique.
Tag string `json:"tag"`
// CreatedAt is the time at which the snapshot was created, when available.
CreatedAt *time.Time `json:"createdAt,omitempty"`
}
// 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)
ApplySnapshot(ctx context.Context, id string) error
DeleteSnapshot(ctx context.Context, id string) error
ListSnapshots(ctx context.Context) ([]Snapshot, error)
}
// GuestAgent defines operations for the guest agent.
+23 -12
View File
@@ -195,47 +195,58 @@ func (d *DriverClient) CreateSnapshot(ctx context.Context, tag string) error {
return nil
}
func (d *DriverClient) ApplySnapshot(ctx context.Context, tag string) error {
d.logger.Debugf("Applying snapshot with tag: %s", tag)
func (d *DriverClient) ApplySnapshot(ctx context.Context, id string) error {
d.logger.Debugf("Applying snapshot with ID: %s", id)
_, err := d.DriverSvc.ApplySnapshot(ctx, &pb.ApplySnapshotRequest{
Tag: tag,
Id: id,
})
if err != nil {
d.logger.WithError(err).Error("Failed to apply snapshot")
return err
}
d.logger.Debugf("Snapshot '%s' applied successfully", tag)
d.logger.Debugf("Snapshot %q applied successfully", id)
return nil
}
func (d *DriverClient) DeleteSnapshot(ctx context.Context, tag string) error {
d.logger.Debugf("Deleting snapshot with tag: %s", tag)
func (d *DriverClient) DeleteSnapshot(ctx context.Context, id string) error {
d.logger.Debugf("Deleting snapshot with ID: %s", id)
_, err := d.DriverSvc.DeleteSnapshot(ctx, &pb.DeleteSnapshotRequest{
Tag: tag,
Id: id,
})
if err != nil {
d.logger.WithError(err).Error("Failed to delete snapshot")
return err
}
d.logger.Debugf("Snapshot '%s' deleted successfully", tag)
d.logger.Debugf("Snapshot %q deleted successfully", id)
return nil
}
func (d *DriverClient) ListSnapshots(ctx context.Context) (string, error) {
func (d *DriverClient) ListSnapshots(ctx context.Context) ([]driver.Snapshot, error) {
d.logger.Debug("Listing snapshots")
resp, err := d.DriverSvc.ListSnapshots(ctx, &emptypb.Empty{})
if err != nil {
d.logger.WithError(err).Error("Failed to list snapshots")
return "", err
return nil, err
}
d.logger.Debugf("Snapshots listed successfully: %s", resp.Snapshots)
return resp.Snapshots, nil
snapshots := make([]driver.Snapshot, len(resp.Snapshots))
for i, snapshot := range resp.Snapshots {
snapshots[i] = driver.Snapshot{ID: snapshot.Id, Tag: snapshot.Tag}
if snapshot.CreatedAt != nil {
if err := snapshot.CreatedAt.CheckValid(); err != nil {
return nil, err
}
createdAt := snapshot.CreatedAt.AsTime()
snapshots[i].CreatedAt = &createdAt
}
}
d.logger.Debugf("Snapshots listed successfully: %+v", snapshots)
return snapshots, nil
}
func (d *DriverClient) ForwardGuestAgent(ctx context.Context) bool {
+14 -9
View File
@@ -1,6 +1,6 @@
‰
driver.protogoogle/protobuf/empty.proto"Ž
 
driver.protogoogle/protobuf/empty.protogoogle/protobuf/timestamp.proto"Ž
BootScriptsResponse;
scripts ( 2!.BootScriptsResponse.ScriptsEntryRscripts:
ScriptsEntry
@@ -24,13 +24,18 @@
connection ( R
connection")
CreateSnapshotRequest
tag ( Rtag"(
ApplySnapshotRequest
tag ( Rtag")
DeleteSnapshotRequest
tag ( Rtag"5
ListSnapshotsResponse
snapshots ( R snapshots"B
tag ( Rtag"&
ApplySnapshotRequest
id ( Rid"'
DeleteSnapshotRequest
id ( Rid"g
Snapshot
id ( Rid
tag ( Rtag9
created_at ( 2.google.protobuf.TimestampR createdAt"F
ListSnapshotsResponse'
snapshots ( 2 .SnapshotR snapshotsJ"B
ForwardGuestAgentResponse%
should_forward (R␍shouldForward2î
Driver:
+151 -78
View File
@@ -10,6 +10,7 @@ import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
@@ -460,8 +461,8 @@ func (x *CreateSnapshotRequest) GetTag() string {
// ApplySnapshotRequest identifies the snapshot to restore.
type ApplySnapshotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Names the snapshot to restore.
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
// Unique driver-specific identifier of the snapshot to restore.
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -496,9 +497,9 @@ func (*ApplySnapshotRequest) Descriptor() ([]byte, []int) {
return file_driver_proto_rawDescGZIP(), []int{9}
}
func (x *ApplySnapshotRequest) GetTag() string {
func (x *ApplySnapshotRequest) GetId() string {
if x != nil {
return x.Tag
return x.Id
}
return ""
}
@@ -506,8 +507,8 @@ func (x *ApplySnapshotRequest) GetTag() string {
// DeleteSnapshotRequest identifies the snapshot to delete.
type DeleteSnapshotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Names the snapshot to delete.
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
// Unique driver-specific identifier of the snapshot to delete.
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -542,25 +543,88 @@ func (*DeleteSnapshotRequest) Descriptor() ([]byte, []int) {
return file_driver_proto_rawDescGZIP(), []int{10}
}
func (x *DeleteSnapshotRequest) GetTag() string {
func (x *DeleteSnapshotRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
// Snapshot describes a VM snapshot.
type Snapshot struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Primary driver-specific identifier; unique when available.
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Human-readable snapshot name; not necessarily unique.
Tag string `protobuf:"bytes,2,opt,name=tag,proto3" json:"tag,omitempty"`
// Time at which the snapshot was created, when available.
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Snapshot) Reset() {
*x = Snapshot{}
mi := &file_driver_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Snapshot) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Snapshot) ProtoMessage() {}
func (x *Snapshot) ProtoReflect() protoreflect.Message {
mi := &file_driver_proto_msgTypes[11]
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 Snapshot.ProtoReflect.Descriptor instead.
func (*Snapshot) Descriptor() ([]byte, []int) {
return file_driver_proto_rawDescGZIP(), []int{11}
}
func (x *Snapshot) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Snapshot) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *Snapshot) GetCreatedAt() *timestamppb.Timestamp {
if x != nil {
return x.CreatedAt
}
return nil
}
// ListSnapshotsResponse carries the existing snapshots.
type ListSnapshotsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Driver-formatted list of existing snapshots.
Snapshots string `protobuf:"bytes,1,opt,name=snapshots,proto3" json:"snapshots,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
Snapshots []*Snapshot `protobuf:"bytes,2,rep,name=snapshots,proto3" json:"snapshots,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListSnapshotsResponse) Reset() {
*x = ListSnapshotsResponse{}
mi := &file_driver_proto_msgTypes[11]
mi := &file_driver_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -572,7 +636,7 @@ func (x *ListSnapshotsResponse) String() string {
func (*ListSnapshotsResponse) ProtoMessage() {}
func (x *ListSnapshotsResponse) ProtoReflect() protoreflect.Message {
mi := &file_driver_proto_msgTypes[11]
mi := &file_driver_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -585,14 +649,14 @@ func (x *ListSnapshotsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListSnapshotsResponse.ProtoReflect.Descriptor instead.
func (*ListSnapshotsResponse) Descriptor() ([]byte, []int) {
return file_driver_proto_rawDescGZIP(), []int{11}
return file_driver_proto_rawDescGZIP(), []int{12}
}
func (x *ListSnapshotsResponse) GetSnapshots() string {
func (x *ListSnapshotsResponse) GetSnapshots() []*Snapshot {
if x != nil {
return x.Snapshots
}
return ""
return nil
}
// ForwardGuestAgentResponse reports how the guest agent is reached.
@@ -607,7 +671,7 @@ type ForwardGuestAgentResponse struct {
func (x *ForwardGuestAgentResponse) Reset() {
*x = ForwardGuestAgentResponse{}
mi := &file_driver_proto_msgTypes[12]
mi := &file_driver_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -619,7 +683,7 @@ func (x *ForwardGuestAgentResponse) String() string {
func (*ForwardGuestAgentResponse) ProtoMessage() {}
func (x *ForwardGuestAgentResponse) ProtoReflect() protoreflect.Message {
mi := &file_driver_proto_msgTypes[12]
mi := &file_driver_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -632,7 +696,7 @@ func (x *ForwardGuestAgentResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ForwardGuestAgentResponse.ProtoReflect.Descriptor instead.
func (*ForwardGuestAgentResponse) Descriptor() ([]byte, []int) {
return file_driver_proto_rawDescGZIP(), []int{12}
return file_driver_proto_rawDescGZIP(), []int{13}
}
func (x *ForwardGuestAgentResponse) GetShouldForward() bool {
@@ -646,7 +710,7 @@ var File_driver_proto protoreflect.FileDescriptor
const file_driver_proto_rawDesc = "" +
"\n" +
"\fdriver.proto\x1a\x1bgoogle/protobuf/empty.proto\"\x8e\x01\n" +
"\fdriver.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8e\x01\n" +
"\x13BootScriptsResponse\x12;\n" +
"\ascripts\x18\x01 \x03(\v2!.BootScriptsResponse.ScriptsEntryR\ascripts\x1a:\n" +
"\fScriptsEntry\x12\x10\n" +
@@ -670,13 +734,18 @@ const file_driver_proto_rawDesc = "" +
"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" +
"\x03tag\x18\x01 \x01(\tR\x03tag\"&\n" +
"\x14ApplySnapshotRequest\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\"'\n" +
"\x15DeleteSnapshotRequest\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\"g\n" +
"\bSnapshot\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" +
"\x03tag\x18\x02 \x01(\tR\x03tag\x129\n" +
"\n" +
"created_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\"F\n" +
"\x15ListSnapshotsResponse\x12'\n" +
"\tsnapshots\x18\x02 \x03(\v2\t.SnapshotR\tsnapshotsJ\x04\b\x01\x10\x02\"B\n" +
"\x19ForwardGuestAgentResponse\x12%\n" +
"\x0eshould_forward\x18\x01 \x01(\bR\rshouldForward2\xee\t\n" +
"\x06Driver\x12:\n" +
@@ -715,7 +784,7 @@ func file_driver_proto_rawDescGZIP() []byte {
return file_driver_proto_rawDescData
}
var file_driver_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
var file_driver_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_driver_proto_goTypes = []any{
(*BootScriptsResponse)(nil), // 0: BootScriptsResponse
(*SSHAddressResponse)(nil), // 1: SSHAddressResponse
@@ -728,58 +797,62 @@ var file_driver_proto_goTypes = []any{
(*CreateSnapshotRequest)(nil), // 8: CreateSnapshotRequest
(*ApplySnapshotRequest)(nil), // 9: ApplySnapshotRequest
(*DeleteSnapshotRequest)(nil), // 10: DeleteSnapshotRequest
(*ListSnapshotsResponse)(nil), // 11: ListSnapshotsResponse
(*ForwardGuestAgentResponse)(nil), // 12: ForwardGuestAgentResponse
nil, // 13: BootScriptsResponse.ScriptsEntry
(*emptypb.Empty)(nil), // 14: google.protobuf.Empty
(*Snapshot)(nil), // 11: Snapshot
(*ListSnapshotsResponse)(nil), // 12: ListSnapshotsResponse
(*ForwardGuestAgentResponse)(nil), // 13: ForwardGuestAgentResponse
nil, // 14: BootScriptsResponse.ScriptsEntry
(*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 16: google.protobuf.Empty
}
var file_driver_proto_depIdxs = []int32{
13, // 0: BootScriptsResponse.scripts:type_name -> BootScriptsResponse.ScriptsEntry
14, // 1: Driver.Validate:input_type -> google.protobuf.Empty
14, // 2: Driver.Create:input_type -> google.protobuf.Empty
14, // 3: Driver.CreateDisk:input_type -> google.protobuf.Empty
14, // 4: Driver.Start:input_type -> google.protobuf.Empty
14, // 5: Driver.Stop:input_type -> google.protobuf.Empty
14, // 6: Driver.Delete:input_type -> google.protobuf.Empty
14, // 7: Driver.BootScripts:input_type -> google.protobuf.Empty
14, // 8: Driver.RunGUI:input_type -> google.protobuf.Empty
6, // 9: Driver.ChangeDisplayPassword:input_type -> ChangeDisplayPasswordRequest
14, // 10: Driver.GetDisplayConnection:input_type -> google.protobuf.Empty
8, // 11: Driver.CreateSnapshot:input_type -> CreateSnapshotRequest
9, // 12: Driver.ApplySnapshot:input_type -> ApplySnapshotRequest
10, // 13: Driver.DeleteSnapshot:input_type -> DeleteSnapshotRequest
14, // 14: Driver.ListSnapshots:input_type -> google.protobuf.Empty
14, // 15: Driver.ForwardGuestAgent:input_type -> google.protobuf.Empty
14, // 16: Driver.GuestAgentConn:input_type -> google.protobuf.Empty
4, // 17: Driver.Configure:input_type -> SetConfigRequest
14, // 18: Driver.Info:input_type -> google.protobuf.Empty
14, // 19: Driver.SSHAddress:input_type -> google.protobuf.Empty
14, // 20: Driver.AdditionalSetupForSSH:input_type -> google.protobuf.Empty
14, // 21: Driver.Validate:output_type -> google.protobuf.Empty
14, // 22: Driver.Create:output_type -> google.protobuf.Empty
14, // 23: Driver.CreateDisk:output_type -> google.protobuf.Empty
3, // 24: Driver.Start:output_type -> StartResponse
14, // 25: Driver.Stop:output_type -> google.protobuf.Empty
14, // 26: Driver.Delete:output_type -> google.protobuf.Empty
0, // 27: Driver.BootScripts:output_type -> BootScriptsResponse
14, // 28: Driver.RunGUI:output_type -> google.protobuf.Empty
14, // 29: Driver.ChangeDisplayPassword:output_type -> google.protobuf.Empty
7, // 30: Driver.GetDisplayConnection:output_type -> GetDisplayConnectionResponse
14, // 31: Driver.CreateSnapshot:output_type -> google.protobuf.Empty
14, // 32: Driver.ApplySnapshot:output_type -> google.protobuf.Empty
14, // 33: Driver.DeleteSnapshot:output_type -> google.protobuf.Empty
11, // 34: Driver.ListSnapshots:output_type -> ListSnapshotsResponse
12, // 35: Driver.ForwardGuestAgent:output_type -> ForwardGuestAgentResponse
14, // 36: Driver.GuestAgentConn:output_type -> google.protobuf.Empty
5, // 37: Driver.Configure:output_type -> SetConfigResponse
2, // 38: Driver.Info:output_type -> InfoResponse
1, // 39: Driver.SSHAddress:output_type -> SSHAddressResponse
14, // 40: Driver.AdditionalSetupForSSH:output_type -> google.protobuf.Empty
21, // [21:41] is the sub-list for method output_type
1, // [1:21] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
14, // 0: BootScriptsResponse.scripts:type_name -> BootScriptsResponse.ScriptsEntry
15, // 1: Snapshot.created_at:type_name -> google.protobuf.Timestamp
11, // 2: ListSnapshotsResponse.snapshots:type_name -> Snapshot
16, // 3: Driver.Validate:input_type -> google.protobuf.Empty
16, // 4: Driver.Create:input_type -> google.protobuf.Empty
16, // 5: Driver.CreateDisk:input_type -> google.protobuf.Empty
16, // 6: Driver.Start:input_type -> google.protobuf.Empty
16, // 7: Driver.Stop:input_type -> google.protobuf.Empty
16, // 8: Driver.Delete:input_type -> google.protobuf.Empty
16, // 9: Driver.BootScripts:input_type -> google.protobuf.Empty
16, // 10: Driver.RunGUI:input_type -> google.protobuf.Empty
6, // 11: Driver.ChangeDisplayPassword:input_type -> ChangeDisplayPasswordRequest
16, // 12: Driver.GetDisplayConnection:input_type -> google.protobuf.Empty
8, // 13: Driver.CreateSnapshot:input_type -> CreateSnapshotRequest
9, // 14: Driver.ApplySnapshot:input_type -> ApplySnapshotRequest
10, // 15: Driver.DeleteSnapshot:input_type -> DeleteSnapshotRequest
16, // 16: Driver.ListSnapshots:input_type -> google.protobuf.Empty
16, // 17: Driver.ForwardGuestAgent:input_type -> google.protobuf.Empty
16, // 18: Driver.GuestAgentConn:input_type -> google.protobuf.Empty
4, // 19: Driver.Configure:input_type -> SetConfigRequest
16, // 20: Driver.Info:input_type -> google.protobuf.Empty
16, // 21: Driver.SSHAddress:input_type -> google.protobuf.Empty
16, // 22: Driver.AdditionalSetupForSSH:input_type -> google.protobuf.Empty
16, // 23: Driver.Validate:output_type -> google.protobuf.Empty
16, // 24: Driver.Create:output_type -> google.protobuf.Empty
16, // 25: Driver.CreateDisk:output_type -> google.protobuf.Empty
3, // 26: Driver.Start:output_type -> StartResponse
16, // 27: Driver.Stop:output_type -> google.protobuf.Empty
16, // 28: Driver.Delete:output_type -> google.protobuf.Empty
0, // 29: Driver.BootScripts:output_type -> BootScriptsResponse
16, // 30: Driver.RunGUI:output_type -> google.protobuf.Empty
16, // 31: Driver.ChangeDisplayPassword:output_type -> google.protobuf.Empty
7, // 32: Driver.GetDisplayConnection:output_type -> GetDisplayConnectionResponse
16, // 33: Driver.CreateSnapshot:output_type -> google.protobuf.Empty
16, // 34: Driver.ApplySnapshot:output_type -> google.protobuf.Empty
16, // 35: Driver.DeleteSnapshot:output_type -> google.protobuf.Empty
12, // 36: Driver.ListSnapshots:output_type -> ListSnapshotsResponse
13, // 37: Driver.ForwardGuestAgent:output_type -> ForwardGuestAgentResponse
16, // 38: Driver.GuestAgentConn:output_type -> google.protobuf.Empty
5, // 39: Driver.Configure:output_type -> SetConfigResponse
2, // 40: Driver.Info:output_type -> InfoResponse
1, // 41: Driver.SSHAddress:output_type -> SSHAddressResponse
16, // 42: Driver.AdditionalSetupForSSH:output_type -> google.protobuf.Empty
23, // [23:43] is the sub-list for method output_type
3, // [3:23] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_driver_proto_init() }
@@ -793,7 +866,7 @@ func file_driver_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_driver_proto_rawDesc), len(file_driver_proto_rawDesc)),
NumEnums: 0,
NumMessages: 14,
NumMessages: 15,
NumExtensions: 0,
NumServices: 1,
},
+18 -7
View File
@@ -1,6 +1,7 @@
syntax = "proto3";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
option go_package = "github.com/lima-vm/lima/v2/pkg/driver/external";
@@ -90,7 +91,7 @@ service Driver {
// DeleteSnapshot removes the snapshot identified by the given tag.
rpc DeleteSnapshot(DeleteSnapshotRequest) returns (google.protobuf.Empty);
// ListSnapshots returns the driver-formatted list of existing snapshots.
// ListSnapshots returns the existing snapshots.
rpc ListSnapshots(google.protobuf.Empty) returns (ListSnapshotsResponse);
// ForwardGuestAgent reports whether the host agent must forward the guest
@@ -194,20 +195,30 @@ message CreateSnapshotRequest {
// ApplySnapshotRequest identifies the snapshot to restore.
message ApplySnapshotRequest {
// Names the snapshot to restore.
string tag = 1;
// Unique driver-specific identifier of the snapshot to restore.
string id = 1;
}
// DeleteSnapshotRequest identifies the snapshot to delete.
message DeleteSnapshotRequest {
// Names the snapshot to delete.
string tag = 1;
// Unique driver-specific identifier of the snapshot to delete.
string id = 1;
}
// Snapshot describes a VM snapshot.
message Snapshot {
// Primary driver-specific identifier; unique when available.
string id = 1;
// Human-readable snapshot name; not necessarily unique.
string tag = 2;
// Time at which the snapshot was created, when available.
google.protobuf.Timestamp created_at = 3;
}
// ListSnapshotsResponse carries the existing snapshots.
message ListSnapshotsResponse {
// Driver-formatted list of existing snapshots.
string snapshots = 1;
reserved 1; // Used by the former string snapshots field.
repeated Snapshot snapshots = 2;
}
// ForwardGuestAgentResponse reports how the guest agent is reached.
+2 -2
View File
@@ -119,7 +119,7 @@ type DriverClient interface {
ApplySnapshot(ctx context.Context, in *ApplySnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// DeleteSnapshot removes the snapshot identified by the given tag.
DeleteSnapshot(ctx context.Context, in *DeleteSnapshotRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// ListSnapshots returns the driver-formatted list of existing snapshots.
// ListSnapshots returns the existing snapshots.
ListSnapshots(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListSnapshotsResponse, error)
// ForwardGuestAgent reports whether the host agent must forward the guest
// agent socket over SSH. When false, the driver provides a direct guest agent
@@ -441,7 +441,7 @@ type DriverServer interface {
ApplySnapshot(context.Context, *ApplySnapshotRequest) (*emptypb.Empty, error)
// DeleteSnapshot removes the snapshot identified by the given tag.
DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*emptypb.Empty, error)
// ListSnapshots returns the driver-formatted list of existing snapshots.
// ListSnapshots returns the existing snapshots.
ListSnapshots(context.Context, *emptypb.Empty) (*ListSnapshotsResponse, error)
// ForwardGuestAgent reports whether the host agent must forward the guest
// agent socket over SSH. When false, the driver provides a direct guest agent
+18 -5
View File
@@ -13,8 +13,10 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/lima-vm/lima/v2/pkg/bicopy"
"github.com/lima-vm/lima/v2/pkg/driver"
pb "github.com/lima-vm/lima/v2/pkg/driver/external"
"github.com/lima-vm/lima/v2/pkg/limatype"
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
@@ -269,8 +271,8 @@ func (s *DriverServer) CreateSnapshot(ctx context.Context, req *pb.CreateSnapsho
}
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)
s.logger.Debugf("Received ApplySnapshot request with ID: %s", req.Id)
err := s.driver.ApplySnapshot(ctx, req.Id)
if err != nil {
s.logger.WithError(err).Error("ApplySnapshot failed")
return &emptypb.Empty{}, err
@@ -280,8 +282,8 @@ func (s *DriverServer) ApplySnapshot(ctx context.Context, req *pb.ApplySnapshotR
}
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)
s.logger.Debugf("Received DeleteSnapshot request with ID: %s", req.Id)
err := s.driver.DeleteSnapshot(ctx, req.Id)
if err != nil {
s.logger.WithError(err).Error("DeleteSnapshot failed")
return &emptypb.Empty{}, err
@@ -298,7 +300,18 @@ func (s *DriverServer) ListSnapshots(ctx context.Context, _ *emptypb.Empty) (*pb
return nil, err
}
s.logger.Debug("ListSnapshots succeeded")
return &pb.ListSnapshotsResponse{Snapshots: snapshots}, nil
return &pb.ListSnapshotsResponse{Snapshots: snapshotsToProto(snapshots)}, nil
}
func snapshotsToProto(snapshots []driver.Snapshot) []*pb.Snapshot {
result := make([]*pb.Snapshot, len(snapshots))
for i, snapshot := range snapshots {
result[i] = &pb.Snapshot{Id: snapshot.ID, Tag: snapshot.Tag}
if snapshot.CreatedAt != nil {
result[i].CreatedAt = timestamppb.New(*snapshot.CreatedAt)
}
}
return result
}
func (s *DriverServer) ForwardGuestAgent(ctx context.Context, _ *emptypb.Empty) (*pb.ForwardGuestAgentResponse, error) {
+2 -2
View File
@@ -432,8 +432,8 @@ func (l *LimaHcsDriver) DeleteSnapshot(_ context.Context, _ string) error {
return errUnimplemented
}
func (l *LimaHcsDriver) ListSnapshots(_ context.Context) (string, error) {
return "", errUnimplemented
func (l *LimaHcsDriver) ListSnapshots(_ context.Context) ([]driver.Snapshot, error) {
return nil, errUnimplemented
}
func (l *LimaHcsDriver) ForwardGuestAgent(_ context.Context) bool {
@@ -397,8 +397,8 @@ func (l *LimaKrunkitDriver) DeleteSnapshot(_ context.Context, _ string) error {
return errUnimplemented
}
func (l *LimaKrunkitDriver) ListSnapshots(_ context.Context) (string, error) {
return "", errUnimplemented
func (l *LimaKrunkitDriver) ListSnapshots(_ context.Context) ([]driver.Snapshot, error) {
return nil, errUnimplemented
}
func (l *LimaKrunkitDriver) Register(_ context.Context) error {
+73 -20
View File
@@ -25,9 +25,12 @@ import (
"github.com/digitalocean/go-qemu/qmp"
"github.com/digitalocean/go-qemu/qmp/raw"
"github.com/docker/go-units"
"github.com/lima-vm/go-qcow2reader"
"github.com/lima-vm/go-qcow2reader/image"
"github.com/mattn/go-shellwords"
"github.com/sirupsen/logrus"
"github.com/lima-vm/lima/v2/pkg/driver"
"github.com/lima-vm/lima/v2/pkg/fileutils"
"github.com/lima-vm/lima/v2/pkg/iso9660util"
"github.com/lima-vm/lima/v2/pkg/limatype"
@@ -180,7 +183,11 @@ func execImgCommand(ctx context.Context, cfg Config, args ...string) (string, er
return string(b), err
}
func Del(ctx context.Context, cfg Config, run bool, tag string) error {
func Del(ctx context.Context, cfg Config, run bool, id string) error {
tag, err := snapshotTagForID(ctx, cfg, run, id)
if err != nil {
return err
}
if run {
out, err := sendHmpCommand(cfg, "delvm", tag)
// there can still be output, even if no error!
@@ -190,7 +197,7 @@ func Del(ctx context.Context, cfg Config, run bool, tag string) error {
return err
}
// -d deletes a snapshot
_, err := execImgCommand(ctx, cfg, "snapshot", "-d", tag)
_, err = execImgCommand(ctx, cfg, "snapshot", "-d", tag)
return err
}
@@ -208,7 +215,11 @@ func Save(ctx context.Context, cfg Config, run bool, tag string) error {
return err
}
func Load(ctx context.Context, cfg Config, run bool, tag string) error {
func Load(ctx context.Context, cfg Config, run bool, id string) error {
tag, err := snapshotTagForID(ctx, cfg, run, id)
if err != nil {
return err
}
if run {
out, err := sendHmpCommand(cfg, "loadvm", tag)
// there can still be output, even if no error!
@@ -218,29 +229,71 @@ func Load(ctx context.Context, cfg Config, run bool, tag string) error {
return err
}
// -a applies a snapshot
_, err := execImgCommand(ctx, cfg, "snapshot", "-a", tag)
_, err = execImgCommand(ctx, cfg, "snapshot", "-a", tag)
return err
}
// List returns a space-separated list of all snapshots, with header and newlines.
func List(ctx context.Context, cfg Config, run bool) (string, error) {
if run {
out, err := sendHmpCommand(cfg, "info", "snapshots")
if err == nil {
out = strings.ReplaceAll(out, "\r", "")
out = strings.Replace(out, "List of snapshots present on all disks:\n", "", 1)
out = strings.Replace(out, "There is no snapshot available.\n", "", 1)
// List returns all snapshots stored in the instance disk.
func List(_ context.Context, cfg Config, _ bool) ([]driver.Snapshot, error) {
diskPath := filepath.Join(cfg.InstanceDir, filenames.Disk)
disk, err := os.Open(diskPath)
if err != nil {
return nil, err
}
defer disk.Close()
img, err := qcow2reader.Open(disk)
if err != nil {
return nil, err
}
defer img.Close()
snapshotReader, ok := img.(image.SnapshotReader)
if !ok {
return nil, fmt.Errorf("image type %q does not support snapshots", img.Type())
}
imageSnapshots, err := snapshotReader.Snapshots()
if err != nil {
return nil, err
}
snapshots := make([]driver.Snapshot, len(imageSnapshots))
for i, snapshot := range imageSnapshots {
snapshots[i] = driver.Snapshot{
ID: snapshot.ID,
Tag: snapshot.Name,
CreatedAt: snapshot.CreatedAt,
}
return out, err
}
// -l lists all snapshots
args := []string{"snapshot", "-l"}
out, err := execImgCommand(ctx, cfg, args...)
if err == nil {
// remove the redundant heading, result is not machine-parseable
out = strings.Replace(out, "Snapshot list:\n", "", 1)
return snapshots, nil
}
func snapshotTagForID(ctx context.Context, cfg Config, run bool, id string) (string, error) {
snapshots, err := List(ctx, cfg, run)
if err != nil {
return "", err
}
return out, err
return findSnapshotTagByID(snapshots, id)
}
func findSnapshotTagByID(snapshots []driver.Snapshot, id string) (string, error) {
var tag string
for _, snapshot := range snapshots {
if snapshot.ID == id {
tag = snapshot.Tag
break
}
}
if tag == "" {
return "", fmt.Errorf("snapshot ID %q not found", id)
}
for _, snapshot := range snapshots {
if snapshot.ID != id && snapshot.Tag == tag {
return "", fmt.Errorf("snapshot ID %q has non-unique tag %q; QEMU cannot target it unambiguously", id, tag)
}
}
return tag, nil
}
func argValue(args []string, key string) (string, bool) {
+5 -5
View File
@@ -796,13 +796,13 @@ func logPipeRoutine(r io.Reader, header string) {
}
}
func (l *LimaQemuDriver) DeleteSnapshot(ctx context.Context, tag string) error {
func (l *LimaQemuDriver) DeleteSnapshot(ctx context.Context, id string) error {
qCfg := Config{
Name: l.Instance.Name,
InstanceDir: l.Instance.Dir,
LimaYAML: l.Instance.Config,
}
return Del(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, tag)
return Del(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, id)
}
func (l *LimaQemuDriver) CreateSnapshot(ctx context.Context, tag string) error {
@@ -814,16 +814,16 @@ func (l *LimaQemuDriver) CreateSnapshot(ctx context.Context, tag string) error {
return Save(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, tag)
}
func (l *LimaQemuDriver) ApplySnapshot(ctx context.Context, tag string) error {
func (l *LimaQemuDriver) ApplySnapshot(ctx context.Context, id string) error {
qCfg := Config{
Name: l.Instance.Name,
InstanceDir: l.Instance.Dir,
LimaYAML: l.Instance.Config,
}
return Load(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, tag)
return Load(ctx, qCfg, l.Instance.Status == limatype.StatusRunning, id)
}
func (l *LimaQemuDriver) ListSnapshots(ctx context.Context) (string, error) {
func (l *LimaQemuDriver) ListSnapshots(ctx context.Context) ([]driver.Snapshot, error) {
qCfg := Config{
Name: l.Instance.Name,
InstanceDir: l.Instance.Dir,
+19
View File
@@ -12,11 +12,30 @@ import (
"github.com/coreos/go-semver/semver"
"gotest.tools/v3/assert"
"github.com/lima-vm/lima/v2/pkg/driver"
"github.com/lima-vm/lima/v2/pkg/limatype"
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
"github.com/lima-vm/lima/v2/pkg/osutil"
)
func TestFindSnapshotTagByID(t *testing.T) {
snapshots := []driver.Snapshot{
{ID: "1", Tag: "initial"},
{ID: "2", Tag: "duplicate"},
{ID: "3", Tag: "duplicate"},
}
tag, err := findSnapshotTagByID(snapshots, "1")
assert.NilError(t, err)
assert.Equal(t, tag, "initial")
_, err = findSnapshotTagByID(snapshots, "2")
assert.ErrorContains(t, err, `snapshot ID "2" has non-unique tag "duplicate"`)
_, err = findSnapshotTagByID(snapshots, "missing")
assert.ErrorContains(t, err, `snapshot ID "missing" not found`)
}
func TestArgValue(t *testing.T) {
type testCase struct {
key string
+2 -2
View File
@@ -663,8 +663,8 @@ func (l *LimaVzDriver) DeleteSnapshot(_ context.Context, _ string) error {
return errUnimplemented
}
func (l *LimaVzDriver) ListSnapshots(_ context.Context) (string, error) {
return "", errUnimplemented
func (l *LimaVzDriver) ListSnapshots(_ context.Context) ([]driver.Snapshot, error) {
return nil, errUnimplemented
}
func (l *LimaVzDriver) ForwardGuestAgent(_ context.Context) bool {
+2 -2
View File
@@ -383,8 +383,8 @@ func (l *LimaWslDriver) DeleteSnapshot(_ context.Context, _ string) error {
return errUnimplemented
}
func (l *LimaWslDriver) ListSnapshots(_ context.Context) (string, error) {
return "", errUnimplemented
func (l *LimaWslDriver) ListSnapshots(_ context.Context) ([]driver.Snapshot, error) {
return nil, errUnimplemented
}
func (l *LimaWslDriver) ForwardGuestAgent(_ context.Context) bool {
+1 -1
View File
@@ -39,7 +39,7 @@ func (m *mockDriver) DisplayConnection(_ context.Context) (string, error)
func (m *mockDriver) CreateSnapshot(_ context.Context, _ string) error { return nil }
func (m *mockDriver) ApplySnapshot(_ context.Context, _ string) error { return nil }
func (m *mockDriver) DeleteSnapshot(_ context.Context, _ string) error { return nil }
func (m *mockDriver) ListSnapshots(_ context.Context) (string, error) { return "", nil }
func (m *mockDriver) ListSnapshots(_ context.Context) ([]driver.Snapshot, error) { return nil, nil }
func (m *mockDriver) Register(_ context.Context) error { return nil }
func (m *mockDriver) Unregister(_ context.Context) error { return nil }
func (m *mockDriver) ForwardGuestAgent(_ context.Context) bool { return false }
+6 -6
View File
@@ -13,13 +13,13 @@ import (
"github.com/lima-vm/lima/v2/pkg/limayaml"
)
func Del(ctx context.Context, inst *limatype.Instance, tag string) error {
func Del(ctx context.Context, inst *limatype.Instance, id string) error {
limaDriver, err := createConfiguredDriver(ctx, inst)
if err != nil {
return fmt.Errorf("failed to create driver instance: %w", err)
}
return limaDriver.DeleteSnapshot(ctx, tag)
return limaDriver.DeleteSnapshot(ctx, id)
}
func Save(ctx context.Context, inst *limatype.Instance, tag string) error {
@@ -30,18 +30,18 @@ func Save(ctx context.Context, inst *limatype.Instance, tag string) error {
return limaDriver.CreateSnapshot(ctx, tag)
}
func Load(ctx context.Context, inst *limatype.Instance, tag string) error {
func Load(ctx context.Context, inst *limatype.Instance, id string) error {
limaDriver, err := createConfiguredDriver(ctx, inst)
if err != nil {
return fmt.Errorf("failed to create driver instance: %w", err)
}
return limaDriver.ApplySnapshot(ctx, tag)
return limaDriver.ApplySnapshot(ctx, id)
}
func List(ctx context.Context, inst *limatype.Instance) (string, error) {
func List(ctx context.Context, inst *limatype.Instance) ([]driver.Snapshot, error) {
limaDriver, err := createConfiguredDriver(ctx, inst)
if err != nil {
return "", fmt.Errorf("failed to create driver instance: %w", err)
return nil, fmt.Errorf("failed to create driver instance: %w", err)
}
return limaDriver.ListSnapshots(ctx)
}
@@ -7,6 +7,9 @@ weight: 20
- The experimental `_LIMA_WINDOWS_EXTRA_PATH` environment variable was removed. It prepended directories to
`limactl.exe`'s own PATH on Windows hosts, leaving the calling shell's PATH untouched. Put them on `PATH`
instead.
- The `SnapshotManager.ListSnapshots()` method now returns structured snapshot metadata instead of raw text.
`ApplySnapshot()` and `DeleteSnapshot()` now identify snapshots by ID instead of tag. External drivers must
update their snapshot implementations and regenerate their protobuf bindings.
## v2.2.0
- The default [`socket_vmnet` group](../config/network/vmnet.md) in `networks.yaml` was changed from `everyone` to