diff --git a/app/app.go b/app/app.go index 16cdbbda..de9ecf04 100644 --- a/app/app.go +++ b/app/app.go @@ -252,7 +252,7 @@ func (c colimaApp) SSH(args ...string) error { // peek the current directory to see if it is mounted to prevent `cd` errors // with limactl ssh if err := func() error { - conf, err := limautil.InstanceConfig() + conf, err := configmanager.LoadInstance() if err != nil { return err } @@ -303,7 +303,7 @@ func (c colimaApp) Status(extended bool) error { } driver := "QEMU" - conf, _ := limautil.InstanceConfig() + conf, _ := configmanager.LoadInstance() if !conf.Empty() { driver = conf.DriverLabel() } diff --git a/cmd/start.go b/cmd/start.go index 1ce56d2b..0c2d3d9f 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -20,6 +20,7 @@ import ( "github.com/abiosoft/colima/environment/container/docker" "github.com/abiosoft/colima/environment/container/kubernetes" "github.com/abiosoft/colima/util" + "github.com/abiosoft/colima/util/osutil" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -58,16 +59,11 @@ Run 'colima template' to set the default configurations or 'colima start --edit' } // edit flag is specified - err := editConfigFile() + conf, err := editConfigFile() if err != nil { return err } - conf, err = configmanager.Load() - if err != nil { - return fmt.Errorf("error opening config file: %w", err) - } - // validate config if err := configmanager.ValidateConfig(conf); err != nil { return fmt.Errorf("error in config file: %w", err) @@ -100,9 +96,11 @@ Run 'colima template' to set the default configurations or 'colima start --edit' return fmt.Errorf("error in config: %w", err) } - // persist in preparing of application start - if err := configmanager.Save(startCmdArgs.Config); err != nil { - return fmt.Errorf("error preparing config file: %w", err) + // persist in preparation for application start + if startCmdArgs.Flags.SaveConfig { + if err := configmanager.Save(startCmdArgs.Config); err != nil { + return fmt.Errorf("error preparing config file: %w", err) + } } return nil @@ -121,6 +119,7 @@ const ( ) var defaultK3sArgs = []string{"--disable=traefik"} +var envSaveConfig = osutil.EnvVar("COLIMA_SAVE_CONFIG") var startCmdArgs struct { config.Config @@ -134,6 +133,7 @@ var startCmdArgs struct { ActivateRuntime bool DNSHosts []string Foreground bool + SaveConfig bool } } @@ -144,6 +144,11 @@ func init() { mounts := strings.Join([]string{defaultMountTypeQEMU, "9p", "virtiofs"}, ", ") types := strings.Join([]string{defaultVMType, "vz"}, ", ") + saveConfigDefault := true + if envSaveConfig.Exists() { + saveConfigDefault = envSaveConfig.Bool() + } + root.Cmd().AddCommand(startCmd) startCmd.Flags().StringVarP(&startCmdArgs.Runtime, "runtime", "r", docker.Name, "container runtime ("+runtimes+")") startCmd.Flags().BoolVar(&startCmdArgs.Flags.ActivateRuntime, "activate", true, "set as active Docker/Kubernetes context on startup") @@ -180,6 +185,7 @@ func init() { // config startCmd.Flags().BoolVarP(&startCmdArgs.Flags.Edit, "edit", "e", false, "edit the configuration file before starting") startCmd.Flags().StringVar(&startCmdArgs.Flags.Editor, "editor", "", `editor to use for edit e.g. vim, nano, code (default "$EDITOR" env var)`) + startCmd.Flags().BoolVar(&startCmdArgs.Flags.SaveConfig, "save-config", saveConfigDefault, "persist and overwrite config file with (newly) specified flags") // mounts startCmd.Flags().StringSliceVarP(&startCmdArgs.Flags.Mounts, "mount", "V", nil, "directories to mount, suffix ':w' for writable") @@ -468,11 +474,13 @@ func prepareConfig(cmd *cobra.Command) { } // editConfigFile launches an editor to edit the config file. -func editConfigFile() error { +func editConfigFile() (config.Config, error) { + var c config.Config + // preserve the current file in case the user terminates currentFile, err := os.ReadFile(config.CurrentProfile().File()) if err != nil { - return fmt.Errorf("error reading config file: %w", err) + return c, fmt.Errorf("error reading config file: %w", err) } // prepend the config file with termination instruction @@ -483,18 +491,23 @@ func editConfigFile() error { tmpFile, err := waitForUserEdit(startCmdArgs.Flags.Editor, []byte(abort+"\n"+string(currentFile))) if err != nil { - return fmt.Errorf("error editing config file: %w", err) + return c, fmt.Errorf("error editing config file: %w", err) } // if file is empty, abort if tmpFile == "" { - return fmt.Errorf("empty file, startup aborted") + return c, fmt.Errorf("empty file, startup aborted") } defer func() { _ = os.Remove(tmpFile) }() - return configmanager.SaveFromFile(tmpFile) + if startCmdArgs.Flags.SaveConfig { + if err := configmanager.SaveFromFile(tmpFile); err != nil { + return c, err + } + } + return configmanager.LoadFrom(tmpFile) } func start(app app.App, conf config.Config) error { diff --git a/config/configmanager/configmanager.go b/config/configmanager/configmanager.go index af07c174..02edccbc 100644 --- a/config/configmanager/configmanager.go +++ b/config/configmanager/configmanager.go @@ -20,11 +20,11 @@ func Save(c config.Config) error { // SaveFromFile loads configuration from file and save as config. func SaveFromFile(file string) error { - cf, err := LoadFrom(file) + c, err := LoadFrom(file) if err != nil { return err } - return Save(cf) + return Save(c) } // SaveToFile saves configuration to file. @@ -79,24 +79,29 @@ func ValidateConfig(c config.Config) error { // Error is only returned if the config file exists but could not be loaded. // No error is returned if the config file does not exist. func Load() (config.Config, error) { - cFile := config.CurrentProfile().File() - if _, err := os.Stat(cFile); err != nil { - oldCFile := oldConfigFile() + f := config.CurrentProfile().File() + if _, err := os.Stat(f); err != nil { + oldF := oldConfigFile() // config file does not exist, check older version for backward compatibility - if _, err := os.Stat(oldCFile); err != nil { + if _, err := os.Stat(oldF); err != nil { return config.Config{}, nil } // older version exists logrus.Infof("settings from older %s version detected and copied", config.AppName) - if err := cli.Command("cp", oldCFile, cFile).Run(); err != nil { + if err := cli.Command("cp", oldF, f).Run(); err != nil { logrus.Warn(fmt.Errorf("error copying config: %w, proceeding with defaults", err)) return config.Config{}, nil } } - return LoadFrom(cFile) + return LoadFrom(f) +} + +// LoadInstance is like Load but returns the config of the currently running instance. +func LoadInstance() (config.Config, error) { + return LoadFrom(config.CurrentProfile().StateFile()) } // Teardown deletes the config. diff --git a/environment/vm/lima/lima.go b/environment/vm/lima/lima.go index 88314044..c4dc87b3 100644 --- a/environment/vm/lima/lima.go +++ b/environment/vm/lima/lima.go @@ -182,7 +182,7 @@ func (l limaVM) Stop(ctx context.Context, force bool) error { a.Stage("stopping") if util.MacOS() { - conf, _ := limautil.InstanceConfig() + conf, _ := configmanager.LoadInstance() a.Retry("", time.Second*1, 10, func(retryCount int) error { err := l.daemon.Stop(ctx, conf) if err != nil { @@ -208,7 +208,7 @@ func (l limaVM) Teardown(ctx context.Context) error { a := l.Init(ctx) if util.MacOS() { - conf, _ := limautil.InstanceConfig() + conf, _ := configmanager.LoadInstance() a.Retry("", time.Second*1, 10, func(retryCount int) error { return l.daemon.Stop(ctx, conf) }) @@ -300,7 +300,7 @@ func (l *limaVM) setDiskImage() error { func (l *limaVM) syncDiskSize(ctx context.Context, conf config.Config) config.Config { log := l.Logger(ctx) - instance, err := limautil.InstanceConfig() + instance, err := configmanager.LoadInstance() if err != nil { // instance config missing, ignore return conf diff --git a/environment/vm/lima/limautil/instance.go b/environment/vm/lima/limautil/instance.go index 1bc90675..99cf1ebe 100644 --- a/environment/vm/lima/limautil/instance.go +++ b/environment/vm/lima/limautil/instance.go @@ -16,15 +16,6 @@ func Instance() (InstanceInfo, error) { return getInstance(config.CurrentProfile().ID) } -// InstanceConfig returns the current instance config. -func InstanceConfig() (config.Config, error) { - i, err := Instance() - if err != nil { - return config.Config{}, err - } - return i.Config() -} - // InstanceInfo is the information about a Lima instance type InstanceInfo struct { Name string `json:"name,omitempty"` diff --git a/environment/vm/lima/network.go b/environment/vm/lima/network.go index 8351252b..80b114e8 100644 --- a/environment/vm/lima/network.go +++ b/environment/vm/lima/network.go @@ -6,6 +6,7 @@ import ( "path/filepath" "github.com/abiosoft/colima/config" + "github.com/abiosoft/colima/config/configmanager" "github.com/abiosoft/colima/embedded" "github.com/abiosoft/colima/environment/vm/lima/limautil" "github.com/abiosoft/colima/util" @@ -39,7 +40,7 @@ func (l *limaVM) replicateHostAddresses(conf config.Config) error { } func (l *limaVM) removeHostAddresses() { - conf, _ := limautil.InstanceConfig() + conf, _ := configmanager.LoadInstance() if !conf.Network.Address && conf.Network.HostAddresses { for _, ip := range util.HostIPAddresses() { _ = l.RunQuiet("sudo", "ip", "address", "del", ip.String()+"/24", "dev", "lo") diff --git a/util/osutil/os.go b/util/osutil/os.go index 6f986e4c..220f7480 100644 --- a/util/osutil/os.go +++ b/util/osutil/os.go @@ -5,11 +5,32 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "github.com/sirupsen/logrus" ) +// EnvVar is environment variable +type EnvVar string + +// Exists checks if the environment variable has been set. +func (e EnvVar) Exists() bool { + _, ok := os.LookupEnv(string(e)) + return ok +} + +// Bool returns the environment variable value as boolean. +func (e EnvVar) Bool() bool { + ok, _ := strconv.ParseBool(e.Val()) + return ok +} + +// Bool returns the environment variable value. +func (e EnvVar) Val() string { + return os.Getenv(string(e)) +} + const EnvColimaBinary = "COLIMA_BINARY" // Executable returns the path name for the executable that started