Skip to content

Commit b28bf20

Browse files
committed
docker(install): use undock to extract image
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
1 parent a54d83c commit b28bf20

File tree

4 files changed

+102
-236
lines changed

4 files changed

+102
-236
lines changed

__tests__/docker/install.test.itg.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {describe, test, expect} from '@jest/globals';
17+
import {beforeAll, describe, test, expect} from '@jest/globals';
1818
import fs from 'fs';
1919
import os from 'os';
2020
import path from 'path';
2121

2222
import {Install, InstallSource, InstallSourceArchive, InstallSourceImage} from '../../src/docker/install';
2323
import {Docker} from '../../src/docker/docker';
24+
import {Undock} from '../../src/undock/undock';
25+
import {Install as UndockInstall} from '../../src/undock/install';
2426
import {Exec} from '../../src/exec';
2527

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

30+
beforeAll(async () => {
31+
const undockInstall = new UndockInstall();
32+
const binPath = await undockInstall.download('v0.9.0', true);
33+
await undockInstall.install(binPath);
34+
});
35+
2836
describe('root', () => {
2937
// prettier-ignore
3038
test.each(getSources(true))(
@@ -34,7 +42,8 @@ describe('root', () => {
3442
source: source,
3543
runDir: tmpDir(),
3644
contextName: 'foo',
37-
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`
45+
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`,
46+
undock: new Undock()
3847
});
3948
await expect(tryInstall(install)).resolves.not.toThrow();
4049
}, 30 * 60 * 1000);
@@ -54,7 +63,8 @@ describe('rootless', () => {
5463
runDir: tmpDir(),
5564
contextName: 'foo',
5665
daemonConfig: `{"debug":true}`,
57-
rootless: true
66+
rootless: true,
67+
undock: new Undock()
5868
});
5969
await expect(
6070
tryInstall(install, async () => {
@@ -79,7 +89,8 @@ describe('tcp', () => {
7989
runDir: tmpDir(),
8090
contextName: 'foo',
8191
daemonConfig: `{"debug":true}`,
82-
localTCPPort: 2378
92+
localTCPPort: 2378,
93+
undock: new Undock()
8394
});
8495
await expect(
8596
tryInstall(install, async () => {

__tests__/docker/install.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {describe, expect, jest, test, beforeEach, afterEach, it} from '@jest/globals';
17+
import {describe, expect, jest, test, beforeEach, afterEach, it, beforeAll} from '@jest/globals';
1818
import fs from 'fs';
1919
import os from 'os';
2020
import path from 'path';
2121
import * as rimraf from 'rimraf';
2222
import osm = require('os');
2323

2424
import {Install, InstallSourceArchive, InstallSourceImage} from '../../src/docker/install';
25+
import {Undock} from '../../src/undock/undock';
26+
import {Install as UndockInstall} from '../../src/undock/install';
2527

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

30+
beforeAll(async () => {
31+
const undockInstall = new UndockInstall();
32+
const binPath = await undockInstall.download('v0.9.0', true);
33+
await undockInstall.install(binPath);
34+
});
35+
2836
afterEach(function () {
2937
rimraf.sync(tmpDir);
3038
});
@@ -63,6 +71,7 @@ describe('download', () => {
6371
const install = new Install({
6472
source: source,
6573
runDir: tmpDir,
74+
undock: new Undock()
6675
});
6776
const toolPath = await install.download();
6877
expect(fs.existsSync(toolPath)).toBe(true);

src/docker/install.ts

Lines changed: 77 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ import * as tc from '@actions/tool-cache';
2828

2929
import {Context} from '../context';
3030
import {Docker} from './docker';
31+
import {ImageTools} from '../buildx/imagetools';
32+
import {Undock} from '../undock/undock';
3133
import {Exec} from '../exec';
3234
import {Util} from '../util';
3335
import {limaYamlData, dockerServiceLogsPs1, setupDockerWinPs1} from './assets';
36+
3437
import {GitHubRelease} from '../types/github';
35-
import {HubRepository} from '../hubRepository';
3638
import {Image} from '../types/oci/config';
3739

3840
export interface InstallSourceImage {
@@ -57,6 +59,8 @@ export interface InstallOpts {
5759
daemonConfig?: string;
5860
rootless?: boolean;
5961
localTCPPort?: number;
62+
63+
undock: Undock;
6064
}
6165

6266
interface LimaImage {
@@ -72,6 +76,7 @@ export class Install {
7276
private readonly daemonConfig?: string;
7377
private readonly rootless: boolean;
7478
private readonly localTCPPort?: number;
79+
private readonly undock: Undock;
7580

7681
private _version: string | undefined;
7782
private _toolDir: string | undefined;
@@ -91,36 +96,13 @@ export class Install {
9196
this.daemonConfig = opts.daemonConfig;
9297
this.rootless = opts.rootless || false;
9398
this.localTCPPort = opts.localTCPPort;
99+
this.undock = opts.undock;
94100
}
95101

96102
get toolDir(): string {
97103
return this._toolDir || Context.tmpDir();
98104
}
99105

100-
async downloadStaticArchive(component: 'docker' | 'docker-rootless-extras', src: InstallSourceArchive): Promise<string> {
101-
const release: GitHubRelease = await Install.getRelease(src.version);
102-
this._version = release.tag_name.replace(/^v+|v+$/g, '');
103-
core.debug(`docker.Install.download version: ${this._version}`);
104-
105-
const downloadURL = this.downloadURL(component, this._version, src.channel);
106-
core.info(`Downloading ${downloadURL}`);
107-
108-
const downloadPath = await tc.downloadTool(downloadURL);
109-
core.debug(`docker.Install.download downloadPath: ${downloadPath}`);
110-
111-
let extractFolder;
112-
if (os.platform() == 'win32') {
113-
extractFolder = await tc.extractZip(downloadPath, extractFolder);
114-
} else {
115-
extractFolder = await tc.extractTar(downloadPath, extractFolder);
116-
}
117-
if (Util.isDirectory(path.join(extractFolder, component))) {
118-
extractFolder = path.join(extractFolder, component);
119-
}
120-
core.debug(`docker.Install.download extractFolder: ${extractFolder}`);
121-
return extractFolder;
122-
}
123-
124106
public async download(): Promise<string> {
125107
let extractFolder: string;
126108
let cacheKey: string;
@@ -132,35 +114,8 @@ export class Install {
132114
this._version = tag;
133115
cacheKey = `docker-image`;
134116

135-
core.info(`Downloading docker cli from dockereng/cli-bin:${tag}`);
136-
const cli = await HubRepository.build('dockereng/cli-bin');
137-
extractFolder = await cli.extractImage(tag);
138-
139-
const moby = await HubRepository.build('moby/moby-bin');
140-
if (['win32', 'linux'].includes(platform)) {
141-
core.info(`Downloading dockerd from moby/moby-bin:${tag}`);
142-
await moby.extractImage(tag, extractFolder);
143-
} else if (platform == 'darwin') {
144-
// On macOS, the docker daemon binary will be downloaded inside the lima VM.
145-
// However, we will get the exact git revision from the image config
146-
// to get the matching systemd unit files.
147-
core.info(`Getting git revision from moby/moby-bin:${tag}`);
148-
149-
// There's no macOS image for moby/moby-bin - a linux daemon is run inside lima.
150-
const manifest = await moby.getPlatformManifest(tag, 'linux');
151-
152-
const config = await moby.getJSONBlob<Image>(manifest.config.digest);
153-
core.debug(`Config ${JSON.stringify(config.config)}`);
154-
155-
this.gitCommit = config.config?.Labels?.['org.opencontainers.image.revision'];
156-
if (!this.gitCommit) {
157-
core.warning(`No git revision can be determined from the image. Will use master.`);
158-
this.gitCommit = 'master';
159-
}
160-
core.info(`Git revision is ${this.gitCommit}`);
161-
} else {
162-
core.warning(`dockerd not supported on ${platform}, only the Docker cli will be available`);
163-
}
117+
core.info(`Downloading Docker ${tag} from image`);
118+
extractFolder = await this.downloadSourceImage(platform);
164119
break;
165120
}
166121
case 'archive': {
@@ -170,10 +125,10 @@ export class Install {
170125
this._version = version;
171126

172127
core.info(`Downloading Docker ${version} from ${this.source.channel} at download.docker.com`);
173-
extractFolder = await this.downloadStaticArchive('docker', this.source);
128+
extractFolder = await this.downloadSourceArchive('docker', this.source);
174129
if (this.rootless) {
175130
core.info(`Downloading Docker rootless extras ${version} from ${this.source.channel} at download.docker.com`);
176-
const extrasFolder = await this.downloadStaticArchive('docker-rootless-extras', this.source);
131+
const extrasFolder = await this.downloadSourceArchive('docker-rootless-extras', this.source);
177132
fs.readdirSync(extrasFolder).forEach(file => {
178133
const src = path.join(extrasFolder, file);
179134
const dest = path.join(extractFolder, file);
@@ -191,7 +146,9 @@ export class Install {
191146
}
192147
// eslint-disable-next-line @typescript-eslint/no-unused-vars
193148
files.forEach(function (file, index) {
194-
fs.chmodSync(path.join(extractFolder, file), '0755');
149+
if (!Util.isDirectory(path.join(extractFolder, file))) {
150+
fs.chmodSync(path.join(extractFolder, file), '0755');
151+
}
195152
});
196153
});
197154

@@ -203,6 +160,69 @@ export class Install {
203160
return tooldir;
204161
}
205162

163+
private async downloadSourceImage(platform: string): Promise<string> {
164+
const dest = path.join(Context.tmpDir(), 'docker-install-image');
165+
166+
const cliImage = `dockereng/cli-bin:${this._version}`;
167+
core.info(`Downloading docker cli from ${cliImage}`);
168+
await this.undock.run({
169+
source: cliImage,
170+
dist: dest
171+
});
172+
173+
const engineImage = `moby/moby-bin:${this._version}`;
174+
if (['win32', 'linux'].includes(platform)) {
175+
core.info(`Downloading docker engine from ${engineImage}`);
176+
await this.undock.run({
177+
source: cliImage,
178+
dist: dest
179+
});
180+
} else if (platform == 'darwin') {
181+
// On macOS, the docker daemon binary will be downloaded inside the lima VM.
182+
// However, we will get the exact git revision from the image config
183+
// to get the matching systemd unit files. There's no macOS image for
184+
// moby/moby-bin - a linux daemon is run inside lima.
185+
const engineImageConfig = (await new ImageTools().inspectImage(engineImage)) as Record<string, Image>;
186+
core.debug(`docker.Install.downloadSourceImage engineImageConfig: ${JSON.stringify(engineImageConfig)}`);
187+
188+
this.gitCommit = engineImageConfig['linux/arm64'].config?.Labels?.['org.opencontainers.image.revision'];
189+
if (!this.gitCommit) {
190+
core.warning(`No git revision can be determined from the image. Will use default branch as Git revision.`);
191+
this.gitCommit = 'master';
192+
}
193+
194+
core.debug(`docker.Install.downloadSourceImage gitCommit: ${this.gitCommit}`);
195+
} else {
196+
core.warning(`Docker engine not supported on ${platform}, only the Docker cli will be available`);
197+
}
198+
199+
return dest;
200+
}
201+
202+
private async downloadSourceArchive(component: 'docker' | 'docker-rootless-extras', src: InstallSourceArchive): Promise<string> {
203+
const release: GitHubRelease = await Install.getRelease(src.version);
204+
this._version = release.tag_name.replace(/^v+|v+$/g, '');
205+
core.debug(`docker.Install.downloadSourceArchive version: ${this._version}`);
206+
207+
const downloadURL = this.downloadURL(component, this._version, src.channel);
208+
core.info(`Downloading ${downloadURL}`);
209+
210+
const downloadPath = await tc.downloadTool(downloadURL);
211+
core.debug(`docker.Install.downloadSourceArchive downloadPath: ${downloadPath}`);
212+
213+
let extractFolder;
214+
if (os.platform() == 'win32') {
215+
extractFolder = await tc.extractZip(downloadPath, extractFolder);
216+
} else {
217+
extractFolder = await tc.extractTar(downloadPath, extractFolder);
218+
}
219+
if (Util.isDirectory(path.join(extractFolder, component))) {
220+
extractFolder = path.join(extractFolder, component);
221+
}
222+
core.debug(`docker.Install.downloadSourceArchive extractFolder: ${extractFolder}`);
223+
return extractFolder;
224+
}
225+
206226
public async install(): Promise<string> {
207227
if (!this.toolDir) {
208228
throw new Error('toolDir must be set. Run download first.');

0 commit comments

Comments
 (0)