chore: refactor daemon (#312)

* chore: refactor network daemon
This commit is contained in:
Abiola Ibrahim
2022-05-30 10:11:24 +01:00
committed by GitHub
parent 2b32ee9fce
commit 3c03f809f9
25 changed files with 385 additions and 383 deletions
+4 -3
View File
@@ -15,6 +15,7 @@ import (
"github.com/abiosoft/colima/environment/container/ubuntu"
"github.com/abiosoft/colima/environment/host"
"github.com/abiosoft/colima/environment/vm/lima"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
log "github.com/sirupsen/logrus"
)
@@ -208,7 +209,7 @@ func (c colimaApp) SSH(layer bool, args ...string) error {
return c.guest.RunInteractive(args...)
}
conf, err := lima.InstanceConfig()
conf, err := limautil.InstanceConfig()
if err != nil {
return err
}
@@ -216,7 +217,7 @@ func (c colimaApp) SSH(layer bool, args ...string) error {
return c.guest.RunInteractive(args...)
}
resp, err := lima.ShowSSH(config.CurrentProfile().ID, layer, "args")
resp, err := limautil.ShowSSH(config.CurrentProfile().ID, layer, "args")
if err != nil {
return fmt.Errorf("error getting ssh config: %w", err)
}
@@ -256,7 +257,7 @@ func (c colimaApp) Status() error {
log.Println(config.CurrentProfile().DisplayName, "is running")
log.Println("arch:", c.guest.Arch())
log.Println("runtime:", currentRuntime)
if conf, err := lima.InstanceConfig(); err == nil {
if conf, err := limautil.InstanceConfig(); err == nil {
log.Println("mountType:", conf.MountType)
}
if currentRuntime == docker.Name {
+11 -4
View File
@@ -10,12 +10,12 @@ import (
_ "github.com/abiosoft/colima/cmd" // for other commands
_ "github.com/abiosoft/colima/cmd/daemon" // for vmnet daemon
_ "github.com/abiosoft/colima/embedded" // for embedded assets
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/gvproxy"
"github.com/abiosoft/colima/util"
"github.com/sirupsen/logrus"
"github.com/abiosoft/colima/cmd/root"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/daemon/process/gvproxy"
"github.com/abiosoft/colima/util"
"github.com/sirupsen/logrus"
)
func main() {
@@ -27,6 +27,7 @@ func main() {
root.Execute()
}
}
func qemuWrapper(qemu string) {
if profile := os.Getenv(config.SubprocessProfileEnvVar); profile != "" {
config.SetProfile(profile)
@@ -79,6 +80,12 @@ func qemuWrapper(qemu string) {
cmd.ExtraFiles = append(cmd.ExtraFiles, fd)
}
_ = cmd.Run()
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); ok {
os.Exit(err.ExitCode())
}
os.Exit(1)
}
}
+12 -11
View File
@@ -4,9 +4,9 @@ import (
"context"
"time"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/gvproxy"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/vmnet"
"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/daemon/process/gvproxy"
"github.com/abiosoft/colima/daemon/process/vmnet"
"github.com/abiosoft/colima/cmd/root"
"github.com/abiosoft/colima/config"
@@ -18,10 +18,6 @@ var daemonCmd = &cobra.Command{
Short: "daemon",
Long: `runner for background daemons.`,
Hidden: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
cmd.SilenceUsage = true
cmd.SilenceErrors = true
},
}
var startCmd = &cobra.Command{
@@ -31,8 +27,9 @@ var startCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
config.SetProfile(args[0])
ctx := cmd.Context()
var processes []daemon.Process
var processes []process.Process
if daemonArgs.vmnet {
processes = append(processes, vmnet.New())
}
@@ -40,7 +37,7 @@ var startCmd = &cobra.Command{
processes = append(processes, gvproxy.New())
}
return start(cmd.Context(), processes)
return start(ctx, processes)
},
}
@@ -74,8 +71,11 @@ var statusCmd = &cobra.Command{
}
var daemonArgs struct {
vmnet bool
gvproxy bool
vmnet bool
gvproxy bool
fsnotify bool
verbose bool
}
func init() {
@@ -87,4 +87,5 @@ func init() {
startCmd.Flags().BoolVar(&daemonArgs.vmnet, "vmnet", false, "start vmnet")
startCmd.Flags().BoolVar(&daemonArgs.gvproxy, "gvproxy", false, "start gvproxy")
startCmd.Flags().BoolVar(&daemonArgs.fsnotify, "fsnotify", false, "start fsnotify")
}
+34 -4
View File
@@ -7,16 +7,17 @@ import (
"os/signal"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
"github.com/abiosoft/colima/cli"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
"github.com/abiosoft/colima/daemon/process"
godaemon "github.com/sevlyar/go-daemon"
"github.com/sirupsen/logrus"
)
var dir = daemon.Dir
var dir = process.Dir
// daemonize creates the daemon and returns if this is a child process
func daemonize() (ctx *godaemon.Context, child bool, err error) {
@@ -49,7 +50,7 @@ func daemonize() (ctx *godaemon.Context, child bool, err error) {
return ctx, true, nil
}
func start(ctx context.Context, processes []daemon.Process) error {
func start(ctx context.Context, processes []process.Process) error {
if status() == nil {
logrus.Info("daemon already running, startup ignored")
return nil
@@ -75,7 +76,7 @@ func start(ctx context.Context, processes []daemon.Process) error {
ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer stop()
return daemon.Run(ctx, processes...)
return RunProcesses(ctx, processes...)
}
func stop(ctx context.Context) error {
@@ -153,3 +154,32 @@ func Info() struct {
LogFile: filepath.Join(dir, logFileName),
}
}
// Run runs the daemon with background processes.
// NOTE: this must be called from the program entrypoint with minimal intermediary logic
// due to the creation of the daemon.
func RunProcesses(ctx context.Context, processes ...process.Process) error {
ctx, stop := context.WithCancel(ctx)
defer stop()
var wg sync.WaitGroup
wg.Add(len(processes))
for _, bg := range processes {
go func(bg process.Process) {
err := bg.Start(ctx)
if err != nil {
logrus.Error(fmt.Errorf("error starting %s: %w", bg.Name(), err))
stop()
}
wg.Done()
}(bg)
}
<-ctx.Done()
logrus.Info("terminate signal received")
wg.Wait()
return ctx.Err()
}
+43 -10
View File
@@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
"github.com/abiosoft/colima/daemon/process"
)
var testDir string
@@ -19,22 +19,28 @@ func setDir(t *testing.T) {
dir = func() string { return testDir }
}
func TestStart(t *testing.T) {
setDir(t)
info := Info()
func getProcesses() []process.Process {
var addresses = []string{
"localhost",
"127.0.0.1",
}
t.Log("pidfile", info.PidFile)
var processes []daemon.Process
var processes []process.Process
for _, add := range addresses {
processes = append(processes, &pinger{address: add})
}
return processes
}
func TestStart(t *testing.T) {
setDir(t)
info := Info()
processes := getProcesses()
t.Log("pidfile", info.PidFile)
timeout := time.Second * 5
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
@@ -54,6 +60,8 @@ func TestStart(t *testing.T) {
default:
if p, err := os.ReadFile(info.PidFile); err == nil && len(p) > 0 {
break loop
} else if err != nil {
t.Logf("encountered err: %v", err)
}
time.Sleep(1 * time.Second)
}
@@ -79,7 +87,32 @@ func TestStart(t *testing.T) {
}
var _ daemon.Process = (*pinger)(nil)
func TestRunProcesses(t *testing.T) {
processes := getProcesses()
timeout := time.Second * 5
ctx, cancel := context.WithTimeout(context.Background(), timeout)
// start the processes
done := make(chan error, 1)
go func() {
done <- RunProcesses(ctx, processes...)
}()
cancel()
select {
case <-ctx.Done():
if err := ctx.Err(); err != context.Canceled {
t.Error(err)
}
case err := <-done:
t.Error(err)
}
}
var _ process.Process = (*pinger)(nil)
type pinger struct {
address string
@@ -98,7 +131,7 @@ func (p *pinger) Start(ctx context.Context) error {
}
// Start implements BgProcess
func (p *pinger) Dependencies() ([]daemon.Dependency, bool) { return nil, false }
func (p *pinger) Dependencies() ([]process.Dependency, bool) { return nil, false }
func (p *pinger) run(ctx context.Context, command string, args ...string) error {
cmd := exec.CommandContext(ctx, command, args...)
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"text/tabwriter"
"github.com/abiosoft/colima/cmd/root"
"github.com/abiosoft/colima/environment/vm/lima"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/docker/go-units"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -26,7 +26,7 @@ var listCmd = &cobra.Command{
A new instance can be created during 'colima start' by specifying the '--profile' flag.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
instances, err := lima.Instances()
instances, err := limautil.Instances()
if err != nil {
return err
}
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"github.com/abiosoft/colima/cmd/root"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/environment/vm/lima"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/spf13/cobra"
)
@@ -16,7 +16,7 @@ var sshConfigCmd = &cobra.Command{
Long: `Show configuration of the SSH connection to the VM.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
resp, err := lima.ShowSSH(config.CurrentProfile().ID, sshConfigCmdArgs.layer, sshConfigCmdArgs.format)
resp, err := limautil.ShowSSH(config.CurrentProfile().ID, sshConfigCmdArgs.layer, sshConfigCmdArgs.format)
if err == nil {
fmt.Println(resp.Output)
}
+11
View File
@@ -132,6 +132,17 @@ func (m Mount) CleanPath() (string, error) {
return strings.TrimSuffix(str, "/") + "/", nil
}
func (c Config) MountsOrDefault() []Mount {
if len(c.Mounts) > 0 {
return c.Mounts
}
return []Mount{
{Location: util.HomeDir(), Writable: true},
{Location: filepath.Join("/tmp", CurrentProfile().ID), Writable: true},
}
}
// Empty checks if the configuration is empty.
func (c Config) Empty() bool { return c.Runtime == "" } // this may be better but not really needed.
@@ -1,4 +1,4 @@
package network
package daemon
import (
"context"
@@ -6,18 +6,18 @@ import (
"os"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/daemon/process/gvproxy"
"github.com/abiosoft/colima/daemon/process/vmnet"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/gvproxy"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/vmnet"
)
// Manager handles networking between the host and the vm.
// Manager handles running background processes.
type Manager interface {
Start(context.Context) error
Stop(context.Context) error
Running(ctx context.Context) (Status, error)
Dependencies(ctx context.Context) (deps daemon.Dependency, root bool)
Dependencies(ctx context.Context) (deps process.Dependency, root bool)
}
type Status struct {
@@ -32,35 +32,35 @@ type processStatus struct {
Error error
}
// NewManager creates a new network manager.
// NewManager creates a new process manager.
func NewManager(host environment.HostActions) Manager {
return &limaNetworkManager{
return &processManager{
host: host,
}
}
func CtxKey(s string) any { return struct{ key string }{key: s} }
var _ Manager = (*limaNetworkManager)(nil)
var _ Manager = (*processManager)(nil)
type limaNetworkManager struct {
type processManager struct {
host environment.HostActions
}
func (l limaNetworkManager) Dependencies(ctx context.Context) (deps daemon.Dependency, root bool) {
func (l processManager) Dependencies(ctx context.Context) (deps process.Dependency, root bool) {
processes := processesFromCtx(ctx)
return daemon.Dependencies(processes...)
return process.Dependencies(processes...)
}
func (l limaNetworkManager) init() error {
func (l processManager) init() error {
// dependencies for network
if err := os.MkdirAll(daemon.Dir(), 0755); err != nil {
if err := os.MkdirAll(process.Dir(), 0755); err != nil {
return fmt.Errorf("error preparing vmnet: %w", err)
}
return nil
}
func (l limaNetworkManager) Running(ctx context.Context) (s Status, err error) {
func (l processManager) Running(ctx context.Context) (s Status, err error) {
err = l.host.RunQuiet(os.Args[0], "daemon", "status", config.CurrentProfile().ShortName)
if err != nil {
return
@@ -78,7 +78,7 @@ func (l limaNetworkManager) Running(ctx context.Context) (s Status, err error) {
return
}
func (l limaNetworkManager) Start(ctx context.Context) error {
func (l processManager) Start(ctx context.Context) error {
_ = l.Stop(ctx) // this is safe, nothing is done when not running
if err := l.init(); err != nil {
@@ -93,10 +93,13 @@ func (l limaNetworkManager) Start(ctx context.Context) error {
if opts.GVProxy {
args = append(args, "--gvproxy")
}
if opts.FSNotify {
args = append(args, "--fsnotify")
}
return l.host.RunQuiet(args...)
}
func (l limaNetworkManager) Stop(ctx context.Context) error {
func (l processManager) Stop(ctx context.Context) error {
if s, err := l.Running(ctx); err != nil || !s.Running {
return nil
}
@@ -104,12 +107,14 @@ func (l limaNetworkManager) Stop(ctx context.Context) error {
}
func optsFromCtx(ctx context.Context) struct {
Vmnet bool
GVProxy bool
Vmnet bool
GVProxy bool
FSNotify bool
} {
var opts = struct {
Vmnet bool
GVProxy bool
Vmnet bool
GVProxy bool
FSNotify bool
}{}
opts.Vmnet, _ = ctx.Value(CtxKey(vmnet.Name())).(bool)
opts.GVProxy, _ = ctx.Value(CtxKey(gvproxy.Name())).(bool)
@@ -117,8 +122,8 @@ func optsFromCtx(ctx context.Context) struct {
return opts
}
func processesFromCtx(ctx context.Context) []daemon.Process {
var processes []daemon.Process
func processesFromCtx(ctx context.Context) []process.Process {
var processes []process.Process
opts := optsFromCtx(ctx)
if opts.Vmnet {
@@ -8,11 +8,11 @@ import (
"strings"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
)
var _ daemon.Dependency = qemuBinsSymlinks{}
var _ process.Dependency = qemuBinsSymlinks{}
// only these two are required for Lima
var qemuBins = []string{"qemu-system-aarch64", "qemu-system-x86_64"}
@@ -52,7 +52,7 @@ func (q qemuBinsSymlinks) Install(host environment.HostActions) error {
return nil
}
var _ daemon.Dependency = qemuShareDirSymlink{}
var _ process.Dependency = qemuShareDirSymlink{}
type qemuShareDirSymlink struct{}
@@ -11,7 +11,7 @@ import (
"runtime"
"strings"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/util"
"github.com/containers/gvisor-tap-vsock/pkg/transport"
"github.com/containers/gvisor-tap-vsock/pkg/types"
@@ -20,7 +20,7 @@ import (
)
// New creates a new Process for gvproxy.
func New() daemon.Process {
func New() process.Process {
return &gvproxyProcess{}
}
@@ -39,12 +39,12 @@ func Info() struct {
Socket Socket
MacAddress string
}{
Socket: Socket(filepath.Join(daemon.Dir(), socketFileName)),
Socket: Socket(filepath.Join(process.Dir(), socketFileName)),
MacAddress: MacAddress(),
}
}
var _ daemon.Process = (*gvproxyProcess)(nil)
var _ process.Process = (*gvproxyProcess)(nil)
type gvproxyProcess struct{}
@@ -56,10 +56,10 @@ func (*gvproxyProcess) Alive(context.Context) error {
return nil
}
// Name implements daemon.BgProcess
// Name implements daemon.Process
func (*gvproxyProcess) Name() string { return Name() }
// Start implements daemon.BgProcess
// Start implements daemon.Process
func (*gvproxyProcess) Start(ctx context.Context) error {
info := Info()
return run(ctx, info.Socket)
@@ -87,7 +87,7 @@ func MacAddress() string {
// there is not much concern about the precision of the uniqueness.
// this can be revisited
if macAddress == nil {
sum := util.SHA256Hash(daemon.Dir())
sum := util.SHA256Hash(process.Dir())
macAddress = append(macAddress, baseHWAddr...)
macAddress = append(macAddress, sum[0:3]...)
}
@@ -207,8 +207,8 @@ func searchDomains() []string {
return nil
}
func (gvproxyProcess) Dependencies() (deps []daemon.Dependency, root bool) {
return []daemon.Dependency{
func (gvproxyProcess) Dependencies() (deps []process.Dependency, root bool) {
return []process.Dependency{
qemuBinsSymlinks{},
qemuShareDirSymlink{},
}, false
@@ -1,15 +1,13 @@
package daemon
package process
import (
"context"
"fmt"
"path/filepath"
"sync"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/environment"
"github.com/sirupsen/logrus"
)
// Process is a background process managed by the daemon.
@@ -27,8 +25,8 @@ type Process interface {
Dependencies() (deps []Dependency, root bool)
}
// Dir is the directory for network related files.
func Dir() string { return filepath.Join(config.Dir(), "network") }
// Dir is the directory for daemon files.
func Dir() string { return filepath.Join(config.Dir(), "daemon") }
// Dependency is a requirement to be fulfilled before a process can be started.
type Dependency interface {
@@ -83,32 +81,3 @@ func (p processDeps) Install(host environment.HostActions) error {
return nil
}
// Run runs the daemon with background processes.
// NOTE: this must be called from the program entrypoint with minimal intermediary logic
// due to the creation of the daemon.
func Run(ctx context.Context, processes ...Process) error {
ctx, stop := context.WithCancel(ctx)
defer stop()
var wg sync.WaitGroup
wg.Add(len(processes))
for _, bg := range processes {
go func(bg Process) {
err := bg.Start(ctx)
if err != nil {
logrus.Error(fmt.Errorf("error starting %s: %w", bg.Name(), err))
stop()
}
wg.Done()
}(bg)
}
<-ctx.Done()
logrus.Info("terminate signal received")
wg.Wait()
return ctx.Err()
}
@@ -8,12 +8,12 @@ import (
"runtime"
"strings"
"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/embedded"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
)
var _ daemon.Dependency = sudoerFile{}
var _ process.Dependency = sudoerFile{}
type sudoerFile struct{}
@@ -55,7 +55,7 @@ func (s sudoerFile) Install(host environment.HostActions) error {
return nil
}
var _ daemon.Dependency = vmnetFile{}
var _ process.Dependency = vmnetFile{}
const BinaryPath = "/opt/colima/bin/vde_vmnet"
const LibraryPath = "/opt/colima/lib/libvdeplug.3.dylib"
@@ -113,7 +113,7 @@ func (v vmnetFile) Install(host environment.HostActions) error {
return nil
}
var _ daemon.Dependency = vmnetRunDir{}
var _ process.Dependency = vmnetRunDir{}
type vmnetRunDir struct{}
@@ -9,7 +9,7 @@ import (
"github.com/abiosoft/colima/cli"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon"
"github.com/abiosoft/colima/daemon/process"
)
const (
@@ -18,10 +18,10 @@ const (
NetInterface = "col0"
)
var _ daemon.Process = (*vmnetProcess)(nil)
var _ process.Process = (*vmnetProcess)(nil)
func New() daemon.Process { return &vmnetProcess{} }
func Name() string { return "vmnet" }
func New() process.Process { return &vmnetProcess{} }
func Name() string { return "vmnet" }
type vmnetProcess struct{}
@@ -44,10 +44,10 @@ func (*vmnetProcess) Alive(ctx context.Context) error {
return nil
}
// Name implements daemon.BgProcess
// Name implements process.BgProcess
func (*vmnetProcess) Name() string { return Name() }
// Start implements daemon.BgProcess
// Start implements process.BgProcess
func (*vmnetProcess) Start(ctx context.Context) error {
info := Info()
ptp := info.PTPFile
@@ -88,8 +88,8 @@ func (*vmnetProcess) Start(ctx context.Context) error {
return nil
}
func (vmnetProcess) Dependencies() (deps []daemon.Dependency, root bool) {
return []daemon.Dependency{
func (vmnetProcess) Dependencies() (deps []process.Dependency, root bool) {
return []process.Dependency{
sudoerFile{},
vmnetFile{},
vmnetRunDir{},
@@ -124,6 +124,6 @@ func Info() struct {
PTPFile string
}{
PidFile: filepath.Join(runDir(), "vmnet-"+config.CurrentProfile().ShortName+".pid"),
PTPFile: filepath.Join(daemon.Dir(), "vmnet.ptp"),
PTPFile: filepath.Join(process.Dir(), "vmnet.ptp"),
}
}
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/container/containerd"
"github.com/abiosoft/colima/environment/container/docker"
"github.com/abiosoft/colima/environment/vm/lima"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/abiosoft/colima/util/downloader"
"github.com/sirupsen/logrus"
)
@@ -128,7 +128,7 @@ func installK3sCluster(
}
// replace ip address if networking is enabled
ipAddress := lima.IPAddress(config.CurrentProfile().ID)
ipAddress := limautil.IPAddress(config.CurrentProfile().ID)
if ipAddress == "127.0.0.1" {
args = append(args, "--flannel-iface", "eth0")
} else {
@@ -9,13 +9,13 @@ import (
"github.com/abiosoft/colima/cli"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/environment/vm/lima"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
)
const masterAddressKey = "master_address"
func (c kubernetesRuntime) provisionKubeconfig(ctx context.Context) error {
ip := lima.IPAddress(config.CurrentProfile().ID)
ip := limautil.IPAddress(config.CurrentProfile().ID)
if ip == c.guest.Get(masterAddressKey) {
return nil
}
+1 -7
View File
@@ -7,7 +7,6 @@ import (
"strconv"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/util"
)
type buildArgs struct {
@@ -158,12 +157,7 @@ func (u ubuntuRuntime) createContainer(conf config.Config) error {
"--volume", "/:/host",
)
mounts := conf.Mounts
if len(mounts) == 0 {
// TODO: should be not be repeated here but rather populated externally
mounts = append(mounts, config.Mount{Location: util.HomeDir()})
mounts = append(mounts, config.Mount{Location: filepath.Join("/tmp", config.CurrentProfile().ID)})
}
mounts := conf.MountsOrDefault()
for _, m := range mounts {
args = append(args, "--volume", m.Location+":"+m.Location)
}
+23 -23
View File
@@ -11,13 +11,14 @@ import (
"path/filepath"
"time"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/gvproxy"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/vmnet"
"github.com/abiosoft/colima/daemon"
"github.com/abiosoft/colima/daemon/process/gvproxy"
"github.com/abiosoft/colima/daemon/process/vmnet"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/abiosoft/colima/cli"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/vm/lima/network"
"github.com/abiosoft/colima/util"
"github.com/abiosoft/colima/util/yamlutil"
"github.com/sirupsen/logrus"
@@ -46,7 +47,7 @@ func New(host environment.HostActions) environment.VM {
host: host.WithEnv(envs...),
home: home,
CommandChain: cli.New("vm"),
network: network.NewManager(host),
daemon: daemon.NewManager(host),
}
}
@@ -54,7 +55,6 @@ const (
limaInstanceEnvVar = "LIMA_INSTANCE"
lima = "lima"
limactl = "limactl"
layerEnvVar = "COLIMA_LAYER_SSH_PORT"
)
func limaHome() (string, error) {
@@ -96,7 +96,7 @@ type limaVM struct {
home string
// network between host and the vm
network network.Manager
daemon daemon.Manager
}
func (l limaVM) Dependencies() []string {
@@ -105,14 +105,14 @@ func (l limaVM) Dependencies() []string {
}
}
func (l *limaVM) prepareNetwork(ctx context.Context, conf config.Network) (context.Context, error) {
func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.Context, error) {
// limited to macOS for now
if !util.MacOS() {
return ctx, nil
}
ctxKeyVmnet := network.CtxKey(vmnet.Name())
ctxKeyGVProxy := network.CtxKey(gvproxy.Name())
ctxKeyVmnet := daemon.CtxKey(vmnet.Name())
ctxKeyGVProxy := daemon.CtxKey(gvproxy.Name())
// use a nested chain for convenience
a := l.Init(ctx)
@@ -121,10 +121,10 @@ func (l *limaVM) prepareNetwork(ctx context.Context, conf config.Network) (conte
a.Stage("preparing network")
a.Add(func() error {
ctx = context.WithValue(ctx, ctxKeyGVProxy, true)
if conf.Address {
if conf.Network.Address {
ctx = context.WithValue(ctx, ctxKeyVmnet, true)
}
deps, root := l.network.Dependencies(ctx)
deps, root := l.daemon.Dependencies(ctx)
if deps.Installed() {
return nil
}
@@ -139,14 +139,14 @@ func (l *limaVM) prepareNetwork(ctx context.Context, conf config.Network) (conte
})
a.Add(func() error {
return l.network.Start(ctx)
return l.daemon.Start(ctx)
})
// delay to ensure that the vmnet is running
statusKey := "networkStatus"
if conf.Address {
statusKey := struct{ key string }{key: "networkStatus"}
if conf.Network.Address {
a.Retry("", time.Second*3, 5, func(i int) error {
s, err := l.network.Running(ctx)
s, err := l.daemon.Running(ctx)
ctx = context.WithValue(ctx, statusKey, s)
if err != nil {
return err
@@ -166,7 +166,7 @@ func (l *limaVM) prepareNetwork(ctx context.Context, conf config.Network) (conte
// network failure is not fatal
if err := a.Exec(); err != nil {
func() {
status, ok := ctx.Value(statusKey).(network.Status)
status, ok := ctx.Value(statusKey).(daemon.Status)
if !ok {
return
}
@@ -177,7 +177,7 @@ func (l *limaVM) prepareNetwork(ctx context.Context, conf config.Network) (conte
for _, p := range status.Processes {
if !p.Running {
ctx = context.WithValue(ctx, network.CtxKey(p.Name), false)
ctx = context.WithValue(ctx, daemon.CtxKey(p.Name), false)
log.Warnln(fmt.Errorf("error starting %s: %w", p.Name, err))
}
}
@@ -185,7 +185,7 @@ func (l *limaVM) prepareNetwork(ctx context.Context, conf config.Network) (conte
}
// preserve gvproxy context
if gvproxyEnabled, _ := ctx.Value(network.CtxKey(gvproxy.Name())).(bool); gvproxyEnabled {
if gvproxyEnabled, _ := ctx.Value(daemon.CtxKey(gvproxy.Name())).(bool); gvproxyEnabled {
l.host = l.host.WithEnv(gvproxy.SubProcessEnvVar + "=1")
}
@@ -200,7 +200,7 @@ func (l *limaVM) Start(ctx context.Context, conf config.Config) error {
}
a.Add(func() (err error) {
ctx, err = l.prepareNetwork(ctx, conf.Network)
ctx, err = l.startDaemon(ctx, conf)
return err
})
@@ -255,7 +255,7 @@ func (l limaVM) resume(ctx context.Context, conf config.Config) error {
}
a.Add(func() (err error) {
ctx, err = l.prepareNetwork(ctx, conf.Network)
ctx, err = l.startDaemon(ctx, conf)
return err
})
@@ -281,7 +281,7 @@ func (l limaVM) resume(ctx context.Context, conf config.Config) error {
}
func (l limaVM) Running(ctx context.Context) bool {
i, err := Instance()
i, err := limautil.Instance()
if err != nil {
logrus.Trace(fmt.Errorf("error retrieving running instance: %w", err))
return false
@@ -301,7 +301,7 @@ func (l limaVM) Stop(ctx context.Context, force bool) error {
if util.MacOS() {
a.Retry("", time.Second*1, 10, func(retryCount int) error {
return l.network.Stop(ctx)
return l.daemon.Stop(ctx)
})
}
@@ -320,7 +320,7 @@ func (l limaVM) Teardown(ctx context.Context) error {
if util.MacOS() {
a.Retry("", time.Second*1, 10, func(retryCount int) error {
return l.network.Stop(ctx)
return l.daemon.Stop(ctx)
})
}
@@ -1,4 +1,4 @@
package lima
package limautil
import (
"bufio"
@@ -16,6 +16,44 @@ import (
"gopkg.in/yaml.v3"
)
const (
LayerEnvVar = "COLIMA_LAYER_SSH_PORT"
)
// Instance returns current instance.
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()
}
// IPAddress returns the ip address for profile.
// It returns the PTP address if networking is enabled or falls back to 127.0.0.1.
// It is guaranteed to return a value.
// TODO: unnecessary round-trip is done to get instance details from Lima.
func IPAddress(profileID string) string {
// profile = toUserFriendlyName(profile)
const fallback = "127.0.0.1"
instance, err := getInstance(profileID)
if err != nil {
return fallback
}
if len(instance.Network) > 0 {
return getIPAddress(profileID, instance.Network[0].Interface)
}
return fallback
}
// InstanceInfo is the information about a Lima instance
type InstanceInfo struct {
Name string `json:"name,omitempty"`
@@ -61,154 +99,6 @@ func (i InstanceInfo) Config() (config.Config, error) {
return c, nil
}
// Lima statuses
const (
limaStatusRunning = "Running"
)
// Instance returns current instance.
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()
}
func getInstance(profileID string) (InstanceInfo, error) {
var i InstanceInfo
var buf bytes.Buffer
cmd := cli.Command("limactl", "list", profileID, "--json")
cmd.Stderr = nil
cmd.Stdout = &buf
if err := cmd.Run(); err != nil {
return i, fmt.Errorf("error retrieving instance: %w", err)
}
if err := json.Unmarshal(buf.Bytes(), &i); err != nil {
return i, fmt.Errorf("error retrieving instance: %w", err)
}
return i, nil
}
// Instances returns Lima instances created by colima.
func Instances() ([]InstanceInfo, error) {
var buf bytes.Buffer
cmd := cli.Command("limactl", "list", "--json")
cmd.Stderr = nil
cmd.Stdout = &buf
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("error retrieving instances: %w", err)
}
var instances []InstanceInfo
scanner := bufio.NewScanner(&buf)
for scanner.Scan() {
var i InstanceInfo
line := scanner.Bytes()
if err := json.Unmarshal(line, &i); err != nil {
return nil, fmt.Errorf("error retrieving instances: %w", err)
}
// limit to colima instances
if !strings.HasPrefix(i.Name, "colima") {
continue
}
if i.Running() {
if len(i.Network) > 0 && i.Network[0].Interface != "" {
i.IPAddress = getIPAddress(i.Name, i.Network[0].Interface)
}
conf, _ := i.Config()
i.Runtime = getRuntime(conf)
}
// rename to local friendly names
i.Name = config.Profile(i.Name).ShortName
// network is low level, remove
i.Network = nil
instances = append(instances, i)
}
return instances, nil
}
func getIPAddress(profileID, interfaceName string) string {
var buf bytes.Buffer
// TODO: this should be less hacky
cmd := cli.Command("limactl", "shell", profileID, "sh", "-c",
`ifconfig `+interfaceName+` | grep "inet addr:" | awk -F' ' '{print $2}' | awk -F':' '{print $2}'`)
cmd.Stderr = nil
cmd.Stdout = &buf
_ = cmd.Run()
return strings.TrimSpace(buf.String())
}
func ubuntuSSHPort(profileID string) (int, error) {
var buf bytes.Buffer
cmd := cli.Command("limactl", "shell", profileID, "--", "sh", "-c", "echo $"+layerEnvVar)
cmd.Stdout = &buf
if err := cmd.Run(); err != nil {
return 0, fmt.Errorf("cannot retrieve ubuntu layer SSH port: %w", err)
}
port, err := strconv.Atoi(strings.TrimSpace(buf.String()))
if err != nil {
return 0, fmt.Errorf("invalid ubuntu layer SSH port '%d': %w", port, err)
}
return port, nil
}
func getRuntime(conf config.Config) string {
var runtime string
switch conf.Runtime {
case "docker":
runtime = "docker"
case "containerd":
runtime = "containerd"
default:
return ""
}
if conf.Kubernetes.Enabled {
runtime += "+k3s"
}
return runtime
}
// IPAddress returns the ip address for profile.
// It returns the PTP address if networking is enabled or falls back to 127.0.0.1.
// It is guaranteed to return a value.
// TODO: unnecessary round-trip is done to get instance details from Lima.
func IPAddress(profileID string) string {
// profile = toUserFriendlyName(profile)
const fallback = "127.0.0.1"
instance, err := getInstance(profileID)
if err != nil {
return fallback
}
if len(instance.Network) > 0 {
return getIPAddress(profileID, instance.Network[0].Interface)
}
return fallback
}
// ShowSSH runs the show-ssh command in Lima.
// returns the ssh output, if in layer, and an error if any
func ShowSSH(profileID string, layer bool, format string) (resp struct {
@@ -314,3 +204,114 @@ func replaceSSHConfig(conf string, name string, ip string, port int) string {
}
return out.String()
}
// Lima statuses
const (
limaStatusRunning = "Running"
)
func getInstance(profileID string) (InstanceInfo, error) {
var i InstanceInfo
var buf bytes.Buffer
cmd := cli.Command("limactl", "list", profileID, "--json")
cmd.Stderr = nil
cmd.Stdout = &buf
if err := cmd.Run(); err != nil {
return i, fmt.Errorf("error retrieving instance: %w", err)
}
if err := json.Unmarshal(buf.Bytes(), &i); err != nil {
return i, fmt.Errorf("error retrieving instance: %w", err)
}
return i, nil
}
// Instances returns Lima instances created by colima.
func Instances() ([]InstanceInfo, error) {
var buf bytes.Buffer
cmd := cli.Command("limactl", "list", "--json")
cmd.Stderr = nil
cmd.Stdout = &buf
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("error retrieving instances: %w", err)
}
var instances []InstanceInfo
scanner := bufio.NewScanner(&buf)
for scanner.Scan() {
var i InstanceInfo
line := scanner.Bytes()
if err := json.Unmarshal(line, &i); err != nil {
return nil, fmt.Errorf("error retrieving instances: %w", err)
}
// limit to colima instances
if !strings.HasPrefix(i.Name, "colima") {
continue
}
if i.Running() {
if len(i.Network) > 0 && i.Network[0].Interface != "" {
i.IPAddress = getIPAddress(i.Name, i.Network[0].Interface)
}
conf, _ := i.Config()
i.Runtime = getRuntime(conf)
}
// rename to local friendly names
i.Name = config.Profile(i.Name).ShortName
// network is low level, remove
i.Network = nil
instances = append(instances, i)
}
return instances, nil
}
func getIPAddress(profileID, interfaceName string) string {
var buf bytes.Buffer
// TODO: this should be less hacky
cmd := cli.Command("limactl", "shell", profileID, "sh", "-c",
`ifconfig `+interfaceName+` | grep "inet addr:" | awk -F' ' '{print $2}' | awk -F':' '{print $2}'`)
cmd.Stderr = nil
cmd.Stdout = &buf
_ = cmd.Run()
return strings.TrimSpace(buf.String())
}
func ubuntuSSHPort(profileID string) (int, error) {
var buf bytes.Buffer
cmd := cli.Command("limactl", "shell", profileID, "--", "sh", "-c", "echo $"+LayerEnvVar)
cmd.Stdout = &buf
if err := cmd.Run(); err != nil {
return 0, fmt.Errorf("cannot retrieve ubuntu layer SSH port: %w", err)
}
port, err := strconv.Atoi(strings.TrimSpace(buf.String()))
if err != nil {
return 0, fmt.Errorf("invalid ubuntu layer SSH port '%d': %w", port, err)
}
return port, nil
}
func getRuntime(conf config.Config) string {
var runtime string
switch conf.Runtime {
case "docker", "containerd":
runtime = conf.Runtime
default:
return ""
}
if conf.Kubernetes.Enabled {
runtime += "+k3s"
}
return runtime
}
@@ -1,70 +0,0 @@
package daemon
import (
"context"
"os"
"os/exec"
"testing"
"time"
)
func TestStart(t *testing.T) {
var addresses = []string{
"localhost",
"127.0.0.1",
}
var processes []Process
for _, add := range addresses {
processes = append(processes, &pinger{address: add})
}
timeout := time.Second * 30
ctx, cancel := context.WithTimeout(context.Background(), timeout)
// start the processes
done := make(chan error, 1)
go func() {
done <- Run(ctx, processes...)
}()
cancel()
select {
case <-ctx.Done():
if err := ctx.Err(); err != context.Canceled {
t.Error(err)
}
case err := <-done:
t.Error(err)
}
}
var _ Process = (*pinger)(nil)
type pinger struct {
address string
}
func (p pinger) Alive(ctx context.Context) error {
return nil
}
// Name implements BgProcess
func (pinger) Name() string { return "pinger" }
// Start implements BgProcess
func (p *pinger) Start(ctx context.Context) error {
return p.run(ctx, "ping", "-c10", p.address)
}
// Start implements BgProcess
func (p *pinger) Dependencies() ([]Dependency, bool) { return nil, false }
func (p *pinger) run(ctx context.Context, command string, args ...string) error {
cmd := exec.CommandContext(ctx, command, args...)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
return cmd.Run()
}
+9 -8
View File
@@ -10,15 +10,16 @@ import (
"strconv"
"strings"
"github.com/abiosoft/colima/environment/vm/lima/network"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/gvproxy"
"github.com/abiosoft/colima/environment/vm/lima/network/daemon/vmnet"
"github.com/abiosoft/colima/daemon"
"github.com/abiosoft/colima/daemon/process/gvproxy"
"github.com/abiosoft/colima/daemon/process/vmnet"
"gopkg.in/yaml.v3"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/embedded"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/container/docker"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/abiosoft/colima/util"
"github.com/sirupsen/logrus"
)
@@ -60,11 +61,11 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) {
l.DNS = conf.Network.DNS
if len(l.DNS) == 0 {
gvProxyEnabled, _ := ctx.Value(network.CtxKey(gvproxy.Name())).(bool)
gvProxyEnabled, _ := ctx.Value(daemon.CtxKey(gvproxy.Name())).(bool)
if gvProxyEnabled {
l.DNS = append(l.DNS, net.ParseIP(gvproxy.GatewayIP))
}
reachableIPAddress, _ := ctx.Value(network.CtxKey(vmnet.Name())).(bool)
reachableIPAddress, _ := ctx.Value(daemon.CtxKey(vmnet.Name())).(bool)
if reachableIPAddress {
l.DNS = append(l.DNS, net.ParseIP(vmnet.NetGateway))
}
@@ -84,7 +85,7 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) {
// network setup
{
reachableIPAddress, _ := ctx.Value(network.CtxKey(vmnet.Name())).(bool)
reachableIPAddress, _ := ctx.Value(daemon.CtxKey(vmnet.Name())).(bool)
// network is currently limited to macOS.
// gvproxy is cross platform but not needed on Linux as slirp is only erratic on macOS.
@@ -128,7 +129,7 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) {
values.Vmnet.Interface = vmnet.NetInterface
}
gvProxyEnabled, _ := ctx.Value(network.CtxKey(gvproxy.Name())).(bool)
gvProxyEnabled, _ := ctx.Value(daemon.CtxKey(gvproxy.Name())).(bool)
if gvProxyEnabled {
values.GVProxy.Enabled = true
values.GVProxy.MacAddress = strings.ToUpper(gvproxy.MacAddress())
@@ -185,7 +186,7 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) {
if conf.Layer {
port := util.RandomAvailablePort()
// set port for future retrieval
l.Env[layerEnvVar] = strconv.Itoa(port)
l.Env[limautil.LayerEnvVar] = strconv.Itoa(port)
// forward port
l.PortForwards = append(l.PortForwards,
PortForward{
+2 -1
View File
@@ -35,9 +35,10 @@ require (
require (
github.com/containers/gvisor-tap-vsock v0.3.0
github.com/docker/go-units v0.4.0
github.com/fsnotify/fsnotify v1.5.4
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
)
+4 -1
View File
@@ -219,6 +219,8 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -890,8 +892,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+4 -2
View File
@@ -16,10 +16,12 @@ go build \
-o "$OUTPUT_DIR/$OUTPUT_BIN" \
./cmd/colima
# sha256sum is not on macOS by default, use shasum
# sha256sum is not on macOS by default, use shasum if missing
SHA256SUM=sha256sum
if [[ "$OSTYPE" == "darwin"* ]]; then
SHA256SUM="shasum -a 256"
if ! command -v "${SHA256SUM}" &>/dev/null; then
SHA256SUM="shasum -a 256"
fi
fi
cd "${OUTPUT_DIR}" && ${SHA256SUM} "${OUTPUT_BIN}" >"${OUTPUT_BIN}.sha256sum"
+13
View File
@@ -0,0 +1,13 @@
{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
# nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs.buildPackages; [
go_1_18
git
coreutils
];
shellHook = ''
export CGO_ENABLED=0
'';
}