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: Fix latest image install on lima #480

Merged
merged 1 commit into from
Oct 30, 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
22 changes: 14 additions & 8 deletions __tests__/docker/install.test.itg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import {jest, describe, expect, test, beforeEach, afterEach} from '@jest/globals';
import {jest, describe, test, beforeEach, afterEach, expect} from '@jest/globals';
import fs from 'fs';
import os from 'os';
import path from 'path';
Expand Down Expand Up @@ -43,6 +43,7 @@ aarch64:https://cloud.debian.org/images/cloud/bookworm/20231013-1532/debian-12-g
test.each([
{type: 'image', tag: '27.3.1'} as InstallSourceImage,
{type: 'image', tag: 'master'} as InstallSourceImage,
{type: 'image', tag: 'latest'} as InstallSourceImage,
{type: 'archive', version: 'v26.1.4', channel: 'stable'} as InstallSourceArchive,
{type: 'archive', version: 'latest', channel: 'stable'} as InstallSourceArchive,
])(
Expand All @@ -65,12 +66,17 @@ aarch64:https://cloud.debian.org/images/cloud/bookworm/20231013-1532/debian-12-g
daemonConfig: `{"debug":true,"features":{"containerd-snapshotter":true}}`
});
await expect((async () => {
await install.download();
await install.install();
await Docker.printVersion();
await Docker.printInfo();
})().finally(async () => {
await install.tearDown();
})).resolves.not.toThrow();
try {
await install.download();
await install.install();
await Docker.printVersion();
await Docker.printInfo();
} catch (error) {
console.error(error);
throw error;
} finally {
await install.tearDown();
}
})()).resolves.not.toThrow();
}, 30 * 60 * 1000);
});
10 changes: 4 additions & 6 deletions src/docker/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,10 @@ provision:

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
wget https://raw.githubusercontent.com/moby/moby/{{gitCommit}}/contrib/init/systemd/docker.service \
-O /etc/systemd/system/docker.service
wget https://raw.githubusercontent.com/moby/moby/{{gitCommit}}/contrib/init/systemd/docker.socket \
-O /etc/systemd/system/docker.socket

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
Expand Down
27 changes: 25 additions & 2 deletions src/docker/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {Util} from '../util';
import {limaYamlData, dockerServiceLogsPs1, setupDockerWinPs1} from './assets';
import {GitHubRelease} from '../types/github';
import {HubRepository} from '../hubRepository';
import {Image} from '../types/oci/config';

