Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/cmd/limactl/edit.go
2611 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package main
5
6
import (
7
"bytes"
8
"errors"
9
"fmt"
10
"os"
11
"path/filepath"
12
13
"github.com/sirupsen/logrus"
14
"github.com/spf13/cobra"
15
16
"github.com/lima-vm/lima/v2/cmd/limactl/editflags"
17
"github.com/lima-vm/lima/v2/pkg/autostart"
18
"github.com/lima-vm/lima/v2/pkg/driverutil"
19
"github.com/lima-vm/lima/v2/pkg/editutil"
20
"github.com/lima-vm/lima/v2/pkg/instance"
21
"github.com/lima-vm/lima/v2/pkg/limatype"
22
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
23
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
24
"github.com/lima-vm/lima/v2/pkg/limayaml"
25
"github.com/lima-vm/lima/v2/pkg/networks/reconcile"
26
"github.com/lima-vm/lima/v2/pkg/store"
27
"github.com/lima-vm/lima/v2/pkg/uiutil"
28
"github.com/lima-vm/lima/v2/pkg/yqutil"
29
)
30
31
func newEditCommand() *cobra.Command {
32
editCommand := &cobra.Command{
33
Use: "edit INSTANCE|FILE.yaml",
34
Short: "Edit an instance of Lima or a template",
35
Args: WrapArgsError(cobra.MaximumNArgs(1)),
36
RunE: editAction,
37
ValidArgsFunction: editBashComplete,
38
GroupID: basicCommand,
39
}
40
editCommand.Flags().Bool("start", false, "Start the instance after editing")
41
editflags.RegisterEdit(editCommand, "")
42
return editCommand
43
}
44
45
func editAction(cmd *cobra.Command, args []string) error {
46
ctx := cmd.Context()
47
var arg string
48
if len(args) > 0 {
49
arg = args[0]
50
}
51
52
var filePath string
53
var err error
54
var inst *limatype.Instance
55
56
if arg == "" {
57
arg = DefaultInstanceName
58
}
59
if err := dirnames.ValidateInstName(arg); err == nil {
60
inst, err = store.Inspect(ctx, arg)
61
if err != nil {
62
if errors.Is(err, os.ErrNotExist) {
63
return fmt.Errorf("instance %q not found", arg)
64
}
65
return err
66
}
67
if inst.Status == limatype.StatusRunning {
68
return errors.New("cannot edit a running instance")
69
}
70
filePath = filepath.Join(inst.Dir, filenames.LimaYAML)
71
} else {
72
// absolute path is required for `limayaml.Validate`
73
filePath, err = filepath.Abs(arg)
74
if err != nil {
75
return err
76
}
77
}
78
79
yContent, err := os.ReadFile(filePath)
80
if err != nil {
81
return err
82
}
83
flags := cmd.Flags()
84
tty, err := flags.GetBool("tty")
85
if err != nil {
86
return err
87
}
88
yqExprs, err := editflags.YQExpressions(flags, false)
89
if err != nil {
90
return err
91
}
92
var yBytes []byte
93
if len(yqExprs) > 0 {
94
yq := yqutil.Join(yqExprs)
95
yBytes, err = yqutil.EvaluateExpression(yq, yContent)
96
if err != nil {
97
return err
98
}
99
} else if tty {
100
var hdr string
101
if inst != nil {
102
hdr = fmt.Sprintf("# Please edit the following configuration for Lima instance %q\n", inst.Name)
103
} else {
104
hdr = fmt.Sprintf("# Please edit the following configuration %q\n", filePath)
105
}
106
hdr += "# and an empty file will abort the edit.\n"
107
hdr += "\n"
108
hdr += editutil.GenerateEditorWarningHeader()
109
yBytes, err = editutil.OpenEditor(ctx, yContent, hdr)
110
if err != nil {
111
return err
112
}
113
}
114
if len(yBytes) == 0 {
115
logrus.Info("Aborting, as requested by saving the file with empty content")
116
return nil
117
}
118
if bytes.Equal(yBytes, yContent) {
119
logrus.Info("Aborting, no changes made to the instance")
120
return nil
121
}
122
y, err := limayaml.LoadWithWarnings(ctx, yBytes, filePath)
123
if err != nil {
124
return err
125
}
126
if err := driverutil.ResolveVMType(ctx, y, filePath); err != nil {
127
return fmt.Errorf("failed to resolve vm for %q: %w", filePath, err)
128
}
129
if err := limayaml.Validate(y, true); err != nil {
130
return saveRejectedYAML(yBytes, err)
131
}
132
133
if err := limayaml.ValidateAgainstLatestConfig(ctx, yBytes, yContent); err != nil {
134
return saveRejectedYAML(yBytes, err)
135
}
136
137
if err := os.WriteFile(filePath, yBytes, 0o644); err != nil {
138
return err
139
}
140
141
if inst != nil {
142
logrus.Infof("Instance %q configuration edited", inst.Name)
143
}
144
145
if inst == nil {
146
// edited a limayaml file directly
147
return nil
148
}
149
150
start, err := flags.GetBool("start")
151
if err != nil {
152
return err
153
}
154
155
if tty && !flags.Changed("start") {
156
start, err = askWhetherToStart(cmd)
157
if err != nil {
158
return err
159
}
160
}
161
if !start {
162
return nil
163
}
164
// Network reconciliation will be performed by the process launched by the autostart manager
165
if registered, err := autostart.IsRegistered(ctx, inst); err != nil && !errors.Is(err, autostart.ErrNotSupported) {
166
return fmt.Errorf("failed to check if the autostart entry for instance %q is registered: %w", inst.Name, err)
167
} else if !registered {
168
err = reconcile.Reconcile(ctx, inst.Name)
169
if err != nil {
170
return err
171
}
172
}
173
174
// store.Inspect() syncs values between inst.YAML and the store.
175
// This call applies the validated template to the store.
176
inst, err = store.Inspect(ctx, inst.Name)
177
if err != nil {
178
return err
179
}
180
return instance.Start(ctx, inst, false, false)
181
}
182
183
func askWhetherToStart(cmd *cobra.Command) (bool, error) {
184
isTTY := uiutil.InputIsTTY(cmd.InOrStdin())
185
if isTTY {
186
message := "Do you want to start the instance now? "
187
return uiutil.Confirm(message, true)
188
}
189
return false, nil
190
}
191
192
func editBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
193
return bashCompleteInstanceNames(cmd)
194
}
195
196
// saveRejectedYAML writes the rejected config and returns an error.
197
func saveRejectedYAML(y []byte, origErr error) error {
198
rejectedYAML := "lima.REJECTED.yaml"
199
if writeErr := os.WriteFile(rejectedYAML, y, 0o644); writeErr != nil {
200
return fmt.Errorf("the YAML is invalid, attempted to save the buffer as %q but failed: %w", rejectedYAML, errors.Join(writeErr, origErr))
201
}
202
// TODO: may need to support editing the rejected YAML
203
return fmt.Errorf("the YAML is invalid, saved the buffer as %q: %w", rejectedYAML, origErr)
204
}
205
206