Merge pull request #213 from crazy-max/switch-to-lima

docker(install): switch to lima
This commit is contained in:
CrazyMax 2023-11-29 02:47:26 -08:00 committed by GitHub
commit 248fc44d7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 217 deletions

View File

@ -29,8 +29,7 @@ describe('install', () => {
jest.resetModules(); jest.resetModules();
process.env = { process.env = {
...originalEnv, ...originalEnv,
SIGN_QEMU_BINARY: '1', LIMA_START_ARGS: '--cpus 4 --memory 8'
COLIMA_START_ARGS: '--cpu 4 --memory 8 --disk 32'
}; };
}); });
afterEach(() => { afterEach(() => {

View File

@ -57,7 +57,6 @@
"async-retry": "^1.3.3", "async-retry": "^1.3.3",
"csv-parse": "^5.5.2", "csv-parse": "^5.5.2",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"js-yaml": "^4.1.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"semver": "^7.5.4", "semver": "^7.5.4",
"tmp": "^0.2.1" "tmp": "^0.2.1"

View File

@ -25,8 +25,8 @@ export const dockerServiceLogsPs1 = (): string => {
return get('docker-service-logs.ps1', dockerServiceLogsPs1Data); return get('docker-service-logs.ps1', dockerServiceLogsPs1Data);
}; };
export const colimaYaml = (): string => { export const limaYaml = (): string => {
return get('colima.yaml', colimaYamlData); return get('lima.yaml', limaYamlData);
}; };
const get = (filename: string, data: string, mode?: string): string => { 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)"} ForEach-Object {"$($_.TimeCreated.ToUniversalTime().ToString("o")) [$($_.LevelDisplayName)] $($_.Message)"}
`; `;
export const colimaYamlData = ` export const limaYamlData = `
# Number of CPUs to be allocated to the virtual machine. # VM type: "qemu" or "vz" (on macOS 13 and later).
# Default: 2 # The vmType can be specified only on creating the instance.
cpu: 2 # The vmType of existing instances cannot be changed.
# Builtin default: "qemu"
# 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
vmType: qemu vmType: qemu
# Volume mount driver for the virtual machine (virtiofs, 9p, sshfs). # OS: "Linux".
# # Builtin default: "Linux"
# virtiofs is limited to macOS and vmType \`vz\`. It is the fastest of the options. os: null
#
# 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\`). # Arch: "default", "x86_64", "aarch64".
# Options available for host emulation can be checked with: \`qemu-system-$(arch) -cpu help\`. # Builtin default: "default" (corresponds to the host architecture)
# Instructions are also supported by appending to the cpu type e.g. "qemu64,+ssse3". arch: null
# Default: host
cpuType: host 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 QEMUs 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
# 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: []
provision: provision:
- mode: system - 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: | script: |
wget -qO- "https://download.docker.com/linux/static/{{dockerBinChannel}}/{{dockerBinArch}}/docker-{{dockerBinVersion}}.tgz" | tar xvz --strip 1 -C /usr/bin/ #!/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}}
# Modify ~/.ssh/config automatically to include a SSH config for the virtual machine. probes:
# SSH config will still be generated in ~/.colima/ssh_config regardless. - script: |
# Default: true #!/bin/bash
sshConfig: false 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
# Configure volume mounts for the virtual machine. hostResolver:
# Colima mounts user's home directory by default to provide a familiar # hostResolver.hosts requires lima 0.8.3 or later. Names defined here will also
# user experience. # resolve inside containers, and not just inside the VM itself.
# hosts:
# EXAMPLE host.docker.internal: host.lima.internal
# 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. portForwards:
# - guestSocket: "/var/run/docker.sock"
# EXAMPLE hostSocket: "{{dockerSock}}"
# env:
# KEY: value audio:
# ANOTHER_KEY: another value # EXPERIMENTAL
# # QEMU audiodev, e.g., "none", "coreaudio", "pa", "alsa", "oss".
# Default: {} # VZ driver, use "vz" as device name
env: {} # Choosing "none" will mute the audio output, and not play any sound.
`; # Builtin default: ""
device: none
export const qemuEntitlements = `
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hypervisor</key>
<true/>
</dict>
</plist>
`; `;

View File

@ -20,7 +20,6 @@ import fsp from 'fs/promises';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import retry from 'async-retry'; import retry from 'async-retry';
import yaml from 'js-yaml';
import * as handlebars from 'handlebars'; import * as handlebars from 'handlebars';
import * as util from 'util'; import * as util from 'util';
import * as core from '@actions/core'; import * as core from '@actions/core';
@ -31,7 +30,7 @@ import * as tc from '@actions/tool-cache';
import {Context} from '../context'; import {Context} from '../context';
import {Exec} from '../exec'; import {Exec} from '../exec';
import {Util} from '../util'; import {Util} from '../util';
import {colimaYamlData, dockerServiceLogsPs1, qemuEntitlements, setupDockerWinPs1} from './assets'; import {limaYamlData, dockerServiceLogsPs1, setupDockerWinPs1} from './assets';
import {GitHubRelease} from '../types/github'; import {GitHubRelease} from '../types/github';
export interface InstallOpts { export interface InstallOpts {
@ -51,6 +50,8 @@ export class Install {
private _version: string | undefined; private _version: string | undefined;
private _toolDir: string | undefined; private _toolDir: string | undefined;
private readonly limaInstanceName = 'docker-actions-toolkit';
constructor(opts: InstallOpts) { constructor(opts: InstallOpts) {
this.runDir = opts.runDir; this.runDir = opts.runDir;
this.version = opts.version || 'latest'; this.version = opts.version || 'latest';
@ -131,30 +132,40 @@ export class Install {
} }
private async installDarwin(): Promise<void> { private async installDarwin(): Promise<void> {
const colimaDir = path.join(os.homedir(), '.colima', 'default'); // TODO: create a custom colima profile to avoid overlap with other actions const limaDir = path.join(os.homedir(), '.lima', this.limaInstanceName);
await io.mkdirP(colimaDir); await io.mkdirP(limaDir);
const dockerHost = `unix://${colimaDir}/docker.sock`; const dockerHost = `unix://${limaDir}/docker.sock`;
if (!(await Install.colimaInstalled())) { // avoid brew to upgrade unrelated packages.
await core.group('Installing colima', async () => { let envs = Object.assign({}, process.env, {
await Exec.exec('brew', ['install', 'colima']); HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: '1'
}) as {
[key: string]: string;
};
if (!(await Install.limaInstalled())) {
await core.group('Installing lima', async () => {
await Exec.exec('brew', ['install', 'lima'], {env: envs});
}); });
} }
await core.group('Creating colima config', async () => { await core.group('Creating lima config', async () => {
let colimaDaemonConfig = {}; let limaDaemonConfig = {};
if (this.daemonConfig) { if (this.daemonConfig) {
colimaDaemonConfig = JSON.parse(this.daemonConfig); limaDaemonConfig = JSON.parse(this.daemonConfig);
} }
const colimaCfg = handlebars.compile(colimaYamlData)({ handlebars.registerHelper('stringify', function (obj) {
daemonConfig: yaml.dump(yaml.load(JSON.stringify({docker: colimaDaemonConfig}))), return new handlebars.SafeString(JSON.stringify(obj));
dockerBinVersion: this._version,
dockerBinChannel: this.channel,
dockerBinArch: Install.platformArch()
}); });
core.info(`Writing colima config to ${path.join(colimaDir, 'colima.yaml')}`); const limaCfg = handlebars.compile(limaYamlData)({
fs.writeFileSync(path.join(colimaDir, 'colima.yaml'), colimaCfg); daemonConfig: limaDaemonConfig,
core.info(colimaCfg); dockerSock: `${limaDir}/docker.sock`,
dockerBinVersion: this._version,
dockerBinChannel: this.channel
});
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(); const qemuArch = await Install.qemuArch();
@ -162,40 +173,29 @@ export class Install {
await Exec.exec(`qemu-system-${qemuArch} --version`); await Exec.exec(`qemu-system-${qemuArch} --version`);
}); });
// https://github.com/abiosoft/colima/issues/786#issuecomment-1693629650 // lima might already be started on the runner so env var added in download
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
// method is not expanded to the running process. // method is not expanded to the running process.
const envs = Object.assign({}, process.env, { envs = Object.assign({}, envs, {
PATH: `${this.toolDir}:${process.env.PATH}` PATH: `${this.toolDir}:${process.env.PATH}`
}) as { }) as {
[key: string]: string; [key: string]: string;
}; };
await core.group('Starting colima', async () => { await core.group('Starting lima instance', async () => {
const colimaStartArgs = ['start', '--very-verbose']; const limaStartArgs = ['start', `--name=${this.limaInstanceName}`, '--tty=false'];
if (process.env.COLIMA_START_ARGS) { if (process.env.LIMA_START_ARGS) {
colimaStartArgs.push(process.env.COLIMA_START_ARGS); limaStartArgs.push(process.env.LIMA_START_ARGS);
} }
try { try {
await Exec.exec(`colima ${colimaStartArgs.join(' ')}`, [], {env: envs}); await Exec.exec(`limactl ${limaStartArgs.join(' ')}`, [], {env: envs});
} catch (e) { } catch (e) {
const limaColimaDir = path.join(os.homedir(), '.lima', 'colima');
fsp fsp
.readdir(limaColimaDir) .readdir(limaDir)
.then(files => { .then(files => {
files files
.filter(f => path.extname(f) === '.log') .filter(f => path.extname(f) === '.log')
.forEach(f => { .forEach(f => {
const logfile = path.join(limaColimaDir, f); const logfile = path.join(limaDir, f);
const logcontent = fs.readFileSync(logfile, {encoding: 'utf8'}).trim(); const logcontent = fs.readFileSync(logfile, {encoding: 'utf8'}).trim();
if (logcontent.length > 0) { if (logcontent.length > 0) {
core.info(`### ${logfile}:\n${logcontent}`); core.info(`### ${logfile}:\n${logcontent}`);
@ -362,14 +362,15 @@ EOF`,
private async tearDownDarwin(): Promise<void> { private async tearDownDarwin(): Promise<void> {
await core.group('Docker daemon logs', async () => { await core.group('Docker daemon logs', async () => {
await Exec.exec('colima', ['exec', '--', 'cat', '/var/log/docker.log']).catch(async () => { await Exec.exec('limactl', ['shell', '--tty=false', this.limaInstanceName, 'sudo', 'journalctl', '-u', 'docker.service', '-l', '--no-pager']).catch(() => {
await Exec.exec('colima', ['exec', '--', 'sudo', 'journalctl', '-u', 'docker.service', '-l', '--no-pager']).catch(() => {
core.warning(`Failed to get Docker daemon logs`); core.warning(`Failed to get Docker daemon logs`);
}); });
}); });
await core.group('Stopping lima instance', async () => {
await Exec.exec('limactl', ['stop', '--tty=false', this.limaInstanceName, '--force']);
}); });
await core.group('Stopping colima', async () => { await core.group('Removing lima instance', async () => {
await Exec.exec('colima', ['stop', '--very-verbose']); await Exec.exec('limactl', ['delete', '--tty=false', this.limaInstanceName, '--force']);
}); });
await core.group('Removing Docker context', async () => { await core.group('Removing Docker context', async () => {
await Exec.exec('docker', ['context', 'rm', '-f', this.contextName]); await Exec.exec('docker', ['context', 'rm', '-f', this.contextName]);
@ -464,15 +465,15 @@ EOF`,
} }
} }
private static async colimaInstalled(): Promise<boolean> { private static async limaInstalled(): Promise<boolean> {
return await io return await io
.which('colima', true) .which('lima', true)
.then(res => { .then(res => {
core.debug(`docker.Install.colimaAvailable ok: ${res}`); core.debug(`docker.Install.limaAvailable ok: ${res}`);
return true; return true;
}) })
.catch(error => { .catch(error => {
core.debug(`docker.Install.colimaAvailable error: ${error}`); core.debug(`docker.Install.limaAvailable error: ${error}`);
return false; return false;
}); });
} }

View File

@ -1065,7 +1065,6 @@ __metadata:
eslint-plugin-prettier: ^5.0.0 eslint-plugin-prettier: ^5.0.0
handlebars: ^4.7.8 handlebars: ^4.7.8
jest: ^29.6.4 jest: ^29.6.4
js-yaml: ^4.1.0
jwt-decode: ^4.0.0 jwt-decode: ^4.0.0
prettier: ^3.0.3 prettier: ^3.0.3
rimraf: ^5.0.1 rimraf: ^5.0.1