mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 11:44:35 +08:00
driver: return structured snapshot metadata
Signed-off-by: olamilekan000 <odukoyaonline@gmail.com>
This commit is contained in:
1 parent
2eceab6c93
commit
5b5c0aeb28
19 files changed
+537
-187
No files matched your search
+109
-31
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Vendored
+14
-9
@@ -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:
|
||||
|
||||
Vendored
+151
-78
@@ -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,
|
||||
},
|
||||
|
||||
Vendored
+18
-7
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user