diff --git a/__tests__/docker/install.test.e2e.ts b/__tests__/docker/install.test.e2e.ts index 9b55615..e75d60f 100644 --- a/__tests__/docker/install.test.e2e.ts +++ b/__tests__/docker/install.test.e2e.ts @@ -26,7 +26,7 @@ describe('install', () => { await expect((async () => { const install = new Install(); const toolPath = await install.download(version); - await install.install(toolPath); + await install.install(toolPath, version); await Docker.printVersion(); await Docker.printInfo(); })()).resolves.not.toThrow(); diff --git a/package.json b/package.json index 19386bb..f8aaf19 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@actions/io": "^1.1.2", "@actions/tool-cache": "^2.0.1", "csv-parse": "^5.3.5", + "handlebars": "^4.7.7", "jwt-decode": "^3.1.2", "semver": "^7.3.8", "tmp": "^0.2.1" diff --git a/scripts/colima.yaml b/scripts/colima.yaml new file mode 100644 index 0000000..1c30642 --- /dev/null +++ b/scripts/colima.yaml @@ -0,0 +1,175 @@ +# 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 + + # 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 + +# 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: {} +docker: {} + +# 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 + +# 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 + +# 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. +# +# 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: + - mode: system + script: | + mkdir -p /tmp/docker-bins + cd /tmp/docker-bins + wget -qO- "https://download.docker.com/linux/static/{{dockerChannel}}/{{hostArch}}/docker-{{dockerVersion}}.tgz" | tar xvz --strip 1 + mv -f /tmp/docker-bins/* /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: {} diff --git a/src/docker/install.ts b/src/docker/install.ts index a3d6507..d722ae2 100644 --- a/src/docker/install.ts +++ b/src/docker/install.ts @@ -17,10 +17,11 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; +import * as handlebars from 'handlebars'; +import * as util from 'util'; import * as core from '@actions/core'; import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; -import * as util from 'util'; import * as scripts from '../scripts'; import {Context} from '../context'; @@ -64,10 +65,11 @@ export class Install { return tooldir; } - public async install(toolDir: string): Promise { + public async install(toolDir: string, version: string, channel?: string): Promise { + channel = channel || 'stable'; switch (os.platform()) { case 'darwin': { - await this.installDarwin(toolDir); + await this.installDarwin(toolDir, version, channel); break; } case 'linux': { @@ -84,12 +86,33 @@ export class Install { } } - private async installDarwin(toolDir: string): Promise { + private async installDarwin(toolDir: string, version: string, channel?: string): Promise { + const colimaDir = path.join(os.homedir(), '.colima', 'default'); + await io.mkdirP(colimaDir); + const dockerHost = `unix://${colimaDir}/docker.sock`; + if (!(await Install.colimaInstalled())) { await core.group('Installing colima', async () => { await Exec.exec('brew', ['install', 'colima']); }); } + + await core.group('Creating colima config', async () => { + const colimaCfg = handlebars.compile( + fs.readFileSync(scripts.colimaConfig, { + encoding: 'utf8', + flag: 'r' + }) + )({ + hostArch: Install.platformArch(), + dockerVersion: version, + dockerChannel: channel + }); + core.info(`Writing colima config to ${path.join(colimaDir, 'colima.yaml')}`); + fs.writeFileSync(path.join(colimaDir, 'colima.yaml'), colimaCfg); + core.info(colimaCfg); + }); + // colima is already started on the runner so env var added in download // method is not expanded to the running process. const envs = Object.assign({}, process.env, { @@ -98,7 +121,12 @@ export class Install { [key: string]: string; }; await core.group('Starting colima', async () => { - await Exec.exec('colima', ['start', '--runtime', 'docker', '--mount-type', '9p'], {env: envs}); + await Exec.exec('colima', ['start', '--very-verbose'], {env: envs}); + }); + + await core.group('Create Docker context', async () => { + await Exec.exec('docker', ['context', 'create', 'setup-docker-action', '--docker', `host=${dockerHost}`]); + await Exec.exec('docker', ['context', 'use', 'setup-docker-action']); }); } @@ -109,6 +137,7 @@ export class Install { private async installWindows(toolDir: string): Promise { const dockerHost = 'npipe:////./pipe/setup_docker_action'; + const setupCmd = await Util.powershellCommand(scripts.setupDockerPowershell, { ToolDir: toolDir, TmpDir: Context.tmpDir(), @@ -117,6 +146,7 @@ export class Install { await core.group('Install Docker daemon service', async () => { await Exec.exec(setupCmd.command, setupCmd.args); }); + await core.group('Create Docker context', async () => { await Exec.exec('docker', ['context', 'create', 'setup-docker-action', '--docker', `host=${dockerHost}`]); await Exec.exec('docker', ['context', 'use', 'setup-docker-action']); @@ -124,60 +154,56 @@ export class Install { } private downloadURL(version: string, channel: string): string { - let platformOS, platformArch: string; + const platformOS = Install.platformOS(); + const platformArch = Install.platformArch(); + const ext = platformOS === 'win' ? '.zip' : '.tgz'; + return util.format('https://download.docker.com/%s/static/%s/%s/docker-%s%s', platformOS, channel, platformArch, version, ext); + } + + private static platformOS(): string { switch (os.platform()) { case 'darwin': { - platformOS = 'mac'; - break; + return 'mac'; } case 'linux': { - platformOS = 'linux'; - break; + return 'linux'; } case 'win32': { - platformOS = 'win'; - break; + return 'win'; } default: { - platformOS = os.platform(); - break; + return os.platform(); } } + } + + private static platformArch(): string { switch (os.arch()) { case 'x64': { - platformArch = 'x86_64'; - break; + return 'x86_64'; } case 'ppc64': { - platformArch = 'ppc64le'; - break; + return 'ppc64le'; } case 'arm': { // eslint-disable-next-line @typescript-eslint/no-explicit-any const arm_version = (process.config.variables as any).arm_version; switch (arm_version) { case 6: { - platformArch = 'armel'; - break; + return 'armel'; } case 7: { - platformArch = 'armhf'; - break; + return 'armhf'; } default: { - platformArch = `v${arm_version}`; - break; + return `v${arm_version}`; } } - break; } default: { - platformArch = os.arch(); - break; + return os.arch(); } } - const ext = platformOS === 'win' ? '.zip' : '.tgz'; - return util.format('https://download.docker.com/%s/static/%s/%s/docker-%s%s', platformOS, channel, platformArch, version, ext); } private static async colimaInstalled(): Promise { diff --git a/src/scripts.ts b/src/scripts.ts index 0056f5a..ac33853 100644 --- a/src/scripts.ts +++ b/src/scripts.ts @@ -19,3 +19,4 @@ import path from 'path'; const scriptsPath = path.join(__dirname, '..', 'scripts'); export const setupDockerPowershell = path.join(scriptsPath, 'setup-docker.ps1'); +export const colimaConfig = path.join(scriptsPath, 'colima.yaml'); diff --git a/yarn.lock b/yarn.lock index 2e18697..5a5562a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -782,6 +782,7 @@ __metadata: eslint-plugin-import: ^2.27.5 eslint-plugin-jest: ^26.9.0 eslint-plugin-prettier: ^4.2.1 + handlebars: ^4.7.7 jest: ^27.5.1 jwt-decode: ^3.1.2 prettier: ^2.8.3 @@ -3667,6 +3668,24 @@ __metadata: languageName: node linkType: hard +"handlebars@npm:^4.7.7": + version: 4.7.7 + resolution: "handlebars@npm:4.7.7" + dependencies: + minimist: ^1.2.5 + neo-async: ^2.6.0 + source-map: ^0.6.1 + uglify-js: ^3.1.4 + wordwrap: ^1.0.0 + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 1e79a43f5e18d15742977cb987923eab3e2a8f44f2d9d340982bcb69e1735ed049226e534d7c1074eaddaf37e4fb4f471a8adb71cddd5bc8cf3f894241df5cee + languageName: node + linkType: hard + "hard-rejection@npm:^2.1.0": version: 2.1.0 resolution: "hard-rejection@npm:2.1.0" @@ -5144,6 +5163,13 @@ __metadata: languageName: node linkType: hard +"minimist@npm:^1.2.5": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 + languageName: node + linkType: hard + "minipass-collect@npm:^1.0.2": version: 1.0.2 resolution: "minipass-collect@npm:1.0.2" @@ -5267,6 +5293,13 @@ __metadata: languageName: node linkType: hard +"neo-async@npm:^2.6.0": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9 + languageName: node + linkType: hard + "nested-error-stacks@npm:^2.0.0, nested-error-stacks@npm:^2.1.0": version: 2.1.1 resolution: "nested-error-stacks@npm:2.1.1" @@ -6694,6 +6727,15 @@ __metadata: languageName: node linkType: hard +"uglify-js@npm:^3.1.4": + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" + bin: + uglifyjs: bin/uglifyjs + checksum: 7b3897df38b6fc7d7d9f4dcd658599d81aa2b1fb0d074829dd4e5290f7318dbca1f4af2f45acb833b95b1fe0ed4698662ab61b87e94328eb4c0a0d3435baf924 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.0.2": version: 1.0.2 resolution: "unbox-primitive@npm:1.0.2" @@ -6939,6 +6981,13 @@ __metadata: languageName: node linkType: hard +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 2a44b2788165d0a3de71fd517d4880a8e20ea3a82c080ce46e294f0b68b69a2e49cff5f99c600e275c698a90d12c5ea32aff06c311f0db2eb3f1201f3e7b2a04 + languageName: node + linkType: hard + "wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0"