From 141d5291eebba6a022b2103f76a7af4f66d69704 Mon Sep 17 00:00:00 2001 From: somaz Date: Tue, 6 Oct 2026 16:38:53 +0900 Subject: [PATCH 1/2] cli: prepare the restart config the same way as start Signed-off-by: somaz --- cmd/restart.go | 30 ++++++++++++---- cmd/restart_test.go | 84 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 cmd/restart_test.go diff --git a/cmd/restart.go b/cmd/restart.go index 0c3de3fd9..2eaefe474 100644 --- a/cmd/restart.go +++ b/cmd/restart.go @@ -1,9 +1,11 @@ package cmd import ( + "fmt" "time" "github.com/abiosoft/colima/cmd/root" + "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/config/configmanager" "github.com/abiosoft/colima/environment/vm/lima/limautil" "github.com/spf13/cobra" @@ -28,6 +30,14 @@ should return it back to its previous state.`, return err } + conf, err := restartConfig() + if err != nil { + return err + } + if err := configmanager.ValidateConfig(conf); err != nil { + return fmt.Errorf("error in config: %w", err) + } + app := newApp() if err := app.Stop(restartCmdArgs.force); err != nil { @@ -37,15 +47,23 @@ should return it back to its previous state.`, // delay a bit before starting time.Sleep(time.Second * 3) - config, err := configmanager.Load() - if err != nil { - return err - } - - return app.Start(config) + return app.Start(conf) }, } +func restartConfig() (config.Config, error) { + current, err := configmanager.Load() + if err != nil { + return config.Config{}, err + } + // prepareConfig would ignore it and use plain defaults, which can grow the data disk + if current.Empty() { + return configmanager.LoadInstance() + } + prepareConfig(startCmd) + return startCmdArgs.Config, nil +} + func init() { root.Cmd().AddCommand(restartCmd) diff --git a/cmd/restart_test.go b/cmd/restart_test.go new file mode 100644 index 000000000..4706fc3bf --- /dev/null +++ b/cmd/restart_test.go @@ -0,0 +1,84 @@ +package cmd + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/abiosoft/colima/config" +) + +func TestMain(m *testing.M) { + // config dirs are cached on first use, so point them at a temp dir before any test runs + home, err := os.MkdirTemp("", "colima-test") + if err != nil { + panic(err) + } + _ = os.Setenv("COLIMA_HOME", home) + _ = os.Unsetenv("LIMA_HOME") + code := m.Run() + _ = os.RemoveAll(home) + os.Exit(code) +} + +func Test_restartConfig(t *testing.T) { + home := os.Getenv("COLIMA_HOME") + config.SetProfile("restart-test") + + file := config.CurrentProfile().File() + stateFile := config.CurrentProfile().StateFile() + if !strings.HasPrefix(file, home) || !strings.HasPrefix(stateFile, home) { + t.Fatalf("config files %s and %s are outside COLIMA_HOME %s", file, stateFile, home) + } + + write := func(t *testing.T, path, content string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.Remove(path) }) + } + + t.Run("partial config gets start defaults", func(t *testing.T) { + write(t, file, "cpu: 3\ndisk: 30\nruntime: containerd\n") + + conf, err := restartConfig() + if err != nil { + t.Fatalf("restartConfig() error = %v", err) + } + if conf.CPU != 3 || conf.Disk != 30 || conf.Runtime != "containerd" { + t.Errorf("restartConfig() did not keep file values: cpu=%d disk=%d runtime=%q", conf.CPU, conf.Disk, conf.Runtime) + } + if conf.RootDisk != defaultRootDisk { + t.Errorf("restartConfig() RootDisk = %d, want %d", conf.RootDisk, defaultRootDisk) + } + if conf.Binfmt == nil || !*conf.Binfmt { + t.Errorf("restartConfig() Binfmt = %v, want true", conf.Binfmt) + } + }) + + t.Run("missing config uses the instance state", func(t *testing.T) { + write(t, stateFile, "cpu: 4\ndisk: 60\nruntime: containerd\n") + + conf, err := restartConfig() + if err != nil { + t.Fatalf("restartConfig() error = %v", err) + } + if conf.CPU != 4 || conf.Disk != 60 || conf.Runtime != "containerd" { + t.Errorf("restartConfig() did not use the instance state: cpu=%d disk=%d runtime=%q", conf.CPU, conf.Disk, conf.Runtime) + } + }) + + t.Run("unreadable config is an error", func(t *testing.T) { + write(t, stateFile, "cpu: 4\ndisk: 60\nruntime: containerd\n") + write(t, file, "cpu: [\n") + + if _, err := restartConfig(); err == nil { + t.Error("restartConfig() error = nil, want an error") + } + }) +} From f0b8f31792465346d3ef7e4115cc1b4d4b23e590 Mon Sep 17 00:00:00 2001 From: somaz Date: Wed, 14 Oct 2026 10:48:10 +0900 Subject: [PATCH 2/2] fix: preserve saved custom diskImage on restart Signed-off-by: somaz --- cmd/restart.go | 3 +++ cmd/restart_test.go | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/cmd/restart.go b/cmd/restart.go index 2eaefe474..75661439c 100644 --- a/cmd/restart.go +++ b/cmd/restart.go @@ -61,6 +61,9 @@ func restartConfig() (config.Config, error) { return configmanager.LoadInstance() } prepareConfig(startCmd) + if startCmdArgs.DiskImage == "" && current.DiskImage != "" { + startCmdArgs.DiskImage = current.DiskImage + } return startCmdArgs.Config, nil } diff --git a/cmd/restart_test.go b/cmd/restart_test.go index 4706fc3bf..17b86207a 100644 --- a/cmd/restart_test.go +++ b/cmd/restart_test.go @@ -73,6 +73,18 @@ func Test_restartConfig(t *testing.T) { } }) + t.Run("preserves saved custom disk image", func(t *testing.T) { + write(t, file, "cpu: 2\ndisk: 20\nruntime: docker\ndiskImage: /path/to/custom.img\n") + + conf, err := restartConfig() + if err != nil { + t.Fatalf("restartConfig() error = %v", err) + } + if conf.DiskImage != "/path/to/custom.img" { + t.Errorf("restartConfig() DiskImage = %q, want %q", conf.DiskImage, "/path/to/custom.img") + } + }) + t.Run("unreadable config is an error", func(t *testing.T) { write(t, stateFile, "cpu: 4\ndisk: 60\nruntime: containerd\n") write(t, file, "cpu: [\n")