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