Skip to content
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
62 changes: 62 additions & 0 deletions common/__tests__/docker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {parseMount} from '../src/docker'

describe('parseMount', () => {

test('handles type,src,dst', () => {
const input = 'type=bind,src=/my/source,dst=/my/dest'
const result = parseMount(input)
expect(result.type).toBe('bind')
expect(result.source).toBe('/my/source')
expect(result.target).toBe('/my/dest')
})
test('handles type,source,destination', () => {
const input = 'type=bind,source=/my/source,destination=/my/dest'
const result = parseMount(input)
expect(result.type).toBe('bind')
expect(result.source).toBe('/my/source')
expect(result.target).toBe('/my/dest')
})
test('handles type,source,target', () => {
const input = 'type=bind,source=/my/source,target=/my/dest'
const result = parseMount(input)
expect(result.type).toBe('bind')
expect(result.source).toBe('/my/source')
expect(result.target).toBe('/my/dest')
})

test('throws on unexpected option', () => {
const input = 'type=bind,source=/my/source,target=/my/dest,made-up'
const action = () => parseMount(input)
expect(action).toThrow("Unhandled mount option 'made-up'")
})

test('ignores readonly', () => {
const input = 'type=bind,source=/my/source,target=/my/dest,readonly'
const result = parseMount(input)
expect(result.type).toBe('bind')
expect(result.source).toBe('/my/source')
expect(result.target).toBe('/my/dest')
})
test('ignores ro', () => {
const input = 'type=bind,source=/my/source,target=/my/dest,ro'
const result = parseMount(input)
expect(result.type).toBe('bind')
expect(result.source).toBe('/my/source')
expect(result.target).toBe('/my/dest')
})
test('ignores readonly with value', () => {
const input = 'type=bind,source=/my/source,target=/my/dest,readonly=false'
const result = parseMount(input)
expect(result.type).toBe('bind')
expect(result.source).toBe('/my/source')
expect(result.target).toBe('/my/dest')
})
test('ignores ro with value', () => {
const input = 'type=bind,source=/my/source,target=/my/dest,ro=0'
const result = parseMount(input)
expect(result.type).toBe('bind')
expect(result.source).toBe('/my/source')
expect(result.target).toBe('/my/dest')
})

})
72 changes: 69 additions & 3 deletions common/src/docker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from 'path'
import * as fs from 'fs'
import * as config from './config'
import {ExecFunction} from './exec'
import {getAbsolutePath} from './file'
Expand Down Expand Up @@ -70,7 +71,8 @@ export async function runContainer(
checkoutPath: string,
subFolder: string,
command: string,
envs?: string[]
envs?: string[],
mounts?: string[]
): Promise<void> {
const checkoutPathAbsolute = getAbsolutePath(checkoutPath, process.cwd())
const folder = path.join(checkoutPathAbsolute, subFolder)
Expand All @@ -89,13 +91,28 @@ export async function runContainer(
'--mount',
`type=bind,src=${checkoutPathAbsolute},dst=${workspaceFolder}`
)
if (devcontainerConfig.mounts) {
devcontainerConfig.mounts
.map(m => substituteValues(m))
.forEach(m => {
const mount = parseMount(m)
if (mount.type === "bind") {
// check path exists
if (!fs.existsSync(mount.source)) {
console.log(`Skipping mount as source does not exist: '${m}'`)
return;
}
}
args.push('--mount', m)
});
}
args.push('--workdir', workspaceFolder)
args.push('--user', remoteUser)
if (devcontainerConfig.runArgs) {
const subtitutedRunArgs = devcontainerConfig.runArgs.map(a =>
const substitutedRunArgs = devcontainerConfig.runArgs.map(a =>
substituteValues(a)
)
args.push(...subtitutedRunArgs)
args.push(...substitutedRunArgs)
}
if (envs) {
for (const env of envs) {
Expand Down Expand Up @@ -132,3 +149,52 @@ export async function pushImage(exec: ExecFunction, imageName: string): Promise<
// core.endGroup()
}
}

export interface DockerMount {
type: string,
source: string,
target: string,
// ignoring readonly as not relevant
}

export function parseMount(mountString: string): DockerMount {
// https://docs.docker.com/engine/reference/commandline/service_create/#add-bind-mounts-volumes-or-memory-filesystems
// examples:
// type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock
// src=home-cache,target=/home/vscode/.cache

let type = ''
let source = ''
let target = ''

const options= mountString.split(',')

for (const option of options) {
const parts = option.split('=');

switch (parts[0]) {
case 'type':
type = parts[1]
break;
case 'src':
case 'source':
source = parts[1]
break;
case 'dst':
case 'destination':
case 'target':
target = parts[1]
break;

case 'readonly':
case 'ro':
// ignore
break;

default:
throw new Error(`Unhandled mount option '${parts[0]}'`);
}
}

return {type, source, target}
}
8 changes: 7 additions & 1 deletion common_lib/docker.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ExecFunction } from './exec';
export declare function isDockerBuildXInstalled(exec: ExecFunction): Promise<boolean>;
export declare function buildImage(exec: ExecFunction, imageName: string, checkoutPath: string, subFolder: string): Promise<void>;
export declare function runContainer(exec: ExecFunction, imageName: string, checkoutPath: string, subFolder: string, command: string, envs?: string[]): Promise<void>;
export declare function runContainer(exec: ExecFunction, imageName: string, checkoutPath: string, subFolder: string, command: string, envs?: string[], mounts?: string[]): Promise<void>;
export declare function pushImage(exec: ExecFunction, imageName: string): Promise<void>;
export interface DockerMount {
type: string;
source: string;
target: string;
}
export declare function parseMount(mountString: string): DockerMount;
59 changes: 55 additions & 4 deletions common_lib/docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.pushImage = exports.runContainer = exports.buildImage = exports.isDockerBuildXInstalled = void 0;
exports.parseMount = exports.pushImage = exports.runContainer = exports.buildImage = exports.isDockerBuildXInstalled = void 0;
const path_1 = __importDefault(require("path"));
const fs = __importStar(require("fs"));
const config = __importStar(require("./config"));
const file_1 = require("./file");
const envvars_1 = require("./envvars");
Expand Down Expand Up @@ -85,7 +86,7 @@ function buildImage(exec, imageName, checkoutPath, subFolder) {
});
}
exports.buildImage = buildImage;
function runContainer(exec, imageName, checkoutPath, subFolder, command, envs) {
function runContainer(exec, imageName, checkoutPath, subFolder, command, envs, mounts) {
return __awaiter(this, void 0, void 0, function* () {
const checkoutPathAbsolute = file_1.getAbsolutePath(checkoutPath, process.cwd());
const folder = path_1.default.join(checkoutPathAbsolute, subFolder);
Expand All @@ -95,11 +96,26 @@ function runContainer(exec, imageName, checkoutPath, subFolder, command, envs) {
const remoteUser = config.getRemoteUser(devcontainerConfig);
const args = ['run'];
args.push('--mount', `type=bind,src=${checkoutPathAbsolute},dst=${workspaceFolder}`);
if (devcontainerConfig.mounts) {
devcontainerConfig.mounts
.map(m => envvars_1.substituteValues(m))
.forEach(m => {
const mount = parseMount(m);
if (mount.type === "bind") {
// check path exists
if (!fs.existsSync(mount.source)) {
console.log(`Skipping mount as source does not exist: '${m}'`);
return;
}
}
args.push('--mount', m);
});
}
args.push('--workdir', workspaceFolder);
args.push('--user', remoteUser);
if (devcontainerConfig.runArgs) {
const subtitutedRunArgs = devcontainerConfig.runArgs.map(a => envvars_1.substituteValues(a));
args.push(...subtitutedRunArgs);
const substitutedRunArgs = devcontainerConfig.runArgs.map(a => envvars_1.substituteValues(a));
args.push(...substitutedRunArgs);
}
if (envs) {
for (const env of envs) {
Expand Down Expand Up @@ -138,3 +154,38 @@ function pushImage(exec, imageName) {
});
}
exports.pushImage = pushImage;
function parseMount(mountString) {
// https://docs.docker.com/engine/reference/commandline/service_create/#add-bind-mounts-volumes-or-memory-filesystems
// examples:
// type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock
// src=home-cache,target=/home/vscode/.cache
let type = '';
let source = '';
let target = '';
const options = mountString.split(',');
for (const option of options) {
const parts = option.split('=');
switch (parts[0]) {
case 'type':
type = parts[1];
break;
case 'src':
case 'source':
source = parts[1];
break;
case 'dst':
case 'destination':
case 'target':
target = parts[1];
break;
case 'readonly':
case 'ro':
// ignore
break;
default:
throw new Error(`Unhandled mount option '${parts[0]}'`);
}
}
return { type, source, target };
}
exports.parseMount = parseMount;
8 changes: 7 additions & 1 deletion github-action/dist/docker.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ExecFunction } from './exec';
export declare function isDockerBuildXInstalled(exec: ExecFunction): Promise<boolean>;
export declare function buildImage(exec: ExecFunction, imageName: string, checkoutPath: string, subFolder: string): Promise<void>;
export declare function runContainer(exec: ExecFunction, imageName: string, checkoutPath: string, subFolder: string, command: string, envs?: string[]): Promise<void>;
export declare function runContainer(exec: ExecFunction, imageName: string, checkoutPath: string, subFolder: string, command: string, envs?: string[], mounts?: string[]): Promise<void>;
export declare function pushImage(exec: ExecFunction, imageName: string): Promise<void>;
export interface DockerMount {
type: string;
source: string;
target: string;
}
export declare function parseMount(mountString: string): DockerMount;
57 changes: 54 additions & 3 deletions github-action/dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion github-action/dist/index.js.map

Large diffs are not rendered by default.