From 22d7e5fbc86d5b8e3b27065a762800bc7960a0ff Mon Sep 17 00:00:00 2001 From: Abiola Ibrahim Date: Tue, 14 Nov 2023 07:57:21 +0100 Subject: [PATCH] net: revert gateway ip address to 192.168.5.2 (#860) * net: revert gateway IP to 192.168.5.2 * chore: minor refactor --- embedded/network/networks.yaml | 8 + environment/vm/lima/config.go | 49 ++++ environment/vm/lima/daemon.go | 148 ++++++++++ environment/vm/lima/dependencies.go | 102 +++++++ environment/vm/lima/lima.go | 350 +---------------------- environment/vm/lima/limautil/files.go | 28 ++ environment/vm/lima/limautil/limautil.go | 75 ----- environment/vm/lima/limautil/ssh.go | 72 +++++ environment/vm/lima/network.go | 26 ++ environment/vm/lima/shell.go | 83 ++++++ 10 files changed, 519 insertions(+), 422 deletions(-) create mode 100644 embedded/network/networks.yaml create mode 100644 environment/vm/lima/config.go create mode 100644 environment/vm/lima/daemon.go create mode 100644 environment/vm/lima/dependencies.go create mode 100644 environment/vm/lima/limautil/files.go create mode 100644 environment/vm/lima/limautil/ssh.go create mode 100644 environment/vm/lima/network.go create mode 100644 environment/vm/lima/shell.go diff --git a/embedded/network/networks.yaml b/embedded/network/networks.yaml new file mode 100644 index 000000000..abd37f1a2 --- /dev/null +++ b/embedded/network/networks.yaml @@ -0,0 +1,8 @@ +# DO NOT EDIT! +# This file would be replaced by Colima on startup. + +networks: + user-v2: + mode: user-v2 + gateway: 192.168.5.2 + netmask: 255.255.255.0 diff --git a/environment/vm/lima/config.go b/environment/vm/lima/config.go new file mode 100644 index 000000000..c7c055850 --- /dev/null +++ b/environment/vm/lima/config.go @@ -0,0 +1,49 @@ +package lima + +import ( + "encoding/json" + "fmt" + "path/filepath" +) + +const configFile = "/etc/colima/colima.json" + +func (l limaVM) getConf() map[string]string { + obj := map[string]string{} + b, err := l.Read(configFile) + if err != nil { + return obj + } + + // we do not care if it fails + _ = json.Unmarshal([]byte(b), &obj) + + return obj +} +func (l limaVM) Get(key string) string { + if val, ok := l.getConf()[key]; ok { + return val + } + + return "" +} + +func (l limaVM) Set(key, value string) error { + obj := l.getConf() + obj[key] = value + + b, err := json.Marshal(obj) + if err != nil { + return fmt.Errorf("error marshalling settings to json: %w", err) + } + + if err := l.Run("sudo", "mkdir", "-p", filepath.Dir(configFile)); err != nil { + return fmt.Errorf("error saving settings: %w", err) + } + + if err := l.Write(configFile, b); err != nil { + return fmt.Errorf("error saving settings: %w", err) + } + + return nil +} diff --git a/environment/vm/lima/daemon.go b/environment/vm/lima/daemon.go new file mode 100644 index 000000000..3c7ee72a8 --- /dev/null +++ b/environment/vm/lima/daemon.go @@ -0,0 +1,148 @@ +package lima + +import ( + "context" + "fmt" + "time" + + "github.com/abiosoft/colima/config" + "github.com/abiosoft/colima/daemon" + "github.com/abiosoft/colima/daemon/process/inotify" + "github.com/abiosoft/colima/daemon/process/vmnet" + "github.com/abiosoft/colima/util" +) + +func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.Context, error) { + isQEMU := conf.VMType == QEMU + isVZ := conf.VMType == VZ + + // limited to macOS (with Qemu driver) + // or vz with inotify enabled + if !util.MacOS() || (isVZ && !conf.MountINotify) { + return ctx, nil + } + + ctxKeyVmnet := daemon.CtxKey(vmnet.Name) + ctxKeyInotify := daemon.CtxKey(inotify.Name) + + // use a nested chain for convenience + a := l.Init(ctx) + log := l.Logger(ctx) + + networkInstalledKey := struct{ key string }{key: "network_installed"} + + // add inotify to daemon + if conf.MountINotify { + a.Add(func() error { + ctx = context.WithValue(ctx, ctxKeyInotify, true) + deps, _ := l.daemon.Dependencies(ctx, conf) + if err := deps.Install(l.host); err != nil { + return fmt.Errorf("error setting up inotify dependencies: %w", err) + } + return nil + }) + } + + // add network processes to daemon + if isQEMU { + a.Stage("preparing network") + a.Add(func() error { + if conf.Network.Address { + ctx = context.WithValue(ctx, ctxKeyVmnet, true) + } + deps, root := l.daemon.Dependencies(ctx, conf) + if deps.Installed() { + ctx = context.WithValue(ctx, networkInstalledKey, true) + return nil + } + + // if user interaction is not required (i.e. root), + // no need for another verbose info. + if root { + log.Println("dependencies missing for setting up reachable IP address") + log.Println("sudo password may be required") + } + + // install deps + err := deps.Install(l.host) + if err != nil { + ctx = context.WithValue(ctx, networkInstalledKey, false) + } + return err + }) + } + + // start daemon + a.Add(func() error { + return l.daemon.Start(ctx, conf) + }) + + statusKey := struct{ key string }{key: "daemonStatus"} + // delay to ensure that the processes have started + if conf.Network.Address || conf.MountINotify { + a.Retry("", time.Second*1, 15, func(i int) error { + s, err := l.daemon.Running(ctx, conf) + ctx = context.WithValue(ctx, statusKey, s) + if err != nil { + return err + } + if !s.Running { + return fmt.Errorf("daemon is not running") + } + for _, p := range s.Processes { + if !p.Running { + return p.Error + } + } + return nil + }) + } + + // network failure is not fatal + if err := a.Exec(); err != nil { + if isQEMU { + func() { + installed, _ := ctx.Value(networkInstalledKey).(bool) + if !installed { + log.Warnln(fmt.Errorf("error setting up network dependencies: %w", err)) + return + } + + status, ok := ctx.Value(statusKey).(daemon.Status) + if !ok { + return + } + if !status.Running { + log.Warnln(fmt.Errorf("error starting network: %w", err)) + return + } + + for _, p := range status.Processes { + // TODO: handle inotify separate from network + if p.Name == inotify.Name { + continue + } + if !p.Running { + ctx = context.WithValue(ctx, daemon.CtxKey(p.Name), false) + log.Warnln(fmt.Errorf("error starting %s: %w", p.Name, err)) + } + } + }() + } + } + + // check if inotify is running + if conf.MountINotify { + if inotifyEnabled, _ := ctx.Value(ctxKeyInotify).(bool); !inotifyEnabled { + log.Warnln("error occurred enabling inotify daemon") + } + } + + // preserve vmnet context + if vmnetEnabled, _ := ctx.Value(ctxKeyVmnet).(bool); vmnetEnabled { + // env var for subprocess to detect vmnet + l.host = l.host.WithEnv(vmnet.SubProcessEnvVar + "=1") + } + + return ctx, nil +} diff --git a/environment/vm/lima/dependencies.go b/environment/vm/lima/dependencies.go new file mode 100644 index 000000000..65a8d12b2 --- /dev/null +++ b/environment/vm/lima/dependencies.go @@ -0,0 +1,102 @@ +package lima + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/abiosoft/colima/config" + "github.com/abiosoft/colima/environment" + "github.com/abiosoft/colima/util/fsutil" + "github.com/abiosoft/colima/util/terminal" + "github.com/sirupsen/logrus" +) + +var dependencyPackages = []string{ + // docker + "docker.io", + // utilities + "htop", "vim", "inetutils-ping", "dnsutils", +} + +// cacheDependencies downloads the ubuntu deb files to a path on the host. +// The return value is the directory of the downloaded deb files. +func (l *limaVM) cacheDependencies(log *logrus.Entry, conf config.Config) (string, error) { + codename, err := l.RunOutput("sh", "-c", `grep "^UBUNTU_CODENAME" /etc/os-release | cut -d= -f2`) + if err != nil { + return "", fmt.Errorf("error retrieving OS version from vm: %w", err) + } + + arch := environment.Arch(conf.Arch).Value() + dir := filepath.Join(config.CacheDir(), "packages", codename, string(arch)) + if err := fsutil.MkdirAll(dir, 0755); err != nil { + return "", fmt.Errorf("error creating cache directory for OS packages: %w", err) + } + + doneFile := filepath.Join(dir, ".downloaded") + if _, err := os.Stat(doneFile); err == nil { + // already downloaded + return dir, nil + } + + output := "" + for _, p := range dependencyPackages { + line := fmt.Sprintf(`sudo apt-get install --reinstall --print-uris -qq "%s" | cut -d"'" -f2`, p) + out, err := l.RunOutput("sh", "-c", line) + if err != nil { + return "", fmt.Errorf("error fetching dependencies list: %w", err) + } + output += out + " " + } + + debPackages := strings.Fields(output) + + // progress bar for Ubuntu deb packages download. + // TODO: extract this into re-usable progress bar for multi-downloads + for i, p := range debPackages { + // status feedback + log.Infof("downloading package %d of %d ...", i+1, len(debPackages)) + + // download + if err := l.host.RunInteractive( + "sh", "-c", + fmt.Sprintf(`cd %s && curl -LO -# %s`, dir, p), + ); err != nil { + return "", fmt.Errorf("error downloading dependency: %w", err) + } + + // clear terminal + terminal.ClearLine() // for curl output + terminal.ClearLine() // for log message + } + + // write a file to signify it is done + return dir, l.host.RunQuiet("touch", doneFile) +} + +func (l *limaVM) installDependencies(log *logrus.Entry, conf config.Config) error { + // cache dependencies + dir, err := l.cacheDependencies(log, conf) + if err != nil { + log.Warnln("error caching dependencies: %w", err) + log.Warnln("falling back to normal package install", err) + return l.Run("sudo apt install -y " + strings.Join(dependencyPackages, " ")) + } + + // validate if packages were previously installed + installed := true + for _, p := range dependencyPackages { + if err := l.RunQuiet("dpkg", "-s", p); err != nil { + installed = false + break + } + } + + if installed { + return nil + } + + // install packages + return l.Run("sh", "-c", "sudo dpkg -i "+dir+"/*.deb") +} diff --git a/environment/vm/lima/lima.go b/environment/vm/lima/lima.go index 0de77259c..7d04121d6 100644 --- a/environment/vm/lima/lima.go +++ b/environment/vm/lima/lima.go @@ -2,12 +2,9 @@ package lima import ( "context" - "encoding/json" "fmt" - "io" "os" "path/filepath" - "strings" "time" "github.com/abiosoft/colima/cli" @@ -15,15 +12,11 @@ import ( "github.com/abiosoft/colima/config/configmanager" "github.com/abiosoft/colima/core" "github.com/abiosoft/colima/daemon" - "github.com/abiosoft/colima/daemon/process/inotify" - "github.com/abiosoft/colima/daemon/process/vmnet" "github.com/abiosoft/colima/environment" "github.com/abiosoft/colima/environment/container/containerd" "github.com/abiosoft/colima/environment/vm/lima/limautil" "github.com/abiosoft/colima/util" - "github.com/abiosoft/colima/util/fsutil" "github.com/abiosoft/colima/util/osutil" - "github.com/abiosoft/colima/util/terminal" "github.com/abiosoft/colima/util/yamlutil" "github.com/sirupsen/logrus" ) @@ -84,141 +77,6 @@ func (l limaVM) Dependencies() []string { } } -func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.Context, error) { - isQEMU := conf.VMType == QEMU - isVZ := conf.VMType == VZ - - // limited to macOS (with Qemu driver) - // or vz with inotify enabled - if !util.MacOS() || (isVZ && !conf.MountINotify) { - return ctx, nil - } - - ctxKeyVmnet := daemon.CtxKey(vmnet.Name) - ctxKeyInotify := daemon.CtxKey(inotify.Name) - - // use a nested chain for convenience - a := l.Init(ctx) - log := l.Logger(ctx) - - networkInstalledKey := struct{ key string }{key: "network_installed"} - - // add inotify to daemon - if conf.MountINotify { - a.Add(func() error { - ctx = context.WithValue(ctx, ctxKeyInotify, true) - deps, _ := l.daemon.Dependencies(ctx, conf) - if err := deps.Install(l.host); err != nil { - return fmt.Errorf("error setting up inotify dependencies: %w", err) - } - return nil - }) - } - - // add network processes to daemon - if isQEMU { - a.Stage("preparing network") - a.Add(func() error { - if conf.Network.Address { - ctx = context.WithValue(ctx, ctxKeyVmnet, true) - } - deps, root := l.daemon.Dependencies(ctx, conf) - if deps.Installed() { - ctx = context.WithValue(ctx, networkInstalledKey, true) - return nil - } - - // if user interaction is not required (i.e. root), - // no need for another verbose info. - if root { - log.Println("dependencies missing for setting up reachable IP address") - log.Println("sudo password may be required") - } - - // install deps - err := deps.Install(l.host) - if err != nil { - ctx = context.WithValue(ctx, networkInstalledKey, false) - } - return err - }) - } - - // start daemon - a.Add(func() error { - return l.daemon.Start(ctx, conf) - }) - - statusKey := struct{ key string }{key: "daemonStatus"} - // delay to ensure that the processes have started - if conf.Network.Address || conf.MountINotify { - a.Retry("", time.Second*1, 15, func(i int) error { - s, err := l.daemon.Running(ctx, conf) - ctx = context.WithValue(ctx, statusKey, s) - if err != nil { - return err - } - if !s.Running { - return fmt.Errorf("daemon is not running") - } - for _, p := range s.Processes { - if !p.Running { - return p.Error - } - } - return nil - }) - } - - // network failure is not fatal - if err := a.Exec(); err != nil { - if isQEMU { - func() { - installed, _ := ctx.Value(networkInstalledKey).(bool) - if !installed { - log.Warnln(fmt.Errorf("error setting up network dependencies: %w", err)) - return - } - - status, ok := ctx.Value(statusKey).(daemon.Status) - if !ok { - return - } - if !status.Running { - log.Warnln(fmt.Errorf("error starting network: %w", err)) - return - } - - for _, p := range status.Processes { - // TODO: handle inotify separate from network - if p.Name == inotify.Name { - continue - } - if !p.Running { - ctx = context.WithValue(ctx, daemon.CtxKey(p.Name), false) - log.Warnln(fmt.Errorf("error starting %s: %w", p.Name, err)) - } - } - }() - } - } - - // check if inotify is running - if conf.MountINotify { - if inotifyEnabled, _ := ctx.Value(ctxKeyInotify).(bool); !inotifyEnabled { - log.Warnln("error occurred enabling inotify daemon") - } - } - - // preserve vmnet context - if vmnetEnabled, _ := ctx.Value(ctxKeyVmnet).(bool); vmnetEnabled { - // env var for subprocess to detect vmnet - l.host = l.host.WithEnv(vmnet.SubProcessEnvVar + "=1") - } - - return ctx, nil -} - func (l *limaVM) Start(ctx context.Context, conf config.Config) error { a := l.Init(ctx) @@ -241,6 +99,7 @@ func (l *limaVM) Start(ctx context.Context, conf config.Config) error { } return yamlutil.WriteYAML(l.limaConf, configFile) }) + a.Add(l.writeNetworkFile) a.Add(func() error { return l.host.Run(limactl, "start", "--tty=false", configFile) }) @@ -284,6 +143,8 @@ func (l *limaVM) resume(ctx context.Context, conf config.Config) error { return yamlutil.WriteYAML(l.limaConf, l.limaConfFile()) }) + a.Add(l.writeNetworkFile) + a.Stage("starting") a.Add(func() error { return l.host.Run(limactl, "start", config.CurrentProfile().ID) @@ -370,81 +231,6 @@ func (l limaVM) Restart(ctx context.Context) error { return nil } -func (l limaVM) Run(args ...string) error { - args = append([]string{lima}, args...) - - a := l.Init(context.Background()) - - a.Add(func() error { - return l.host.Run(args...) - }) - - return a.Exec() -} - -func (l limaVM) SSH(workingDir string, args ...string) error { - args = append([]string{limactl, "shell", "--workdir", workingDir, config.CurrentProfile().ID}, args...) - - a := l.Init(context.Background()) - - a.Add(func() error { - return l.host.RunInteractive(args...) - }) - - return a.Exec() -} - -func (l limaVM) RunInteractive(args ...string) error { - args = append([]string{lima}, args...) - - a := l.Init(context.Background()) - - a.Add(func() error { - return l.host.RunInteractive(args...) - }) - - return a.Exec() -} - -func (l limaVM) RunWith(stdin io.Reader, stdout io.Writer, args ...string) error { - args = append([]string{lima}, args...) - - a := l.Init(context.Background()) - - a.Add(func() error { - return l.host.RunWith(stdin, stdout, args...) - }) - - return a.Exec() -} - -func (l limaVM) RunOutput(args ...string) (out string, err error) { - args = append([]string{lima}, args...) - - a := l.Init(context.Background()) - - a.Add(func() (err error) { - out, err = l.host.RunOutput(args...) - return - }) - - err = a.Exec() - return -} - -func (l limaVM) RunQuiet(args ...string) (err error) { - args = append([]string{lima}, args...) - - a := l.Init(context.Background()) - - a.Add(func() (err error) { - return l.host.RunQuiet(args...) - }) - - err = a.Exec() - return -} - func (l limaVM) Host() environment.HostActions { return l.host } @@ -462,48 +248,6 @@ func (l limaVM) Created() bool { return err == nil && !stat.IsDir() } -const configFile = "/etc/colima/colima.json" - -func (l limaVM) getConf() map[string]string { - obj := map[string]string{} - b, err := l.Read(configFile) - if err != nil { - return obj - } - - // we do not care if it fails - _ = json.Unmarshal([]byte(b), &obj) - - return obj -} -func (l limaVM) Get(key string) string { - if val, ok := l.getConf()[key]; ok { - return val - } - - return "" -} - -func (l limaVM) Set(key, value string) error { - obj := l.getConf() - obj[key] = value - - b, err := json.Marshal(obj) - if err != nil { - return fmt.Errorf("error marshalling settings to json: %w", err) - } - - if err := l.Run("sudo", "mkdir", "-p", filepath.Dir(configFile)); err != nil { - return fmt.Errorf("error saving settings: %w", err) - } - - if err := l.Write(configFile, b); err != nil { - return fmt.Errorf("error saving settings: %w", err) - } - - return nil -} - func (l limaVM) User() (string, error) { return l.RunOutput("whoami") } @@ -614,91 +358,3 @@ func (l *limaVM) addPostStartActions(a *cli.ActiveCommandChain, conf config.Conf return nil }) } - -var dependencyPackages = []string{ - // docker - "docker.io", - // utilities - "htop", "vim", "inetutils-ping", "dnsutils", -} - -// cacheDependencies downloads the ubuntu deb files to a path on the host. -// The return value is the directory of the downloaded deb files. -func (l *limaVM) cacheDependencies(log *logrus.Entry, conf config.Config) (string, error) { - codename, err := l.RunOutput("sh", "-c", `grep "^UBUNTU_CODENAME" /etc/os-release | cut -d= -f2`) - if err != nil { - return "", fmt.Errorf("error retrieving OS version from vm: %w", err) - } - - arch := environment.Arch(conf.Arch).Value() - dir := filepath.Join(config.CacheDir(), "packages", codename, string(arch)) - if err := fsutil.MkdirAll(dir, 0755); err != nil { - return "", fmt.Errorf("error creating cache directory for OS packages: %w", err) - } - - doneFile := filepath.Join(dir, ".downloaded") - if _, err := os.Stat(doneFile); err == nil { - // already downloaded - return dir, nil - } - - output := "" - for _, p := range dependencyPackages { - line := fmt.Sprintf(`sudo apt-get install --reinstall --print-uris -qq "%s" | cut -d"'" -f2`, p) - out, err := l.RunOutput("sh", "-c", line) - if err != nil { - return "", fmt.Errorf("error fetching dependencies list: %w", err) - } - output += out + " " - } - - debPackages := strings.Fields(output) - - // progress bar for Ubuntu deb packages download. - // TODO: extract this into re-usable progress bar for multi-downloads - for i, p := range debPackages { - // status feedback - log.Infof("downloading package %d of %d ...", i+1, len(debPackages)) - - // download - if err := l.host.RunInteractive( - "sh", "-c", - fmt.Sprintf(`cd %s && curl -LO -# %s`, dir, p), - ); err != nil { - return "", fmt.Errorf("error downloading dependency: %w", err) - } - - // clear terminal - terminal.ClearLine() // for curl output - terminal.ClearLine() // for log message - } - - // write a file to signify it is done - return dir, l.host.RunQuiet("touch", doneFile) -} - -func (l *limaVM) installDependencies(log *logrus.Entry, conf config.Config) error { - // cache dependencies - dir, err := l.cacheDependencies(log, conf) - if err != nil { - log.Warnln("error caching dependencies: %w", err) - log.Warnln("falling back to normal package install", err) - return l.Run("sudo apt install -y " + strings.Join(dependencyPackages, " ")) - } - - // validate if packages were previously installed - installed := true - for _, p := range dependencyPackages { - if err := l.RunQuiet("dpkg", "-s", p); err != nil { - installed = false - break - } - } - - if installed { - return nil - } - - // install packages - return l.Run("sh", "-c", "sudo dpkg -i "+dir+"/*.deb") -} diff --git a/environment/vm/lima/limautil/files.go b/environment/vm/lima/limautil/files.go new file mode 100644 index 000000000..f153dd6e5 --- /dev/null +++ b/environment/vm/lima/limautil/files.go @@ -0,0 +1,28 @@ +package limautil + +import ( + "path/filepath" + + "github.com/abiosoft/colima/config" +) + +const colimaStateFile = "colima.yaml" + +// ColimaStateFile returns path to the colima state yaml file. +func ColimaStateFile(profileID string) string { + return filepath.Join(LimaHome(), config.Profile(profileID).ID, colimaStateFile) +} + +const colimaDiffDiskFile = "diffdisk" + +// ColimaDiffDisk returns path to the diffdisk for the colima VM. +func ColimaDiffDisk(profileID string) string { + return filepath.Join(LimaHome(), config.Profile(profileID).ID, colimaDiffDiskFile) +} + +const networkFile = "networks.yaml" + +// NetworkFile returns path to the network file. +func NetworkFile() string { + return filepath.Join(LimaHome(), "_config", networkFile) +} diff --git a/environment/vm/lima/limautil/limautil.go b/environment/vm/lima/limautil/limautil.go index 179b1957c..f7c3d9afc 100644 --- a/environment/vm/lima/limautil/limautil.go +++ b/environment/vm/lima/limautil/limautil.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "os/exec" - "path/filepath" "strings" "github.com/abiosoft/colima/cli" @@ -91,45 +90,6 @@ func (i InstanceInfo) Config() (config.Config, error) { return configmanager.LoadFrom(ColimaStateFile(i.Name)) } -// ShowSSH runs the show-ssh command in Lima. -// returns the ssh output, if in layer, and an error if any -func ShowSSH(profileID string) (resp struct { - Output string - File struct { - Lima string - Colima string - } -}, err error) { - ssh := sshConfig(profileID) - sshConf, err := ssh.Contents() - if err != nil { - return resp, fmt.Errorf("error retrieving ssh config: %w", err) - } - - resp.Output = replaceSSHConfig(sshConf, profileID) - resp.File.Lima = ssh.File() - resp.File.Colima = config.SSHConfigFile() - return resp, nil -} - -func replaceSSHConfig(conf string, profileID string) string { - profileID = config.Profile(profileID).ID - - var out bytes.Buffer - scanner := bufio.NewScanner(strings.NewReader(conf)) - - for scanner.Scan() { - line := scanner.Text() - - if strings.HasPrefix(line, "Host ") { - line = "Host " + profileID - } - - _, _ = fmt.Fprintln(&out, line) - } - return out.String() -} - // Lima statuses const ( limaStatusRunning = "Running" @@ -245,38 +205,3 @@ func LimaHome() string { return config.LimaDir() } - -const colimaStateFileName = "colima.yaml" - -// ColimaStateFile returns path to the colima state yaml file. -func ColimaStateFile(profileID string) string { - return filepath.Join(LimaHome(), config.Profile(profileID).ID, colimaStateFileName) -} - -const colimaDiffDisk = "diffdisk" - -// ColimaDiffDisk returns path to the diffdisk for the colima VM. -func ColimaDiffDisk(profileID string) string { - return filepath.Join(LimaHome(), config.Profile(profileID).ID, colimaDiffDisk) -} - -const sshConfigFile = "ssh.config" - -// sshConfig is the ssh configuration file for a Colima profile. -type sshConfig string - -// Contents returns the content of the SSH config file. -func (s sshConfig) Contents() (string, error) { - profile := config.Profile(string(s)) - b, err := os.ReadFile(s.File()) - if err != nil { - return "", fmt.Errorf("error retrieving Lima SSH config file for profile '%s': %w", strings.TrimPrefix(profile.DisplayName, "lima"), err) - } - return string(b), nil -} - -// File returns the path to the SSH config file. -func (s sshConfig) File() string { - profile := config.Profile(string(s)) - return filepath.Join(LimaHome(), profile.ID, sshConfigFile) -} diff --git a/environment/vm/lima/limautil/ssh.go b/environment/vm/lima/limautil/ssh.go new file mode 100644 index 000000000..2de5d685c --- /dev/null +++ b/environment/vm/lima/limautil/ssh.go @@ -0,0 +1,72 @@ +package limautil + +import ( + "bufio" + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/abiosoft/colima/config" +) + +// ShowSSH runs the show-ssh command in Lima. +// returns the ssh output, if in layer, and an error if any +func ShowSSH(profileID string) (resp struct { + Output string + File struct { + Lima string + Colima string + } +}, err error) { + ssh := sshConfig(profileID) + sshConf, err := ssh.Contents() + if err != nil { + return resp, fmt.Errorf("error retrieving ssh config: %w", err) + } + + resp.Output = replaceSSHConfig(sshConf, profileID) + resp.File.Lima = ssh.File() + resp.File.Colima = config.SSHConfigFile() + return resp, nil +} + +func replaceSSHConfig(conf string, profileID string) string { + profileID = config.Profile(profileID).ID + + var out bytes.Buffer + scanner := bufio.NewScanner(strings.NewReader(conf)) + + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "Host ") { + line = "Host " + profileID + } + + _, _ = fmt.Fprintln(&out, line) + } + return out.String() +} + +const sshConfigFile = "ssh.config" + +// sshConfig is the ssh configuration file for a Colima profile. +type sshConfig string + +// Contents returns the content of the SSH config file. +func (s sshConfig) Contents() (string, error) { + profile := config.Profile(string(s)) + b, err := os.ReadFile(s.File()) + if err != nil { + return "", fmt.Errorf("error retrieving Lima SSH config file for profile '%s': %w", strings.TrimPrefix(profile.DisplayName, "lima"), err) + } + return string(b), nil +} + +// File returns the path to the SSH config file. +func (s sshConfig) File() string { + profile := config.Profile(string(s)) + return filepath.Join(LimaHome(), profile.ID, sshConfigFile) +} diff --git a/environment/vm/lima/network.go b/environment/vm/lima/network.go new file mode 100644 index 000000000..0d353a7e0 --- /dev/null +++ b/environment/vm/lima/network.go @@ -0,0 +1,26 @@ +package lima + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/abiosoft/colima/embedded" + "github.com/abiosoft/colima/environment/vm/lima/limautil" +) + +func (l *limaVM) writeNetworkFile() error { + networkFile := limautil.NetworkFile() + embeddedFile, err := embedded.Read("network/networks.yaml") + if err != nil { + return fmt.Errorf("error reading embedded network config file: %w", err) + } + + if err := os.MkdirAll(filepath.Dir(networkFile), 0755); err != nil { + return fmt.Errorf("error creating Lima config directory: %w", err) + } + if err := os.WriteFile(networkFile, embeddedFile, 0755); err != nil { + return fmt.Errorf("error writing Lima network config file: %w", err) + } + return nil +} diff --git a/environment/vm/lima/shell.go b/environment/vm/lima/shell.go new file mode 100644 index 000000000..c45f47ad1 --- /dev/null +++ b/environment/vm/lima/shell.go @@ -0,0 +1,83 @@ +package lima + +import ( + "context" + "io" + + "github.com/abiosoft/colima/config" +) + +func (l limaVM) Run(args ...string) error { + args = append([]string{lima}, args...) + + a := l.Init(context.Background()) + + a.Add(func() error { + return l.host.Run(args...) + }) + + return a.Exec() +} + +func (l limaVM) SSH(workingDir string, args ...string) error { + args = append([]string{limactl, "shell", "--workdir", workingDir, config.CurrentProfile().ID}, args...) + + a := l.Init(context.Background()) + + a.Add(func() error { + return l.host.RunInteractive(args...) + }) + + return a.Exec() +} + +func (l limaVM) RunInteractive(args ...string) error { + args = append([]string{lima}, args...) + + a := l.Init(context.Background()) + + a.Add(func() error { + return l.host.RunInteractive(args...) + }) + + return a.Exec() +} + +func (l limaVM) RunWith(stdin io.Reader, stdout io.Writer, args ...string) error { + args = append([]string{lima}, args...) + + a := l.Init(context.Background()) + + a.Add(func() error { + return l.host.RunWith(stdin, stdout, args...) + }) + + return a.Exec() +} + +func (l limaVM) RunOutput(args ...string) (out string, err error) { + args = append([]string{lima}, args...) + + a := l.Init(context.Background()) + + a.Add(func() (err error) { + out, err = l.host.RunOutput(args...) + return + }) + + err = a.Exec() + return +} + +func (l limaVM) RunQuiet(args ...string) (err error) { + args = append([]string{lima}, args...) + + a := l.Init(context.Background()) + + a.Add(func() (err error) { + return l.host.RunQuiet(args...) + }) + + err = a.Exec() + return +}