docker(install): opt to set daemon config

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2023-08-27 12:23:24 +02:00
parent c3e31dcc03
commit 144df6eecc
No known key found for this signature in database
GPG Key ID: 3248E46B6BB8C7F7
5 changed files with 109 additions and 78 deletions

View File

@ -36,13 +36,14 @@ describe('install', () => {
process.env = originalEnv;
});
// prettier-ignore
test.each(['v24.0.5'])(
test.each(['v24.0.4'])(
'install docker %s', async (version) => {
await expect((async () => {
const install = new Install({
version: version,
runDir: tmpDir,
contextName: 'foo'
contextName: 'foo',
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`
});
await install.download();
await install.install();

View File

@ -56,12 +56,14 @@
"async-retry": "^1.3.3",
"csv-parse": "^5.4.0",
"handlebars": "^4.7.8",
"js-yaml": "^4.1.0",
"jwt-decode": "^3.1.2",
"semver": "^7.5.4",
"tmp": "^0.2.1"
},
"devDependencies": {
"@types/csv-parse": "^1.2.2",
"@types/js-yaml": "^4.0.5",
"@types/node": "^16.18.21",
"@types/semver": "^7.5.0",
"@types/tmp": "^0.2.3",

View File

@ -17,10 +17,6 @@
import fs from 'fs';
import {Context} from '../context';
export const setupDockerLinuxSh = (): string => {
return get('docker-setup-linux.sh', setupDockerLinuxShData, '0755');
};
export const setupDockerWinPs1 = (): string => {
return get('docker-setup-win.ps1', setupDockerWinPs1Data);
};
@ -45,43 +41,6 @@ const get = (filename: string, data: string, mode?: string): string => {
return assetPath;
};
export const setupDockerLinuxShData = `
#!/usr/bin/env bash
set -eu
: "\${TOOLDIR=}"
: "\${RUNDIR=}"
: "\${DOCKER_HOST=}"
export PATH="$TOOLDIR::$PATH"
if [ -z "$DOCKER_HOST" ]; then
echo >&2 'error: DOCKER_HOST required'
false
fi
if ! command -v dockerd &> /dev/null; then
echo >&2 'error: dockerd missing from PATH'
false
fi
mkdir -p "$RUNDIR"
(
echo "Starting dockerd"
set -x
exec dockerd \\
--debug \\
--host="$DOCKER_HOST" \\
--exec-root="$RUNDIR/execroot" \\
--data-root="$RUNDIR/data" \\
--pidfile="$RUNDIR/docker.pid" \\
--userland-proxy=false \\
2>&1 | tee "$RUNDIR/dockerd.log"
) &
`;
export const setupDockerWinPs1Data = `
[CmdletBinding()]
param(
@ -92,7 +51,10 @@ param(
[string]$RunDir,
[Parameter(Mandatory = $true)]
[string]$DockerHost)
[string]$DockerHost,
[Parameter(Mandatory = $false)]
[string]$DaemonConfig)
$pwver = (Get-ItemProperty -Path HKLM:\\SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine -Name 'PowerShellVersion').PowerShellVersion
Write-Host "PowerShell version: $pwver"
@ -120,6 +82,12 @@ if (Get-Service docker -ErrorAction SilentlyContinue) {
$env:DOCKER_HOST = $DockerHost
Write-Host "DOCKER_HOST: $env:DOCKER_HOST"
if ($DaemonConfig) {
Write-Host "Writing Docker daemon config"
New-Item -ItemType Directory -Force -Path "$env:ProgramData\\Docker\\config"
$DaemonConfig | Out-File -FilePath "$env:ProgramData\\Docker\\config\\daemon.json"
}
Write-Host "Creating service"
New-Item -ItemType Directory "$RunDir\\moby-root" -ErrorAction SilentlyContinue | Out-Null
New-Item -ItemType Directory "$RunDir\\moby-exec" -ErrorAction SilentlyContinue | Out-Null
@ -246,7 +214,7 @@ forwardAgent: false
#
# Colima default behaviour: buildkit enabled
# Default: {}
docker: {}
{{daemonConfig}}
# Virtual Machine type (qemu, vz)
# NOTE: this is macOS 13 only. For Linux and macOS <13.0, qemu is always used.

View File

@ -19,6 +19,7 @@ import fs from 'fs';
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';
@ -29,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, setupDockerLinuxSh, setupDockerWinPs1} from './assets';
import {colimaYamlData, dockerServiceLogsPs1, qemuEntitlements, setupDockerWinPs1} from './assets';
import {GitHubRelease} from '../types/github';
export interface InstallOpts {
@ -37,6 +38,7 @@ export interface InstallOpts {
channel?: string;
runDir: string;
contextName?: string;
daemonConfig?: string;
}
export class Install {
@ -44,6 +46,7 @@ export class Install {
private readonly version: string;
private readonly channel: string;
private readonly contextName: string;
private readonly daemonConfig?: string;
private _version: string | undefined;
private _toolDir: string | undefined;
@ -52,6 +55,7 @@ export class Install {
this.version = opts.version || 'latest';
this.channel = opts.channel || 'stable';
this.contextName = opts.contextName || 'setup-docker-action';
this.daemonConfig = opts.daemonConfig;
}
get toolDir(): string {
@ -137,10 +141,15 @@ export class Install {
}
await core.group('Creating colima config', async () => {
let daemonConfig = yaml.dump({docker: {}});
if (this.daemonConfig) {
daemonConfig = yaml.dump(yaml.load(JSON.stringify({docker: JSON.parse(this.daemonConfig)})));
}
const colimaCfg = handlebars.compile(colimaYamlData)({
hostArch: Install.platformArch(),
dockerVersion: this._version,
dockerChannel: this.channel
dockerChannel: this.channel,
daemonConfig: daemonConfig
});
core.info(`Writing colima config to ${path.join(colimaDir, 'colima.yaml')}`);
fs.writeFileSync(path.join(colimaDir, 'colima.yaml'), colimaCfg);
@ -192,44 +201,65 @@ export class Install {
const dockerHost = `unix://${path.join(this.runDir, 'docker.sock')}`;
await io.mkdirP(this.runDir);
const daemonConfigPath = path.join(this.runDir, 'daemon.json');
await fs.writeFileSync(daemonConfigPath, '{}');
let daemonConfig = undefined;
const daemonConfigDefaultPath = '/etc/docker/daemon.json';
if (fs.existsSync(daemonConfigDefaultPath)) {
await core.group('Default Docker daemon config found', async () => {
core.info(JSON.stringify(JSON.parse(fs.readFileSync(daemonConfigDefaultPath, {encoding: 'utf8'})), null, 2));
});
daemonConfig = JSON.parse(fs.readFileSync(daemonConfigDefaultPath, {encoding: 'utf8'}));
}
if (this.daemonConfig) {
daemonConfig = Object.assign(daemonConfig || {}, JSON.parse(this.daemonConfig));
}
if (daemonConfig) {
const daemonConfigStr = JSON.stringify(daemonConfig, null, 2);
await core.group('Writing Docker daemon config', async () => {
fs.writeFileSync(daemonConfigPath, daemonConfigStr);
core.info(daemonConfigStr);
});
}
await core.group('Start Docker daemon', async () => {
const bashPath: string = await io.which('bash', true);
const proc = await child_process.spawn(`sudo -E ${bashPath} ${setupDockerLinuxSh()}`, [], {
detached: true,
shell: true,
stdio: ['ignore', process.stdout, process.stderr],
env: Object.assign({}, process.env, {
TOOLDIR: this.toolDir,
RUNDIR: this.runDir,
DOCKER_HOST: dockerHost
}) as {
[key: string]: string;
const cmd = `${this.toolDir}/dockerd --host="${dockerHost}" --config-file="${daemonConfigPath}" --exec-root="${this.runDir}/execroot" --data-root="${this.runDir}/data" --pidfile="${this.runDir}/docker.pid" --userland-proxy=false`;
core.info(`[command] ${cmd}`); // https://github.com/actions/toolkit/blob/3d652d3133965f63309e4b2e1c8852cdbdcb3833/packages/exec/src/toolrunner.ts#L47
const proc = await child_process.spawn(
// We can't use Exec.exec here because we need to detach the process to
// avoid killing it when the action finishes running. Even if detached,
// we also need to run dockerd in a subshell and unref the process so
// GitHub Action doesn't wait for it to finish.
`sudo -E ${bashPath} << EOF
( ${cmd} 2>&1 | tee "${this.runDir}/dockerd.log" ) &
EOF`,
[],
{
detached: true,
shell: true,
stdio: ['ignore', process.stdout, process.stderr]
}
});
);
proc.unref();
const retries = 20;
await Util.sleep(3);
const retries = 10;
await retry(
async bail => {
await Exec.getExecOutput(`docker version`, undefined, {
ignoreReturnCode: true,
silent: true,
env: Object.assign({}, process.env, {
DOCKER_HOST: dockerHost
}) as {
[key: string]: string;
}
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
bail(new Error(res.stderr));
return false;
try {
await Exec.getExecOutput(`docker version`, undefined, {
silent: true,
env: Object.assign({}, process.env, {
DOCKER_HOST: dockerHost
}) as {
[key: string]: string;
}
return res.exitCode == 0;
})
.catch(error => {
bail(error);
return false;
});
} catch (e) {
bail(e);
}
},
{
retries: retries,
@ -251,11 +281,32 @@ export class Install {
private async installWindows(): Promise<void> {
const dockerHost = 'npipe:////./pipe/setup_docker_action';
let daemonConfig = undefined;
const daemonConfigPath = path.join(this.runDir, 'daemon.json');
if (fs.existsSync(daemonConfigPath)) {
await core.group('Default Docker daemon config found', async () => {
core.info(JSON.stringify(JSON.parse(fs.readFileSync(daemonConfigPath, {encoding: 'utf8'})), null, 2));
});
daemonConfig = JSON.parse(fs.readFileSync(daemonConfigPath, {encoding: 'utf8'}));
}
if (this.daemonConfig) {
daemonConfig = Object.assign(daemonConfig || {}, JSON.parse(this.daemonConfig));
}
let daemonConfigStr = '{}';
if (daemonConfig) {
daemonConfigStr = JSON.stringify(daemonConfig, null, 2);
await core.group('Docker daemon config', async () => {
core.info(daemonConfigStr);
});
}
await core.group('Install Docker daemon service', async () => {
const setupCmd = await Util.powershellCommand(setupDockerWinPs1(), {
ToolDir: this.toolDir,
RunDir: this.runDir,
DockerHost: dockerHost
DockerHost: dockerHost,
DaemonConfig: daemonConfigStr
});
await Exec.exec(setupCmd.command, setupCmd.args);
const logCmd = await Util.powershellCommand(dockerServiceLogsPs1());

View File

@ -974,6 +974,7 @@ __metadata:
"@actions/tool-cache": ^2.0.1
"@octokit/plugin-rest-endpoint-methods": ^7.2.3
"@types/csv-parse": ^1.2.2
"@types/js-yaml": ^4.0.5
"@types/node": ^16.18.21
"@types/semver": ^7.5.0
"@types/tmp": ^0.2.3
@ -990,6 +991,7 @@ __metadata:
eslint-plugin-prettier: ^4.2.1
handlebars: ^4.7.8
jest: ^29.5.0
js-yaml: ^4.1.0
jwt-decode: ^3.1.2
prettier: ^2.8.7
rimraf: ^4.4.1
@ -1825,6 +1827,13 @@ __metadata:
languageName: node
linkType: hard
"@types/js-yaml@npm:^4.0.5":
version: 4.0.5
resolution: "@types/js-yaml@npm:4.0.5"
checksum: 7dcac8c50fec31643cc9d6444b5503239a861414cdfaa7ae9a38bc22597c4d850c4b8cec3d82d73b3fbca408348ce223b0408d598b32e094470dfffc6d486b4d
languageName: node
linkType: hard
"@types/json-schema@npm:^7.0.9":
version: 7.0.9
resolution: "@types/json-schema@npm:7.0.9"