diff --git a/cmd/limactl/edit.go b/cmd/limactl/edit.go index 3d0dde19935..e8ef2d14298 100644 --- a/cmd/limactl/edit.go +++ b/cmd/limactl/edit.go @@ -10,12 +10,9 @@ import ( "github.com/lima-vm/lima/cmd/limactl/editflags" "github.com/lima-vm/lima/cmd/limactl/guessarg" "github.com/lima-vm/lima/pkg/editutil" - "github.com/lima-vm/lima/pkg/instance" "github.com/lima-vm/lima/pkg/limayaml" - networks "github.com/lima-vm/lima/pkg/networks/reconcile" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" - "github.com/lima-vm/lima/pkg/uiutil" "github.com/lima-vm/lima/pkg/yqutil" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -132,34 +129,14 @@ func editAction(cmd *cobra.Command, args []string) error { } if inst != nil { logrus.Infof("Instance %q configuration edited", inst.Name) - } - - if !tty { - // use "start" to start it - return nil - } - if inst == nil { - // edited a limayaml file directly - return nil - } - startNow, err := askWhetherToStart() - if err != nil { - return err - } - if !startNow { - return nil - } - ctx := cmd.Context() - err = networks.Reconcile(ctx, inst.Name) - if err != nil { + tty, err := interactive(cmd) + if tty && err == nil { + err = askToStart(cmd, inst.Name, false) + } return err } - return instance.Start(ctx, inst, "", false) -} - -func askWhetherToStart() (bool, error) { - message := "Do you want to start the instance now? " - return uiutil.Confirm(message, true) + // inst is nil if edited a limayaml file directly + return nil } func editBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { diff --git a/cmd/limactl/shell.go b/cmd/limactl/shell.go index 65d8fad7b29..473d83b96b7 100644 --- a/cmd/limactl/shell.go +++ b/cmd/limactl/shell.go @@ -51,6 +51,7 @@ func newShellCommand() *cobra.Command { } func shellAction(cmd *cobra.Command, args []string) error { + _ = os.Setenv("_LIMACTL_SHELL_IN_ACTION", "") // simulate the behavior of double dash newArg := []string{} if len(args) >= 2 && args[1] == "--" { @@ -68,15 +69,36 @@ func shellAction(cmd *cobra.Command, args []string) error { } } + tty, err := interactive(cmd) + if err != nil { + return err + } inst, err := store.Inspect(instName) if err != nil { - if errors.Is(err, os.ErrNotExist) { + if !errors.Is(err, os.ErrNotExist) { + return err + } + if !tty { return fmt.Errorf("instance %q does not exist, run `limactl create %s` to create a new instance", instName, instName) } + if err := askToStart(cmd, instName, true); err != nil { + return err + } + inst, err = store.Inspect(instName) + } else if inst.Status == store.StatusStopped { + if !tty { + return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName) + } + if err := askToStart(cmd, instName, false); err != nil { + return err + } + inst, err = store.Inspect(instName) + } + if err != nil { return err } - if inst.Status == store.StatusStopped { - return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName) + if inst.Status != store.StatusRunning { + return fmt.Errorf("instance %q status is not %q but %q", inst.Name, store.StatusRunning, inst.Status) } // When workDir is explicitly set, the shell MUST have workDir as the cwd, or exit with an error. diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go index 90f329c59b3..a0e1a9cf527 100644 --- a/cmd/limactl/start.go +++ b/cmd/limactl/start.go @@ -23,6 +23,7 @@ import ( "github.com/lima-vm/lima/pkg/templatestore" "github.com/lima-vm/lima/pkg/uiutil" "github.com/lima-vm/lima/pkg/yqutil" + "github.com/mattn/go-isatty" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -529,3 +530,55 @@ func startBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobr compTmpl, _ := bashCompleteTemplateNames(cmd) return append(compInst, compTmpl...), cobra.ShellCompDirectiveDefault } + +// interactive returns true if --tty is true and both STDIN and STDOUT are terminals. +func interactive(cmd *cobra.Command) (bool, error) { + flags := cmd.Flags() + tty, err := flags.GetBool("tty") + if err != nil { + return false, err + } + if !isatty.IsTerminal(os.Stdin.Fd()) && !isatty.IsCygwinTerminal(os.Stdin.Fd()) { + tty = false + } + if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) { + tty = false + } + return tty, nil +} + +func askToStart(cmd *cobra.Command, instName string, create bool) error { + template := "default" + templates, err := templatestore.Templates() + if err != nil { + return err + } + for _, t := range templates { + if t.Name == instName { + template = instName + break + } + } + var message string + if create { + message = fmt.Sprintf("Do you want to create and start the instance %q using the %q template now?", instName, template) + } else { + message = fmt.Sprintf("Do you want to start the instance %q now?", instName) + } + ans, err := uiutil.Confirm(message, true) + if !ans || err != nil { + return err + } + + rootCmd := cmd.Root() + if create { + // The create command shows the template chooser UI, etc. + rootCmd.SetArgs([]string{"create", "template://" + template}) + if err := rootCmd.Execute(); err != nil { + return err + } + } + // The start command reconciles the networks, etc. + rootCmd.SetArgs([]string{"start", instName}) + return rootCmd.Execute() +} diff --git a/pkg/instance/start.go b/pkg/instance/start.go index f2b1c105b79..4ecbb3d614c 100644 --- a/pkg/instance/start.go +++ b/pkg/instance/start.go @@ -307,10 +307,17 @@ func watchHostAgentEvents(ctx context.Context, inst *store.Instance, haStdoutPat err = xerr return true } - if *inst.Config.Plain { - logrus.Infof("READY. Run `ssh -F %q %s` to open the shell.", inst.SSHConfigFile, inst.Hostname) + // _LIMACTL_SHELL_IN_ACTION is set if `limactl shell` invoked `limactl start`. + // In this case we shouldn't print "Run `lima` to open the shell", + // because the user has already executed the `lima` command. + if _, limactlShellInAction := os.LookupEnv("_LIMACTL_SHELL_IN_ACTION"); limactlShellInAction { + logrus.Infof("READY.") } else { - logrus.Infof("READY. Run `%s` to open the shell.", LimactlShellCmd(inst.Name)) + if *inst.Config.Plain { + logrus.Infof("READY. Run `ssh -F %q %s` to open the shell.", inst.SSHConfigFile, inst.Hostname) + } else { + logrus.Infof("READY. Run `%s` to open the shell.", LimactlShellCmd(inst.Name)) + } } _ = ShowMessage(inst) err = nil