From 0a04d5661f842f361d2acc97d67d13598a5860d4 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Sun, 26 Nov 2023 22:15:16 +0100 Subject: [PATCH] docker(install): switch to lima Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- __tests__/docker/install.test.itg.ts | 3 +- package.json | 1 - src/docker/assets.ts | 284 +++++++++++---------------- src/docker/install.ts | 88 ++++----- yarn.lock | 1 - 5 files changed, 158 insertions(+), 219 deletions(-) diff --git a/__tests__/docker/install.test.itg.ts b/__tests__/docker/install.test.itg.ts index b4a24263..479e4531 100644 --- a/__tests__/docker/install.test.itg.ts +++ b/__tests__/docker/install.test.itg.ts @@ -29,8 +29,7 @@ describe('install', () => { jest.resetModules(); process.env = { ...originalEnv, - SIGN_QEMU_BINARY: '1', - COLIMA_START_ARGS: '--cpu 4 --memory 8 --disk 32' + LIMA_START_ARGS: '--cpus 4 --memory 8' }; }); afterEach(() => { diff --git a/package.json b/package.json index 843f84de..8e36a59b 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "async-retry": "^1.3.3", "csv-parse": "^5.5.2", "handlebars": "^4.7.8", - "js-yaml": "^4.1.0", "jwt-decode": "^4.0.0", "semver": "^7.5.4", "tmp": "^0.2.1" diff --git a/src/docker/assets.ts b/src/docker/assets.ts index 30fe7699..a99f7de7 100644 --- a/src/docker/assets.ts +++ b/src/docker/assets.ts @@ -25,8 +25,8 @@ export const dockerServiceLogsPs1 = (): string => { return get('docker-service-logs.ps1', dockerServiceLogsPs1Data); }; -export const colimaYaml = (): string => { - return get('colima.yaml', colimaYamlData); +export const limaYaml = (): string => { + return get('lima.yaml', limaYamlData); }; const get = (filename: string, data: string, mode?: string): string => { @@ -128,174 +128,122 @@ Get-WinEvent -ea SilentlyContinue \` ForEach-Object {"$($_.TimeCreated.ToUniversalTime().ToString("o")) [$($_.LevelDisplayName)] $($_.Message)"} `; -export const colimaYamlData = ` -# Number of CPUs to be allocated to the virtual machine. -# Default: 2 -cpu: 2 - -# Size of the disk in GiB to be allocated to the virtual machine. -# NOTE: changing this has no effect after the virtual machine has been created. -# Default: 60 -disk: 60 - -# Size of the memory in GiB to be allocated to the virtual machine. -# Default: 2 -memory: 2 - -# Architecture of the virtual machine (x86_64, aarch64, host). -# Default: host -arch: host - -# Container runtime to be used (docker, containerd). -# Default: docker -runtime: docker - -# Kubernetes configuration for the virtual machine. -kubernetes: - enabled: false - -# Auto-activate on the Host for client access. -# Setting to true does the following on startup -# - sets as active Docker context (for Docker runtime). -# - sets as active Kubernetes context (if Kubernetes is enabled). -# Default: true -autoActivate: false - -# Network configurations for the virtual machine. -network: - # Assign reachable IP address to the virtual machine. - # NOTE: this is currently macOS only and ignored on Linux. - # Default: false - address: false - - # Custom DNS resolvers for the virtual machine. - # - # EXAMPLE - # dns: [8.8.8.8, 1.1.1.1] - # - # Default: [] - dns: [] - - # DNS hostnames to resolve to custom targets using the internal resolver. - # This setting has no effect if a custom DNS resolver list is supplied above. - # It does not configure the /etc/hosts files of any machine or container. - # The value can be an IP address or another host. - # - # EXAMPLE - # dnsHosts: - # example.com: 1.2.3.4 - dnsHosts: - host.docker.internal: host.lima.internal - -# Forward the host's SSH agent to the virtual machine. -# Default: false -forwardAgent: false - -# Docker daemon configuration that maps directly to daemon.json. -# https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file. -# NOTE: some settings may affect Colima's ability to start docker. e.g. \`hosts\`. -# -# EXAMPLE - disable buildkit -# docker: -# features: -# buildkit: false -# -# EXAMPLE - add insecure registries -# docker: -# insecure-registries: -# - myregistry.com:5000 -# - host.docker.internal:5000 -# -# Colima default behaviour: buildkit enabled -# Default: {} -{{daemonConfig}} - -# Virtual Machine type (qemu, vz) -# NOTE: this is macOS 13 only. For Linux and macOS <13.0, qemu is always used. -# -# vz is macOS virtualization framework and requires macOS 13 -# -# Default: qemu +export const limaYamlData = ` +# VM type: "qemu" or "vz" (on macOS 13 and later). +# The vmType can be specified only on creating the instance. +# The vmType of existing instances cannot be changed. +# Builtin default: "qemu" vmType: qemu -# Volume mount driver for the virtual machine (virtiofs, 9p, sshfs). -# -# virtiofs is limited to macOS and vmType \`vz\`. It is the fastest of the options. -# -# 9p is the recommended and the most stable option for vmType \`qemu\`. -# -# sshfs is faster than 9p but the least reliable of the options (when there are lots -# of concurrent reads or writes). -# -# Default: virtiofs (for vz), sshfs (for qemu) -mountType: 9p - -# The CPU type for the virtual machine (requires vmType \`qemu\`). -# Options available for host emulation can be checked with: \`qemu-system-$(arch) -cpu help\`. -# Instructions are also supported by appending to the cpu type e.g. "qemu64,+ssse3". -# Default: host -cpuType: host - -# Custom provision scripts for the virtual machine. -# Provisioning scripts are executed on startup and therefore needs to be idempotent. -# -# EXAMPLE - script exected as root -# provision: -# - mode: system -# script: apk add htop vim -# -# EXAMPLE - script exected as user -# provision: -# - mode: user -# script: | -# [ -f ~/.provision ] && exit 0; -# echo provisioning as $USER... -# touch ~/.provision -# -# Default: [] +# OS: "Linux". +# Builtin default: "Linux" +os: null + +# Arch: "default", "x86_64", "aarch64". +# Builtin default: "default" (corresponds to the host architecture) +arch: null + +images: +- location: "https://cloud-images.ubuntu.com/releases/22.04/release-20231026/ubuntu-22.04-server-cloudimg-amd64.img" + arch: "x86_64" + digest: "sha256:054db2d88c454bb0ad8dfd8883955e3946b57d2b0bf0d023f3ade3c93cdd14e5" +- location: "https://cloud-images.ubuntu.com/releases/22.04/release-20231026/ubuntu-22.04-server-cloudimg-arm64.img" + arch: "aarch64" + digest: "sha256:eafa7742ce5ff109222ea313d31ea366d587b4e89b900b11d8285ae775dfe8c3" + +# CPUs +# Builtin default: min(4, host CPU cores) +cpus: null + +# Memory size +# Builtin default: min("4GiB", half of host memory) +memory: null + +# Disk size +# Builtin default: "100GiB" +disk: 60GiB + +# Expose host directories to the guest, the mount point might be accessible from all UIDs in the guest +# Builtin default: null (Mount nothing) +# This file: Mount the home as read-only, /tmp/lima as writable +mounts: +- location: "~" +- location: "/tmp/lima" + writable: true + +# Mount type for above mounts, such as "reverse-sshfs" (from sshocker), "9p" (EXPERIMENTAL, from QEMU’s virtio-9p-pci, aka virtfs), +# or "virtiofs" (EXPERIMENTAL, needs \`vmType: vz\`) +# Builtin default: "reverse-sshfs" (for QEMU), "virtiofs" (for vz) +mountType: null + +containerd: + system: false + user: false + provision: - - mode: system - script: | - wget -qO- "https://download.docker.com/linux/static/{{dockerBinChannel}}/{{dockerBinArch}}/docker-{{dockerBinVersion}}.tgz" | tar xvz --strip 1 -C /usr/bin/ - -# Modify ~/.ssh/config automatically to include a SSH config for the virtual machine. -# SSH config will still be generated in ~/.colima/ssh_config regardless. -# Default: true -sshConfig: false - -# Configure volume mounts for the virtual machine. -# Colima mounts user's home directory by default to provide a familiar -# user experience. -# -# EXAMPLE -# mounts: -# - location: ~/secrets -# writable: false -# - location: ~/projects -# writable: true -# -# Colima default behaviour: $HOME and /tmp/colima are mounted as writable. -# Default: [] -mounts: [] - -# Environment variables for the virtual machine. -# -# EXAMPLE -# env: -# KEY: value -# ANOTHER_KEY: another value -# -# Default: {} -env: {} -`; +- mode: system + # This script defines the host.docker.internal hostname when hostResolver is disabled. + # It is also needed for lima 0.8.2 and earlier, which does not support hostResolver.hosts. + # Names defined in /etc/hosts inside the VM are not resolved inside containers when + # using the hostResolver; use hostResolver.hosts instead (requires lima 0.8.3 or later). + script: | + #!/bin/sh + sed -i 's/host.lima.internal.*/host.lima.internal host.docker.internal/' /etc/hosts +- mode: system + script: | + #!/bin/sh + apt-get install -f -y iptables +- mode: system + script: | + #!/bin/bash + set -eux -o pipefail + command -v docker >/dev/null 2>&1 && exit 0 + if [ ! -e /etc/systemd/system/docker.socket.d/override.conf ]; then + mkdir -p /etc/systemd/system/docker.socket.d + # Alternatively we could just add the user to the "docker" group, but that requires restarting the user session + cat <<-EOF >/etc/systemd/system/docker.socket.d/override.conf + [Socket] + SocketUser=\${LIMA_CIDATA_USER} + EOF + fi + if [ ! -e /etc/docker/daemon.json ]; then + mkdir -p /etc/docker + cat <<-EOF >/etc/docker/daemon.json + {{stringify daemonConfig}} + EOF + fi + export DEBIAN_FRONTEND=noninteractive + curl -fsSL https://get.docker.com | sh -s -- --channel {{dockerBinChannel}} --version {{dockerBinVersion}} + +probes: +- script: | + #!/bin/bash + set -eux -o pipefail + if ! timeout 30s bash -c "until command -v docker >/dev/null 2>&1; do sleep 3; done"; then + echo >&2 "docker is not installed yet" + exit 1 + fi + if ! timeout 30s bash -c "until pgrep dockerd; do sleep 3; done"; then + echo >&2 "dockerd is not running" + exit 1 + fi + hint: See "/var/log/cloud-init-output.log". in the guest + +hostResolver: + # hostResolver.hosts requires lima 0.8.3 or later. Names defined here will also + # resolve inside containers, and not just inside the VM itself. + hosts: + host.docker.internal: host.lima.internal -export const qemuEntitlements = ` - - - - - com.apple.security.hypervisor - - - +portForwards: +- guestSocket: "/var/run/docker.sock" + hostSocket: "{{dockerSock}}" + +audio: + # EXPERIMENTAL + # QEMU audiodev, e.g., "none", "coreaudio", "pa", "alsa", "oss". + # VZ driver, use "vz" as device name + # Choosing "none" will mute the audio output, and not play any sound. + # Builtin default: "" + device: none `; diff --git a/src/docker/install.ts b/src/docker/install.ts index 91e83c4b..bca2c25b 100644 --- a/src/docker/install.ts +++ b/src/docker/install.ts @@ -20,7 +20,6 @@ import fsp from 'fs/promises'; import os from 'os'; import path from 'path'; import retry from 'async-retry'; -import yaml from 'js-yaml'; import * as handlebars from 'handlebars'; import * as util from 'util'; import * as core from '@actions/core'; @@ -31,7 +30,7 @@ import * as tc from '@actions/tool-cache'; import {Context} from '../context'; import {Exec} from '../exec'; import {Util} from '../util'; -import {colimaYamlData, dockerServiceLogsPs1, qemuEntitlements, setupDockerWinPs1} from './assets'; +import {limaYamlData, dockerServiceLogsPs1, setupDockerWinPs1} from './assets'; import {GitHubRelease} from '../types/github'; export interface InstallOpts { @@ -51,6 +50,8 @@ export class Install { private _version: string | undefined; private _toolDir: string | undefined; + private readonly limaInstanceName = 'docker-actions-toolkit'; + constructor(opts: InstallOpts) { this.runDir = opts.runDir; this.version = opts.version || 'latest'; @@ -131,30 +132,33 @@ export class Install { } private async installDarwin(): Promise { - const colimaDir = path.join(os.homedir(), '.colima', 'default'); // TODO: create a custom colima profile to avoid overlap with other actions - await io.mkdirP(colimaDir); - const dockerHost = `unix://${colimaDir}/docker.sock`; + const limaDir = path.join(os.homedir(), '.lima', this.limaInstanceName); + await io.mkdirP(limaDir); + const dockerHost = `unix://${limaDir}/docker.sock`; - if (!(await Install.colimaInstalled())) { - await core.group('Installing colima', async () => { - await Exec.exec('brew', ['install', 'colima']); + if (!(await Install.limaInstalled())) { + await core.group('Installing lima', async () => { + await Exec.exec('brew', ['install', 'lima']); }); } - await core.group('Creating colima config', async () => { - let colimaDaemonConfig = {}; + await core.group('Creating lima config', async () => { + let limaDaemonConfig = {}; if (this.daemonConfig) { - colimaDaemonConfig = JSON.parse(this.daemonConfig); + limaDaemonConfig = JSON.parse(this.daemonConfig); } - const colimaCfg = handlebars.compile(colimaYamlData)({ - daemonConfig: yaml.dump(yaml.load(JSON.stringify({docker: colimaDaemonConfig}))), + handlebars.registerHelper('stringify', function (obj) { + return new handlebars.SafeString(JSON.stringify(obj)); + }); + const limaCfg = handlebars.compile(limaYamlData)({ + daemonConfig: limaDaemonConfig, + dockerSock: `${limaDir}/docker.sock`, dockerBinVersion: this._version, - dockerBinChannel: this.channel, - dockerBinArch: Install.platformArch() + dockerBinChannel: this.channel }); - core.info(`Writing colima config to ${path.join(colimaDir, 'colima.yaml')}`); - fs.writeFileSync(path.join(colimaDir, 'colima.yaml'), colimaCfg); - core.info(colimaCfg); + core.info(`Writing lima config to ${path.join(limaDir, 'lima.yaml')}`); + fs.writeFileSync(path.join(limaDir, 'lima.yaml'), limaCfg); + core.info(limaCfg); }); const qemuArch = await Install.qemuArch(); @@ -162,17 +166,7 @@ export class Install { await Exec.exec(`qemu-system-${qemuArch} --version`); }); - // https://github.com/abiosoft/colima/issues/786#issuecomment-1693629650 - if (process.env.SIGN_QEMU_BINARY === '1') { - await core.group('Signing QEMU binary with entitlements', async () => { - const qemuEntitlementsFile = path.join(Context.tmpDir(), 'qemu-entitlements.xml'); - core.info(`Writing entitlements to ${qemuEntitlementsFile}`); - fs.writeFileSync(qemuEntitlementsFile, qemuEntitlements); - await Exec.exec(`codesign --sign - --entitlements ${qemuEntitlementsFile} --force /usr/local/bin/qemu-system-${qemuArch}`); - }); - } - - // colima is already started on the runner so env var added in download + // lima might already be started on the runner so env var added in download // method is not expanded to the running process. const envs = Object.assign({}, process.env, { PATH: `${this.toolDir}:${process.env.PATH}` @@ -180,22 +174,21 @@ export class Install { [key: string]: string; }; - await core.group('Starting colima', async () => { - const colimaStartArgs = ['start', '--very-verbose']; - if (process.env.COLIMA_START_ARGS) { - colimaStartArgs.push(process.env.COLIMA_START_ARGS); + await core.group('Starting lima instance', async () => { + const limaStartArgs = ['start', `--name=${this.limaInstanceName}`, '--tty=false']; + if (process.env.LIMA_START_ARGS) { + limaStartArgs.push(process.env.LIMA_START_ARGS); } try { - await Exec.exec(`colima ${colimaStartArgs.join(' ')}`, [], {env: envs}); + await Exec.exec(`limactl ${limaStartArgs.join(' ')}`, [], {env: envs}); } catch (e) { - const limaColimaDir = path.join(os.homedir(), '.lima', 'colima'); fsp - .readdir(limaColimaDir) + .readdir(limaDir) .then(files => { files .filter(f => path.extname(f) === '.log') .forEach(f => { - const logfile = path.join(limaColimaDir, f); + const logfile = path.join(limaDir, f); const logcontent = fs.readFileSync(logfile, {encoding: 'utf8'}).trim(); if (logcontent.length > 0) { core.info(`### ${logfile}:\n${logcontent}`); @@ -362,14 +355,15 @@ EOF`, private async tearDownDarwin(): Promise { await core.group('Docker daemon logs', async () => { - await Exec.exec('colima', ['exec', '--', 'cat', '/var/log/docker.log']).catch(async () => { - await Exec.exec('colima', ['exec', '--', 'sudo', 'journalctl', '-u', 'docker.service', '-l', '--no-pager']).catch(() => { - core.warning(`Failed to get Docker daemon logs`); - }); + await Exec.exec('limactl', ['shell', '--tty=false', this.limaInstanceName, 'sudo', 'journalctl', '-u', 'docker.service', '-l', '--no-pager']).catch(() => { + core.warning(`Failed to get Docker daemon logs`); }); }); - await core.group('Stopping colima', async () => { - await Exec.exec('colima', ['stop', '--very-verbose']); + await core.group('Stopping lima instance', async () => { + await Exec.exec('limactl', ['stop', '--tty=false', this.limaInstanceName, '--force']); + }); + await core.group('Removing lima instance', async () => { + await Exec.exec('limactl', ['delete', '--tty=false', this.limaInstanceName, '--force']); }); await core.group('Removing Docker context', async () => { await Exec.exec('docker', ['context', 'rm', '-f', this.contextName]); @@ -464,15 +458,15 @@ EOF`, } } - private static async colimaInstalled(): Promise { + private static async limaInstalled(): Promise { return await io - .which('colima', true) + .which('lima', true) .then(res => { - core.debug(`docker.Install.colimaAvailable ok: ${res}`); + core.debug(`docker.Install.limaAvailable ok: ${res}`); return true; }) .catch(error => { - core.debug(`docker.Install.colimaAvailable error: ${error}`); + core.debug(`docker.Install.limaAvailable error: ${error}`); return false; }); } diff --git a/yarn.lock b/yarn.lock index e917206b..061d3736 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1065,7 +1065,6 @@ __metadata: eslint-plugin-prettier: ^5.0.0 handlebars: ^4.7.8 jest: ^29.6.4 - js-yaml: ^4.1.0 jwt-decode: ^4.0.0 prettier: ^3.0.3 rimraf: ^5.0.1