Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docker/install: Support version: master #438

Merged
merged 6 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions __tests__/docker/install.test.itg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import fs from 'fs';
import os from 'os';
import path from 'path';

import {Install} from '../../src/docker/install';
import {Install, InstallSourceArchive, InstallSourceImage} from '../../src/docker/install';
import {Docker} from '../../src/docker/docker';
import {Exec} from '../../src/exec';

Expand All @@ -40,8 +40,12 @@ aarch64:https://cloud.debian.org/images/cloud/bookworm/20231013-1532/debian-12-g
process.env = originalEnv;
});
// prettier-ignore
test.each(['v26.1.4'])(
'install docker %s', async (version) => {
test.each([
{type: 'image', tag: '27.3.1'} as InstallSourceImage,
{type: 'image', tag: 'master'} as InstallSourceImage,
{type: 'archive', version: 'v26.1.4', channel: 'stable'} as InstallSourceArchive,
])(
'install docker %s', async (source) => {
if (process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu')) {
// Remove containerd first on ubuntu runners to make sure it takes
// ones packaged with docker
Expand All @@ -53,18 +57,19 @@ aarch64:https://cloud.debian.org/images/cloud/bookworm/20231013-1532/debian-12-g
}
});
}
const install = new Install({
source: source,
runDir: tmpDir,
contextName: 'foo',
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`
});
await expect((async () => {
const install = new Install({
version: version,
runDir: tmpDir,
contextName: 'foo',
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`
});
await install.download();
await install.install();
await Docker.printVersion();
await Docker.printInfo();
})().finally(async () => {
await install.tearDown();
})()).resolves.not.toThrow();
}, 1200000);
})).resolves.not.toThrow();
}, 30 * 60 * 1000);
});
35 changes: 28 additions & 7 deletions __tests__/docker/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,47 @@ import path from 'path';
import * as rimraf from 'rimraf';
import osm = require('os');

import {Install} from '../../src/docker/install';
import {Install, InstallSourceArchive, InstallSourceImage} from '../../src/docker/install';

const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'docker-install-'));

afterEach(function () {
rimraf.sync(tmpDir);
});

const archive = (version: string, channel: string): InstallSourceArchive => {
return {
type: 'archive',
version: version,
channel: channel
};
};

const image = (tag: string): InstallSourceImage => {
return {
type: 'image',
tag: tag
};
};

