Skip to content

Commit 517a5c5

Browse files
authored
Merge pull request #438 from vvoland/install-from-binimage
docker(install): support image source
2 parents c65952e + e3d0e4e commit 517a5c5

7 files changed

Lines changed: 349 additions & 46 deletions

File tree

__tests__/docker/install.test.itg.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import fs from 'fs';
1919
import os from 'os';
2020
import path from 'path';
2121

22-
import {Install} from '../../src/docker/install';
22+
import {Install, InstallSourceArchive, InstallSourceImage} from '../../src/docker/install';
2323
import {Docker} from '../../src/docker/docker';
2424
import {Exec} from '../../src/exec';
2525

@@ -40,8 +40,12 @@ aarch64:https://cloud.debian.org/images/cloud/bookworm/20231013-1532/debian-12-g
4040
process.env = originalEnv;
4141
});
4242
// prettier-ignore
43-
test.each(['v26.1.4'])(
44-
'install docker %s', async (version) => {
43+
test.each([
44+
{type: 'image', tag: '27.3.1'} as InstallSourceImage,
45+
{type: 'image', tag: 'master'} as InstallSourceImage,
46+
{type: 'archive', version: 'v26.1.4', channel: 'stable'} as InstallSourceArchive,
47+
])(
48+
'install docker %s', async (source) => {
4549
if (process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu')) {
4650
// Remove containerd first on ubuntu runners to make sure it takes
4751
// ones packaged with docker
@@ -53,18 +57,19 @@ aarch64:https://cloud.debian.org/images/cloud/bookworm/20231013-1532/debian-12-g
5357
}
5458
});
5559
}
60+
const install = new Install({
61+
source: source,
62+
runDir: tmpDir,
63+
contextName: 'foo',
64+
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`
65+
});
5666
await expect((async () => {
57-
const install = new Install({
58-
version: version,
59-
runDir: tmpDir,
60-
contextName: 'foo',
61-
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`
62-
});
6367
await install.download();
6468
await install.install();
6569
await Docker.printVersion();
6670
await Docker.printInfo();
71+
})().finally(async () => {
6772
await install.tearDown();
68-
})()).resolves.not.toThrow();
69-
}, 1200000);
73+
})).resolves.not.toThrow();
74+
}, 30 * 60 * 1000);
7075
});

__tests__/docker/install.test.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,47 @@ import path from 'path';
2121
import * as rimraf from 'rimraf';
2222
import osm = require('os');
2323

24-
import {Install} from '../../src/docker/install';
24+
import {Install, InstallSourceArchive, InstallSourceImage} from '../../src/docker/install';
2525

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

2828
afterEach(function () {
2929
rimraf.sync(tmpDir);
3030
});
3131

