Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/cmd/limactl/main.go
2621 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package main
5
6
import (
7
"errors"
8
"fmt"
9
"os"
10
"path/filepath"
11
"runtime"
12
"strings"
13
14
"github.com/mattn/go-isatty"
15
"github.com/sirupsen/logrus"
16
"github.com/spf13/cobra"
17
18
"github.com/lima-vm/lima/v2/cmd/yq"
19
"github.com/lima-vm/lima/v2/pkg/debugutil"
20
"github.com/lima-vm/lima/v2/pkg/driver/external/server"
21
"github.com/lima-vm/lima/v2/pkg/fsutil"
22
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
23
"github.com/lima-vm/lima/v2/pkg/osutil"
24
"github.com/lima-vm/lima/v2/pkg/plugins"
25
"github.com/lima-vm/lima/v2/pkg/version"
26
)
27
28
const (
29
DefaultInstanceName = "default"
30
basicCommand = "basic"
31
advancedCommand = "advanced"
32
)
33
34
func main() {
35
if os.Geteuid() == 0 && (len(os.Args) < 2 || os.Args[1] != "generate-doc") {
36
fmt.Fprint(os.Stderr, "limactl: must not run as the root user\n")
37
os.Exit(1)
38
}
39
40
yq.MaybeRunYQ()
41
if runtime.GOOS == "windows" {
42
extras, hasExtra := os.LookupEnv("_LIMA_WINDOWS_EXTRA_PATH")
43
if hasExtra && strings.TrimSpace(extras) != "" {
44
p := os.Getenv("PATH")
45
err := os.Setenv("PATH", strings.TrimSpace(extras)+string(filepath.ListSeparator)+p)
46
if err != nil {
47
logrus.Warning("Can't add extras to PATH, relying entirely on system PATH")
48
}
49
}
50
}
51
err := newApp().Execute()
52
server.StopAllExternalDrivers()
53
osutil.HandleExitError(err)
54
if err != nil {
55
logrus.Fatal(err)
56
}
57
}
58
59
func processGlobalFlags(rootCmd *cobra.Command) error {
60
// --log-level will override --debug, but will not reset debugutil.Debug
61
if debug, _ := rootCmd.Flags().GetBool("debug"); debug {
62
logrus.SetLevel(logrus.DebugLevel)
63
debugutil.Debug = true
64
}
65
66
l, _ := rootCmd.Flags().GetString("log-level")
67
if l != "" {
68
lvl, err := logrus.ParseLevel(l)
69
if err != nil {
70
return err
71
}
72
logrus.SetLevel(lvl)
73
}
74
75
logFormat, _ := rootCmd.Flags().GetString("log-format")
76
switch logFormat {
77
case "json":
78
formatter := new(logrus.JSONFormatter)
79
logrus.StandardLogger().SetFormatter(formatter)
80
case "text":
81
// logrus use text format by default.
82
if runtime.GOOS == "windows" && isatty.IsCygwinTerminal(os.Stderr.Fd()) {
83
formatter := new(logrus.TextFormatter)
84
// the default setting does not recognize cygwin on windows
85
formatter.ForceColors = true
86
logrus.StandardLogger().SetFormatter(formatter)
87
}
88
default:
89
return fmt.Errorf("unsupported log-format: %q", logFormat)
90
}
91
return nil
92
}
93
94
func newApp() *cobra.Command {
95
templatesDir := "$PREFIX/share/lima/templates"
96
if exe, err := os.Executable(); err == nil {
97
binDir := filepath.Dir(exe)
98
prefixDir := filepath.Dir(binDir)
99
templatesDir = filepath.Join(prefixDir, "share/lima/templates")
100
}
101
102
rootCmd := &cobra.Command{
103
Use: "limactl",
104
Short: "Lima: Linux virtual machines",
105
Version: strings.TrimPrefix(version.Version, "v"),
106
Example: fmt.Sprintf(` Start the default instance:
107
$ limactl start
108
109
Open a shell:
110
$ lima
111
112
Run a container:
113
$ lima nerdctl run -d --name nginx -p 8080:80 nginx:alpine
114
115
Stop the default instance:
116
$ limactl stop
117
118
See also template YAMLs: %s`, templatesDir),
119
SilenceUsage: true,
120
SilenceErrors: true,
121
DisableAutoGenTag: true,
122
}
123
rootCmd.PersistentFlags().String("log-level", "", "Set the logging level [trace, debug, info, warn, error]")
124
rootCmd.PersistentFlags().String("log-format", "text", "Set the logging format [text, json]")
125
rootCmd.PersistentFlags().Bool("debug", false, "Debug mode")
126
// TODO: "survey" does not support using cygwin terminal on windows yet
127
rootCmd.PersistentFlags().Bool("tty", isatty.IsTerminal(os.Stdout.Fd()), "Enable TUI interactions such as opening an editor. Defaults to true when stdout is a terminal. Set to false for automation.")
128
rootCmd.PersistentFlags().BoolP("yes", "y", false, "Alias of --tty=false")
129
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
130
if err := processGlobalFlags(rootCmd); err != nil {
131
return err
132
}
133
134
if osutil.IsBeingRosettaTranslated() && cmd.Parent().Name() != "completion" && cmd.Name() != "generate-doc" && cmd.Name() != "validate" {
135
// running under rosetta would provide inappropriate runtime.GOARCH info, see: https://github.com/lima-vm/lima/issues/543
136
// allow commands that are used for packaging to run under rosetta to allow cross-architecture builds
137
return errors.New("limactl is running under rosetta, please reinstall lima with native arch")
138
}
139
140
// Make sure either $HOME or $LIMA_HOME is defined, so we don't need
141
// to check for errors later
142
dir, err := dirnames.LimaDir()
143
if err != nil {
144
return err
145
}
146
nfs, err := fsutil.IsNFS(dir)
147
if err != nil {
148
return err
149
}
150
if nfs {
151
return errors.New("must not run on NFS dir")
152
}
153
154
if cmd.Flags().Changed("yes") && cmd.Flags().Changed("tty") {
155
return errors.New("cannot use both --tty and --yes flags at the same time")
156
}
157
158
if cmd.Flags().Changed("yes") {
159
switch cmd.Name() {
160
case "clone", "edit", "rename":
161
logrus.Warn("--yes flag is deprecated (--tty=false is still supported and works in the same way. Also consider using --start)")
162
}
163
164
// Sets the value of the yesValue flag by using the yes flag.
165
yesValue, _ := cmd.Flags().GetBool("yes")
166
if yesValue {
167
// Sets to the default value false
168
err := cmd.Flags().Set("tty", "false")
169
if err != nil {
170
return err
171
}
172
}
173
}
174
return nil
175
}
176
rootCmd.AddGroup(&cobra.Group{ID: "basic", Title: "Basic Commands:"})
177
rootCmd.AddGroup(&cobra.Group{ID: "advanced", Title: "Advanced Commands:"})
178
rootCmd.AddGroup(&cobra.Group{ID: "plugin", Title: "Available Plugins (Experimental):"})
179
180
rootCmd.AddCommand(
181
newCreateCommand(),
182
newStartCommand(),
183
newStopCommand(),
184
newShellCommand(),
185
newCopyCommand(),
186
newListCommand(),
187
newDeleteCommand(),
188
newValidateCommand(),
189
newPruneCommand(),
190
newHostagentCommand(),
191
newGuestInstallCommand(),
192
newInfoCommand(),
193
newShowSSHCommand(),
194
newDebugCommand(),
195
newEditCommand(),
196
newFactoryResetCommand(),
197
newDiskCommand(),
198
newUsernetCommand(),
199
newGenDocCommand(),
200
newGenSchemaCommand(),
201
newSnapshotCommand(),
202
newProtectCommand(),
203
newUnprotectCommand(),
204
newTunnelCommand(),
205
newTemplateCommand(),
206
newRestartCommand(),
207
newSudoersCommand(),
208
newStartAtLoginCommand(),
209
newNetworkCommand(),
210
newCloneCommand(),
211
newRenameCommand(),
212
newWatchCommand(),
213
)
214
addPluginCommands(rootCmd)
215
216
return rootCmd
217
}
218
219
func addPluginCommands(rootCmd *cobra.Command) {
220
// The global options are only processed when rootCmd.Execute() is called.
221
// Let's take a sneak peek here to help debug the plugin discovery code.
222
if len(os.Args) > 1 && os.Args[1] == "--debug" {
223
logrus.SetLevel(logrus.DebugLevel)
224
}
225
226
allPlugins, err := plugins.Discover()
227
if err != nil {
228
logrus.Warnf("Failed to discover plugins: %v", err)
229
return
230
}
231
232
for _, plugin := range allPlugins {
233
pluginCmd := &cobra.Command{
234
Use: plugin.Name,
235
Short: plugin.Description,
236
GroupID: "plugin",
237
DisableFlagParsing: true,
238
SilenceErrors: true,
239
SilenceUsage: true,
240
PreRunE: func(*cobra.Command, []string) error {
241
for i, arg := range os.Args {
242
if arg == plugin.Name {
243
// parse global options but ignore plugin options
244
err := rootCmd.ParseFlags(os.Args[1:i])
245
if err == nil {
246
err = processGlobalFlags(rootCmd)
247
}
248
return err
249
}
250
}
251
// unreachable
252
return nil
253
},
254
Run: func(cmd *cobra.Command, _ []string) {
255
for i, arg := range os.Args {
256
if arg == plugin.Name {
257
// ignore global options
258
plugin.Run(cmd.Context(), os.Args[i+1:])
259
// plugin.Run() never returns because it calls os.Exit()
260
}
261
}
262
// unreachable
263
},
264
}
265
// Don't show the url scheme helper in the help output.
266
if strings.HasPrefix(plugin.Name, "url-") {
267
pluginCmd.Hidden = true
268
}
269
rootCmd.AddCommand(pluginCmd)
270
}
271
}
272
273
// WrapArgsError annotates cobra args error with some context, so the error message is more user-friendly.
274
func WrapArgsError(argFn cobra.PositionalArgs) cobra.PositionalArgs {
275
return func(cmd *cobra.Command, args []string) error {
276
err := argFn(cmd, args)
277
if err == nil {
278
return nil
279
}
280
281
return fmt.Errorf("%q %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
282
cmd.CommandPath(), err.Error(),
283
cmd.CommandPath(),
284
cmd.UseLine(), cmd.Short,
285
)
286
}
287
}
288
289