describe('download', () => {
// prettier-ignore
test.each([
['v19.03.14', 'linux'],
['v20.10.22', 'linux'],
['v20.10.22', 'darwin'],
['v20.10.22', 'win32'],
[archive('v19.03.14', 'stable'), 'linux'],
[archive('v20.10.22', 'stable'), 'linux'],
[archive('v20.10.22', 'stable'), 'darwin'],
[archive('v20.10.22', 'stable'), 'win32'],

[image('master'), 'linux'],
[image('master'), 'win32'],

[image('27.3.1'), 'linux'],
[image('27.3.1'), 'win32'],
])(
'acquires %p of docker (%s)', async (version, platformOS) => {
'acquires %p of docker (%s)', async (source, platformOS) => {
jest.spyOn(osm, 'platform').mockImplementation(() => platformOS as NodeJS.Platform);
const install = new Install({
version: version,
source: source,
runDir: tmpDir,
});
const toolPath = await install.download();
Expand Down
43 changes: 38 additions & 5 deletions src/docker/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,49 @@ provision:
EOF
fi
export DEBIAN_FRONTEND=noninteractive
curl -fsSL https://get.docker.com | sh -s -- --channel {{dockerBinChannel}} --version {{dockerBinVersion}}
if [ "{{srcType}}" == "archive" ]; then
curl -fsSL https://get.docker.com | sh -s -- --channel {{srcArchiveChannel}} --version {{srcArchiveVersion}}
elif [ "{{srcType}}" == "image" ]; then
arch=$(uname -m)
case $arch in
x86_64) arch=amd64;;
aarch64) arch=arm64;;
esac
url="https://github.com/crazy-max/undock/releases/download/v0.8.0/undock_0.8.0_linux_$arch.tar.gz"

wget "$url" -O /tmp/undock.tar.gz
tar -C /usr/local/bin -xvf /tmp/undock.tar.gz
undock --version

HOME=/tmp undock moby/moby-bin:{{srcImageTag}} /usr/local/bin

wget https://raw.githubusercontent.com/moby/moby/{{srcImageTag}}/contrib/init/systemd/docker.service \
https://raw.githubusercontent.com/moby/moby/v{{srcImageTag}}/contrib/init/systemd/docker.service \
-O /etc/systemd/system/docker.service || true
wget https://raw.githubusercontent.com/moby/moby/{{srcImageTag}}/contrib/init/systemd/docker.socket \
https://raw.githubusercontent.com/moby/moby/v{{srcImageTag}}/contrib/init/systemd/docker.socket \
-O /etc/systemd/system/docker.socket || true

sed -i 's|^ExecStart=.*|ExecStart=/usr/local/bin/dockerd -H fd://|' /etc/systemd/system/docker.service
sed -i 's|containerd.service||' /etc/systemd/system/docker.service
if ! getent group docker; then
groupadd --system docker
fi
systemctl daemon-reload
fail=0
if ! systemctl enable --now docker; then
fail=1
fi
systemctl status docker.socket || true
systemctl status docker.service || true
exit $fail
fi

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
# Don't check for docker CLI as it's not installed in the VM (only on the host)
if ! timeout 30s bash -c "until pgrep dockerd; do sleep 3; done"; then
echo >&2 "dockerd is not running"
exit 1
Expand Down
90 changes: 77 additions & 13 deletions src/docker/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,25 @@ import {Exec} from '../exec';
import {Util} from '../util';
import {limaYamlData, dockerServiceLogsPs1, setupDockerWinPs1} from './assets';
import {GitHubRelease} from '../types/github';
import {HubRepository} from '../hubRepository';

export interface InstallSourceImage {
type: 'image';
tag: string;
}

export interface InstallSourceArchive {
type: 'archive';
version: string;
channel: string;
}

export type InstallSource = InstallSourceImage | InstallSourceArchive;

export interface InstallOpts {
version?: string;
channel?: string;
source?: InstallSource;

// ...
runDir: string;
contextName?: string;
daemonConfig?: string;
Expand All @@ -50,8 +65,7 @@ interface LimaImage {

export class Install {
private readonly runDir: string;
private readonly version: string;
private readonly channel: string;
private readonly source: InstallSource;
private readonly contextName: string;
private readonly daemonConfig?: string;
private _version: string | undefined;
Expand All @@ -61,8 +75,11 @@ export class Install {

constructor(opts: InstallOpts) {
this.runDir = opts.runDir;
this.version = opts.version || 'latest';
this.channel = opts.channel || 'stable';
this.source = opts.source || {
type: 'archive',
version: 'latest',
channel: 'stable'
};
this.contextName = opts.contextName || 'setup-docker-action';
this.daemonConfig = opts.daemonConfig;
}
Expand All @@ -71,12 +88,12 @@ export class Install {
return this._toolDir || Context.tmpDir();
}

public async download(): Promise<string> {
const release: GitHubRelease = await Install.getRelease(this.version);
async downloadStaticArchive(src: InstallSourceArchive): Promise<string> {
const release: GitHubRelease = await Install.getRelease(src.version);
this._version = release.tag_name.replace(/^v+|v+$/g, '');
core.debug(`docker.Install.download version: ${this._version}`);

const downloadURL = this.downloadURL(this._version, this.channel);
const downloadURL = this.downloadURL(this._version, src.channel);
core.info(`Downloading ${downloadURL}`);

const downloadPath = await tc.downloadTool(downloadURL);
Expand All @@ -92,6 +109,46 @@ export class Install {
extractFolder = path.join(extractFolder, 'docker');
}
core.debug(`docker.Install.download extractFolder: ${extractFolder}`);
return extractFolder;
}

public async download(): Promise<string> {
let extractFolder: string;
let cacheKey: string;
const platform = os.platform();

switch (this.source.type) {
case 'image': {
const tag = this.source.tag;
this._version = tag;
cacheKey = `docker-image`;

core.info(`Downloading docker cli from dockereng/cli-bin:${tag}`);
const cli = await HubRepository.build('dockereng/cli-bin');
extractFolder = await cli.extractImage(tag);

if (['win32', 'linux'].includes(platform)) {
core.info(`Downloading dockerd from moby/moby-bin:${tag}`);
const moby = await HubRepository.build('moby/moby-bin');
await moby.extractImage(tag, extractFolder);
} else if (platform == 'darwin') {
// On macOS, the docker daemon binary will be downloaded inside the lima VM
} else {
core.warning(`dockerd not supported on ${platform}, only the Docker cli will be available`);
}
break;
}
case 'archive': {
const version = this.source.version;
const channel = this.source.channel;
cacheKey = `docker-archive-${channel}`;
this._version = version;

core.info(`Downloading Docker ${version} from ${this.source.channel} at download.docker.com`);
extractFolder = await this.downloadStaticArchive(this.source);
break;
}
}

core.info('Fixing perms');
fs.readdir(path.join(extractFolder), function (err, files) {
Expand All @@ -104,7 +161,7 @@ export class Install {
});
});

const tooldir = await tc.cacheDir(extractFolder, `docker-${this.channel}`, this._version.replace(/(0+)([1-9]+)/, '$2'));
const tooldir = await tc.cacheDir(extractFolder, cacheKey, this._version.replace(/(0+)([1-9]+)/, '$2'));
core.addPath(tooldir);
core.info('Added Docker to PATH');

Expand Down Expand Up @@ -136,6 +193,7 @@ export class Install {
}

private async installDarwin(): Promise<string> {
const src = this.source;
const limaDir = path.join(os.homedir(), '.lima', this.limaInstanceName);
await io.mkdirP(limaDir);
const dockerHost = `unix://${limaDir}/docker.sock`;
Expand Down Expand Up @@ -166,12 +224,15 @@ export class Install {
handlebars.registerHelper('stringify', function (obj) {
return new handlebars.SafeString(JSON.stringify(obj));
});
const srcArchive = src as InstallSourceArchive;
const limaCfg = handlebars.compile(limaYamlData)({
customImages: Install.limaCustomImages(),
daemonConfig: limaDaemonConfig,
dockerSock: `${limaDir}/docker.sock`,
dockerBinVersion: this._version,
dockerBinChannel: this.channel
srcType: src.type,
srcArchiveVersion: srcArchive.version?.replace(/^v/, ''),
srcArchiveChannel: srcArchive.channel,
srcImageTag: (src as InstallSourceImage).tag
});
core.info(`Writing lima config to ${path.join(limaDir, 'lima.yaml')}`);
fs.writeFileSync(path.join(limaDir, 'lima.yaml'), limaCfg);
Expand Down Expand Up @@ -527,7 +588,10 @@ EOF`,
}
const releases = <Record<string, GitHubRelease>>JSON.parse(body);
if (!releases[version]) {
throw new Error(`Cannot find Docker release ${version} in ${url}`);
if (!releases['v' + version]) {
throw new Error(`Cannot find Docker release ${version} in ${url}`);
}
return releases['v' + version];
}
return releases[version];
}
Expand Down
24 changes: 14 additions & 10 deletions src/dockerhub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,21 @@ export class DockerHub {
const body = await resp.readBody();
resp.message.statusCode = resp.message.statusCode || HttpCodes.InternalServerError;
if (resp.message.statusCode < 200 || resp.message.statusCode >= 300) {
if (resp.message.statusCode == HttpCodes.Unauthorized) {
throw new Error(`Docker Hub API: operation not permitted`);
}
const errResp = <Record<string, string>>JSON.parse(body);
for (const k of ['message', 'detail', 'error']) {
if (errResp[k]) {
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}: ${errResp[k]}`);
}
}
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}`);
throw DockerHub.parseError(resp, body);
}
return body;
}

public static parseError(resp: httpm.HttpClientResponse, body: string): Error {
if (resp.message.statusCode == HttpCodes.Unauthorized) {
throw new Error(`Docker Hub API: operation not permitted`);
}
const errResp = <Record<string, string>>JSON.parse(body);
for (const k of ['message', 'detail', 'error']) {
if (errResp[k]) {
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}: ${errResp[k]}`);
}
}
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}`);
}
}
Loading
Loading