Skip to content

Commit

Permalink
add 'templates metadata' subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
joshspicer authored Aug 1, 2024
1 parent 0ab3f98 commit ff24b1a
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/spec-node/devContainersSpecCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { readFeaturesConfig } from './featureUtils';
import { featuresGenerateDocsHandler, featuresGenerateDocsOptions } from './featuresCLI/generateDocs';
import { templatesGenerateDocsHandler, templatesGenerateDocsOptions } from './templatesCLI/generateDocs';
import { mapNodeOSToGOOS, mapNodeArchitectureToGOARCH } from '../spec-configuration/containerCollectionsOCI';
import { templateMetadataHandler, templateMetadataOptions } from './templatesCLI/metadata';

const defaultDefaultUserEnvProbe: UserEnvProbe = 'loginInteractiveShell';

Expand Down Expand Up @@ -85,6 +86,7 @@ const mountRegex = /^type=(bind|volume),source=([^,]+),target=([^,]+)(?:,externa
y.command('templates', 'Templates commands', (y: Argv) => {
y.command('apply', 'Apply a template to the project', templateApplyOptions, templateApplyHandler);
y.command('publish <target>', 'Package and publish templates', templatesPublishOptions, templatesPublishHandler);
y.command('metadata <templateId>', 'Fetch a published Template\'s metadata', templateMetadataOptions, templateMetadataHandler);
y.command('generate-docs', 'Generate documentation', templatesGenerateDocsOptions, templatesGenerateDocsHandler);
});
y.command(restArgs ? ['exec', '*'] : ['exec <cmd> [args..]'], 'Execute a command on a running dev container', execOptions, execHandler);
Expand Down
74 changes: 74 additions & 0 deletions src/spec-node/templatesCLI/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Argv } from 'yargs';
import { LogLevel, mapLogLevel } from '../../spec-utils/log';
import { getPackageConfig } from '../../spec-utils/product';
import { createLog } from '../devContainers';
import { fetchOCIManifestIfExists, getRef } from '../../spec-configuration/containerCollectionsOCI';

import { UnpackArgv } from '../devContainersSpecCLI';

export function templateMetadataOptions(y: Argv) {
return y
.options({
'log-level': { choices: ['info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'info' as 'info', description: 'Log level.' },
})
.positional('templateId', { type: 'string', demandOption: true, description: 'Template Identifier' });
}

export type TemplateMetadataArgs = UnpackArgv<ReturnType<typeof templateMetadataOptions>>;

export function templateMetadataHandler(args: TemplateMetadataArgs) {
(async () => await templateMetadata(args))().catch(console.error);
}

async function templateMetadata({
'log-level': inputLogLevel,
'templateId': templateId,
}: TemplateMetadataArgs) {
const disposables: (() => Promise<unknown> | undefined)[] = [];
const dispose = async () => {
await Promise.all(disposables.map(d => d()));
};

const pkg = getPackageConfig();

const output = createLog({
logLevel: mapLogLevel(inputLogLevel),
logFormat: 'text',
log: (str) => process.stderr.write(str),
terminalDimensions: undefined,
}, pkg, new Date(), disposables);

const params = { output, env: process.env };
output.write(`Fetching metadata for ${templateId}`, LogLevel.Trace);

const templateRef = getRef(output, templateId);
if (!templateRef) {
console.log(JSON.stringify({}));
process.exit(1);
}

const manifestContainer = await fetchOCIManifestIfExists(params, templateRef, undefined);
if (!manifestContainer) {
console.log(JSON.stringify({}));
process.exit(1);
}

const { manifestObj, canonicalId } = manifestContainer;
output.write(`Template '${templateId}' and resolved to '${canonicalId}'`, LogLevel.Trace);

// Templates must have been published with a CLI post commit
// https://github.com/devcontainers/cli/commit/6c6aebfa7b74aea9d67760fd1e74b09573d31536
// in order to contain attached metadata.
const metadata = manifestObj.annotations?.['dev.containers.metadata'];
if (!metadata) {
output.write(`Template resolved to '${canonicalId}' but does not contain metadata on its manifest.`, LogLevel.Warning);
output.write(`Ask the Template owner to republish this Template to populate the manifest.`, LogLevel.Warning);
console.log(JSON.stringify({}));
process.exit(1);
}

const unescaped = JSON.parse(metadata);
console.log(JSON.stringify(unescaped));
await dispose();
process.exit();
}

0 comments on commit ff24b1a

Please sign in to comment.