Skip to content

Commit

Permalink
Remove vscode-dev-containers dependency (#682)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrmarti authored Nov 15, 2023
1 parent 53ee019 commit bd19f61
Show file tree
Hide file tree
Showing 16 changed files with 20 additions and 323 deletions.
2 changes: 0 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ The specification repo uses the following [labels](https://github.com/microsoft/

- Create a PR:
- Updating the package version in the `package.json`.
- Updating the `vscode-dev-containers` version in the `package.json`'s dependencies (if there is an update).
- Run `yarn` to update `yarn.lock`.
- List notable changes in the `CHANGELOG.md`.
- Update ThirdPartyNotices.txt with any new dependencies.
- After the PR is merged to `main` wait for the CI workflow to succeed (this builds the artifact that will be published). (TBD: Let the `publish-dev-containers` workflow wait for the CI workflow.)
Expand Down
1 change: 0 additions & 1 deletion esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ const watch = process.argv.indexOf('--watch') !== -1;
minify,
platform: 'node',
target: 'node14.17.0',
external: ['vscode-dev-containers'],
mainFields: ['module', 'main'],
outdir: 'dist',
plugins: [plugin],
Expand Down
11 changes: 3 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
"node": "^16.13.0 || >=18.0.0"
},
"scripts": {
"compile": "npm-run-all clean-dist definitions compile-dev",
"watch": "npm-run-all clean-dist definitions compile-watch",
"package": "npm-run-all clean-dist definitions compile-prod store-packagejson patch-packagejson npm-pack restore-packagejson",
"compile": "npm-run-all clean-dist compile-dev",
"watch": "npm-run-all clean-dist compile-watch",
"package": "npm-run-all clean-dist compile-prod store-packagejson patch-packagejson npm-pack restore-packagejson",
"store-packagejson": "copyfiles package.json build-tmp/",
"patch-packagejson": "node build/patch-packagejson.js",
"restore-packagejson": "copyfiles --up 1 build-tmp/package.json .",
Expand All @@ -32,10 +32,7 @@
"tsc-b": "tsc -b",
"tsc-b-w": "tsc -b -w",
"precommit": "node build/hygiene.js",
"definitions": "npm-run-all definitions-clean definitions-copy",
"lint": "eslint -c .eslintrc.js --rulesdir ./build/eslint --max-warnings 0 --ext .ts ./src",
"definitions-clean": "rimraf dist/node_modules/vscode-dev-containers",
"definitions-copy": "copyfiles \"node_modules/vscode-dev-containers/container-features/{devcontainer-features.json,feature-scripts.env,fish-debian.sh,homebrew-debian.sh,install.sh}\" dist",
"npm-pack": "npm pack",
"clean": "npm-run-all clean-dist clean-built",
"clean-dist": "rimraf dist",
Expand All @@ -52,7 +49,6 @@
"ThirdPartyNotices.txt",
"devcontainer.js",
"dist/spec-node/devContainersSpecCLI.js",
"dist/node_modules/vscode-dev-containers",
"package.json",
"scripts/updateUID.Dockerfile"
],
Expand Down Expand Up @@ -106,7 +102,6 @@
"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
104 changes: 8 additions & 96 deletions src/spec-configuration/containerFeaturesConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Lockfile, readLockfile, writeLockfile } from './lockfile';
import { computeDependsOnInstallationOrder } from './containerFeaturesOrder';
import { logFeatureAdvisories } from './featureAdvisories';
import { getEntPasswdShellCommand } from '../spec-common/commonUtils';
import { ContainerError } from '../spec-common/errors';

// v1
const V1_ASSET_NAME = 'devcontainer-features.tgz';
Expand Down Expand Up @@ -116,18 +117,14 @@ export function parseMount(str: string): Mount {
.reduce((acc, [key, value]) => ({ ...acc, [(normalizedMountKeys[key] || key)]: value }), {}) as Mount;
}

