Skip to content

Commit

Permalink
Merge branch 'main' into add-buildx-cache-to
Browse files Browse the repository at this point in the history
  • Loading branch information
yokonao committed Jul 21, 2023
2 parents eafbf64 + a74814e commit 7f1a7a6
Show file tree
Hide file tree
Showing 42 changed files with 756 additions and 188 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

Notable changes.

## July 2023

### [0.50.0]
- Publish without node-pty dependency (https://github.com/devcontainers/cli/pull/585)
- Record feature dependencies in the lockfile (https://github.com/devcontainers/cli/pull/566)
- Record features referenced by tarball URI in lockfile (https://github.com/devcontainers/cli/pull/594)
- Update proxy-agent to avoid vm2 (https://github.com/devcontainers/cli/pull/596)

### [0.49.0]
- Outdated command (https://github.com/devcontainers/cli/pull/565)
- Case-insensitive instructions (https://github.com/microsoft/vscode-remote-release/issues/6850)
- Automatically set execute bit when running dotfiles install script (https://github.com/devcontainers/cli/pull/541)
- Use getent passwd (https://github.com/microsoft/vscode-remote-release/issues/2957)

## June 2023

### [0.48.0]
Expand Down
11 changes: 5 additions & 6 deletions build/patch-packagejson.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ const path = require('path');

const packageJsonPath = path.join(__dirname, '..', 'package.json');
const packageJsonText = fs.readFileSync(packageJsonPath, 'utf8');
const packageJson = jsonc.parse(packageJsonText);

const dependencies = {
'node-pty': packageJson.dependencies['node-pty'],
};
let patchedText = packageJsonText;
for (const key of ['dependencies', 'devDependencies']) {
const edits = jsonc.modify(patchedText, [key], {}, {});
patchedText = jsonc.applyEdits(patchedText, edits);
}

const edits = jsonc.modify(packageJsonText, ['dependencies'], dependencies, {});
const patchedText = jsonc.applyEdits(packageJsonText, edits);
fs.writeFileSync(packageJsonPath, patchedText);
4 changes: 2 additions & 2 deletions example-usage/image-build/build-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ image_name="${1:-"devcontainer-cli-test-image"}"
# Push will upload the image to a registry when done (if logged in via docker CLI)
push_flag="${2:-false}"

# If more than one plaftorm is specified, then push must be set.
# If more than one platform is specified, then push must be set.
platforms="${3:-linux/amd64}"

devcontainer build --image-name $image_name --platform "$platforms" --push $push_flag --workspace-folder ../workspace

echo "\nImage ${image_name} built successfully!"
echo "\nImage ${image_name} built successfully!"
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@devcontainers/cli",
"description": "Dev Containers CLI",
"version": "0.48.0",
"version": "0.50.0",
"bin": {
"devcontainer": "devcontainer.js"
},
Expand All @@ -13,7 +13,7 @@
"bugs": {
"url": "https://github.com/devcontainers/cli/issues"
},
"license": "SEE LICENSE IN LICENSE.txt",
"license": "MIT",
"engines": {
"node": "^16.13.0 || >=18.0.0"
},
Expand Down Expand Up @@ -69,6 +69,7 @@
"@types/semver": "^7.3.13",
"@types/shell-quote": "^1.7.1",
"@types/tar": "^6.1.4",
"@types/text-table": "^0.2.2",
"@types/yargs": "^17.0.22",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/experimental-utils": "^5.55.0",
Expand Down Expand Up @@ -97,13 +98,14 @@
"jsonc-parser": "^3.2.0",
"ncp": "^2.0.0",
"node-pty": "^0.10.1",
"proxy-agent": "^6.2.0",
"proxy-agent": "^6.3.0",
"pull-stream": "^3.7.0",
"recursive-readdir": "^2.2.3",
"semver": "^7.3.8",
"shell-quote": "^1.8.0",
"stream-to-pull-stream": "^1.7.3",
"tar": "^6.1.13",
"text-table": "^0.2.0",
"vscode-dev-containers": "https://github.com/microsoft/vscode-dev-containers/releases/download/v0.245.2/vscode-dev-containers-0.245.2.tgz",
"vscode-uri": "^3.0.7",
"yargs": "~17.7.1"
Expand Down
4 changes: 2 additions & 2 deletions src/spec-common/cliHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ export enum FileTypeBitmask {
SymbolicLink = 64
}

export async function getCLIHost(localCwd: string, loadNativeModule: <T>(moduleName: string) => Promise<T | undefined>): Promise<CLIHost> {
export async function getCLIHost(localCwd: string, loadNativeModule: <T>(moduleName: string) => Promise<T | undefined>, allowInheritTTY: boolean): Promise<CLIHost> {
const exec = plainExec(localCwd);
const ptyExec = await plainPtyExec(localCwd, loadNativeModule);
const ptyExec = await plainPtyExec(localCwd, loadNativeModule, allowInheritTTY);
return createLocalCLIHostFromExecFunctions(localCwd, exec, ptyExec, connectLocal);
}

Expand Down
56 changes: 51 additions & 5 deletions src/spec-common/commonUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as ptyType from 'node-pty';
import { StringDecoder } from 'string_decoder';

import { toErrorText } from './errors';
import { Disposable, Event } from '../spec-utils/event';
import { Disposable, Event, NodeEventEmitter } from '../spec-utils/event';
import { isLocalFile } from '../spec-utils/pfs';
import { Log, nullLog } from '../spec-utils/log';
import { ShellServer } from './shellServer';
Expand All @@ -32,6 +32,7 @@ export interface ExecParameters {
cwd?: string;
cmd: string;
args?: string[];
stdio?: [cp.StdioNull | cp.StdioPipe, cp.StdioNull | cp.StdioPipe, cp.StdioNull | cp.StdioPipe];
output: Log;
}

Expand Down Expand Up @@ -284,15 +285,15 @@ export const processSignals: Record<string, number | undefined> = {

export function plainExec(defaultCwd: string | undefined): ExecFunction {
return async function (params: ExecParameters): Promise<Exec> {
const { cmd, args, output } = params;
const { cmd, args, stdio, output } = params;

const text = `Run: ${cmd} ${(args || []).join(' ').replace(/\n.*/g, '')}`;
const start = output.start(text);

const cwd = params.cwd || defaultCwd;
const env = params.env ? { ...process.env, ...params.env } : process.env;
const exec = await findLocalWindowsExecutable(cmd, cwd, env, output);
const p = cp.spawn(exec, args, { cwd, env, windowsHide: true });
const p = cp.spawn(exec, args, { cwd, env, stdio: stdio as any, windowsHide: true });

return {
stdin: p.stdin,
Expand All @@ -315,10 +316,11 @@ export function plainExec(defaultCwd: string | undefined): ExecFunction {
};
}

export async function plainPtyExec(defaultCwd: string | undefined, loadNativeModule: <T>(moduleName: string) => Promise<T | undefined>): Promise<PtyExecFunction> {
export async function plainPtyExec(defaultCwd: string | undefined, loadNativeModule: <T>(moduleName: string) => Promise<T | undefined>, allowInheritTTY: boolean): Promise<PtyExecFunction> {
const pty = await loadNativeModule<typeof ptyType>('node-pty');
if (!pty) {
throw new Error('Missing node-pty');
const plain = plainExec(defaultCwd);
return plainExecAsPtyExec(plain, allowInheritTTY);
}

return async function (params: PtyExecParameters): Promise<PtyExec> {
Expand Down Expand Up @@ -368,6 +370,50 @@ export async function plainPtyExec(defaultCwd: string | undefined, loadNativeMod
};
}

export function plainExecAsPtyExec(plain: ExecFunction, allowInheritTTY: boolean): PtyExecFunction {
return async function (params: PtyExecParameters): Promise<PtyExec> {
const p = await plain({
...params,
stdio: allowInheritTTY && params.output !== nullLog ? [
process.stdin.isTTY ? 'inherit' : 'pipe',
process.stdout.isTTY ? 'inherit' : 'pipe',
process.stderr.isTTY ? 'inherit' : 'pipe',
] : undefined,
});
const onDataEmitter = new NodeEventEmitter<string>();
if (p.stdout) {
const stdoutDecoder = new StringDecoder();
p.stdout.on('data', data => onDataEmitter.fire(stdoutDecoder.write(data)));
p.stdout.on('close', () => {
const end = stdoutDecoder.end();
if (end) {
onDataEmitter.fire(end);
}
});
}
if (p.stderr) {
const stderrDecoder = new StringDecoder();
p.stderr.on('data', data => onDataEmitter.fire(stderrDecoder.write(data)));
p.stderr.on('close', () => {
const end = stderrDecoder.end();
if (end) {
onDataEmitter.fire(end);
}
});
}
return {
onData: onDataEmitter.event,
write: p.stdin ? p.stdin.write.bind(p.stdin) : () => {},
resize: () => {},
exit: p.exit.then(({ code, signal }) => ({
code: typeof code === 'number' ? code : undefined,
signal: typeof signal === 'string' ? processSignals[signal] : undefined,
})),
terminate: p.terminate.bind(p),
};
};
}

async function findLocalWindowsExecutable(command: string, cwd = process.cwd(), env: Record<string, string | undefined>, output: Log): Promise<string> {
if (process.platform !== 'win32') {
return command;
Expand Down
40 changes: 29 additions & 11 deletions src/spec-common/dotfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,33 @@ export async function installDotfiles(params: ResolverParameters, properties: Co
status: 'running',
});
if (installCommand) {
await shellServer.exec(`# Clone & install dotfiles
await shellServer.exec(`# Clone & install dotfiles via '${installCommand}'
${createFileCommand(markerFile)} || (echo dotfiles marker found && exit 1) || exit 0
command -v git >/dev/null 2>&1 || (echo git not found && exit 1) || exit 0
[ -e ${targetPath} ] || ${allEnv}git clone ${repository} ${targetPath} || exit $?
if [ -x ${installCommand} ]
echo Setting current directory to '${targetPath}'
cd ${targetPath}
if [ -f "./${installCommand}" ]
then
if [ ! -x "./${installCommand}" ]
then
echo Setting './${installCommand}' as executable
chmod +x "./${installCommand}"
fi
echo Executing command './${installCommand}'..\n
${allEnv}"./${installCommand}"
elif [ -f "${installCommand}" ]
then
echo Executing script ${installCommand}
cd ${targetPath} && ${allEnv}${installCommand}
if [ ! -x "${installCommand}" ]
then
echo Setting '${installCommand}' as executable
chmod +x "${installCommand}"
fi
echo Executing command '${installCommand}'...\n
${allEnv}"${installCommand}"
else
echo Error: ${installCommand} not executable
echo Could not locate '${installCommand}'...\n
exit 126
fi
`, { logOutput: 'continuous', logLevel: LogLevel.Info });
Expand All @@ -58,6 +75,7 @@ fi
${createFileCommand(markerFile)} || (echo dotfiles marker found && exit 1) || exit 0
command -v git >/dev/null 2>&1 || (echo git not found && exit 1) || exit 0
[ -e ${targetPath} ] || ${allEnv}git clone ${repository} ${targetPath} || exit $?
echo Setting current directory to ${targetPath}
cd ${targetPath}
for f in ${installCommands.join(' ')}
do
Expand All @@ -78,14 +96,14 @@ then
echo No dotfiles found.
fi
else
if [ -x "$installCommand" ]
if [ ! -x "$installCommand" ]
then
echo Executing script '${targetPath}'/"$installCommand"
${allEnv}./"$installCommand"
else
echo Error: '${targetPath}'/"$installCommand" not executable
exit 126
echo Setting '${targetPath}'/"$installCommand" as executable
chmod +x "$installCommand"
fi
echo Executing command '${targetPath}'/"$installCommand"...\n
${allEnv}./"$installCommand"
fi
`, { logOutput: 'continuous', logLevel: LogLevel.Info });
}
Expand Down
34 changes: 17 additions & 17 deletions src/spec-common/injectHeadless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface ResolverParameters {
getLogLevel: () => LogLevel;
onDidChangeLogLevel: Event<LogLevel>;
loadNativeModule: <T>(moduleName: string) => Promise<T | undefined>;
allowInheritTTY: boolean;
shutdowns: (() => Promise<void>)[];
backgroundTasks: (Promise<void> | (() => Promise<void>))[];
persistedFolder: string; // A path where config can be persisted and restored at a later time. Should default to tmpdir() folder if not provided.
Expand Down Expand Up @@ -237,9 +238,9 @@ export async function getContainerProperties(options: {
remoteExecAsRoot = remoteExec;
}
const osRelease = await getOSRelease(shellServer);
const passwdUser = await getUserFromEtcPasswd(shellServer, containerUser);
const passwdUser = await getUserFromPasswdDB(shellServer, containerUser);
if (!passwdUser) {
params.output.write(toWarningText(`User ${containerUser} not found in /etc/passwd.`));
params.output.write(toWarningText(`User ${containerUser} not found with 'getent passwd'.`));
}
const shell = await getUserShell(containerEnv, passwdUser);
const homeFolder = await getHomeFolder(containerEnv, passwdUser);
Expand Down Expand Up @@ -284,9 +285,9 @@ async function getUserShell(containerEnv: NodeJS.ProcessEnv, passwdUser: PasswdU
return containerEnv.SHELL || (passwdUser && passwdUser.shell) || '/bin/sh';
}

export async function getUserFromEtcPasswd(shellServer: ShellServer, userNameOrId: string) {
const { stdout } = await shellServer.exec('cat /etc/passwd', { logOutput: false });
return findUserInEtcPasswd(stdout, userNameOrId);
export async function getUserFromPasswdDB(shellServer: ShellServer, userNameOrId: string) {
const { stdout } = await shellServer.exec(`getent passwd ${userNameOrId}`, { logOutput: false });
return parseUserInPasswdDB(stdout);
}

export interface PasswdUser {
Expand All @@ -297,18 +298,17 @@ export interface PasswdUser {
shell: string;
}

export function findUserInEtcPasswd(etcPasswd: string, nameOrId: string): PasswdUser | undefined {
const users = etcPasswd
.split(/\r?\n/)
.map(line => line.split(':'))
.map(row => ({
name: row[0],
uid: row[2],
gid: row[3],
home: row[5],
shell: row[6]
}));
return users.find(user => user.name === nameOrId || user.uid === nameOrId);
function parseUserInPasswdDB(etcPasswdLine: string): PasswdUser | undefined {
const row = etcPasswdLine
.replace(/\n$/, '')
.split(':');
return {
name: row[0],
uid: row[2],
gid: row[3],
home: row[5],
shell: row[6]
};
}

export function getUserDataFolder(homeFolder: string, params: ResolverParameters) {
Expand Down
Loading

0 comments on commit 7f1a7a6

Please sign in to comment.