Skip to content

Commit

Permalink
Merge branch 'main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
samruddhikhandale authored Mar 6, 2024
2 parents a050da3 + ab79dd6 commit 56d83a1
Show file tree
Hide file tree
Showing 34 changed files with 319 additions and 71 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/dev-containers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ jobs:
registry-url: 'https://npm.pkg.github.com'
scope: '@microsoft'
- name: Install Dependencies
run: yarn install --frozen-lockfile
run: |
yarn install --frozen-lockfile
docker run --privileged --rm tonistiigi/binfmt --install all
- name: Type-Check
run: yarn type-check
- name: Package
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Notable changes.

## March 2024

### [0.57.0]
- Fix crash updating UID/GID when the image's platform is different from the native CPU arch (https://github.com/devcontainers/cli/pull/746)
- Add tags with build command (https://github.com/devcontainers/ci/issues/271)

## February 2024

### [0.56.2]
Expand Down
2 changes: 1 addition & 1 deletion 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.56.2",
"version": "0.57.0",
"bin": {
"devcontainer": "devcontainer.js"
},
Expand Down
2 changes: 1 addition & 1 deletion src/spec-common/cliHost.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

Expand Down
9 changes: 9 additions & 0 deletions src/spec-common/commonUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ export interface ExecFunction {
(params: ExecParameters): Promise<Exec>;
}

export type GoOS = { [OS in NodeJS.Platform]: OS extends 'win32' ? 'windows' : OS; }[NodeJS.Platform];
export type GoARCH = { [ARCH in NodeJS.Architecture]: ARCH extends 'x64' ? 'amd64' : ARCH; }[NodeJS.Architecture];

export interface PlatformInfo {
os: GoOS;
arch: GoARCH;
variant?: string;
}

export interface PtyExec {
onData: Event<string>;
write?(data: string): void;
Expand Down
25 changes: 12 additions & 13 deletions src/spec-configuration/containerCollectionsOCI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as crypto from 'crypto';
import { Log, LogLevel } from '../spec-utils/log';
import { isLocalFile, mkdirpLocal, readLocalFile, writeLocalFile } from '../spec-utils/pfs';
import { requestEnsureAuthenticated } from './httpOCIRegistry';
import { GoARCH, GoOS, PlatformInfo } from '../spec-common/commonUtils';

export const DEVCONTAINER_MANIFEST_MEDIATYPE = 'application/vnd.devcontainers';
export const DEVCONTAINER_TAR_LAYER_MEDIATYPE = 'application/vnd.devcontainers.layer.v1+tar';
Expand Down Expand Up @@ -91,6 +92,7 @@ interface OCIImageIndexEntry {
digest: string;
platform: {
architecture: string;
variant?: string;
os: string;
};
}
Expand All @@ -116,7 +118,7 @@ const regexForVersionOrDigest = /^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$/;

// https://go.dev/doc/install/source#environment
// Expected by OCI Spec as seen here: https://github.com/opencontainers/image-spec/blob/main/image-index.md#image-index-property-descriptions
export function mapNodeArchitectureToGOARCH(arch: NodeJS.Architecture): string {
export function mapNodeArchitectureToGOARCH(arch: NodeJS.Architecture): GoARCH {
switch (arch) {
case 'x64':
return 'amd64';
Expand All @@ -127,7 +129,7 @@ export function mapNodeArchitectureToGOARCH(arch: NodeJS.Architecture): string {

// https://go.dev/doc/install/source#environment
// Expected by OCI Spec as seen here: https://github.com/opencontainers/image-spec/blob/main/image-index.md#image-index-property-descriptions
export function mapNodeOSToGOOS(os: NodeJS.Platform): string {
export function mapNodeOSToGOOS(os: NodeJS.Platform): GoOS {
switch (os) {
case 'win32':
return 'windows';
Expand Down Expand Up @@ -272,13 +274,13 @@ export function getCollectionRef(output: Log, registry: string, namespace: strin
export async function fetchOCIManifestIfExists(params: CommonParams, ref: OCIRef | OCICollectionRef, manifestDigest?: string): Promise<ManifestContainer | undefined> {
const { output } = params;

// Simple mechanism to avoid making a DNS request for
// Simple mechanism to avoid making a DNS request for
// something that is not a domain name.
if (ref.registry.indexOf('.') < 0 && !ref.registry.startsWith('localhost')) {
return;
}

// TODO: Always use the manifest digest (the canonical digest)
// TODO: Always use the manifest digest (the canonical digest)
// instead of the `ref.version` by referencing some lock file (if available).
let reference = ref.version;
if (manifestDigest) {
Expand Down Expand Up @@ -338,7 +340,7 @@ export async function getManifest(params: CommonParams, url: string, ref: OCIRef
}

// https://github.com/opencontainers/image-spec/blob/main/manifest.md
export async function getImageIndexEntryForPlatform(params: CommonParams, url: string, ref: OCIRef | OCICollectionRef, platformInfo: { arch: NodeJS.Architecture; os: NodeJS.Platform }, mimeType?: string): Promise<OCIImageIndexEntry | undefined> {
export async function getImageIndexEntryForPlatform(params: CommonParams, url: string, ref: OCIRef | OCICollectionRef, platformInfo: PlatformInfo, mimeType?: string): Promise<OCIImageIndexEntry | undefined> {
const { output } = params;
const response = await getJsonWithMimeType<OCIImageIndex>(params, url, ref, mimeType || 'application/vnd.oci.image.index.v1+json');
if (!response) {
Expand All @@ -351,15 +353,12 @@ export async function getImageIndexEntryForPlatform(params: CommonParams, url: s
return undefined;
}

const ociFriendlyPlatformInfo = {
arch: mapNodeArchitectureToGOARCH(platformInfo.arch),
os: mapNodeOSToGOOS(platformInfo.os),
};

// Find a manifest for the current architecture and OS.
return imageIndex.manifests.find(m => {
if (m.platform?.architecture === ociFriendlyPlatformInfo.arch && m.platform?.os === ociFriendlyPlatformInfo.os) {
return m;
if (m.platform?.architecture === platformInfo.arch && m.platform?.os === platformInfo.os) {
if (!platformInfo.variant || m.platform?.variant === platformInfo.variant) {
return m;
}
}
return undefined;
});
Expand Down Expand Up @@ -595,4 +594,4 @@ export async function getBlob(params: CommonParams, url: string, ociCacheDir: st
output.write(`Error getting blob: ${e}`, LogLevel.Error);
return;
}
}
}
2 changes: 1 addition & 1 deletion src/spec-node/configContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu

const { dockerCLI, dockerComposeCLI } = params;
const { env } = common;
const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output };
const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, platformInfo: params.platformInfo };
await ensureNoDisallowedFeatures(cliParams, config, additionalFeatures, idLabels);

await runInitializeCommand({ ...params, common: { ...common, output: common.lifecycleHook.output } }, config.initializeCommand, common.lifecycleHook.onDidInput);
Expand Down
20 changes: 15 additions & 5 deletions src/spec-node/containerFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,27 @@ import { ContainerError } from '../spec-common/errors';
// Environment variables must contain:
// - alpha-numeric values, or
// - the '_' character, and
// - a number cannot be the first character
// - a number cannot be the first character
export const getSafeId = (str: string) => str
.replace(/[^\w_]/g, '_')
.replace(/^[\d_]+/g, '_')
.toUpperCase();

export async function extendImage(params: DockerResolverParameters, config: SubstitutedConfig<DevContainerConfig>, imageName: string, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>, canAddLabelsToContainer: boolean) {
export async function extendImage(params: DockerResolverParameters, config: SubstitutedConfig<DevContainerConfig>, imageName: string, additionalImageNames: string[], additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>, canAddLabelsToContainer: boolean) {
const { common } = params;
const { cliHost, output } = common;

const imageBuildInfo = await getImageBuildInfoFromImage(params, imageName, config.substitute);
const extendImageDetails = await getExtendImageBuildInfo(params, config, imageName, imageBuildInfo, undefined, additionalFeatures, canAddLabelsToContainer);
if (!extendImageDetails?.featureBuildInfo) {
// no feature extensions - return
if (additionalImageNames.length) {
if (params.isTTY) {
await Promise.all(additionalImageNames.map(name => dockerPtyCLI(params, 'tag', imageName, name)));
} else {
await Promise.all(additionalImageNames.map(name => dockerCLI(params, 'tag', imageName, name)));
}
}
return {
updatedImageName: [imageName],
imageMetadata: getDevcontainerMetadata(imageBuildInfo.metadata, config, extendImageDetails?.featuresConfig),
Expand Down Expand Up @@ -99,6 +106,7 @@ export async function extendImage(params: DockerResolverParameters, config: Subs
args.push(
'--target', featureBuildInfo.overrideTarget,
'-t', updatedImageName,
...additionalImageNames.map(name => ['-t', name]).flat(),
'-f', dockerfilePath,
emptyTempDir
);
Expand Down Expand Up @@ -344,7 +352,7 @@ function getFeatureEnvVariables(f: Feature) {
const values = getFeatureValueObject(f);
const idSafe = getSafeId(f.id);
const variables = [];

if(f.internalVersion !== '2')
{
if (values) {
Expand All @@ -365,7 +373,7 @@ function getFeatureEnvVariables(f: Feature) {
variables.push(`${f.buildArg}=${getFeatureMainValue(f)}`);
}
return variables;
}
}
}

export async function getRemoteUserUIDUpdateDetails(params: DockerResolverParameters, mergedConfig: MergedDevContainerConfig, imageName: string, imageDetails: () => Promise<ImageDetails>, runArgsUser: string | undefined) {
Expand All @@ -388,6 +396,7 @@ export async function getRemoteUserUIDUpdateDetails(params: DockerResolverParame
imageName: fixedImageName,
remoteUser,
imageUser,
platform: [details.Os, details.Architecture, details.Variant].filter(Boolean).join('/')
};
}

Expand All @@ -399,7 +408,7 @@ export async function updateRemoteUserUID(params: DockerResolverParameters, merg
if (!updateDetails) {
return imageName;
}
const { imageName: fixedImageName, remoteUser, imageUser } = updateDetails;
const { imageName: fixedImageName, remoteUser, imageUser, platform } = updateDetails;

const dockerfileName = 'updateUID.Dockerfile';
const srcDockerfile = path.join(common.extensionPath, 'scripts', dockerfileName);
Expand All @@ -415,6 +424,7 @@ export async function updateRemoteUserUID(params: DockerResolverParameters, merg
'build',
'-f', destDockerfile,
'-t', fixedImageName,
...(platform ? ['--platform', platform] : []),
'--build-arg', `BASE_IMAGE=${imageName}`,
'--build-arg', `REMOTE_USER=${remoteUser}`,
'--build-arg', `NEW_UID=${await cliHost.getuid!()}`,
Expand Down
34 changes: 32 additions & 2 deletions src/spec-node/devContainers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import * as path from 'path';
import * as crypto from 'crypto';
import * as os from 'os';

import { mapNodeOSToGOOS, mapNodeArchitectureToGOARCH } from '../spec-configuration/containerCollectionsOCI';
import { DockerResolverParameters, DevContainerAuthority, UpdateRemoteUserUIDDefault, BindMountConsistency, getCacheFolder } from './utils';
import { createNullLifecycleHook, finishBackgroundTasks, ResolverParameters, UserEnvProbe } from '../spec-common/injectHeadless';
import { getCLIHost, loadNativeModule } from '../spec-common/commonUtils';
import { GoARCH, GoOS, getCLIHost, loadNativeModule } from '../spec-common/commonUtils';
import { resolve } from './configContainer';
import { URI } from 'vscode-uri';
import { LogLevel, LogDimensions, toErrorText, createCombinedLog, createTerminalLog, Log, makeLog, LogFormat, createJSONLog, createPlainLog, LogHandler, replaceAllLog } from '../spec-utils/log';
Expand Down Expand Up @@ -163,12 +164,40 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
env: cliHost.env,
output: common.output,
}, dockerPath, dockerComposePath);

const platformInfo = (() => {
if (common.buildxPlatform) {
const slash1 = common.buildxPlatform.indexOf('/');
const slash2 = common.buildxPlatform.indexOf('/', slash1 + 1);
// `--platform linux/amd64/v3` `--platform linux/arm64/v8`
if (slash2 !== -1) {
return {
os: <GoOS> common.buildxPlatform.slice(0, slash1),
arch: <GoARCH> common.buildxPlatform.slice(slash1 + 1, slash2),
variant: common.buildxPlatform.slice(slash2 + 1),
};
}
// `--platform linux/amd64` and `--platform linux/arm64`
return {
os: <GoOS> common.buildxPlatform.slice(0, slash1),
arch: <GoARCH> common.buildxPlatform.slice(slash1 + 1),
};
} else {
// `--platform` omitted
return {
os: mapNodeOSToGOOS(cliHost.platform),
arch: mapNodeArchitectureToGOARCH(cliHost.arch),
};
}
})();

const buildKitVersion = options.useBuildKit === 'never' ? undefined : (await dockerBuildKitVersion({
cliHost,
dockerCLI: dockerPath,
dockerComposeCLI,
env: cliHost.env,
output
output,
platformInfo
}));
return {
common,
Expand All @@ -195,6 +224,7 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
buildxPush: common.buildxPush,
buildxOutput: common.buildxOutput,
buildxCacheTo: common.buildxCacheTo,
platformInfo
};
}

Expand Down
Loading

0 comments on commit 56d83a1

Please sign in to comment.