diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 19251a11..f9fb71fc 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,6 +14,8 @@ on: - "**/*.nix" - "**/*.lock" +permissions: write-all + jobs: build-linux: runs-on: ubuntu-latest @@ -120,13 +122,12 @@ jobs: run: > tag="${GITHUB_REF##*/}" - hub release create - -a _output/binaries/colima-Darwin-x86_64 - -a _output/binaries/colima-Darwin-x86_64.sha256sum - -a _output/binaries/colima-Darwin-arm64 - -a _output/binaries/colima-Darwin-arm64.sha256sum - -a _output/binaries/colima-Linux-x86_64 - -a _output/binaries/colima-Linux-x86_64.sha256sum - -a _output/binaries/colima-Linux-aarch64 - -a _output/binaries/colima-Linux-aarch64.sha256sum - -m "${tag}" --draft "${tag}" + gh release create "${tag}" --draft --title "${tag}" + _output/binaries/colima-Darwin-x86_64 + _output/binaries/colima-Darwin-x86_64.sha256sum + _output/binaries/colima-Darwin-arm64 + _output/binaries/colima-Darwin-arm64.sha256sum + _output/binaries/colima-Linux-x86_64 + _output/binaries/colima-Linux-x86_64.sha256sum + _output/binaries/colima-Linux-aarch64 + _output/binaries/colima-Linux-aarch64.sha256sum diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 5a399361..72352d5f 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -119,7 +119,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} - name: Start Colima - run: colima start --runtime docker --layer + run: colima start --runtime docker - name: Delay run: sleep 10 @@ -128,62 +128,11 @@ jobs: run: docker ps && docker info - name: Validate DNS - run: colima ssh --layer=false -- nslookup host.docker.internal + run: colima ssh -- nslookup host.docker.internal - name: Build Image run: docker build integration - - name: Validate Layer - run: colima ssh --layer -- cat /etc/os-release - - - name: Stop - run: colima stop - - - name: Teardown - run: colima delete -f - - docker-aarch64: - runs-on: macos-13 - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.21" - - - name: Install CLI deps - run: | - brew install kubectl docker coreutils lima - - - name: Build and Install - run: make && sudo make install - - - name: tmate debugging session - uses: mxschmitt/action-tmate@v3 - with: - limit-access-to-actor: true - github-token: ${{ secrets.GITHUB_TOKEN }} - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} - - - name: Start Colima - run: colima start --runtime docker --arch aarch64 --layer - - - name: Delay - run: sleep 20 - - - name: Validate Docker - run: docker ps && docker info - - - name: Build Image - run: docker build integration - - - name: Validate DNS - run: colima ssh --layer=false -- nslookup host.docker.internal - - - name: Validate Layer - run: colima ssh --layer -- cat /etc/os-release - - name: Stop run: colima stop @@ -215,7 +164,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} - name: Start Colima - run: colima start --runtime containerd --layer + run: colima start --runtime containerd - name: Delay run: sleep 10 @@ -224,64 +173,14 @@ jobs: run: colima nerdctl ps && colima nerdctl info - name: Validate DNS - run: colima ssh --layer=false -- nslookup host.docker.internal + run: colima ssh -- nslookup host.docker.internal - name: Build Image run: colima nerdctl -- build integration - - name: Validate Layer - run: colima ssh --layer -- cat /etc/os-release - - name: Stop run: colima stop - name: Teardown run: colima delete -f - containerd-aarch64: - runs-on: macos-13 - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.21" - - - name: Install CLI deps - run: | - brew install kubectl docker coreutils lima - - - name: Build and Install - run: make && sudo make install - - - name: tmate debugging session - uses: mxschmitt/action-tmate@v3 - with: - limit-access-to-actor: true - github-token: ${{ secrets.GITHUB_TOKEN }} - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} - - - name: Start Colima - run: colima start --runtime containerd --arch aarch64 --layer - - - name: Validate Containerd - run: colima nerdctl ps && colima nerdctl info - - - name: Validate DNS - run: colima ssh --layer=false -- nslookup host.docker.internal - - - name: Build Image - run: colima nerdctl -- build integration - - - name: Delay - run: sleep 10 - - - name: Validate Layer - run: colima ssh --layer -- cat /etc/os-release - - - name: Stop - run: colima stop - - - name: Teardown - run: colima delete -f diff --git a/Makefile b/Makefile index cb8d6561..575eedf8 100644 --- a/Makefile +++ b/Makefile @@ -69,3 +69,7 @@ nix-derivation-shell: $(eval DERIVATION=$(shell nix-build)) echo $(DERIVATION) | grep ^/nix nix-shell -p $(DERIVATION) + +.PHONY: integration +integration: build + GOARCH=$(GOARCH) COLIMA_BINARY=$(OUTPUT_DIR)/$(OUTPUT_BIN) scripts/integration.sh diff --git a/README.md b/README.md index 47eb1f4b..c3ffbc69 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ brew install --HEAD colima ### Upgrading -If upgrading from v0.4.6 or lower, it is required to start afresh by deleting existing instance. +If upgrading from v0.5.6 or lower, it is required to start afresh by deleting existing instance. ```sh colima delete # delete existing instance @@ -158,7 +158,6 @@ Check [here](docs/FAQ.md) for Frequently Asked Questions. ## Help Wanted - Documentation (wiki pages) -- Project Logo ## License diff --git a/app/app.go b/app/app.go index edada02f..ad48a6f6 100644 --- a/app/app.go +++ b/app/app.go @@ -10,13 +10,11 @@ import ( "path/filepath" "strings" - "github.com/abiosoft/colima/cli" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/config/configmanager" "github.com/abiosoft/colima/environment" "github.com/abiosoft/colima/environment/container/docker" "github.com/abiosoft/colima/environment/container/kubernetes" - "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" @@ -30,7 +28,7 @@ type App interface { Start(config.Config) error Stop(force bool) error Delete() error - SSH(layer bool, args ...string) error + SSH(args ...string) error Status(extended bool) error Version() error Runtime() (string, error) @@ -83,14 +81,6 @@ func (c colimaApp) Start(conf config.Config) error { } containers = append(containers, env) } - // ubuntu layer should come last - if conf.Layer { - env, err := c.containerEnvironment(ubuntu.Name) - if err != nil { - return err - } - containers = append(containers, env) - } // the order for start is: // vm start -> container runtime provision -> container runtime start @@ -222,7 +212,7 @@ func (c colimaApp) Delete() error { return nil } -func (c colimaApp) SSH(layer bool, args ...string) error { +func (c colimaApp) SSH(args ...string) error { ctx := context.Background() if !c.guest.Running(ctx) { return fmt.Errorf("%s not running", config.CurrentProfile().DisplayName) @@ -270,34 +260,8 @@ func (c colimaApp) SSH(layer bool, args ...string) error { } } - if !layer { - return c.guest.SSH(workDir, args...) - } - - conf, err := limautil.InstanceConfig() - if err != nil { - return err - } - if !conf.Layer { - return c.guest.SSH(workDir, args...) - } - - resp, err := limautil.ShowSSH(config.CurrentProfile().ID, layer) - if err != nil { - return fmt.Errorf("error getting ssh config: %w", err) - } - if !resp.Layer { - return c.guest.RunInteractive(args...) - } - - if len(args) > 0 { - args = append([]string{"-q", "-t", config.CurrentProfile().ID, "--"}, args...) - } else if workDir != "" { - args = []string{"-q", "-t", config.CurrentProfile().ID, "--", "cd " + workDir + " 2> /dev/null; \"$SHELL\" --login"} - } - - args = append([]string{"-F", resp.File.Colima}, args...) - return cli.CommandInteractive("ssh", args...).Run() + guest := lima.New(host.New()) + return guest.SSH(workDir, args...) } func (c colimaApp) Status(extended bool) error { @@ -341,8 +305,6 @@ func (c colimaApp) Status(extended bool) error { // additional details if extended { - log.Println("networkDriver:", conf.Network.Driver) - if inst, err := limautil.Instance(); err == nil { log.Println("cpu:", inst.CPU) log.Println("mem:", units.BytesSize(float64(inst.Memory))) @@ -370,8 +332,6 @@ func (c colimaApp) Version() error { case kubernetes.Name: kube = cont continue - case ubuntu.Name: - continue } fmt.Println() @@ -436,11 +396,6 @@ func (c colimaApp) currentContainerEnvironments(ctx context.Context) ([]environm containers = append(containers, k) } - // detect and add ubuntu layer - if u, err := c.containerEnvironment(ubuntu.Name); err == nil && u.Running(ctx) { - containers = append(containers, u) - } - return containers, nil } @@ -481,13 +436,7 @@ func generateSSHConfig(modifySSHConfig bool) error { } profile := config.Profile(i.Name) - conf, err := i.Config() - if err != nil { - log.Trace(fmt.Errorf("error retrieving profile config for '%s': %w", i.Name, err)) - continue - } - - resp, err := limautil.ShowSSH(profile.ID, conf.Layer) + resp, err := limautil.ShowSSH(profile.ID) if err != nil { log.Trace(fmt.Errorf("error retrieving SSH config for '%s': %w", i.Name, err)) continue diff --git a/cli/chain.go b/cli/chain.go index a544df96..03ae921e 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -79,6 +79,9 @@ type ActiveCommandChain struct { log *log.Entry } +// Logger returns the logger for the command chain. +func (a *ActiveCommandChain) Logger() *log.Entry { return a.log } + // Add adds a new function to the runner. func (a *ActiveCommandChain) Add(f func() error) { a.funcs = append(a.funcs, cFunc{f: f}) diff --git a/cmd/colima/main.go b/cmd/colima/main.go index cdba869d..fa9d60f5 100644 --- a/cmd/colima/main.go +++ b/cmd/colima/main.go @@ -1,69 +1,13 @@ package main import ( - "os" - "os/exec" - "path/filepath" - "strconv" - _ "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/cmd/root" - "github.com/abiosoft/colima/config" - "github.com/abiosoft/colima/daemon/process/gvproxy" ) func main() { - _, cmd := filepath.Split(os.Args[0]) - switch cmd { - case "qemu-system-x86_64", "qemu-system-aarch64": - qemuWrapper(cmd) - default: - root.Execute() - } -} - -func qemuWrapper(qemu string) { - if profile := os.Getenv(config.SubprocessProfileEnvVar); profile != "" { - config.SetProfile(profile) - } - - gvproxyInfo := gvproxy.Info() - - // check if qemu is meant to run by lima - // decided by -pidfile flag - qemuRunning := false - for _, arg := range os.Args { - if arg == "-pidfile" { - qemuRunning = true - break - } - } - - args := os.Args[1:] // forward all args - - gvproxyEnabled, _ := strconv.ParseBool(os.Getenv(gvproxy.SubProcessEnvVar)) - - if qemuRunning && gvproxyEnabled { - args = append(args, - "-netdev", "stream,id=vlan,addr.type=unix,addr.path="+gvproxyInfo.Socket.File(), - "-device", "virtio-net-pci,netdev=vlan,mac="+gvproxyInfo.MacAddress, - ) - } - - cmd := exec.Command(qemu, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - - err := cmd.Run() - if err != nil { - if err, ok := err.(*exec.ExitError); ok { - os.Exit(err.ExitCode()) - } - os.Exit(1) - } - + root.Execute() } diff --git a/cmd/daemon/cmd.go b/cmd/daemon/cmd.go index a39b5897..b266a931 100644 --- a/cmd/daemon/cmd.go +++ b/cmd/daemon/cmd.go @@ -7,7 +7,6 @@ import ( "github.com/abiosoft/colima/cmd/root" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/daemon/process" - "github.com/abiosoft/colima/daemon/process/gvproxy" "github.com/abiosoft/colima/daemon/process/inotify" "github.com/abiosoft/colima/daemon/process/vmnet" "github.com/abiosoft/colima/environment/host" @@ -35,9 +34,6 @@ var startCmd = &cobra.Command{ if daemonArgs.vmnet { processes = append(processes, vmnet.New()) } - if daemonArgs.gvproxy.enabled { - processes = append(processes, gvproxy.New(daemonArgs.gvproxy.dnsHosts)) - } if daemonArgs.inotify.enabled { processes = append(processes, inotify.New()) guest := lima.New(host.New()) @@ -84,10 +80,6 @@ var statusCmd = &cobra.Command{ var daemonArgs struct { vmnet bool - gvproxy struct { - enabled bool - dnsHosts map[string]string - } inotify struct { enabled bool dirs []string @@ -105,8 +97,6 @@ func init() { daemonCmd.AddCommand(statusCmd) startCmd.Flags().BoolVar(&daemonArgs.vmnet, "vmnet", false, "start vmnet") - startCmd.Flags().BoolVar(&daemonArgs.gvproxy.enabled, "gvproxy", false, "start gvproxy") - startCmd.Flags().StringToStringVar(&daemonArgs.gvproxy.dnsHosts, "gvproxy-hosts", nil, "DNS hosts for gvproxy") startCmd.Flags().BoolVar(&daemonArgs.inotify.enabled, "inotify", false, "start inotify") startCmd.Flags().StringSliceVar(&daemonArgs.inotify.dirs, "inotify-dir", nil, "set inotify directories") startCmd.Flags().StringVar(&daemonArgs.inotify.runtime, "inotify-runtime", "docker", "set runtime") diff --git a/cmd/nerdctl.go b/cmd/nerdctl.go index 9dc8c81b..8efe367b 100644 --- a/cmd/nerdctl.go +++ b/cmd/nerdctl.go @@ -46,7 +46,7 @@ It is recommended to specify '--' to differentiate from Colima flags. } nerdctlArgs := append([]string{"sudo", "nerdctl"}, args...) - return app.SSH(false, nerdctlArgs...) + return app.SSH(nerdctlArgs...) }, } diff --git a/cmd/ssh-config.go b/cmd/ssh-config.go index c316319f..3f568cb0 100644 --- a/cmd/ssh-config.go +++ b/cmd/ssh-config.go @@ -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 := limautil.ShowSSH(config.CurrentProfile().ID, sshConfigCmdArgs.layer) + resp, err := limautil.ShowSSH(config.CurrentProfile().ID) if err == nil { fmt.Println(resp.Output) } @@ -24,12 +24,6 @@ var sshConfigCmd = &cobra.Command{ }, } -var sshConfigCmdArgs struct { - layer bool -} - func init() { root.Cmd().AddCommand(sshConfigCmd) - - sshConfigCmd.Flags().BoolVarP(&sshConfigCmdArgs.layer, "layer", "l", true, "config for the Ubuntu layer (if enabled)") } diff --git a/cmd/ssh.go b/cmd/ssh.go index 7535dccb..499c47c5 100644 --- a/cmd/ssh.go +++ b/cmd/ssh.go @@ -21,7 +21,7 @@ e.g. 'colima ssh -- htop' will run htop. It is recommended to specify '--' to differentiate from colima flags.`, RunE: func(cmd *cobra.Command, args []string) error { - return newApp().SSH(sshCmdArgs.layer, args...) + return newApp().SSH(args...) }, } diff --git a/cmd/start.go b/cmd/start.go index b488f6f6..c4a3836c 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -14,7 +14,7 @@ import ( "github.com/abiosoft/colima/cmd/root" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/config/configmanager" - "github.com/abiosoft/colima/daemon/process/gvproxy" + "github.com/abiosoft/colima/core" "github.com/abiosoft/colima/embedded" "github.com/abiosoft/colima/environment" "github.com/abiosoft/colima/environment/container/docker" @@ -87,6 +87,11 @@ Run 'colima template' to set the default configurations or 'colima start --edit' return start(app, conf) }, PreRunE: func(cmd *cobra.Command, args []string) error { + // validate Lima version + if err := core.LimaVersionSupported(); err != nil { + return fmt.Errorf("lima compatibility error: %w", err) + } + // combine args and current config file(if any) prepareConfig(cmd) @@ -110,8 +115,6 @@ const ( defaultDisk = 60 defaultKubernetesVersion = kubernetes.DefaultVersion - defaultNetworkDriver = "gvproxy" - defaultVMType = "qemu" defaultMountTypeQEMU = "sshfs" defaultMountTypeVZ = "virtiofs" @@ -136,8 +139,8 @@ var startCmdArgs struct { func init() { runtimes := strings.Join(environment.ContainerRuntimes(), ", ") - networkDrivers := strings.Join([]string{gvproxy.Name, "slirp"}, ", ") defaultArch := string(environment.HostArch()) + defaultHostname := config.CurrentProfile().ID mounts := strings.Join([]string{defaultMountTypeQEMU, "9p", "virtiofs"}, ", ") types := strings.Join([]string{defaultVMType, "vz"}, ", ") @@ -151,10 +154,10 @@ func init() { startCmd.Flags().IntVarP(&startCmdArgs.Disk, "disk", "d", defaultDisk, "disk size in GiB") startCmd.Flags().StringVarP(&startCmdArgs.Arch, "arch", "a", defaultArch, "architecture (aarch64, x86_64)") startCmd.Flags().BoolVarP(&startCmdArgs.Flags.Foreground, "foreground", "f", false, "Keep colima in the foreground") + startCmd.Flags().StringVar(&startCmdArgs.Hostname, "hostname", defaultHostname, "custom hostname for the virtual machine") // network if util.MacOS() { - startCmd.Flags().StringVar(&startCmdArgs.Network.Driver, "network-driver", defaultNetworkDriver, "network driver to use ("+networkDrivers+")") startCmd.Flags().BoolVar(&startCmdArgs.Network.Address, "network-address", false, "assign reachable IP address to the VM") } if util.MacOS13OrNewer() { @@ -171,7 +174,7 @@ func init() { // mounts startCmd.Flags().StringSliceVarP(&startCmdArgs.Flags.Mounts, "mount", "V", nil, "directories to mount, suffix ':w' for writable") startCmd.Flags().StringVar(&startCmdArgs.MountType, "mount-type", defaultMountTypeQEMU, "volume driver for the mount ("+mounts+")") - startCmd.Flags().BoolVar(&startCmdArgs.MountINotify, "mount-inotify", false, "propagate inotify file events to the VM") + startCmd.Flags().BoolVar(&startCmdArgs.MountINotify, "mount-inotify", true, "propagate inotify file events to the VM") // ssh agent startCmd.Flags().BoolVarP(&startCmdArgs.ForwardAgent, "ssh-agent", "s", false, "forward SSH agent to the VM") @@ -188,18 +191,12 @@ func init() { startCmd.Flag("with-kubernetes").Hidden = true startCmd.Flag("kubernetes-disable").Hidden = true - // layer - startCmd.Flags().BoolVarP(&startCmdArgs.Layer, "layer", "l", false, "enable Ubuntu container layer") - // env startCmd.Flags().StringToStringVar(&startCmdArgs.Env, "env", nil, "environment variables for the VM") // dns startCmd.Flags().IPSliceVarP(&startCmdArgs.Network.DNSResolvers, "dns", "n", nil, "DNS resolvers for the VM") startCmd.Flags().StringSliceVar(&startCmdArgs.Flags.DNSHosts, "dns-host", nil, "custom DNS names to provide to resolver") - - // cgroups v2 workaround - startCmd.Flags().BoolVar(&startCmdArgs.TempCgroupsV2, "cgroups-v2", false, "cgroups v2 workaround for docker-compose") } func dnsHostsFromFlag(hosts []string) map[string]string { @@ -247,10 +244,6 @@ func setDefaults(cmd *cobra.Command) { startCmdArgs.VMType = defaultVMType } - if startCmdArgs.Network.Driver == "" { - startCmdArgs.Network.Driver = defaultNetworkDriver - } - if util.MacOS13OrNewer() { // changing to vz implies changing mount type to virtiofs if cmd.Flag("vm-type").Changed && startCmdArgs.VMType == "vz" && !cmd.Flag("mount-type").Changed { @@ -291,8 +284,8 @@ func setConfigDefaults(conf *config.Config) { } } - if conf.Network.Driver == "" { - conf.Network.Driver = defaultNetworkDriver + if conf.Hostname == "" { + conf.Hostname = config.CurrentProfile().ID } } @@ -395,22 +388,15 @@ func prepareConfig(cmd *cobra.Command) { if !cmd.Flag("env").Changed { startCmdArgs.Env = current.Env } - if !cmd.Flag("layer").Changed { - startCmdArgs.Layer = current.Layer + if !cmd.Flag("hostname").Changed { + startCmdArgs.Hostname = current.Hostname } if !cmd.Flag("activate").Changed { if current.ActivateRuntime != nil { // backward compatibility for `activate` startCmdArgs.ActivateRuntime = current.ActivateRuntime } } - // cgroups v2 temp workaround - if !cmd.Flag("cgroups-v2").Changed { - startCmdArgs.TempCgroupsV2 = current.TempCgroupsV2 - } if util.MacOS() { - if !cmd.Flag("network-driver").Changed { - startCmdArgs.Network.Driver = current.Network.Driver - } if !cmd.Flag("network-address").Changed { startCmdArgs.Network.Address = current.Network.Address } diff --git a/colima.nix b/colima.nix index f1ff6f85..8458f0bf 100644 --- a/colima.nix +++ b/colima.nix @@ -2,12 +2,12 @@ with pkgs; -buildGo119Module { +buildGo120Module { name = "colima"; pname = "colima"; src = ./.; nativeBuildInputs = [ installShellFiles makeWrapper git ]; - vendorSha256 = "sha256-lsTvzGFoC3Brnr1Q0Hl0ZqEDfcTeQ8vWGe+xylTyvts="; + vendorSha256 = "sha256-7DIhSjHpaCyHyXKhR8KWQc2YGaD8CMq+BZHF4zIkL50="; CGO_ENABLED = 1; subPackages = [ "cmd/colima" ]; diff --git a/config/config.go b/config/config.go index e3292760..0e6b21bb 100644 --- a/config/config.go +++ b/config/config.go @@ -9,8 +9,7 @@ import ( ) const ( - AppName = "colima" - SubprocessProfileEnvVar = "COLIMA_PROFILE" + AppName = "colima" ) var profile = ProfileInfo{ID: AppName, DisplayName: AppName, ShortName: "default"} @@ -78,6 +77,7 @@ type Config struct { ForwardAgent bool `yaml:"forwardAgent,omitempty"` Network Network `yaml:"network,omitempty"` Env map[string]string `yaml:"env,omitempty"` // environment variables + Hostname string `yaml:"hostname"` // VM VMType string `yaml:"vmType,omitempty"` @@ -98,17 +98,11 @@ type Config struct { // Docker configuration Docker map[string]any `yaml:"docker,omitempty"` - // layer - Layer bool `yaml:"layer,omitempty"` - // provision scripts Provision []Provision `yaml:"provision,omitempty"` // SSH config generation SSHConfig bool `yaml:"sshConfig,omitempty"` - - // Temporary workaround for cgroups v2. - TempCgroupsV2 bool `yaml:"cgroupsV2,omitempty"` } // Kubernetes is kubernetes configuration @@ -123,7 +117,6 @@ type Network struct { Address bool `yaml:"address"` DNSResolvers []net.IP `yaml:"dns"` DNSHosts map[string]string `yaml:"dnsHosts"` - Driver string `yaml:"driver"` } // Mount is volume mount diff --git a/config/configmanager/config.go b/config/configmanager/config.go index 6c180e4a..02199f5c 100644 --- a/config/configmanager/config.go +++ b/config/configmanager/config.go @@ -57,13 +57,6 @@ func LoadFrom(file string) (config.Config, error) { // ValidateConfig validates config before we use it func ValidateConfig(c config.Config) error { - if util.MacOS() { - validnetworkDrivers := map[string]bool{"gvproxy": true, "slirp": true} - if _, ok := validnetworkDrivers[c.Network.Driver]; !ok { - return fmt.Errorf("invalid networkDriver: '%s'", c.Network.Driver) - } - } - validMountTypes := map[string]bool{"9p": true, "sshfs": true} if util.MacOS13OrNewer() { validMountTypes["virtiofs"] = true diff --git a/config/dirs.go b/config/dirs.go index ed7e9fb2..caf68361 100644 --- a/config/dirs.go +++ b/config/dirs.go @@ -8,8 +8,6 @@ import ( "github.com/abiosoft/colima/util" "github.com/abiosoft/colima/util/fsutil" - "github.com/abiosoft/colima/util/osutil" - "github.com/abiosoft/colima/util/shautil" "github.com/sirupsen/logrus" ) @@ -102,15 +100,13 @@ var ( }, } - wrapperDir = requiredDir{ + limaDir = requiredDir{ dir: func() (string, error) { dir, err := configBaseDir.dir() if err != nil { return "", err } - // generate unique directory for the current binary - uniqueDir := shautil.SHA1(osutil.Executable()) - return filepath.Join(dir, "_wrapper", uniqueDir.String()), nil + return filepath.Join(dir, "_lima"), nil }, } ) @@ -127,8 +123,8 @@ func CacheDir() string { return cacheDir.Dir() } // TemplatesDir returns the templates' directory. func TemplatesDir() string { return templatesDir.Dir() } -// WrapperDir returns the qemu wrapper directory. -func WrapperDir() string { return wrapperDir.Dir() } +// TemplatesDir returns the templates' directory. +func LimaDir() string { return limaDir.Dir() } const ConfigFileName = "colima.yaml" diff --git a/core/core.go b/core/core.go new file mode 100644 index 00000000..2a739751 --- /dev/null +++ b/core/core.go @@ -0,0 +1,138 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + + "github.com/abiosoft/colima/cli" + "github.com/abiosoft/colima/environment" + "github.com/abiosoft/colima/util/downloader" + "github.com/coreos/go-semver/semver" +) + +const ( + version = "v0.6.0-2" // version of colima-core to use. + limaVersion = "v0.18.0" // minimum Lima version supported + baseURL = "https://github.com/abiosoft/colima-core/releases/download/" + version + "/" +) + +type ( + hostActions = environment.HostActions + guestActions = environment.GuestActions +) + +func downloadSha(url string) *downloader.SHA { + return &downloader.SHA{ + Size: 512, + URL: url + ".sha512sum", + } +} + +// SetupBinfmt downloads and install binfmt +func SetupBinfmt(host hostActions, guest guestActions, arch environment.Arch) error { + qemuArch := environment.AARCH64 + if arch.Value().GoArch() == "arm64" { + qemuArch = environment.X8664 + } + + install := func() error { + if err := guest.Run("sh", "-c", "sudo QEMU_PRESERVE_ARGV0=1 /usr/bin/binfmt --install "+qemuArch.GoArch()); err != nil { + return fmt.Errorf("error installing binfmt: %w", err) + } + return nil + } + + // ignore download and extract if previously installed + if err := guest.RunQuiet("command", "-v", "binfmt"); err == nil { + return install() + } + + // download + url := baseURL + "binfmt-" + arch.Value().GoArch() + ".tar.gz" + dest := "/tmp/binfmt.tar.gz" + if err := downloader.Download(host, guest, downloader.Request{ + URL: url, + SHA: downloadSha(url), + Filename: dest, + }); err != nil { + return fmt.Errorf("error downloading binfmt: %w", err) + } + + // extract + if err := guest.Run("sh", "-c", + strings.NewReplacer( + "{file}", dest, + "{qemu_arch}", string(qemuArch), + ).Replace(`cd /tmp && tar xfz {file} && sudo chown root:root binfmt qemu-{qemu_arch} && sudo mv binfmt qemu-{qemu_arch} /usr/bin`), + ); err != nil { + return fmt.Errorf("error extracting binfmt: %w", err) + } + + return install() +} + +// SetupContainerdUtils downloads and install containerd utils. +func SetupContainerdUtils(host hostActions, guest guestActions, arch environment.Arch) error { + // ignore if already installed + if err := guest.RunQuiet("sh", "-c", "command -v nerdctl && stat /opt/cni/bin/flannel"); err == nil { + return nil + } + + // download + url := baseURL + "containerd-utils-" + arch.Value().GoArch() + ".tar.gz" + dest := "/tmp/containerd-utils.tar.gz" + if err := downloader.Download(host, guest, downloader.Request{ + URL: url, + SHA: downloadSha(url), + Filename: dest, + }); err != nil { + return fmt.Errorf("error downloading containerd-utils: %w", err) + } + + // extract + if err := guest.Run("sh", "-c", + strings.NewReplacer( + "{archive}", dest, + ).Replace(`cd /tmp && sudo tar Cxfz /usr/local {archive} && sudo mkdir -p /opt/cni && sudo mv /usr/local/libexec/cni /opt/cni/bin`), + ); err != nil { + return fmt.Errorf("error extracting containerd utils: %w", err) + } + + return nil +} + +// LimaVersionSupported checks if the currently installed Lima version is supported. +func LimaVersionSupported() error { + var values struct { + Version string `json:"version"` + } + var buf bytes.Buffer + cmd := cli.Command("limactl", "info") + cmd.Stdout = &buf + + if err := cmd.Run(); err != nil { + return fmt.Errorf("error checking Lima version: %w", err) + } + + if err := json.NewDecoder(&buf).Decode(&values); err != nil { + return fmt.Errorf("error decoding 'limactl info' json: %w", err) + } + // remove pre-release hyphen + if str := strings.SplitN(values.Version, "-", 2); len(str) > 0 { + values.Version = str[0] + } + + min := semver.New(strings.TrimPrefix(limaVersion, "v")) + current, err := semver.NewVersion(strings.TrimPrefix(values.Version, "v")) + if err != nil { + return fmt.Errorf("invalid semver version for Lima: %w", err) + } + + if min.Compare(*current) > 0 { + return fmt.Errorf("minimum Lima version supported is %s, current version is %s", limaVersion, values.Version) + } + + return nil +} diff --git a/daemon/daemon.go b/daemon/daemon.go index a3fdd8aa..71a6f2a2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -7,7 +7,6 @@ import ( "github.com/abiosoft/colima/cli" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/daemon/process" - "github.com/abiosoft/colima/daemon/process/gvproxy" "github.com/abiosoft/colima/daemon/process/inotify" "github.com/abiosoft/colima/daemon/process/vmnet" "github.com/abiosoft/colima/environment" @@ -107,12 +106,6 @@ func (l processManager) Start(ctx context.Context, conf config.Config) error { args = append(args, "--inotify-dir", p) } } - if conf.Network.Driver == gvproxy.Name { - args = append(args, "--gvproxy") - for host, ip := range conf.Network.DNSHosts { - args = append(args, "--gvproxy-hosts", host+"="+ip) - } - } if cli.Settings.Verbose { args = append(args, "--very-verbose") @@ -134,9 +127,6 @@ func processesFromConfig(conf config.Config) []process.Process { if conf.Network.Address { processes = append(processes, vmnet.New()) } - if conf.Network.Driver == gvproxy.Name { - processes = append(processes, gvproxy.New(conf.Network.DNSHosts)) - } if conf.MountINotify { processes = append(processes, inotify.New()) } diff --git a/daemon/process/gvproxy/deps.go b/daemon/process/gvproxy/deps.go deleted file mode 100644 index a09efd97..00000000 --- a/daemon/process/gvproxy/deps.go +++ /dev/null @@ -1,77 +0,0 @@ -package gvproxy - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/abiosoft/colima/daemon/process" - "github.com/abiosoft/colima/environment" - "github.com/abiosoft/colima/qemu" - "github.com/abiosoft/colima/util/fsutil" -) - -var _ process.Dependency = qemuBinsSymlinks{} - -type qemuBinsSymlinks struct{} - -func (q qemuBinsSymlinks) Installed() bool { - dir := qemu.LimaDir() - for _, bin := range qemu.Binaries { - bin = filepath.Join(dir.Bin(), bin) - if _, err := os.Stat(bin); err != nil { - return false - } - } - - return true -} - -func (q qemuBinsSymlinks) Install(host environment.HostActions) error { - dir := qemu.LimaDir() - if err := fsutil.MkdirAll(dir.Bin(), 0755); err != nil { - return fmt.Errorf("error preparing qemu wrapper bin directory: %w", err) - } - this, err := os.Executable() - if err != nil { - return fmt.Errorf("cannot retrieve current process: %w", err) - } - - for _, bin := range qemu.Binaries { - bin = filepath.Join(dir.Bin(), bin) - if err := host.Run("ln", "-sf", this, bin); err != nil { - return fmt.Errorf("error wrapping %s: %w", bin, err) - } - } - - return nil -} - -var _ process.Dependency = qemuShareDirSymlink{} - -type qemuShareDirSymlink struct{} - -func (q qemuShareDirSymlink) Installed() bool { - dir := qemu.LimaDir() - if _, err := os.Stat(dir.Share()); err != nil { - return false - } - return true -} - -func (q qemuShareDirSymlink) Install(host environment.HostActions) error { - limaDir := qemu.LimaDir() - if err := fsutil.MkdirAll(limaDir.Root(), 0755); err != nil { - return fmt.Errorf("error preparing qemu wrapper shared directory: %w", err) - } - - hostDir, err := qemu.HostDir() - if err != nil { - return fmt.Errorf("error retrieving qemu installation location: %w", err) - } - if err := host.Run("ln", "-sf", hostDir.Share(), limaDir.Share()); err != nil { - return fmt.Errorf("error wrapping qemu share directory: %w", err) - } - - return nil -} diff --git a/daemon/process/gvproxy/dnshosts.go b/daemon/process/gvproxy/dnshosts.go deleted file mode 100644 index f5fef8d2..00000000 --- a/daemon/process/gvproxy/dnshosts.go +++ /dev/null @@ -1,82 +0,0 @@ -package gvproxy - -import ( - "net" - "strings" - - "github.com/containers/gvisor-tap-vsock/pkg/types" -) - -func extractZones(hosts hostMap) (zones []types.Zone) { - list := make(map[string]types.Zone) - - for host := range hosts { - h := zoneHost(host) - - zone := types.Zone{Name: h.name()} - if existingZone, ok := list[h.name()]; ok { - zone = existingZone - } - - if h.recordName() == "" { - if zone.DefaultIP == nil { - zone.DefaultIP = hosts.hostIP(host) - } - } else { - zone.Records = append(zone.Records, types.Record{ - Name: h.recordName(), - IP: hosts.hostIP(host), - }) - } - - list[h.name()] = zone - } - - for _, zone := range list { - zones = append(zones, zone) - } - return -} - -type hostMap map[string]string - -func (z hostMap) hostIP(host string) net.IP { - for { - // check if host entry exists - h, ok := z[host] - if !ok || h == "" { - return nil - } - - // if it's a valid ip, return - if ip := net.ParseIP(h); ip != nil { - return ip - } - - // otherwise, a string i.e. another host - // loop through the process again. - host = h - } -} - -type zoneHost string - -func (z zoneHost) name() string { - i := z.dotIndex() - if i < 0 { - return string(z) - } - return string(z)[i+1:] + "." -} - -func (z zoneHost) recordName() string { - i := z.dotIndex() - if i < 0 { - return "" - } - return string(z)[:i] -} - -func (z zoneHost) dotIndex() int { - return strings.LastIndex(string(z), ".") -} diff --git a/daemon/process/gvproxy/dnshosts_test.go b/daemon/process/gvproxy/dnshosts_test.go deleted file mode 100644 index 89bede8b..00000000 --- a/daemon/process/gvproxy/dnshosts_test.go +++ /dev/null @@ -1,206 +0,0 @@ -package gvproxy - -import ( - "fmt" - "net" - "sort" - "testing" - - "github.com/containers/gvisor-tap-vsock/pkg/types" -) - -func Test_hostsMapIP(t *testing.T) { - hosts := hostMap{} - hosts["sample"] = "1.1.1.1" - hosts["another.sample"] = "1.2.2.1" - hosts["google.com"] = "8.8.8.8" - hosts["google.ae"] = "google.com" - hosts["google.ie"] = "google.ae" - - tests := []struct { - host string - want net.IP - }{ - {host: "sample", want: net.ParseIP("1.1.1.1")}, - {host: "another.sample", want: net.ParseIP("1.2.2.1")}, - {host: "google.com", want: net.ParseIP("8.8.8.8")}, - {host: "google.ae", want: net.ParseIP("8.8.8.8")}, - {host: "google.ie", want: net.ParseIP("8.8.8.8")}, - {host: "google.sample", want: nil}, - } - for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { - got := hosts.hostIP(tt.host) - if !got.Equal(tt.want) { - t.Errorf("hostsMapIP() = %v, want %v", got, tt.want) - return - } - }) - } -} - -func Test_zoneHost(t *testing.T) { - type val struct { - name string - recordName string - } - tests := []struct { - host zoneHost - want val - }{ - {}, // test for empty value as well - {host: "sample", want: val{name: "sample"}}, - {host: "another.sample", want: val{name: "sample.", recordName: "another"}}, - {host: "another.sample.com", want: val{name: "com.", recordName: "another.sample"}}, - {host: "a.c", want: val{name: "c.", recordName: "a"}}, - {host: "a.b.c.d", want: val{name: "d.", recordName: "a.b.c"}}, - } - for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { - got := val{ - name: tt.host.name(), - recordName: tt.host.recordName(), - } - if got != tt.want { - t.Errorf("host = %+v, want %+v", got, tt.want) - return - } - }) - } -} - -func Test_extractZones(t *testing.T) { - equalZones := func(za, zb []types.Zone) bool { - find := func(list []types.Zone, name string) (types.Zone, bool) { - for _, z := range list { - if z.Name == name { - return z, true - } - } - return types.Zone{}, false - } - equal := func(a, b types.Zone) bool { - if a.Name != b.Name { - return false - } - if !a.DefaultIP.Equal(b.DefaultIP) { - return false - } - for i := range a.Records { - a, b := a.Records[i], b.Records[i] - if !a.IP.Equal(b.IP) { - return false - } - if a.Name != b.Name { - return false - } - } - - return true - } - - for _, a := range za { - b, ok := find(zb, a.Name) - if !ok { - return false - } - if !equal(a, b) { - return false - } - } - return true - } - - hosts := hostMap{ - "google.com": "8.8.4.4", - "local.google.com": "8.8.8.8", - "google.ae": "google.com", - "localhost": "127.0.0.1", - "host.lima.internal": "192.168.5.2", - "host.docker.internal": "host.lima.internal", - } - - tests := []struct { - wantZones []types.Zone - }{ - { - wantZones: []types.Zone{ - { - Name: "ae.", - Records: []types.Record{ - {Name: "google", IP: net.ParseIP("8.8.4.4")}, - }, - }, - { - Name: "com.", - Records: []types.Record{ - {Name: "google", IP: net.ParseIP("8.8.4.4")}, - {Name: "local.google", IP: net.ParseIP("8.8.8.8")}, - }, - }, - { - Name: "internal.", - Records: []types.Record{ - {Name: "host.docker", IP: net.ParseIP("192.168.5.2")}, - {Name: "host.lima", IP: net.ParseIP("192.168.5.2")}, - }, - }, - { - Name: "localhost", - DefaultIP: net.ParseIP("127.0.0.1"), - }, - }, - }, - } - - for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { - gotZones := extractZones(hosts) - for _, zone := range gotZones { - sort.Sort(recordSorter(zone.Records)) - } - sort.Sort(zoneSorter(gotZones)) - - if !equalZones(gotZones, tt.wantZones) { - t.Errorf("extractZones() = %+v, want %+v", gotZones, tt.wantZones) - } - }) - } -} - -var _ sort.Interface = (recordSorter)(nil) -var _ sort.Interface = (zoneSorter)(nil) - -type recordSorter []types.Record - -// Len implements sort.Interface -func (r recordSorter) Len() int { - return len(r) -} - -// Less implements sort.Interface -func (r recordSorter) Less(i int, j int) bool { - return r[i].Name < r[j].Name -} - -// Swap implements sort.Interface -func (r recordSorter) Swap(i int, j int) { - r[i], r[j] = r[j], r[i] -} - -type zoneSorter []types.Zone - -// Len implements sort.Interface -func (z zoneSorter) Len() int { - return len(z) -} - -// Less implements sort.Interface -func (z zoneSorter) Less(i int, j int) bool { - return z[i].Name < z[j].Name -} - -// Swap implements sort.Interface -func (z zoneSorter) Swap(i int, j int) { - z[i], z[j] = z[j], z[i] -} diff --git a/daemon/process/gvproxy/gvproxy.go b/daemon/process/gvproxy/gvproxy.go deleted file mode 100644 index b63533d4..00000000 --- a/daemon/process/gvproxy/gvproxy.go +++ /dev/null @@ -1,212 +0,0 @@ -package gvproxy - -import ( - "bufio" - "bytes" - "context" - "fmt" - "net" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/abiosoft/colima/cli" - "github.com/abiosoft/colima/daemon/process" - "github.com/abiosoft/colima/util/osutil" - "github.com/abiosoft/colima/util/shautil" - "github.com/containers/gvisor-tap-vsock/pkg/transport" - "github.com/containers/gvisor-tap-vsock/pkg/types" - "github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork" - "github.com/sirupsen/logrus" -) - -const Name = "gvproxy" - -// New creates a new Process for gvproxy. -func New(hosts ...map[string]string) process.Process { - dnsHosts := map[string]string{ - "host.lima.internal": "192.168.5.2", - "host.docker.internal": "host.lima.internal", - } - - for _, host := range hosts { - for k, v := range host { - dnsHosts[k] = v - } - } - return &gvproxyProcess{ - hosts: dnsHosts, - } -} - -func Info() struct { - Socket osutil.Socket - MacAddress string -} { - return struct { - Socket osutil.Socket - MacAddress string - }{ - Socket: osutil.Socket(filepath.Join(process.Dir(), socketFileName)), - MacAddress: MacAddress(), - } -} - -var _ process.Process = (*gvproxyProcess)(nil) - -type gvproxyProcess struct { - hosts hostMap -} - -func (*gvproxyProcess) Alive(context.Context) error { - info := Info() - if _, err := os.Stat(info.Socket.File()); err != nil { - return fmt.Errorf("error checking gvproxy socket: %w", err) - } - return nil -} - -// Name implements daemon.Process -func (*gvproxyProcess) Name() string { return Name } - -// Start implements daemon.Process -func (g *gvproxyProcess) Start(ctx context.Context) error { - info := Info() - return run(ctx, info.Socket, extractZones(g.hosts)) -} - -const ( - SubProcessEnvVar = "COLIMA_GVPROXY" - - socketFileName = "gvproxy.sock" - - gatewayMacAddress = "5a:94:ef:e4:0c:dd" - - DeviceIP = "192.168.107.2" - GatewayIP = "192.168.107.1" - natIP = "192.168.107.254" - subnet = "192.168.107.0/24" - - mtu = 1500 -) - -var baseHWAddr = net.HardwareAddr{0x5a, 0x94, 0xef} -var macAddress net.HardwareAddr - -func MacAddress() string { - // there is not much concern about the precision of the uniqueness. - // this can be revisited - if macAddress == nil { - sum := shautil.SHA256(process.Dir()) - macAddress = append(macAddress, baseHWAddr...) - macAddress = append(macAddress, sum.Bytes()[0:3]...) - } - return macAddress.String() -} - -func configuration(zones []types.Zone) types.Configuration { - return types.Configuration{ - Debug: cli.Settings.Verbose, - CaptureFile: "", - MTU: mtu, - Subnet: subnet, - GatewayIP: GatewayIP, - GatewayMacAddress: gatewayMacAddress, - DHCPStaticLeases: map[string]string{ - DeviceIP: MacAddress(), - }, - DNS: zones, - DNSSearchDomains: searchDomains(), - NAT: map[string]string{ - natIP: "127.0.0.1", - }, - GatewayVirtualIPs: []string{natIP}, - Protocol: types.QemuProtocol, - } -} - -func run(ctx context.Context, qemuSocket osutil.Socket, zones []types.Zone) error { - if _, err := os.Stat(qemuSocket.File()); err == nil { - if err := os.Remove(qemuSocket.File()); err != nil { - return fmt.Errorf("error removing existing qemu socket: %w", err) - } - } - - conf := configuration(zones) - vn, err := virtualnetwork.New(&conf) - if err != nil { - return err - } - - logrus.Info("waiting for clients...") - - qemuListener, err := transport.Listen(qemuSocket.Unix()) - if err != nil { - return err - } - - done := make(chan error, 1) - go func() { - conn, err := qemuListener.Accept() - if err != nil { - done <- fmt.Errorf("qemu accept error: %w", err) - return - - } - done <- vn.AcceptQemu(ctx, conn) - }() - - select { - case <-ctx.Done(): - case err := <-done: - if err != nil { - logrus.Errorf("virtual network err: %q", err) - } - } - - if err := qemuListener.Close(); err != nil { - logrus.Errorf("error closing %s: %q", qemuSocket, err) - } - if _, err := os.Stat(qemuSocket.File()); err == nil { - return os.Remove(qemuSocket.File()) - } - return nil -} - -func searchDomains() []string { - if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { - return nil - } - - b, err := os.ReadFile("/etc/resolv.conf") - if err != nil { - logrus.Errorf("open file error: %v", err) - return nil - } - - sc := bufio.NewScanner(bytes.NewReader(b)) - searchPrefix := "search " - for sc.Scan() { - if !strings.HasPrefix(sc.Text(), searchPrefix) { - continue - } - - searchDomains := strings.Fields(strings.TrimPrefix(sc.Text(), searchPrefix)) - logrus.Infof("Using search domains: %v", searchDomains) - return searchDomains - } - if err := sc.Err(); err != nil { - logrus.Errorf("scan file error: %v", err) - return nil - } - - return nil -} - -func (gvproxyProcess) Dependencies() (deps []process.Dependency, root bool) { - return []process.Dependency{ - qemuBinsSymlinks{}, - qemuShareDirSymlink{}, - }, false -} diff --git a/docs/FAQ.md b/docs/FAQ.md index cd54234c..ce411a4b 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -17,15 +17,16 @@ - [Changing the active Docker context](#changing-the-active-docker-context) - [Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?](#cannot-connect-to-the-docker-daemon-at-unixvarrundockersock-is-the-docker-daemon-running) - [How to customize Docker config e.g. add insecure registries?](#how-to-customize-docker-config-eg-add-insecure-registries) - - [Docker plugins are missing (buildx, scan)](#docker-plugins-are-missing-buildx-scan) + - [Docker buildx plugin is missing](#docker-buildx-plugin-is-missing) - [Installing Buildx](#installing-buildx) - - [Installing Docker Scan](#installing-docker-scan) - [How does Colima compare to minikube, Kind, K3d?](#how-does-colima-compare-to-minikube-kind-k3d) - [For Kubernetes](#for-kubernetes) - [For Docker](#for-docker) - [Is another Distro supported?](#is-another-distro-supported) - - [Enabling Ubuntu layer](#enabling-ubuntu-layer) - - [Accessing the underlying Virtual Machine](#accessing-the-underlying-virtual-machine) + - [Version v0.5.6 and lower](#version-v056-and-lower) + - [Enabling Ubuntu layer](#enabling-ubuntu-layer) + - [Accessing the underlying Virtual Machine](#accessing-the-underlying-virtual-machine) + - [Version v0.6.0 and newer](#version-v060-and-newer) - [The Virtual Machine's IP is not reachable](#the-virtual-machines-ip-is-not-reachable) - [Enable reachable IP address](#enable-reachable-ip-address) - [How can disk space be recovered?](#how-can-disk-space-be-recovered) @@ -39,6 +40,7 @@ - [Issues after an upgrade](#issues-after-an-upgrade) - [Colima cannot access the internet.](#colima-cannot-access-the-internet) - [Docker Compose and Buildx showing runc error](#docker-compose-and-buildx-showing-runc-error) + - [Version v0.5.6 or lower](#version-v056-or-lower) ## How does Colima compare to Lima? @@ -173,9 +175,9 @@ This can be fixed by any of the following approaches. Ensure the Docker socket p + - host.docker.internal:5000 ``` -### Docker plugins are missing (buildx, scan) +### Docker buildx plugin is missing -Both `buildx` and `scan` can be installed as Docker plugins +`buildx` can be installed as a Docker plugin #### Installing Buildx @@ -190,34 +192,13 @@ docker buildx version # verify installation Alternatively ```sh ARCH=amd64 # change to 'arm64' for m1 -VERSION=v0.8.2 +VERSION=v0.11.2 curl -LO https://github.com/docker/buildx/releases/download/${VERSION}/buildx-${VERSION}.darwin-${ARCH} mkdir -p ~/.docker/cli-plugins mv buildx-${VERSION}.darwin-${ARCH} ~/.docker/cli-plugins/docker-buildx chmod +x ~/.docker/cli-plugins/docker-buildx docker buildx version # verify installation ``` -#### Installing Docker Scan - -Install Snyk CLI - -```sh -brew install snyk/tap/snyk -``` - -Install Docker Scan - -```sh -ARCH=amd64 # change to 'arm64' for m1 -VERSION=v0.21.0 -curl -LO https://github.com/docker/scan-cli-plugin/releases/download/${VERSION}/docker-scan_darwin_${ARCH} -mkdir -p ~/.docker/cli-plugins -mv docker-scan_darwin_${ARCH} ~/.docker/cli-plugins/docker-scan -chmod +x ~/.docker/cli-plugins/docker-scan -mkdir -p ~/.docker/scan -echo "{}" > ~/.docker/scan/config.json # config file required by the docker scan plugin -docker scan --version # verify installation -``` ## How does Colima compare to minikube, Kind, K3d? @@ -240,12 +221,15 @@ Minikube with Docker runtime can expose the cluster's Docker with `minikube dock ## Is another Distro supported? +### Version v0.5.6 and lower + Colima uses a lightweight Alpine image with bundled dependencies. Therefore, user interaction with the Virtual Machine is expected to be minimal (if any). However, Colima optionally provides Ubuntu container as a layer. -### Enabling Ubuntu layer + +#### Enabling Ubuntu layer * CLI ``` @@ -258,15 +242,19 @@ However, Colima optionally provides Ubuntu container as a layer. + layer: true ``` -### Accessing the underlying Virtual Machine +#### Accessing the underlying Virtual Machine When the layer is enabled, the underlying Virtual Machine is abstracted and both the `ssh` and `ssh-config` commands routes to the layer. The underlying Virtual Machine is still accessible by specifying `--layer=false` to the `ssh` and `ssh-config` commands, or by running `colima` in the SSH session. +### Version v0.6.0 and newer + +Colima uses Ubuntu as the underlying image. Other distros are not supported. + ## The Virtual Machine's IP is not reachable -Reachable IP address is not enabled by default due to slower startup time. +Reachable IP address is not enabled by default due to root privilege and slower startup time. ### Enable reachable IP address @@ -381,6 +369,8 @@ round-trip min/avg/max = 0.082/0.390/0.557 ms ### Docker Compose and Buildx showing runc error +#### Version v0.5.6 or lower + Recent versions of Buildkit may show the following error. ```console @@ -388,3 +378,5 @@ runc run failed: unable to start container process: error during container init: ``` From v0.5.6, start Colima with `--cgroups-v2` flag as a workaround. + +**This is fixed in v0.6.0.** diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 9f163c69..d0c09a58 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -54,7 +54,7 @@ Binaries are available with every release on the [releases page](https://github. ```sh # download binary -curl -LO https://github.com/abiosoft/colima/releases/download/v0.5.6/colima-$(uname)-$(uname -m) +curl -LO https://github.com/abiosoft/colima/releases/download/v0.6.0/colima-$(uname)-$(uname -m) # install in $PATH install colima-$(uname)-$(uname -m) /usr/local/bin/colima # or sudo install if /usr/local/bin requires root. diff --git a/embedded/defaults/colima.yaml b/embedded/defaults/colima.yaml index a5d05f71..61e52986 100644 --- a/embedded/defaults/colima.yaml +++ b/embedded/defaults/colima.yaml @@ -19,6 +19,11 @@ arch: host # Default: docker runtime: docker +# Set custom hostname for the virtual machine. +# Default: colima +# colima-profile_name for other profiles +hostname: null + # Kubernetes configuration for the virtual machine. kubernetes: # Enable kubernetes. @@ -67,12 +72,6 @@ network: dnsHosts: host.docker.internal: host.lima.internal - # Network driver to use (slirp, gvproxy), (requires vmType `qemu`) - # - slirp is the default user mode networking provided by Qemu - # - gvproxy is an alternative to VPNKit based on gVisor https://github.com/containers/gvisor-tap-vsock - # Default: gvproxy - driver: gvproxy - # ===================================================================== # # ADVANCED CONFIGURATION # ===================================================================== # @@ -134,14 +133,6 @@ mountInotify: false # Default: host cpuType: host -# For a more general purpose virtual machine, Ubuntu container is optionally provided -# as a layer on the virtual machine. -# The underlying virtual machine is still accessible via `colima ssh --layer=false` or running `colima` in -# the Ubuntu session. -# -# Default: false -layer: false - # Custom provision scripts for the virtual machine. # Provisioning scripts are executed on startup and therefore needs to be idempotent. # @@ -190,8 +181,3 @@ mounts: [] # # Default: {} env: {} - -# Enable cgroups v2 as a temporary workaround for https://github.com/abiosoft/colima/issues/764. -# NOTE: this is incompatible with Kubernetes and Ubuntu Layer. -# NOTE: this config will be removed in future versions. -cgroupsV2: false diff --git a/embedded/network/ifaces.sh b/embedded/network/ifaces.sh deleted file mode 100644 index fc060097..00000000 --- a/embedded/network/ifaces.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env sh -# set -x - -FILE=/etc/network/interfaces -BACKUP="$FILE.bak" - -# this only happens one in the beginning -if [ ! -f "$BACKUP" ]; then - cp "$FILE" "$BACKUP" -fi - -# reset the network file -cp "$BACKUP" "$FILE" - -validate_ip() { - # Add basic IP address validation, make sure it is in 192.168.106.0/24 subnet - echo $1 | grep -q -E "^192.168.106.(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$" - return $? -} - -update_iface_to_static() { - # update the interface from dhcp to static ip address - local IFACE=$1 - local IP_ADDRESS=$2 - local FILE=$3 - -sed -i "/iface $IFACE inet dhcp/d" $FILE -sed -i "/^auto $IFACE/a iface $IFACE inet static\n address $IP_ADDRESS\n netmask 255.255.255.0\n gateway 192.168.106.1\n" $FILE -} - -set_iface_to_dhcp() { - # update interface to using dhcp - local IFACE=$1 - - cat >>$FILE <>$FILE <>$FILE </dev/null -#!/usr/bin/env sh -set -u - -USER="\$1" -shift - -WD="\$1" -shift - -cd "\$WD" 2> /dev/null || echo > /dev/null - -sudo -u "\$USER" "\$@" -EOF - sudo chmod 700 /host/usr/bin/run-as -fi - -CMD=$(basename "$0") -WD="$PWD" - -if [ "$CMD" == "colima" ]; then - if [ -z "$1" ]; then - sudo chroot /host su - "$USER" - else - sudo chroot /host run-as "$USER" "$WD" "$@" - fi -else - sudo chroot /host run-as "$USER" "$WD" "$CMD" "$@" -fi - -exit $? diff --git a/environment/container/ubuntu/provision.go b/environment/container/ubuntu/provision.go deleted file mode 100644 index 09e20aa2..00000000 --- a/environment/container/ubuntu/provision.go +++ /dev/null @@ -1,204 +0,0 @@ -package ubuntu - -import ( - _ "embed" - "fmt" - "path/filepath" - "strconv" - - "github.com/abiosoft/colima/config" - "github.com/abiosoft/colima/util" -) - -type buildArgs struct { - username string - homeDir string - arch string - uid int - gid int - dockerGid int -} - -//go:embed Dockerfile -var dockerfile []byte - -//go:embed colima.sh -var chrootScript []byte - -func (u ubuntuRuntime) buildArgs() (b buildArgs, err error) { - b.arch = string(u.guest.Arch().Value()) - - if b.username, err = u.guest.User(); err != nil { - return - } - - if b.homeDir, err = u.guest.RunOutput("sh", "-c", "echo $HOME"); err != nil { - return b, fmt.Errorf("error retrieving home dir: %w", err) - } - { - uid, err := u.guest.RunOutput("id", "-u") - if err != nil { - return b, fmt.Errorf("error retrieving user id: %w", err) - } - b.uid, err = strconv.Atoi(uid) - if err != nil { - return b, fmt.Errorf("invalid user id: %v", uid) - } - } - { - gid, err := u.guest.RunOutput("id", "-g") - if err != nil { - return b, fmt.Errorf("error retrieving group id: %w", err) - } - b.gid, err = strconv.Atoi(gid) - if err != nil { - return b, fmt.Errorf("invalid group id: %v", gid) - } - } - { - gid, err := u.guest.RunOutput("sh", "-c", "getent group docker | cut -d: -f3") - if err != nil { - return b, fmt.Errorf("error retrieving docker group id: %w", err) - } - b.dockerGid, err = strconv.Atoi(gid) - if err != nil { - return b, fmt.Errorf("invalid docker group id: %v", gid) - } - } - - return -} - -func (u ubuntuRuntime) imageCreated() bool { - args := nerdctl("image", "inspect", imageName) - return u.guest.RunQuiet(args...) == nil -} - -func (u ubuntuRuntime) createImage() error { - b, err := u.buildArgs() - if err != nil { - return fmt.Errorf("error getting image build args: %w", err) - } - - // prerequisite - { - args := []string{"nerdctl", "--namespace", "buildkit", "load", "--input", imageArchive} - if err := u.guest.Run(args...); err != nil { - return fmt.Errorf("error loading ubuntu layer image: %w", err) - } - } - defer func() { - _ = u.guest.RunQuiet("nerdctl", "--namespace", "buildkit", "rmi", "--force", imageName+"-"+b.arch) - }() - - // build dockerfile - { - - const tmpDir = "/tmp/ubuntu" - dockerfilePath := filepath.Join(tmpDir, "Dockerfile") - - if err := u.guest.Write(dockerfilePath, dockerfile); err != nil { - return fmt.Errorf("error writing ubuntu layer dockerfile: %w", err) - } - if err := u.guest.Write(filepath.Join(tmpDir, "colima"), chrootScript); err != nil { - return fmt.Errorf("error writing ubuntu layer chroot script: %w", err) - } - if err := u.guest.RunQuiet("sudo", "chown", "-R", b.username, tmpDir); err != nil { - return fmt.Errorf("error preparing ubuntu layer cache dir: %w", err) - } - - args := nerdctl( - "build", - "--tag", imageName, - "--build-arg", "ARCH="+b.arch, - "--build-arg", "NONROOT_USER="+b.username, - "--build-arg", "UID="+strconv.Itoa(b.uid), - "--build-arg", "GID="+strconv.Itoa(b.gid), - "--build-arg", "DOCKER_GID="+strconv.Itoa(b.dockerGid), - "--progress", "plain", - "--file", dockerfilePath, - tmpDir, - ) - - if err := u.guest.Run(args...); err != nil { - return fmt.Errorf("error building ubuntu layer image: %w", err) - } - } - - return nil -} - -func (u ubuntuRuntime) containerCreated() bool { - args := nerdctl("container", "inspect", containerName) - return u.guest.RunQuiet(args...) == nil -} - -func (u ubuntuRuntime) createContainer(conf config.Config) error { - username, err := u.guest.User() - if err != nil { - return fmt.Errorf("error retrieving username in guest: %w", err) - } - hostname := config.CurrentProfile().ID - home := "/home/" + username + ".linux" - args := nerdctl("create", - - // essentials - "--name", containerName, - "--hostname", hostname, - "--privileged", - "--net", "host", - "--volume", home+":"+home, - "--volume", "/:/host", - - // systemd - "--mount", "type=tmpfs,destination=/var/lib/journal", - "--mount", "type=bind,source=/sys/fs/cgroup/unified,target=/sys/fs/cgroup/unified", - "--mount", "type=tmpfs,destination=/sys/fs/cgroup/systemd", - "--mount", "type=bind,source=/sys/fs/fuse,target=/sys/fs/fuse", - "--mount", "type=bind,source=/tmp/ubuntu,destination=/tmp", - "--mount", "type=tmpfs,destination=/run", - "--mount", "type=tmpfs,destination=/run/lock", - ) - - // colima mounts - mounts := conf.MountsOrDefault() - for _, m := range mounts { - location := m.MountPoint - if location == "" { - location = m.Location - } - - location, err := util.CleanPath(location) - if err != nil { - return err - } - args = append(args, "--volume", location+":"+location) - } - - // environment variables propagation - env := conf.Env - for k, v := range env { - args = append(args, "--env", k+"="+v) - } - - // image - args = append(args, imageName) - - return u.guest.Run(args...) -} - -func (u ubuntuRuntime) syncHostname() error { - currentHostname := func() string { - args := nerdctl("exec", containerName, "hostname") - hostname, _ := u.guest.RunOutput(args...) - return hostname - }() - - hostname := config.CurrentProfile().ID - if currentHostname == hostname { - return nil - } - - args := nerdctl("exec", containerName, "sudo", "hostname", hostname) - return u.guest.RunQuiet(args...) -} diff --git a/environment/container/ubuntu/ubuntu.go b/environment/container/ubuntu/ubuntu.go deleted file mode 100644 index 84ab87ff..00000000 --- a/environment/container/ubuntu/ubuntu.go +++ /dev/null @@ -1,121 +0,0 @@ -package ubuntu - -import ( - "context" - "fmt" - - "github.com/abiosoft/colima/cli" - "github.com/abiosoft/colima/config" - "github.com/abiosoft/colima/environment" - "github.com/abiosoft/colima/environment/container/containerd" - "github.com/sirupsen/logrus" -) - -// Name is container runtime name -const Name = "ubuntu" -const containerdNamespace = "colima" -const containerName = "ubuntu-layer" -const imageArchive = "/usr/share/colima/ubuntu-layer.tar.gz" -const imageName = "ubuntu-layer" - -func newRuntime(host environment.HostActions, guest environment.GuestActions) environment.Container { - return &ubuntuRuntime{ - host: host, - guest: guest, - CommandChain: cli.New(Name), - } -} - -func nerdctl(args ...string) []string { - return append([]string{"nerdctl", "--namespace", containerdNamespace}, args...) -} - -func init() { - environment.RegisterContainer(Name, newRuntime, true) -} - -type ubuntuRuntime struct { - host environment.HostActions - guest environment.GuestActions - cli.CommandChain -} - -func (u ubuntuRuntime) Name() string { - return Name -} - -func (u ubuntuRuntime) ensureContainerd(ctx context.Context) error { - nerd, err := environment.NewContainer(containerd.Name, u.host, u.guest) - if err != nil { - return fmt.Errorf("%s required for ubuntu layer: %w", containerd.Name, err) - } - if nerd.Running(ctx) { - return nil - } - - ctx = context.WithValue(ctx, cli.CtxKeyQuiet, true) - if err := nerd.Provision(ctx); err != nil { - return err - } - - return nerd.Start(ctx) -} - -func (u ubuntuRuntime) Provision(ctx context.Context) error { - a := u.Init(ctx) - if err := u.ensureContainerd(ctx); err != nil { - return err - } - - if !u.imageCreated() { - a.Stage("creating image") - a.Add(u.createImage) - } - - conf, _ := ctx.Value(config.CtxKey()).(config.Config) - if !u.containerCreated() { - a.Stage("creating container") - a.Add(func() error { - return u.createContainer(conf) - }) - } - - return a.Exec() -} - -func (u ubuntuRuntime) Start(ctx context.Context) error { - a := u.Init(ctx) - - a.Add(func() error { - return u.guest.Run(nerdctl("start", containerName)...) - }) - a.Add(u.syncHostname) - - return a.Exec() -} - -func (u ubuntuRuntime) Stop(context.Context) error { - return u.guest.Run(nerdctl("stop", containerName)...) -} - -func (u ubuntuRuntime) Teardown(context.Context) error { - if err := u.guest.Run(nerdctl("stop", containerName)...); err != nil { - logrus.Warn(fmt.Errorf("error stopping container: %w", err)) - } - return u.guest.Run(nerdctl("rm", "-f", containerName)...) -} - -func (u ubuntuRuntime) Version(ctx context.Context) string { - args := nerdctl("exec", "--", "sh -c '. /etc/os-release && echo $PRETTY_NAME'") - out, _ := u.guest.RunOutput(args...) - return out -} - -func (u ubuntuRuntime) Running(ctx context.Context) bool { - args := nerdctl("exec", containerName, "uname") - return u.guest.RunQuiet(args...) == nil -} - -func (u ubuntuRuntime) Dependencies() []string { - return nil -} diff --git a/environment/vm/lima/lima.go b/environment/vm/lima/lima.go index 6effd8c6..0de77259 100644 --- a/environment/vm/lima/lima.go +++ b/environment/vm/lima/lima.go @@ -1,61 +1,62 @@ package lima import ( - "bufio" "context" "encoding/json" "fmt" "io" - "net" "os" "path/filepath" - "strconv" "strings" "time" "github.com/abiosoft/colima/cli" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/config/configmanager" + "github.com/abiosoft/colima/core" "github.com/abiosoft/colima/daemon" - "github.com/abiosoft/colima/daemon/process/gvproxy" "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/qemu" "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" ) // New creates a new virtual machine. func New(host environment.HostActions) environment.VM { + // lima config directory + limaHome := limautil.LimaHome() + // environment variables for the subprocesses var envs []string - envLimaInstance := limaInstanceEnvVar + "=" + config.CurrentProfile().ID - envSubprocess := config.SubprocessProfileEnvVar + "=" + config.CurrentProfile().ShortName + envHome := limautil.EnvLimaHome + "=" + limaHome + envLimaInstance := envLimaInstance + "=" + config.CurrentProfile().ID envBinary := osutil.EnvColimaBinary + "=" + osutil.Executable() - envs = append(envs, envLimaInstance, envSubprocess, envBinary) + envs = append(envs, envHome, envLimaInstance, envBinary) - home := limautil.LimaHome() // consider making this truly flexible to support other VMs return &limaVM{ host: host.WithEnv(envs...), - home: home, + limaHome: limaHome, CommandChain: cli.New("vm"), daemon: daemon.NewManager(host), } } const ( - limaInstanceEnvVar = "LIMA_INSTANCE" - lima = "lima" - limactl = "limactl" + envLimaInstance = "LIMA_INSTANCE" + lima = "lima" + limactl = limautil.Limactl ) func (l limaVM) limaConfFile() string { - return filepath.Join(l.home, config.CurrentProfile().ID, "lima.yaml") + return filepath.Join(l.limaHome, config.CurrentProfile().ID, "lima.yaml") } var _ environment.VM = (*limaVM)(nil) @@ -71,7 +72,7 @@ type limaVM struct { limaConf Config // lima config directory - home string + limaHome string // network between host and the vm daemon daemon.Manager @@ -95,7 +96,6 @@ func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.C ctxKeyVmnet := daemon.CtxKey(vmnet.Name) ctxKeyInotify := daemon.CtxKey(inotify.Name) - ctxKeyGVProxy := daemon.CtxKey(gvproxy.Name) // use a nested chain for convenience a := l.Init(ctx) @@ -119,9 +119,6 @@ func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.C if isQEMU { a.Stage("preparing network") a.Add(func() error { - if conf.Network.Driver == gvproxy.Name { - ctx = context.WithValue(ctx, ctxKeyGVProxy, true) - } if conf.Network.Address { ctx = context.WithValue(ctx, ctxKeyVmnet, true) } @@ -192,7 +189,6 @@ func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.C return } - // revert gvproxy to boolean for _, p := range status.Processes { // TODO: handle inotify separate from network if p.Name == inotify.Name { @@ -220,18 +216,6 @@ func (l *limaVM) startDaemon(ctx context.Context, conf config.Config) (context.C l.host = l.host.WithEnv(vmnet.SubProcessEnvVar + "=1") } - // preserve gvproxy context - if gvProxyEnabled, _ := ctx.Value(ctxKeyGVProxy).(bool); gvProxyEnabled { - var envs []string - - // env var for subprocess to detect gvproxy - envs = append(envs, gvproxy.SubProcessEnvVar+"=1") - // use qemu wrapper for Lima by specifying wrapper binaries via env var - envs = append(envs, qemu.LimaDir().BinsEnvVar()...) - - l.host = l.host.WithEnv(envs...) - } - return ctx, nil } @@ -529,32 +513,6 @@ func (l limaVM) Arch() environment.Arch { return environment.Arch(a) } -func (l limaVM) addHost(host string, ip net.IP) error { - if hostsFile, err := l.Read("/etc/hosts"); err == nil && includesHost(hostsFile, host, ip) { - return nil - } else if err != nil { - logrus.Warnln(fmt.Errorf("cannot read /etc/hosts in the VM: %w", err)) - } - - line := fmt.Sprintf("%s\t%s", ip.String(), host) - line = fmt.Sprintf("echo -e %s >> /etc/hosts", strconv.Quote(line)) - return l.Run("sudo", "sh", "-c", line) -} - -func includesHost(hostsFileContent, host string, ip net.IP) bool { - scanner := bufio.NewScanner(strings.NewReader(hostsFileContent)) - for scanner.Scan() { - str := strings.Fields(scanner.Text()) - if len(str) == 0 || str[0] != ip.String() { - continue - } - if len(str) > 1 && str[1] == host { - return true - } - } - return false -} - func (l *limaVM) syncDiskSize(ctx context.Context, conf config.Config) config.Config { log := l.Logger(ctx) instance, err := limautil.InstanceConfig() @@ -603,24 +561,30 @@ func (l *limaVM) syncDiskSize(ctx context.Context, conf config.Config) config.Co } func (l *limaVM) addPostStartActions(a *cli.ActiveCommandChain, conf config.Config) { - // host file - { - // add docker host alias - a.Add(func() error { - return l.addHost("host.docker.internal", net.ParseIP("192.168.5.2")) - }) - // prevent chroot host error for layer + // package dependencies + a.Add(func() error { + return l.installDependencies(a.Logger(), conf) + }) + + // containerd dependencies + if conf.Runtime == containerd.Name { a.Add(func() error { - return l.addHost(config.CurrentProfile().ID, net.ParseIP("127.0.0.1")) + return core.SetupContainerdUtils(l.host, l, environment.Arch(conf.Arch)) }) } // registry certs a.Add(l.copyCerts) - // use rosetta for x86_64 emulation + // cross-platform emulation a.Add(func() error { if !l.limaConf.Rosetta.Enabled { + // use binfmt when rosetta is disabled and emulation is disabled i.e. host arch + if environment.HostArch() == environment.Arch(conf.Arch).Value() { + return core.SetupBinfmt(l.host, l, environment.Arch(conf.Arch)) + } + + // rosetta is disabled return nil } @@ -632,9 +596,11 @@ func (l *limaVM) addPostStartActions(a *cli.ActiveCommandChain, conf config.Conf } // disable qemu - err = l.Run("sudo", "sh", "-c", `stat /proc/sys/fs/binfmt_misc/qemu-x86_64 && echo 0 > /proc/sys/fs/binfmt_misc/qemu-x86_64`) - if err != nil { - logrus.Warn(fmt.Errorf("unable to disable qemu x86_84 emulation: %w", err)) + if err := l.RunQuiet("stat", "/proc/sys/fs/binfmt_misc/qemu-x86_64"); err == nil { + err = l.Run("sudo", "sh", "-c", `echo 0 > /proc/sys/fs/binfmt_misc/qemu-x86_64`) + if err != nil { + logrus.Warn(fmt.Errorf("unable to disable qemu x86_84 emulation: %w", err)) + } } return nil @@ -648,3 +614,91 @@ 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/lima_test.go b/environment/vm/lima/lima_test.go deleted file mode 100644 index c3977fe1..00000000 --- a/environment/vm/lima/lima_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package lima - -import ( - "fmt" - "net" - "testing" -) - -func Test_includesHost(t *testing.T) { - type args struct { - hostsFileContent string - host string - ip net.IP - } - tests := []struct { - args args - want bool - }{ - { - want: false, args: args{ - hostsFileContent: "", - host: "localhost", - ip: net.ParseIP("127.0.0.1"), - }, - }, - { - want: false, args: args{ - hostsFileContent: "127.0.0.1", - host: "localhost", - ip: net.ParseIP("127.0.0.1"), - }, - }, - { - want: true, args: args{ - hostsFileContent: "127.0.0.1 myhost", - host: "myhost", - ip: net.ParseIP("127.0.0.1"), - }, - }, - { - want: false, args: args{ - hostsFileContent: "127.0.0.1 myhost\n", - host: "host", - ip: net.ParseIP("127.0.0.1"), - }, - }, - { - want: false, args: args{ - hostsFileContent: "127.0.0.1 host\n", - host: "host", - ip: net.ParseIP("127.0.1.1"), - }, - }, - { - want: true, args: args{ - hostsFileContent: "127.0.0.1 host\n127.0.1.1 host", - host: "host", - ip: net.ParseIP("127.0.1.1"), - }, - }, - } - for i, tt := range tests { - t.Run(fmt.Sprint(i), func(t *testing.T) { - if got := includesHost(tt.args.hostsFileContent, tt.args.host, tt.args.ip); got != tt.want { - t.Errorf("includesHost() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/environment/vm/lima/limautil/limautil.go b/environment/vm/lima/limautil/limautil.go index 2f186d6e..179b1957 100644 --- a/environment/vm/lima/limautil/limautil.go +++ b/environment/vm/lima/limautil/limautil.go @@ -6,20 +6,28 @@ import ( "encoding/json" "fmt" "os" + "os/exec" "path/filepath" - "strconv" "strings" "github.com/abiosoft/colima/cli" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/config/configmanager" - "github.com/abiosoft/colima/util" - "github.com/sirupsen/logrus" + "github.com/abiosoft/colima/daemon/process/vmnet" ) -const ( - LayerEnvVar = "COLIMA_LAYER_SSH_PORT" -) +// EnvLimaHome is the environment variable for the Lima directory. +const EnvLimaHome = "LIMA_HOME" + +// Limactl is the limactl command. +const Limactl = "limactl" + +func limactl(args ...string) *exec.Cmd { + cmd := cli.Command(Limactl, args...) + cmd.Env = append(cmd.Env, os.Environ()...) + cmd.Env = append(cmd.Env, EnvLimaHome+"="+LimaHome()) + return cmd +} // Instance returns current instance. func Instance() (InstanceInfo, error) { @@ -41,8 +49,6 @@ func InstanceConfig() (config.Config, error) { // // 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 { @@ -50,7 +56,11 @@ func IPAddress(profileID string) string { } if len(instance.Network) > 0 { - return getIPAddress(profileID, instance.Network[0].Interface) + for _, n := range instance.Network { + if n.Interface == vmnet.NetInterface { + return getIPAddress(profileID, n.Interface) + } + } } return fallback @@ -83,11 +93,9 @@ func (i InstanceInfo) Config() (config.Config, error) { // 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) (resp struct { - Output string - IPAddress string - Layer bool - File struct { +func ShowSSH(profileID string) (resp struct { + Output string + File struct { Lima string Colima string } @@ -98,40 +106,18 @@ func ShowSSH(profileID string, layer bool) (resp struct { return resp, fmt.Errorf("error retrieving ssh config: %w", err) } - ip := IPAddress(profileID) - var port int - if layer { - port, _ = ubuntuSSHPort(profileID) - // if layer is active and public IP is available, use the fixed port - if port > 0 && ip != "127.0.0.1" { - port = 23 - } - } else { - ip = "127.0.0.1" - } - - resp.Output = replaceSSHConfig(sshConf, profileID, ip, port) - resp.IPAddress = ip - resp.Layer = port > 0 + resp.Output = replaceSSHConfig(sshConf, profileID) resp.File.Lima = ssh.File() resp.File.Colima = config.SSHConfigFile() return resp, nil } -func replaceSSHConfig(conf string, profileID string, ip string, port int) string { +func replaceSSHConfig(conf string, profileID string) string { profileID = config.Profile(profileID).ID - name := config.Profile(profileID).ShortName var out bytes.Buffer scanner := bufio.NewScanner(strings.NewReader(conf)) - hasPrefix := func(line, s string) (pad string, ok bool) { - if s != "" && strings.HasPrefix(strings.TrimSpace(line), s) { - return line[:strings.Index(line, s[:1])], true - } - return "", false - } - for scanner.Scan() { line := scanner.Text() @@ -139,21 +125,6 @@ func replaceSSHConfig(conf string, profileID string, ip string, port int) string line = "Host " + profileID } - if port > 0 { - if pad, ok := hasPrefix(line, "ControlPath "); ok { - configDir := filepath.Join(filepath.Dir(config.Dir()), name) - line = pad + "ControlPath " + strconv.Quote(filepath.Join(configDir, "ssh.sock")) - } - - if pad, ok := hasPrefix(line, "Hostname "); ok { - line = pad + "Hostname " + ip - } - - if pad, ok := hasPrefix(line, "Port"); ok { - line = pad + "Port " + strconv.Itoa(port) - } - } - _, _ = fmt.Fprintln(&out, line) } return out.String() @@ -167,7 +138,7 @@ const ( func getInstance(profileID string) (InstanceInfo, error) { var i InstanceInfo var buf bytes.Buffer - cmd := cli.Command("limactl", "list", profileID, "--json") + cmd := limactl("list", profileID, "--json") cmd.Stderr = nil cmd.Stdout = &buf @@ -194,7 +165,7 @@ func Instances(ids ...string) ([]InstanceInfo, error) { args := append([]string{"list", "--json"}, limaIDs...) var buf bytes.Buffer - cmd := cli.Command("limactl", args...) + cmd := limactl(args...) cmd.Stderr = nil cmd.Stdout = &buf @@ -217,8 +188,10 @@ func Instances(ids ...string) ([]InstanceInfo, error) { } if i.Running() { - if len(i.Network) > 0 && i.Network[0].Interface != "" { - i.IPAddress = getIPAddress(i.Name, i.Network[0].Interface) + for _, n := range i.Network { + if n.Interface == vmnet.NetInterface { + i.IPAddress = getIPAddress(i.Name, vmnet.NetInterface) + } } conf, _ := i.Config() i.Runtime = getRuntime(conf) @@ -238,8 +211,8 @@ func Instances(ids ...string) ([]InstanceInfo, error) { 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 := limactl("shell", profileID, "sh", "-c", + `ip -4 addr show `+interfaceName+` | grep inet | awk -F' ' '{print $2 }' | cut -d/ -f1`) cmd.Stderr = nil cmd.Stdout = &buf @@ -247,24 +220,6 @@ func getIPAddress(profileID, interfaceName string) string { 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 - cmd.Stderr = nil - - 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 @@ -281,45 +236,14 @@ func getRuntime(conf config.Config) string { return runtime } -var limaHome string - // LimaHome returns the config directory for Lima. func LimaHome() string { - if limaHome != "" { - return limaHome - } - - home, err := func() (string, error) { - var buf bytes.Buffer - cmd := cli.Command("limactl", "info") - cmd.Stdout = &buf - - if err := cmd.Run(); err != nil { - return "", fmt.Errorf("error retrieving lima info: %w", err) - } - - var resp struct { - LimaHome string `json:"limaHome"` - } - if err := json.NewDecoder(&buf).Decode(&resp); err != nil { - return "", fmt.Errorf("error decoding json for lima info: %w", err) - } - if resp.LimaHome == "" { - return "", fmt.Errorf("error retrieving lima info, ensure lima version is >0.7.4") - } - - return resp.LimaHome, nil - }() - - if err != nil { - err = fmt.Errorf("error detecting Lima config directory: %w", err) - logrus.Warnln(err) - logrus.Warnln("falling back to default '$HOME/.lima'") - home = filepath.Join(util.HomeDir(), ".lima") + // if LIMA_HOME env var is set, obey it. + if dir := os.Getenv(EnvLimaHome); dir != "" { + return dir } - limaHome = home - return home + return config.LimaDir() } const colimaStateFileName = "colima.yaml" diff --git a/environment/vm/lima/yaml.go b/environment/vm/lima/yaml.go index 1ceb75b0..77d255e9 100644 --- a/environment/vm/lima/yaml.go +++ b/environment/vm/lima/yaml.go @@ -6,18 +6,14 @@ import ( "net" "os" "path/filepath" - "strconv" "strings" "github.com/abiosoft/colima/daemon" - "github.com/abiosoft/colima/daemon/process/gvproxy" "github.com/abiosoft/colima/daemon/process/vmnet" "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" ) @@ -53,8 +49,16 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { } l.Images = append(l.Images, - File{Arch: environment.AARCH64, Location: "https://github.com/abiosoft/alpine-lima/releases/download/colima-v0.5.6/alpine-lima-clm-3.18.3-aarch64.iso", Digest: "sha512:376cc8cb777380757dbcdb87825f076c25e97283dfef8d51f025fae8bad6953462d095187643a5f7a9be35b86687e0f5b654758fc55ded67aa390f657e0b59b3"}, - File{Arch: environment.X8664, Location: "https://github.com/abiosoft/alpine-lima/releases/download/colima-v0.5.6/alpine-lima-clm-3.18.3-x86_64.iso", Digest: "sha512:48bf6c7468fc8acc05d14b3b138958cf4417fa26e478d864c8458a0c7aa8e9742c19058f792debc7585614e0a4ba6ad9608c2e6ff695a2d7ae8daafb8ad64db2"}, + File{ + Arch: environment.AARCH64, + Location: "https://github.com/abiosoft/colima-ubuntu/releases/download/v0.6.0/ubuntu-23.10-minimal-cloudimg-arm64.img", + Digest: "sha512:a45cbba1e3ce8968aa103a8a1ff276905a5013c3b6e25050426f66d2b7c6b30067dfccc900bbadb132d867bb17cf395cb5e89119e1614d3feea24021f6718921", + }, + File{ + Arch: environment.X8664, + Location: "https://github.com/abiosoft/colima-ubuntu/releases/download/v0.6.0/ubuntu-23.10-minimal-cloudimg-amd64.img", + Digest: "sha512:ff16319abfd9b5e81395b0d0e7058a61b4bbd057fbab40fe654fb38cac1ed53b53a6a6d4c8ad10802f800daf5e069597e89ec2b2d360dbbc1dc44d0c2b1372fa", + }, ) if conf.CPU > 0 { @@ -79,19 +83,6 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { if _, ok := l.HostResolver.Hosts["host.docker.internal"]; !ok { l.HostResolver.Hosts["host.docker.internal"] = "host.lima.internal" } - if len(l.DNS) == 0 { - gvProxyEnabled, _ := ctx.Value(daemon.CtxKey(gvproxy.Name)).(bool) - if gvProxyEnabled { - l.DNS = append(l.DNS, net.ParseIP(gvproxy.GatewayIP)) - l.HostResolver.Enabled = false - } - reachableIPAddress, _ := ctx.Value(daemon.CtxKey(vmnet.Name)).(bool) - if reachableIPAddress { - if gvProxyEnabled { - l.DNS = append(l.DNS, net.ParseIP(vmnet.NetGateway)) - } - } - } l.Env = conf.Env if l.Env == nil { @@ -109,47 +100,28 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { // add user to docker group // "sudo", "usermod", "-aG", "docker", user l.Provision = append(l.Provision, Provision{ - Mode: ProvisionModeUser, - Script: "sudo usermod -aG docker $USER", + Mode: ProvisionModeDependency, + Script: "groupadd -f docker && usermod -aG docker $LIMA_CIDATA_USER", }) - // allow env vars propagation for services + // set hostname + hostname := "$LIMA_CIDATA_NAME" + if conf.Hostname != "" { + hostname = conf.Hostname + } l.Provision = append(l.Provision, Provision{ Mode: ProvisionModeSystem, - Script: `grep -q "^rc_env_allow" /etc/rc.conf || echo 'rc_env_allow="*"' >> /etc/rc.conf`, + Script: "hostnamectl set-hostname " + hostname, }) - // cgroups v2 workaround - { - // delete setting if present - l.Provision = append(l.Provision, Provision{ - Mode: ProvisionModeSystem, - Script: `(grep -q "^rc_cgroup_mode" /etc/rc.conf && sed -i '/^rc_cgroup_mode/d' /etc/rc.conf && service cgroups restart) || echo 'cgroup v2 config not found'`, - }) - - // validate workaround is supported - if conf.TempCgroupsV2 { - if conf.Kubernetes.Enabled { - logrus.Warnln("cgroups v2 workaround not compatible with Kubernetes, ignoring...") - conf.TempCgroupsV2 = false - } - if conf.Layer { - logrus.Warnln("cgroups v2 workaround not compatible with Ubuntu layer, ignoring...") - conf.TempCgroupsV2 = false - } - } - - if conf.TempCgroupsV2 { - l.Provision = append(l.Provision, Provision{ - Mode: ProvisionModeSystem, - Script: `echo 'rc_cgroup_mode="unified"' >> /etc/rc.conf && service cgroups restart`, - }) - } - } } // network setup { + l.Networks = append(l.Networks, Network{ + Lima: "user-v2", + }) + reachableIPAddress := true if conf.Network.Address { if l.VMType == VZ { @@ -203,71 +175,6 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { ) } } - - // gvproxy is cross-platform but not needed on Linux as slirp is only erratic on macOS. - gvProxyEnabled, _ := ctx.Value(daemon.CtxKey(gvproxy.Name)).(bool) - if gvProxyEnabled && util.MacOS() { - var values struct { - Vmnet struct { - Enabled bool - Interface string - } - GVProxy struct { - Enabled bool - MacAddress string - IPAddress net.IP - Gateway net.IP - } - } - - if reachableIPAddress { - values.Vmnet.Enabled = true - values.Vmnet.Interface = vmnet.NetInterface - } - - gvProxyEnabled, _ := ctx.Value(daemon.CtxKey(gvproxy.Name)).(bool) - if gvProxyEnabled { - values.GVProxy.Enabled = true - values.GVProxy.MacAddress = strings.ToUpper(gvproxy.MacAddress()) - values.GVProxy.IPAddress = net.ParseIP(gvproxy.DeviceIP) - values.GVProxy.Gateway = net.ParseIP(gvproxy.GatewayIP) - - if err := func() error { - tpl, err := embedded.ReadString("network/ifaces.sh") - if err != nil { - return err - } - - script, err := util.ParseTemplate(tpl, values) - if err != nil { - return fmt.Errorf("error parsing template for network script: %w", err) - } - - l.Provision = append(l.Provision, Provision{ - Mode: ProvisionModeSystem, - Script: string(script), - }) - - return nil - }(); err != nil { - logrus.Warn(fmt.Errorf("error setting up gvproxy network: %w", err)) - } - } - } - } - - // port forwarding - - if conf.Layer { - port := util.RandomAvailablePort() - // set port for future retrieval - l.Env[limautil.LayerEnvVar] = strconv.Itoa(port) - // forward port - l.PortForwards = append(l.PortForwards, - PortForward{ - GuestPort: 23, - HostPort: port, - }) } // ports and sockets @@ -326,6 +233,15 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { } } + // Ubuntu minimal cloud image does not bundle sshfs + // if sshfs is used, add as a dependency + if l.MountType == REVSSHFS { + l.Provision = append(l.Provision, Provision{ + Mode: ProvisionModeDependency, + Script: `which sshfs || apt install -y sshfs`, + }) + } + l.Provision = append(l.Provision, Provision{ Mode: ProvisionModeSystem, Script: "mkmntdirs && mount -a", @@ -334,7 +250,7 @@ func newConf(ctx context.Context, conf config.Config) (l Config, err error) { // trim mounted drive to recover disk space l.Provision = append(l.Provision, Provision{ Mode: ProvisionModeSystem, - Script: `readlink /sbin/fstrim || fstrim -a`, + Script: `readlink /usr/sbin/fstrim || fstrim -a`, }) // workaround for slow virtiofs https://github.com/drud/ddev/issues/4466#issuecomment-1361261185 @@ -507,8 +423,10 @@ type Network struct { type ProvisionMode = string const ( - ProvisionModeSystem ProvisionMode = "system" - ProvisionModeUser ProvisionMode = "user" + ProvisionModeSystem ProvisionMode = "system" + ProvisionModeUser ProvisionMode = "user" + ProvisionModeBoot ProvisionMode = "boot" + ProvisionModeDependency ProvisionMode = "dependency" ) type Provision struct { diff --git a/go.mod b/go.mod index b8c22a25..a91a0f4b 100644 --- a/go.mod +++ b/go.mod @@ -13,28 +13,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require ( - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/apparentlymart/go-cidr v1.1.0 // indirect - github.com/google/btree v1.1.2 // indirect - github.com/google/gopacket v1.1.19 // indirect - github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 // indirect - github.com/josharian/native v1.1.0 // indirect - github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 // indirect - github.com/mdlayher/socket v0.4.1 // indirect - github.com/mdlayher/vsock v1.2.1 // indirect - github.com/miekg/dns v1.1.56 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect - gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db // indirect - inet.af/tcpproxy v0.0.0-20221017015627-91f861402626 // indirect -) +require github.com/stretchr/testify v1.8.4 // indirect require ( github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect @@ -42,7 +21,6 @@ require ( ) require ( - github.com/containers/gvisor-tap-vsock v0.7.1 github.com/docker/go-units v0.5.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 0b49670d..f6a3f758 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,3 @@ -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= -github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= -github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= -github.com/containers/gvisor-tap-vsock v0.7.1 h1:+Rc+sOPplrkQb/BUXeN0ug8TxjgyrIqo/9P/eNS2A4c= -github.com/containers/gvisor-tap-vsock v0.7.1/go.mod h1:WSSsjcuYZkvP8i0J+Ht3LF8yvysn3krD5zxQ74wz7y0= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -13,65 +6,19 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg= -github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE= -github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= -github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= -github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= -github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 h1:DZMFueDbfz6PNc1GwDRA8+6lBx1TB9UnxDQliCqR73Y= -github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2/go.mod h1:SWzULI85WerrFt3u+nIm5F9l7EvxZTKQvd0InF3nmgM= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= -github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= -github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= -github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= -github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= -github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= -github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= -github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= -github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= -github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= -github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= -github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= -github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= -github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= @@ -81,56 +28,15 @@ github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQ github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww= -github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -138,25 +44,8 @@ golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db h1:WZSmkyu/hep9YhWIlBZefwGVBrnGE5yW8JPD56YRsXs= -gvisor.dev/gvisor v0.0.0-20230715022000-fd277b20b8db/go.mod h1:sQuqOkxbfJq/GS2uSnqHphtXclHyk/ZrAGhZBxxsq6g= -inet.af/tcpproxy v0.0.0-20221017015627-91f861402626 h1:2dMP3Ox/Wh5BiItwOt4jxRsfzkgyBrHzx2nW28Yg6nc= -inet.af/tcpproxy v0.0.0-20221017015627-91f861402626/go.mod h1:Tojt5kmHpDIR2jMojxzZK2w2ZR7OILODmUo2gaSwjrk= diff --git a/qemu/qemu.go b/qemu/qemu.go deleted file mode 100644 index a507c53f..00000000 --- a/qemu/qemu.go +++ /dev/null @@ -1,71 +0,0 @@ -package qemu - -import ( - "fmt" - "os/exec" - "path/filepath" - "strings" - - "github.com/abiosoft/colima/config" - "github.com/abiosoft/colima/environment" -) - -// Qemu binaries -const ( - BinAARCH64 = "qemu-system-aarch64" - BinX8664 = "qemu-system-x86_64" -) - -// Binaries is a list of Qemu binaries -var Binaries = []string{ - BinAARCH64, - BinX8664, -} - -// InstallDir is a typical Unix installation directory that contains `bin` and `share`. -type InstallDir string - -// Bin is the directory for Qemu binaries. -// Typically what gets added to PATH. -func (i InstallDir) Bin() string { - return filepath.Join(string(i), "bin") -} - -// Share is the corresponding share directory for BinDir. -func (i InstallDir) Share() string { - return filepath.Join(string(i), "share") -} - -// Root points to this InstallDir. -func (i InstallDir) Root() string { - return string(i) -} - -// BinsEnvVar returns the environment variables for the Qemu binaries. -// QEMU_SYSTEM_X86_64=/path/to/x86-bin -// QEMU_SYSTEM_AARCH64=/path/to/aarch64-bin -func (i InstallDir) BinsEnvVar() []string { - return []string{ - "QEMU_SYSTEM_X86_64=" + filepath.Join(i.Bin(), BinX8664), - "QEMU_SYSTEM_AARCH64=" + filepath.Join(i.Bin(), BinAARCH64), - } -} - -// HostDir returns the install directory for Qemu on the host. -func HostDir() (InstallDir, error) { - qemu, err := exec.LookPath("qemu-system-" + string(environment.HostArch().Value())) - if err != nil { - return "", fmt.Errorf("error locating qemu binaries in PATH: %w", err) - } - qemuBinDir := filepath.Dir(qemu) - if !strings.HasSuffix(qemuBinDir, "/bin") { - return "", fmt.Errorf("unsupport bin directory '%s' for qemu", qemuBinDir) - } - - return InstallDir(filepath.Dir(qemuBinDir)), nil -} - -// LimaDir returns the install directory for Qemu to be utilised by Lima. -func LimaDir() InstallDir { - return InstallDir(config.WrapperDir()) -} diff --git a/scripts/integration.sh b/scripts/integration.sh new file mode 100755 index 00000000..fd10d4d7 --- /dev/null +++ b/scripts/integration.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -ex + +alias colima="$COLIMA_BINARY" +DOCKER_CONTEXT="$(docker info -f '{{json .}}' | jq -r '.ClientInfo.Context')" + +OTHER_ARCH="amd64" +if [ "$GOARCH" == "amd64" ]; then + OTHER_ARCH="arm64" +fi + +stage() ( + set +x + echo + echo "######################################" + echo "$@" + echo "######################################" + echo + set -x +) + +test_runtime() ( + stage "runtime: $2, arch: $1" + + NAME="itest-$2" + COLIMA="$COLIMA_BINARY -p $NAME" + + COMMAND="docker" + if [ "$2" == "containerd" ]; then + COMMAND="$COLIMA nerdctl --" + fi + + # reset + $COLIMA delete -f + + # start + $COLIMA start --arch "$1" --runtime "$2" + + # validate + $COMMAND ps && $COMMAND info + + # validate DNS + $COLIMA ssh -- nslookup host.docker.internal + + # valid building image + $COMMAND build integration + + # teardown + $COLIMA delete -f +) + +test_kubernetes() ( + stage "k8s runtime: $2, arch: $1" + + NAME="itest-$2-k8s" + COLIMA="$COLIMA_BINARY -p $NAME" + + # reset + $COLIMA delete -f + + # start + $COLIMA start --arch "$1" --runtime "$2" --kubernetes + + # short delay + sleep 5 + + # validate + kubectl cluster-info && kubectl version && kubectl get nodes -o wide + + # teardown + $COLIMA delete -f +) + +test_runtime $GOARCH docker +test_runtime $GOARCH containerd +test_kubernetes $GOARCH docker +test_kubernetes $GOARCH containerd +test_runtime $OTHER_ARCH docker +test_runtime $OTHER_ARCH containerd + +if [ -n "$DOCKER_CONTEXT" ]; then + docker context use "$DOCKER_CONTEXT" || echo # prevent error +fi diff --git a/shell.nix b/shell.nix index e1547e74..1322c2fa 100644 --- a/shell.nix +++ b/shell.nix @@ -3,7 +3,7 @@ pkgs.mkShell { # nativeBuildInputs is usually what you want -- tools you need to run nativeBuildInputs = with pkgs.buildPackages; [ - go_1_19 + go_1_20 git lima qemu diff --git a/util/downloader/download.go b/util/downloader/download.go index 8884034a..e27c07f4 100644 --- a/util/downloader/download.go +++ b/util/downloader/download.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "strings" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/environment" @@ -11,65 +13,121 @@ import ( "github.com/abiosoft/colima/util/terminal" ) +type ( + hostActions = environment.HostActions + guestActions = environment.GuestActions +) + +type SHA struct { + URL string // url to download the shasum file + Size int // one of 256 or 512 +} + +func (s SHA) validate(host hostActions, url, cacheFilename string) error { + filename := func() string { + if url == "" { + return "" + } + split := strings.Split(url, "/") + return split[len(split)-1] + }() + dir, cacheFilename := filepath.Split(cacheFilename) + + script := strings.NewReplacer( + "{dir}", dir, + "{url}", s.URL, + "{filename}", filename, + "{size}", strconv.Itoa(s.Size), + "{cache_filename}", cacheFilename, + ).Replace( + `cd {dir} && echo "$(curl -sL {url} | grep ' {filename}$' | awk -F' ' '{print $1}') {cache_filename}" | shasum -a {size} --check --status`, + ) + + return host.Run("sh", "-c", script) +} + +// Request is download request +type Request struct { + URL string // request URL + SHA *SHA // shasum url + Filename string // destination file name (absolute path) +} + // Download downloads file at url and saves it in the destination. // // In the implementation, the file is downloaded (and cached) on the host, but copied to the desired // destination for the guest. -// fileName must be a directory on the guest that does not require root access. -func Download(host environment.HostActions, guest environment.GuestActions, url, fileName string) error { +// Request.Filename must be a directory on the guest that does not require root access. +func Download(host hostActions, guest guestActions, r Request) error { d := downloader{ host: host, guest: guest, } - if !d.hasCache(url) { - if err := d.downloadFile(url); err != nil { - return fmt.Errorf("error downloading '%s': %w", url, err) + // if file is on the filesystem, no need for download. A copy suffices + if strings.HasPrefix(r.URL, "/") { + return guest.RunQuiet("cp", r.URL, r.Filename) + } + + if !d.hasCache(r.URL) { + if err := d.downloadFile(r); err != nil { + return fmt.Errorf("error downloading '%s': %w", r.URL, err) } } - return guest.RunQuiet("cp", d.cacheFileName(url), fileName) + return guest.RunQuiet("cp", d.cacheFilename(r.URL), r.Filename) } type downloader struct { - host environment.HostActions - guest environment.GuestActions + host hostActions + guest guestActions } -func (d downloader) cacheFileName(url string) string { +func (d downloader) cacheFilename(url string) string { return filepath.Join(config.CacheDir(), "caches", shautil.SHA256(url).String()) } func (d downloader) cacheDownloadingFileName(url string) string { - return d.cacheFileName(url) + ".downloading" + return d.cacheFilename(url) + ".downloading" } -func (d downloader) downloadFile(url string) (err error) { +func (d downloader) downloadFile(r Request) (err error) { // save to a temporary file initially before renaming to the desired file after successful download // this prevents having a corrupt file - cacheFileName := d.cacheDownloadingFileName(url) - if err := d.host.RunQuiet("mkdir", "-p", filepath.Dir(cacheFileName)); err != nil { + cacheDownloadingFilename := d.cacheDownloadingFileName(r.URL) + if err := d.host.RunQuiet("mkdir", "-p", filepath.Dir(cacheDownloadingFilename)); err != nil { return fmt.Errorf("error preparing cache dir: %w", err) } // get rid of curl's initial progress bar by getting the redirect url directly. - downloadURL, err := d.host.RunOutput("curl", "-Ls", "-o", "/dev/null", "-w", "%{url_effective}", url) + downloadURL, err := d.host.RunOutput("curl", "-Ls", "-o", "/dev/null", "-w", "%{url_effective}", r.URL) if err != nil { return fmt.Errorf("error retrieving redirect url: %w", err) } // ask curl to resume previous download if possible "-C -" - if err := d.host.RunInteractive("curl", "-L", "-#", "-C", "-", "-o", cacheFileName, downloadURL); err != nil { + if err := d.host.RunInteractive("curl", "-L", "-#", "-C", "-", "-o", cacheDownloadingFilename, downloadURL); err != nil { return err } // clear curl progress line terminal.ClearLine() - return d.host.RunQuiet("mv", d.cacheDownloadingFileName(url), d.cacheFileName(url)) + // validate download if sha is present + if r.SHA != nil { + if err := r.SHA.validate(d.host, r.URL, cacheDownloadingFilename); err != nil { + + // move file to allow subsequent re-download + // error discarded, would not be actioned anyways + _ = d.host.RunQuiet("mv", cacheDownloadingFilename, cacheDownloadingFilename+".invalid") + + return fmt.Errorf("error validating SHA sum for '%s': %w", filepath.Base(r.Filename), err) + } + } + return d.host.RunQuiet("mv", cacheDownloadingFilename, d.cacheFilename(r.URL)) } func (d downloader) hasCache(url string) bool { - _, err := os.Stat(d.cacheFileName(url)) + _, err := os.Stat(d.cacheFilename(url)) return err == nil } diff --git a/util/terminal/output.go b/util/terminal/output.go index 2c8fbc39..55f8742a 100644 --- a/util/terminal/output.go +++ b/util/terminal/output.go @@ -113,6 +113,9 @@ func (v *verboseWriter) printScreen() error { line = v.sanitizeLine(line) if len(line) > v.termWidth { v.overflow += len(line) / v.termWidth + if len(line)%v.termWidth == 0 { + v.overflow -= 1 + } } line = color.HiBlackString(line) fmt.Println(line) @@ -120,7 +123,7 @@ func (v *verboseWriter) printScreen() error { return nil } -func (v verboseWriter) clearScreen() { +func (v *verboseWriter) clearScreen() { for i := 0; i < len(v.lines)+v.overflow; i++ { ClearLine() }