32+
const archive = (version: string, channel: string): InstallSourceArchive => {
33+
return {
34+
type: 'archive',
35+
version: version,
36+
channel: channel
37+
};
38+
};
39+
40+
const image = (tag: string): InstallSourceImage => {
41+
return {
42+
type: 'image',
43+
tag: tag
44+
};
45+
};
46+
3247
describe('download', () => {
3348
// prettier-ignore
3449
test.each([
35-
['v19.03.14', 'linux'],
36-
['v20.10.22', 'linux'],
37-
['v20.10.22', 'darwin'],
38-
['v20.10.22', 'win32'],
50+
[archive('v19.03.14', 'stable'), 'linux'],
51+
[archive('v20.10.22', 'stable'), 'linux'],
52+
[archive('v20.10.22', 'stable'), 'darwin'],
53+
[archive('v20.10.22', 'stable'), 'win32'],
54+
55+
[image('master'), 'linux'],
56+
[image('master'), 'win32'],
57+
58+
[image('27.3.1'), 'linux'],
59+
[image('27.3.1'), 'win32'],
3960
])(
40-
'acquires %p of docker (%s)', async (version, platformOS) => {
61+
'acquires %p of docker (%s)', async (source, platformOS) => {
4162
jest.spyOn(osm, 'platform').mockImplementation(() => platformOS as NodeJS.Platform);
4263
const install = new Install({
43-
version: version,
64+
source: source,
4465
runDir: tmpDir,
4566
});
4667
const toolPath = await install.download();

src/docker/assets.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,49 @@ provision:
221221
EOF
222222
fi
223223
export DEBIAN_FRONTEND=noninteractive
224-
curl -fsSL https://get.docker.com | sh -s -- --channel {{dockerBinChannel}} --version {{dockerBinVersion}}
224+
if [ "{{srcType}}" == "archive" ]; then
225+
curl -fsSL https://get.docker.com | sh -s -- --channel {{srcArchiveChannel}} --version {{srcArchiveVersion}}
226+
elif [ "{{srcType}}" == "image" ]; then
227+
arch=$(uname -m)
228+
case $arch in
229+
x86_64) arch=amd64;;
230+
aarch64) arch=arm64;;
231+
esac
232+
url="https://github.com/crazy-max/undock/releases/download/v0.8.0/undock_0.8.0_linux_$arch.tar.gz"
233+
234+
wget "$url" -O /tmp/undock.tar.gz
235+
tar -C /usr/local/bin -xvf /tmp/undock.tar.gz
236+
undock --version
237+
238+
HOME=/tmp undock moby/moby-bin:{{srcImageTag}} /usr/local/bin
239+
240+
wget https://raw.githubusercontent.com/moby/moby/{{srcImageTag}}/contrib/init/systemd/docker.service \
241+
https://raw.githubusercontent.com/moby/moby/v{{srcImageTag}}/contrib/init/systemd/docker.service \
242+
-O /etc/systemd/system/docker.service || true
243+
wget https://raw.githubusercontent.com/moby/moby/{{srcImageTag}}/contrib/init/systemd/docker.socket \
244+
https://raw.githubusercontent.com/moby/moby/v{{srcImageTag}}/contrib/init/systemd/docker.socket \
245+
-O /etc/systemd/system/docker.socket || true
246+
247+
sed -i 's|^ExecStart=.*|ExecStart=/usr/local/bin/dockerd -H fd://|' /etc/systemd/system/docker.service
248+
sed -i 's|containerd.service||' /etc/systemd/system/docker.service
249+
if ! getent group docker; then
250+
groupadd --system docker
251+
fi
252+
systemctl daemon-reload
253+
fail=0
254+
if ! systemctl enable --now docker; then
255+
fail=1
256+
fi
257+
systemctl status docker.socket || true
258+
systemctl status docker.service || true
259+
exit $fail
260+
fi
225261
226262
probes:
227263
- script: |
228264
#!/bin/bash
229265
set -eux -o pipefail
230-
if ! timeout 30s bash -c "until command -v docker >/dev/null 2>&1; do sleep 3; done"; then
231-
echo >&2 "docker is not installed yet"
232-
exit 1
233-
fi
266+
# Don't check for docker CLI as it's not installed in the VM (only on the host)
234267
if ! timeout 30s bash -c "until pgrep dockerd; do sleep 3; done"; then
235268
echo >&2 "dockerd is not running"
236269
exit 1

src/docker/install.ts

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,25 @@ import {Exec} from '../exec';
3333
import {Util} from '../util';
3434
import {limaYamlData, dockerServiceLogsPs1, setupDockerWinPs1} from './assets';
3535
import {GitHubRelease} from '../types/github';
36+
import {HubRepository} from '../hubRepository';
37+
38+
export interface InstallSourceImage {
39+
type: 'image';
40+
tag: string;
41+
}
42+
43+
export interface InstallSourceArchive {
44+
type: 'archive';
45+
version: string;
46+
channel: string;
47+
}
48+
49+
export type InstallSource = InstallSourceImage | InstallSourceArchive;
3650

3751
export interface InstallOpts {
38-
version?: string;
39-
channel?: string;
52+
source?: InstallSource;
53+
54+
// ...
4055
runDir: string;
4156
contextName?: string;
4257
daemonConfig?: string;
@@ -50,8 +65,7 @@ interface LimaImage {
5065

5166
export class Install {
5267
private readonly runDir: string;
53-
private readonly version: string;
54-
private readonly channel: string;
68+
private readonly source: InstallSource;
5569
private readonly contextName: string;
5670
private readonly daemonConfig?: string;
5771
private _version: string | undefined;
@@ -61,8 +75,11 @@ export class Install {
6175

6276
constructor(opts: InstallOpts) {
6377
this.runDir = opts.runDir;
64-
this.version = opts.version || 'latest';
65-
this.channel = opts.channel || 'stable';
78+
this.source = opts.source || {
79+
type: 'archive',
80+
version: 'latest',
81+
channel: 'stable'
82+
};
6683
this.contextName = opts.contextName || 'setup-docker-action';
6784
this.daemonConfig = opts.daemonConfig;
6885
}
@@ -71,12 +88,12 @@ export class Install {
7188
return this._toolDir || Context.tmpDir();
7289
}
7390

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

79-
const downloadURL = this.downloadURL(this._version, this.channel);
96+
const downloadURL = this.downloadURL(this._version, src.channel);
8097
core.info(`Downloading ${downloadURL}`);
8198

8299
const downloadPath = await tc.downloadTool(downloadURL);
@@ -92,6 +109,46 @@ export class Install {
92109
extractFolder = path.join(extractFolder, 'docker');
93110
}
94111
core.debug(`docker.Install.download extractFolder: ${extractFolder}`);
112+
return extractFolder;
113+
}
114+
115+
public async download(): Promise<string> {
116+
let extractFolder: string;
117+
let cacheKey: string;
118+
const platform = os.platform();
119+
120+
switch (this.source.type) {
121+
case 'image': {
122+
const tag = this.source.tag;
123+
this._version = tag;
124+
cacheKey = `docker-image`;
125+
126+
core.info(`Downloading docker cli from dockereng/cli-bin:${tag}`);
127+
const cli = await HubRepository.build('dockereng/cli-bin');
128+
extractFolder = await cli.extractImage(tag);
129+
130+
if (['win32', 'linux'].includes(platform)) {
131+
core.info(`Downloading dockerd from moby/moby-bin:${tag}`);
132+
const moby = await HubRepository.build('moby/moby-bin');
133+
await moby.extractImage(tag, extractFolder);
134+
} else if (platform == 'darwin') {
135+
// On macOS, the docker daemon binary will be downloaded inside the lima VM
136+
} else {
137+
core.warning(`dockerd not supported on ${platform}, only the Docker cli will be available`);
138+
}
139+
break;
140+
}
141+
case 'archive': {
142+
const version = this.source.version;
143+
const channel = this.source.channel;
144+
cacheKey = `docker-archive-${channel}`;
145+
this._version = version;
146+
147+
core.info(`Downloading Docker ${version} from ${this.source.channel} at download.docker.com`);
148+
extractFolder = await this.downloadStaticArchive(this.source);
149+
break;
150+
}
151+
}
95152

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

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

@@ -136,6 +193,7 @@ export class Install {
136193
}
137194

138195
private async installDarwin(): Promise<string> {
196+
const src = this.source;
139197
const limaDir = path.join(os.homedir(), '.lima', this.limaInstanceName);
140198
await io.mkdirP(limaDir);
141199
const dockerHost = `unix://${limaDir}/docker.sock`;
@@ -166,12 +224,15 @@ export class Install {
166224
handlebars.registerHelper('stringify', function (obj) {
167225
return new handlebars.SafeString(JSON.stringify(obj));
168226
});
227+
const srcArchive = src as InstallSourceArchive;
169228
const limaCfg = handlebars.compile(limaYamlData)({
170229
customImages: Install.limaCustomImages(),
171230
daemonConfig: limaDaemonConfig,
172231
dockerSock: `${limaDir}/docker.sock`,
173-
dockerBinVersion: this._version,
174-
dockerBinChannel: this.channel
232+
srcType: src.type,
233+
srcArchiveVersion: srcArchive.version?.replace(/^v/, ''),
234+
srcArchiveChannel: srcArchive.channel,
235+
srcImageTag: (src as InstallSourceImage).tag
175236
});
176237
core.info(`Writing lima config to ${path.join(limaDir, 'lima.yaml')}`);
177238
fs.writeFileSync(path.join(limaDir, 'lima.yaml'), limaCfg);
@@ -527,7 +588,10 @@ EOF`,
527588
}
528589
const releases = <Record<string, GitHubRelease>>JSON.parse(body);
529590
if (!releases[version]) {
530-
throw new Error(`Cannot find Docker release ${version} in ${url}`);
591+
if (!releases['v' + version]) {
592+
throw new Error(`Cannot find Docker release ${version} in ${url}`);
593+
}
594+
return releases['v' + version];
531595
}
532596
return releases[version];
533597
}

src/dockerhub.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,21 @@ export class DockerHub {
111111
const body = await resp.readBody();
112112
resp.message.statusCode = resp.message.statusCode || HttpCodes.InternalServerError;
113113
if (resp.message.statusCode < 200 || resp.message.statusCode >= 300) {
114-
if (resp.message.statusCode == HttpCodes.Unauthorized) {
115-
throw new Error(`Docker Hub API: operation not permitted`);
116-
}
117-
const errResp = <Record<string, string>>JSON.parse(body);
118-
for (const k of ['message', 'detail', 'error']) {
119-
if (errResp[k]) {
120-
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}: ${errResp[k]}`);
121-
}
122-
}
123-
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}`);
114+
throw DockerHub.parseError(resp, body);
124115
}
125116
return body;
126117
}
118+
119+
public static parseError(resp: httpm.HttpClientResponse, body: string): Error {
120+
if (resp.message.statusCode == HttpCodes.Unauthorized) {
121+
throw new Error(`Docker Hub API: operation not permitted`);
122+
}
123+
const errResp = <Record<string, string>>JSON.parse(body);
124+
for (const k of ['message', 'detail', 'error']) {
125+
if (errResp[k]) {
126+
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}: ${errResp[k]}`);
127+
}
128+
}
129+
throw new Error(`Docker Hub API: bad status code ${resp.message.statusCode}`);
130+
}
127131
}

0 commit comments

Comments
 (0)