export type SourceInformation = LocalCacheSourceInformation | GithubSourceInformation | DirectTarballSourceInformation | FilePathSourceInformation | OCISourceInformation;
export type SourceInformation = GithubSourceInformation | DirectTarballSourceInformation | FilePathSourceInformation | OCISourceInformation;

interface BaseSourceInformation {
type: string;
userFeatureId: string; // Dictates how a supporting tool will locate and download a given feature. See https://github.com/devcontainers/spec/blob/main/proposals/devcontainer-features.md#referencing-a-feature
userFeatureIdWithoutVersion?: string;
}

export interface LocalCacheSourceInformation extends BaseSourceInformation {
type: 'local-cache';
}

export interface OCISourceInformation extends BaseSourceInformation {
type: 'oci';
featureRef: OCIRef;
Expand Down Expand Up @@ -215,18 +212,6 @@ export interface ContainerFeatureInternalParams {
experimentalFrozenLockfile?: boolean;
}

export const multiStageBuildExploration = false;

const isTsnode = path.basename(process.argv[0]) === 'ts-node' || process.argv.indexOf('ts-node/register') !== -1;

export function getContainerFeaturesFolder(_extensionPath: string | { distFolder: string }) {
if (isTsnode) {
return path.join(require.resolve('vscode-dev-containers/package.json'), '..', 'container-features');
}
const distFolder = typeof _extensionPath === 'string' ? path.join(_extensionPath, 'dist') : _extensionPath.distFolder;
return path.join(distFolder, 'node_modules', 'vscode-dev-containers', 'container-features');
}

// TODO: Move to node layer.
export function getContainerFeaturesBaseDockerFile(contentSourceRootPath: string) {
return `
Expand Down Expand Up @@ -467,29 +452,6 @@ async function askGitHubApiForTarballUri(sourceInformation: GithubSourceInformat
return undefined;
}

export async function loadFeaturesJson(jsonBuffer: Buffer, filePath: string, output: Log): Promise<FeatureSet | undefined> {
if (jsonBuffer.length === 0) {
output.write('Parsed featureSet is empty.', LogLevel.Error);
return undefined;
}

const featureSet: FeatureSet = jsonc.parse(jsonBuffer.toString());
if (!featureSet?.features || featureSet.features.length === 0) {
output.write('Parsed featureSet contains no features.', LogLevel.Error);
return undefined;
}
output.write(`Loaded ${filePath}, which declares ${featureSet.features.length} features and ${(!!featureSet.sourceInformation) ? 'contains' : 'does not contain'} explicit source info.`,
LogLevel.Trace);

return updateFromOldProperties(featureSet);
}

export async function loadV1FeaturesJsonFromDisk(pathToDirectory: string, output: Log): Promise<FeatureSet | undefined> {
const filePath = path.join(pathToDirectory, V1_DEVCONTAINER_FEATURES_FILE_NAME);
const jsonBuffer: Buffer = await readLocalFile(filePath);
return loadFeaturesJson(jsonBuffer, filePath, output);
}

function updateFromOldProperties<T extends { features: (Feature & { extensions?: string[]; settings?: object; customizations?: VSCodeCustomizations })[] }>(original: T): T {
// https://github.com/microsoft/dev-container-spec/issues/1
if (!original.features.find(f => f.extensions || f.settings)) {
Expand Down Expand Up @@ -522,7 +484,7 @@ function updateFromOldProperties<T extends { features: (Feature & { extensions?:

// Generate a base featuresConfig object with the set of locally-cached features,
// as well as downloading and merging in remote feature definitions.
export async function generateFeaturesConfig(params: ContainerFeatureInternalParams, dstFolder: string, config: DevContainerConfig, getLocalFeaturesFolder: (d: string) => string, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>) {
export async function generateFeaturesConfig(params: ContainerFeatureInternalParams, dstFolder: string, config: DevContainerConfig, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>) {
const { output } = params;

const workspaceRoot = params.cwd;
Expand All @@ -533,15 +495,6 @@ export async function generateFeaturesConfig(params: ContainerFeatureInternalPar
return undefined;
}

// load local cache of features;
// TODO: Update so that cached features are always version 2
const localFeaturesFolder = getLocalFeaturesFolder(params.extensionPath);
const locallyCachedFeatureSet = await loadV1FeaturesJsonFromDisk(localFeaturesFolder, output); // TODO: Pass dist folder instead to also work with the devcontainer.json support package.
if (!locallyCachedFeatureSet) {
output.write('Failed to load locally cached features', LogLevel.Error);
return undefined;
}

let configPath = config.configFilePath && uriToFsPath(config.configFilePath, params.platform);
output.write(`configPath: ${configPath}`, LogLevel.Trace);

Expand All @@ -567,7 +520,7 @@ export async function generateFeaturesConfig(params: ContainerFeatureInternalPar

// Fetch features, stage into the appropriate build folder, and read the feature's devcontainer-feature.json
output.write('--- Fetching User Features ----', LogLevel.Trace);
await fetchFeatures(params, featuresConfig, locallyCachedFeatureSet, dstFolder, localFeaturesFolder, ociCacheDir, lockfile);
await fetchFeatures(params, featuresConfig, dstFolder, ociCacheDir, lockfile);

await logFeatureAdvisories(params, featuresConfig);
await writeLockfile(params, config, featuresConfig, initLockfile);
Expand Down Expand Up @@ -758,9 +711,10 @@ export async function getFeatureIdType(params: CommonParams, userFeatureId: stri
// (1) A feature backed by a GitHub Release
// Syntax: <repoOwner>/<repoName>/<featureId>[@version]

// DEPRECATED: This is a legacy feature-set ID
// Legacy feature-set ID
if (!userFeatureId.includes('/') && !userFeatureId.includes('\\')) {
return { type: 'local-cache', manifest: undefined };
output.write(`Legacy feature '${userFeatureId}' not supported. Please check https://containers.dev/features for replacements.`, LogLevel.Error);
throw new ContainerError({ description: `Legacy feature '${userFeatureId}' not supported. Please check https://containers.dev/features for replacements.` });
}

// Direct tarball reference
Expand Down Expand Up @@ -797,9 +751,6 @@ export function getBackwardCompatibleFeatureId(output: Log, id: string) {
deprecatedFeaturesIntoOptions.set('maven', 'java');
deprecatedFeaturesIntoOptions.set('jupyterlab', 'python');

// TODO: add warning logs once we have context on the new location for these Features.
// const deprecatedFeatures = ['fish', 'homebrew'];

const newFeaturePath = 'ghcr.io/devcontainers/features';
// Note: Pin the versionBackwardComp to '1' to avoid breaking changes.
const versionBackwardComp = '1';
Expand Down Expand Up @@ -840,29 +791,6 @@ export async function processFeatureIdentifier(params: CommonParams, configPath:

const { type, manifest } = await getFeatureIdType(params, userFeature.userFeatureId, lockfile);

// cached feature
// Resolves deprecated features (fish, maven, gradle, homebrew, jupyterlab)
if (type === 'local-cache') {
output.write(`Cached feature found.`);

let feat: Feature = {
id: userFeature.userFeatureId,
name: userFeature.userFeatureId,
value: userFeature.options,
included: true,
};

let newFeaturesSet: FeatureSet = {
sourceInformation: {
type: 'local-cache',
userFeatureId: originalUserFeatureId
},
features: [feat],
};

return newFeaturesSet;
}

// remote tar file
if (type === 'direct-tarball') {
output.write(`Remote tar file found.`);
Expand Down Expand Up @@ -1036,7 +964,7 @@ export async function processFeatureIdentifier(params: CommonParams, configPath:
// throw new Error(`Unsupported feature source type: ${type}`);
}

async function fetchFeatures(params: { extensionPath: string; cwd: string; output: Log; env: NodeJS.ProcessEnv }, featuresConfig: FeaturesConfig, localFeatures: FeatureSet, dstFolder: string, localFeaturesFolder: string, ociCacheDir: string, lockfile: Lockfile | undefined) {
async function fetchFeatures(params: { extensionPath: string; cwd: string; output: Log; env: NodeJS.ProcessEnv }, featuresConfig: FeaturesConfig, dstFolder: string, ociCacheDir: string, lockfile: Lockfile | undefined) {
const featureSets = featuresConfig.featureSets;
for (let idx = 0; idx < featureSets.length; idx++) { // Index represents the previously computed installation order.
const featureSet = featureSets[idx];
Expand All @@ -1045,10 +973,6 @@ async function fetchFeatures(params: { extensionPath: string; cwd: string; outpu
continue;
}

if (!localFeatures) {
continue;
}

const { output } = params;

const feature = featureSet.features[0];
Expand Down Expand Up @@ -1086,18 +1010,6 @@ async function fetchFeatures(params: { extensionPath: string; cwd: string; outpu
continue;
}

if (sourceInfoType === 'local-cache') {
// create copy of the local features to set the environment variables for them.
await mkdirpLocal(featCachePath);
await cpDirectoryLocal(localFeaturesFolder, featCachePath);

if (!(await applyFeatureConfigToFeature(output, featureSet, feature, featCachePath, undefined))) {
const err = `Failed to parse feature '${featureDebugId}'. Please check your devcontainer.json 'features' attribute.`;
throw new Error(err);
}
continue;
}

if (sourceInfoType === 'file-path') {
output.write(`Detected local file path`, LogLevel.Trace);
await mkdirpLocal(featCachePath);
Expand Down
55 changes: 3 additions & 52 deletions src/spec-node/containerFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
*--------------------------------------------------------------------------------------------*/

import * as path from 'path';
import { StringDecoder } from 'string_decoder';
import * as tar from 'tar';

import { DevContainerConfig } from '../spec-configuration/configuration';
import { dockerCLI, dockerPtyCLI, ImageDetails, toExecParameters, toPtyExecParameters } from '../spec-shutdown/dockerUtils';
import { LogLevel, makeLog, toErrorText } from '../spec-utils/log';
import { FeaturesConfig, getContainerFeaturesFolder, getContainerFeaturesBaseDockerFile, getFeatureInstallWrapperScript, getFeatureLayers, getFeatureMainValue, getFeatureValueObject, generateFeaturesConfig, Feature, V1_DEVCONTAINER_FEATURES_FILE_NAME, generateContainerEnvs } from '../spec-configuration/containerFeaturesConfiguration';
import { LogLevel, makeLog } from '../spec-utils/log';
import { FeaturesConfig, getContainerFeaturesBaseDockerFile, getFeatureInstallWrapperScript, getFeatureLayers, getFeatureMainValue, getFeatureValueObject, generateFeaturesConfig, Feature, generateContainerEnvs } from '../spec-configuration/containerFeaturesConfiguration';
import { readLocalFile } from '../spec-utils/pfs';
import { includeAllConfiguredFeatures } from '../spec-utils/product';
import { createFeaturesTempFolder, DockerResolverParameters, getCacheFolder, getFolderImageName, getEmptyContextFolder, SubstitutedConfig } from './utils';
Expand Down Expand Up @@ -124,15 +122,12 @@ export async function getExtendImageBuildInfo(params: DockerResolverParameters,
// Creates the folder where the working files will be setup.
const dstFolder = await createFeaturesTempFolder(params.common);

// Extracts the local cache of features.
await createLocalFeatures(params, dstFolder);

// Processes the user's configuration.
const platform = params.common.cliHost.platform;

const cacheFolder = await getCacheFolder(params.common.cliHost);
const { experimentalLockfile, experimentalFrozenLockfile } = params;
const featuresConfig = await generateFeaturesConfig({ ...params.common, platform, cacheFolder, experimentalLockfile, experimentalFrozenLockfile }, dstFolder, config.config, getContainerFeaturesFolder, additionalFeatures);
const featuresConfig = await generateFeaturesConfig({ ...params.common, platform, cacheFolder, experimentalLockfile, experimentalFrozenLockfile }, dstFolder, config.config, additionalFeatures);
if (!featuresConfig) {
if (canAddLabelsToContainer && !imageBuildInfo.dockerfile) {
return {
Expand Down Expand Up @@ -170,50 +165,6 @@ export function generateContainerEnvsV1(featuresConfig: FeaturesConfig) {
return result;
}

async function createLocalFeatures(params: DockerResolverParameters, dstFolder: string)
{
const { common } = params;
const { cliHost, output } = common;

// Name of the local cache folder inside the working directory
const localCacheBuildFolderName = 'local-cache';

const srcFolder = getContainerFeaturesFolder(common.extensionPath);
output.write(`local container features stored at: ${srcFolder}`);
await cliHost.mkdirp(`${dstFolder}/${localCacheBuildFolderName}`);
const create = tar.c({
cwd: srcFolder,
filter: path => (path !== './Dockerfile' && path !== `./${V1_DEVCONTAINER_FEATURES_FILE_NAME}`),
}, ['.']);
const createExit = new Promise((resolve, reject) => {
create.on('error', reject);
create.on('finish', resolve);
});
const extract = await cliHost.exec({
cmd: 'tar',
args: [
'--no-same-owner',
'-x',
'-f', '-',
],
cwd: `${dstFolder}/${localCacheBuildFolderName}`,
output,
});
const stdoutDecoder = new StringDecoder();
extract.stdout.on('data', (chunk: Buffer) => {
output.write(stdoutDecoder.write(chunk));
});
const stderrDecoder = new StringDecoder();
extract.stderr.on('data', (chunk: Buffer) => {
output.write(toErrorText(stderrDecoder.write(chunk)));
});
create.pipe(extract.stdin);
await Promise.all([
extract.exit,
createExit, // Allow errors to surface.
]);
}

export interface ImageBuildOptions {
dstFolder: string;
dockerfileContent: string;
Expand Down
4 changes: 2 additions & 2 deletions src/spec-node/featureUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DevContainerConfig } from '../spec-configuration/configuration';
import { FeaturesConfig, generateFeaturesConfig, getContainerFeaturesFolder } from '../spec-configuration/containerFeaturesConfiguration';
import { FeaturesConfig, generateFeaturesConfig } from '../spec-configuration/containerFeaturesConfiguration';
import { DockerCLIParameters } from '../spec-shutdown/dockerUtils';
import { PackageConfiguration } from '../spec-utils/product';
import { createFeaturesTempFolder, getCacheFolder } from './utils';
Expand All @@ -9,5 +9,5 @@ export async function readFeaturesConfig(params: DockerCLIParameters, pkg: Packa
const { cwd, env, platform } = cliHost;
const featuresTmpFolder = await createFeaturesTempFolder({ cliHost, package: pkg });
const cacheFolder = await getCacheFolder(cliHost);
return generateFeaturesConfig({ extensionPath, cacheFolder, cwd, output, env, skipFeatureAutoMapping, platform }, featuresTmpFolder, config, getContainerFeaturesFolder, additionalFeatures);
return generateFeaturesConfig({ extensionPath, cacheFolder, cwd, output, env, skipFeatureAutoMapping, platform }, featuresTmpFolder, config, additionalFeatures);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/javascript-node-mongo
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
{
"name": "Node.js & Mongo DB",
"dockerComposeFile": "docker-compose.yml",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/javascript-node-mongo
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
{
"name": "Node.js & Mongo DB",
"dockerComposeFile": "docker-compose.yml",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/javascript-node-mongo
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
{
"name": "Node.js & Mongo DB",
"dockerComposeFile": "docker-compose.yml",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/javascript-node-mongo
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
{
"name": "Node.js & Mongo DB",
"dockerComposeFile": "docker-compose.yml",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/javascript-node-mongo
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
{
"name": "Node.js & Mongo DB",
"dockerComposeFile": "docker-compose.yml",
Expand Down
Loading

0 comments on commit bd19f61

Please sign in to comment.