export interface InstallSourceImage {
type: 'image';
Expand Down Expand Up @@ -71,6 +72,8 @@ export class Install {
private _version: string | undefined;
private _toolDir: string | undefined;

private gitCommit: string | undefined;

private readonly limaInstanceName = 'docker-actions-toolkit';

constructor(opts: InstallOpts) {
Expand Down Expand Up @@ -127,12 +130,28 @@ export class Install {
const cli = await HubRepository.build('dockereng/cli-bin');
extractFolder = await cli.extractImage(tag);

const moby = await HubRepository.build('moby/moby-bin');
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
// On macOS, the docker daemon binary will be downloaded inside the lima VM.
// However, we will get the exact git revision from the image config
// to get the matching systemd unit files.
core.info(`Getting git revision from moby/moby-bin:${tag}`);

// There's no macOS image for moby/moby-bin - a linux daemon is run inside lima.
const manifest = await moby.getPlatformManifest(tag, 'linux');

const config = await moby.getJSONBlob<Image>(manifest.config.digest);
core.debug(`Config ${JSON.stringify(config.config)}`);

this.gitCommit = config.config?.Labels?.['org.opencontainers.image.revision'];
if (!this.gitCommit) {
core.warning(`No git revision can be determined from the image. Will use master.`);
this.gitCommit = 'master';
}
core.info(`Git revision is ${this.gitCommit}`);
} else {
core.warning(`dockerd not supported on ${platform}, only the Docker cli will be available`);
}
Expand Down Expand Up @@ -193,6 +212,9 @@ export class Install {
}

private async installDarwin(): Promise<string> {
if (this.source.type == 'image' && !this.gitCommit) {
throw new Error('gitCommit must be set. Run download first.');
}
const src = this.source;
const limaDir = path.join(os.homedir(), '.lima', this.limaInstanceName);
await io.mkdirP(limaDir);
Expand Down Expand Up @@ -229,6 +251,7 @@ export class Install {
customImages: Install.limaCustomImages(),
daemonConfig: limaDaemonConfig,
dockerSock: `${limaDir}/docker.sock`,
gitCommit: this.gitCommit,
srcType: src.type,
srcArchiveVersion: this._version, // Use the resolved version (e.g. latest -> 27.4.0)
srcArchiveChannel: srcArchive.channel,
Expand Down
41 changes: 29 additions & 12 deletions src/hubRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import * as core from '@actions/core';
import {Manifest} from './types/oci/manifest';
import * as tc from '@actions/tool-cache';
import fs from 'fs';
import {MEDIATYPE_IMAGE_INDEX_V1, MEDIATYPE_IMAGE_MANIFEST_V1} from './types/oci/mediatype';
import {MEDIATYPE_IMAGE_MANIFEST_V2, MEDIATYPE_IMAGE_MANIFEST_LIST_V2} from './types/docker/mediatype';
import {MEDIATYPE_IMAGE_CONFIG_V1, MEDIATYPE_IMAGE_INDEX_V1, MEDIATYPE_IMAGE_MANIFEST_V1} from './types/oci/mediatype';
import {MEDIATYPE_IMAGE_CONFIG_V1 as DOCKER_MEDIATYPE_IMAGE_CONFIG_V1, MEDIATYPE_IMAGE_MANIFEST_LIST_V2, MEDIATYPE_IMAGE_MANIFEST_V2} from './types/docker/mediatype';
import {DockerHub} from './dockerhub';

export class HubRepository {
Expand All @@ -40,15 +40,20 @@ export class HubRepository {
return new HubRepository(repository, token);
}

// Unpacks the image layers and returns the path to the extracted image.
// Only OCI indexes/manifest list are supported for now.
public async extractImage(tag: string, destDir?: string): Promise<string> {
const index = await this.getManifest<Index>(tag);
public async getPlatformManifest(tagOrDigest: string, os?: string): Promise<Manifest> {
const index = await this.getManifest<Index>(tagOrDigest);
if (index.mediaType != MEDIATYPE_IMAGE_INDEX_V1 && index.mediaType != MEDIATYPE_IMAGE_MANIFEST_LIST_V2) {
core.error(`Unsupported image media type: ${index.mediaType}`);
throw new Error(`Unsupported image media type: ${index.mediaType}`);
}
const digest = HubRepository.getPlatformManifestDigest(index);
const manifest = await this.getManifest<Manifest>(digest);
const digest = HubRepository.getPlatformManifestDigest(index, os);
return await this.getManifest<Manifest>(digest);
}

// Unpacks the image layers and returns the path to the extracted image.
// Only OCI indexes/manifest list are supported for now.
public async extractImage(tag: string, destDir?: string): Promise<string> {
const manifest = await this.getPlatformManifest(tag);

const paths = manifest.layers.map(async layer => {
const url = this.blobUrl(layer.digest);
Expand Down Expand Up @@ -99,25 +104,35 @@ export class HubRepository {
}

public async getManifest<T>(tagOrDigest: string): Promise<T> {
const url = `https://registry-1.docker.io/v2/${this.repo}/manifests/${tagOrDigest}`;
return await this.registryGet<T>(tagOrDigest, 'manifests', [MEDIATYPE_IMAGE_INDEX_V1, MEDIATYPE_IMAGE_MANIFEST_LIST_V2, MEDIATYPE_IMAGE_MANIFEST_V1, MEDIATYPE_IMAGE_MANIFEST_V2]);
}

public async getJSONBlob<T>(tagOrDigest: string): Promise<T> {
return await this.registryGet<T>(tagOrDigest, 'blobs', [MEDIATYPE_IMAGE_CONFIG_V1, DOCKER_MEDIATYPE_IMAGE_CONFIG_V1]);
}

private async registryGet<T>(tagOrDigest: string, endpoint: 'manifests' | 'blobs', accept: Array<string>): Promise<T> {
const url = `https://registry-1.docker.io/v2/${this.repo}/${endpoint}/${tagOrDigest}`;

const headers = {
Authorization: `Bearer ${this.token}`,
Accept: [MEDIATYPE_IMAGE_INDEX_V1, MEDIATYPE_IMAGE_MANIFEST_LIST_V2, MEDIATYPE_IMAGE_MANIFEST_V1, MEDIATYPE_IMAGE_MANIFEST_V2].join(', ')
Accept: accept.join(', ')
};

const resp = await HubRepository.http.get(url, headers);
const body = await resp.readBody();
const statusCode = resp.message.statusCode || 500;
if (statusCode != 200) {
core.error(`registryGet(${this.repo}:${tagOrDigest}) failed: ${statusCode} ${body}`);
throw DockerHub.parseError(resp, body);
}

return <T>JSON.parse(body);
}

private static getPlatformManifestDigest(index: Index): string {
private static getPlatformManifestDigest(index: Index, osOverride?: string): string {
// This doesn't handle all possible platforms normalizations, but it's good enough for now.
let pos: string = os.platform();
let pos: string = osOverride || os.platform();
if (pos == 'win32') {
pos = 'windows';
}
Expand Down Expand Up @@ -150,8 +165,10 @@ export class HubRepository {
return true;
});
if (!manifest) {
core.error(`Cannot find manifest for ${pos}/${arch}/${variant}`);
throw new Error(`Cannot find manifest for ${pos}/${arch}/${variant}`);
}

return manifest.digest;
}
}
2 changes: 2 additions & 0 deletions src/types/docker/mediatype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
export const MEDIATYPE_IMAGE_MANIFEST_LIST_V2 = 'application/vnd.docker.distribution.manifest.list.v2+json';

export const MEDIATYPE_IMAGE_MANIFEST_V2 = 'application/vnd.docker.distribution.manifest.v2+json';

export const MEDIATYPE_IMAGE_CONFIG_V1 = 'application/vnd.docker.container.image.v1+json';
2 changes: 2 additions & 0 deletions src/types/oci/mediatype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ export const MEDIATYPE_IMAGE_INDEX_V1 = 'application/vnd.oci.image.index.v1+json
export const MEDIATYPE_IMAGE_LAYER_V1 = 'application/vnd.oci.image.layer.v1.tar';

export const MEDIATYPE_EMPTY_JSON_V1 = 'application/vnd.oci.empty.v1+json';

export const MEDIATYPE_IMAGE_CONFIG_V1 = 'application/vnd.oci.image.config.v1+json';
Loading