diff --git a/environment/container/containerd/containerd.go b/environment/container/containerd/containerd.go index f52043b82..e7e338e9e 100644 --- a/environment/container/containerd/containerd.go +++ b/environment/container/containerd/containerd.go @@ -46,6 +46,11 @@ func (c containerdRuntime) Name() string { } func (c containerdRuntime) Provision(context.Context) error { + if err := c.guest.RunQuiet("sh", "-c", + `sudo sed -i '/disabled_plugins =/c\disabled_plugins = []' /etc/containerd/config.toml`, + ); err != nil { + return err + } return c.guest.Write(buildKitConfFile, buildKitConf) } @@ -53,7 +58,7 @@ func (c containerdRuntime) Start(ctx context.Context) error { a := c.Init(ctx) a.Add(func() error { - return c.guest.Run("sudo", "service", "containerd", "start") + return c.guest.Run("sudo", "service", "containerd", "restart") }) // service startup takes few seconds, retry at most 10 times before giving up. diff --git a/environment/container/docker/docker.go b/environment/container/docker/docker.go index eef86578a..d6c72c731 100644 --- a/environment/container/docker/docker.go +++ b/environment/container/docker/docker.go @@ -69,8 +69,10 @@ func (d dockerRuntime) Provision(ctx context.Context) error { func (d dockerRuntime) Start(ctx context.Context) error { a := d.Init(ctx) - a.Add(func() error { - return d.guest.Run("sudo", "service", "docker", "start") + // TODO: interval is high due to 0.6.3->0.6.4 docker-ce package transition + // to ensure startup is successful + a.Retry("", time.Second*5, 24, func(int) error { + return d.guest.RunQuiet("sudo", "service", "docker", "start") }) // service startup takes few seconds, retry at most 5 times before giving up. diff --git a/environment/vm/lima/deb/deb.go b/environment/vm/lima/deb/deb.go new file mode 100644 index 000000000..d1b0257cf --- /dev/null +++ b/environment/vm/lima/deb/deb.go @@ -0,0 +1,24 @@ +package deb + +import ( + "github.com/abiosoft/colima/environment" +) + +type ( + hostActions = environment.HostActions + guestActions = environment.GuestActions +) + +// URISource is the source for fetching URI for deb packages. +type URISource interface { + // Name is the name for the URISource. + Name() string + // Packages is the list of package names. + Packages() []string + // URIs return the list of URIs to download the deb files. + URIs(arch environment.Arch) ([]string, error) + // PreInstall is done before the deb package are installed. + PreInstall() error + // Install installs the packages directly using the internet. + Install() error +} diff --git a/environment/vm/lima/deb/docker.go b/environment/vm/lima/deb/docker.go new file mode 100644 index 000000000..161dc80fb --- /dev/null +++ b/environment/vm/lima/deb/docker.go @@ -0,0 +1,77 @@ +package deb + +import ( + "fmt" + "strings" + + "github.com/abiosoft/colima/environment" +) + +var dockerPackages = []string{ + "docker-ce", + "docker-ce-cli", + "containerd.io", + "docker-buildx-plugin", + "docker-compose-plugin", +} + +var _ URISource = (*Docker)(nil) + +// Docker is the URISource for Docker CE packages. +type Docker struct { + Host hostActions + Guest guestActions +} + +// PreInstall implements URISource. +func (d *Docker) PreInstall() error { + return d.Guest.RunQuiet("sh", "-c", "sudo apt remove -y docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc") +} + +// Install implements URISource. +func (d *Docker) Install() error { + return d.Guest.Run("sh", "-c", + `curl -fsSL https://get.docker.com -o /tmp/get-docker.sh && sudo sh /tmp/get-docker.sh`, + ) +} + +// Name implements URISource. +func (*Docker) Name() string { + return "docker-ce" +} + +// Packages implements URISource. +func (*Docker) Packages() []string { + return dockerPackages +} + +// URIs implements URISource. +func (d *Docker) URIs(arch environment.Arch) ([]string, error) { + var uris []string + + pkgFiles, err := d.pkgFiles(arch) + if err != nil { + return nil, fmt.Errorf("error getting package names and version: %w", err) + } + + for _, file := range pkgFiles { + uri := d.debPackageBaseURI(arch) + file + uris = append(uris, uri) + } + + return uris, nil +} + +func (d Docker) pkgFiles(arch environment.Arch) ([]string, error) { + script := fmt.Sprintf(`curl -sL https://download.docker.com/linux/ubuntu/dists/mantic/stable/binary-%s/Packages | grep '^Filename: ' | awk -F'/' '{print $NF}'`, arch.Value().GoArch()) + filenames, err := d.Host.RunOutput("sh", "-c", script) + if err != nil { + return nil, fmt.Errorf("error retrieving deb package filenames: %w", err) + } + + return strings.Fields(filenames), nil +} + +func (d Docker) debPackageBaseURI(arch environment.Arch) string { + return fmt.Sprintf("https://download.docker.com/linux/ubuntu/dists/mantic/pool/stable/%s/", arch.GoArch()) +} diff --git a/environment/vm/lima/deb/mantic.go b/environment/vm/lima/deb/mantic.go new file mode 100644 index 000000000..3eedfd085 --- /dev/null +++ b/environment/vm/lima/deb/mantic.go @@ -0,0 +1,61 @@ +package deb + +import ( + "fmt" + "strings" + + "github.com/abiosoft/colima/environment" +) + +var manticPackages = []string{ + // docker + "iptables", + // k8s + "socat", + // utilities + "htop", "vim", "inetutils-ping", "dnsutils", +} + +var _ URISource = (*Mantic)(nil) + +// Mantic is the URISource for Ubuntu Mantic packages. +type Mantic struct { + Guest guestActions +} + +// PreInstall implements URISource. +func (*Mantic) PreInstall() error { + return nil +} + +// Packages implements URISource. +func (*Mantic) Packages() []string { + return manticPackages +} + +// Name implements URISource. +func (*Mantic) Name() string { + return "mantic-debs" +} + +// URIs implements URISource. +func (m *Mantic) URIs(_ environment.Arch) ([]string, error) { + _ = m.Guest.RunQuiet("sudo apt update -y") + + output := "" + for _, p := range manticPackages { + line := fmt.Sprintf(`sudo apt-get install --reinstall --print-uris -qq "%s" | cut -d"'" -f2`, p) + out, err := m.Guest.RunOutput("sh", "-c", line) + if err != nil { + return nil, fmt.Errorf("error fetching dependencies list: %w", err) + } + output += out + " " + } + + return strings.Fields(output), nil +} + +// Install implements URISource. +func (m *Mantic) Install() error { + return m.Guest.Run("sh", "-c", "sudo apt update && sudo apt install -f -y "+strings.Join(manticPackages, " ")) +} diff --git a/environment/vm/lima/dependencies.go b/environment/vm/lima/dependencies.go index 3a70997a2..081846d18 100644 --- a/environment/vm/lima/dependencies.go +++ b/environment/vm/lima/dependencies.go @@ -4,32 +4,25 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/abiosoft/colima/config" "github.com/abiosoft/colima/environment" + "github.com/abiosoft/colima/environment/vm/lima/deb" "github.com/abiosoft/colima/util/fsutil" "github.com/abiosoft/colima/util/terminal" "github.com/sirupsen/logrus" ) -var dependencyPackages = []string{ - // docker and k8s - "docker.io", "socat", - // 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) { +func (l *limaVM) cacheDependencies(src deb.URISource, 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)) + dir := filepath.Join(config.CacheDir(), "packages", codename, string(arch), src.Name()) if err := fsutil.MkdirAll(dir, 0755); err != nil { return "", fmt.Errorf("error creating cache directory for OS packages: %w", err) } @@ -40,17 +33,12 @@ func (l *limaVM) cacheDependencies(log *logrus.Entry, conf config.Config) (strin 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 + " " + var debPackages []string + packages, err := src.URIs(arch) + if err != nil { + return "", fmt.Errorf("error fetching package URIs using %s: %w", src.Name(), err) } - - debPackages := strings.Fields(output) + debPackages = append(debPackages, packages...) // progress bar for Ubuntu deb packages download. // TODO: extract this into re-usable progress bar for multi-downloads @@ -76,27 +64,54 @@ func (l *limaVM) cacheDependencies(log *logrus.Entry, conf config.Config) (strin } func (l *limaVM) installDependencies(log *logrus.Entry, conf config.Config) error { - // cache dependencies - dir, err := l.cacheDependencies(log, conf) - if err != nil { - log.Warnln(fmt.Errorf("error caching dependencies: %w", err)) - log.Warnln("falling back to normal package install") - return l.Run("sh", "-c", "sudo apt install -y "+strings.Join(dependencyPackages, " ")) + srcs := []deb.URISource{ + &deb.Mantic{Guest: l}, + &deb.Docker{Host: l.host, Guest: l}, } - // validate if packages were previously installed - installed := true - for _, p := range dependencyPackages { - if err := l.RunQuiet("dpkg", "-s", p); err != nil { - installed = false - break + for _, src := range srcs { + if err := src.PreInstall(); err != nil { + log.Warn(fmt.Errorf("preinstall check failed for %s: %w", src.Name(), err)) + } + + // cache dependencies + dir, err := l.cacheDependencies(src, log, conf) + if err != nil { + log.Warnln(fmt.Errorf("error caching dependencies for %s: %w", src.Name(), err)) + log.Warnln("falling back to normal package install") + + if err := src.Install(); err != nil { + return fmt.Errorf("error installing packages using %s: %w", src.Name(), err) + } + + // installed + continue + } + + // validate if packages were previously installed + installed := true + for _, p := range src.Packages() { + if err := l.RunQuiet("dpkg", "-s", p); err != nil { + installed = false + break + } + } + + if installed { + continue + } + + // install packages + if err := l.Run("sh", "-c", "sudo dpkg -i "+dir+"/*.deb"); err != nil { + log.Warn(fmt.Errorf("error installing packages using %s: %w", src.Name(), err)) + log.Warnln("falling back to normal package install") + + if err := src.Install(); err != nil { + return fmt.Errorf("error installing packages using %s: %w", src.Name(), err) + } } - } - if installed { - return nil } - // install packages - return l.Run("sh", "-c", "sudo dpkg -i "+dir+"/*.deb") + return nil }