Unverified Commit 542963cf authored by Samruddhi Khandale's avatar Samruddhi Khandale Committed by GitHub

Prep: republish features to ghcr.io (#78)

* syncing action

* modify release.yaml

* version update ; remove "install"

* add 'latest'

* add workflow condition
parent 69d3df5f
......@@ -22,15 +22,33 @@ inputs:
# 'features' options
base-path-to-features:
required: false
default: './features/src'
default: ''
description: "Relative path to the 'src' folder containing dev container 'feature(s)'"
# 'template' options
base-path-to-templates:
required: false
default: './templates/src'
default: ''
description: "Relative path to the folder containing dev container 'template(s)'"
# EXPERIMENTAL
tag-individual-features:
required: false
default: "false"
description: "Tag individual features"
publish-to-npm:
required: false
default: "false"
description: "Should publish features to NPM?"
publish-release-artifacts:
required: false
default: "false"
description: "Publish release artifacts (classic)"
publish-to-oci:
required: false
default: "false"
description: "Publish to OCI?"
runs:
using: 'node16'
main: 'dist/index.js'
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -520,7 +520,7 @@ minipass
ISC
The ISC License
Copyright (c) npm, Inc. and Contributors
Copyright (c) 2017-2022 npm, Inc., Isaac Z. Schlueter, and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
......
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
......@@ -32,51 +32,91 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateFeaturesDocumentation = void 0;
exports.generateTemplateDocumentation = exports.generateFeaturesDocumentation = void 0;
const fs = __importStar(require("fs"));
const github = __importStar(require("@actions/github"));
const core = __importStar(require("@actions/core"));
const path = __importStar(require("path"));
const utils_1 = require("./utils");
const FEATURES_README_TEMPLATE = `
# #{Name}
#{Description}
## Example Usage
\`\`\`json
"features": {
"#{Nwo}/#{Id}@#{VersionTag}": {
"version": "latest"
}
}
\`\`\`
## Options
#{OptionsTable}
---
_Note: This file was auto-generated from the [devcontainer-feature.json](#{RepoUrl})._
`;
const TEMPLATE_README_TEMPLATE = `
# #{Name}
#{Description}
## Options
#{OptionsTable}
`;
function generateFeaturesDocumentation(basePath) {
return __awaiter(this, void 0, void 0, function* () {
fs.readdir(basePath, (err, files) => {
if (err) {
core.error(err.message);
core.setFailed(`failed to generate 'features' documentation ${err.message}`);
return;
}
files.forEach(f => {
core.info(`Generating docs for feature '${f}'`);
if (f !== '.' && f !== '..') {
yield _generateDocumentation(basePath, FEATURES_README_TEMPLATE, 'devcontainer-feature.json');
});
}
exports.generateFeaturesDocumentation = generateFeaturesDocumentation;
function generateTemplateDocumentation(basePath) {
return __awaiter(this, void 0, void 0, function* () {
yield _generateDocumentation(basePath, TEMPLATE_README_TEMPLATE, 'devcontainer-template.json');
});
}
exports.generateTemplateDocumentation = generateTemplateDocumentation;
function _generateDocumentation(basePath, readmeTemplate, metadataFile) {
return __awaiter(this, void 0, void 0, function* () {
const directories = fs.readdirSync(basePath);
yield Promise.all(directories.map((f) => __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c;
if (!f.startsWith('.')) {
const readmePath = path.join(basePath, f, 'README.md');
// Reads in feature.json
const featureJsonPath = path.join(basePath, f, 'devcontainer-feature.json');
if (!fs.existsSync(featureJsonPath)) {
core.error(`devcontainer-feature.json not found at path '${featureJsonPath}'`);
const jsonPath = path.join(basePath, f, metadataFile);
if (!fs.existsSync(jsonPath)) {
core.error(`${metadataFile} not found at path '${jsonPath}'`);
return;
}
let featureJson = undefined;
let parsedJson = undefined;
try {
featureJson = JSON.parse(fs.readFileSync(featureJsonPath, 'utf8'));
parsedJson = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
}
catch (err) {
core.error(`Failed to parse ${featureJsonPath}: ${err}`);
core.error(`Failed to parse ${jsonPath}: ${err}`);
return;
}
if (!featureJson || !(featureJson === null || featureJson === void 0 ? void 0 : featureJson.id)) {
core.error(`devcontainer-feature.json for feature '${f}' does not contain an 'id'`);
if (!parsedJson || !(parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.id)) {
core.error(`${metadataFile} for '${f}' does not contain an 'id'`);
return;
}
const ref = github.context.ref;
const owner = github.context.repo.owner;
const repo = github.context.repo.repo;
const srcInfo = (0, utils_1.getGitHubMetadata)();
const ref = srcInfo.ref;
const owner = srcInfo.owner;
const repo = srcInfo.repo;
// Add tag if parseable
let versionTag = 'latest';
if (ref.includes('refs/tags/')) {
if (ref && ref.includes('refs/tags/')) {
versionTag = ref.replace('refs/tags/', '');
}
const generateOptionsMarkdown = () => {
const options = featureJson === null || featureJson === void 0 ? void 0 : featureJson.options;
const options = parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.options;
if (!options) {
return '';
}
......@@ -87,18 +127,25 @@ function generateFeaturesDocumentation(basePath) {
return `| ${k} | ${val.description || '-'} | ${val.type || '-'} | ${val.default || '-'} |`;
})
.join('\n');
return ('| Options Id | Description | Type | Default Value |\n' +
'|-----|-----|-----|-----|\n' +
contents);
return '| Options Id | Description | Type | Default Value |\n' + '|-----|-----|-----|-----|\n' + contents;
};
const newReadme = README_TEMPLATE.replace('#{nwo}', `${owner}/${repo}`)
.replace('#{versionTag}', versionTag)
.replace('#{featureId}', featureJson.id)
.replace('#{featureName}', featureJson.name
? `${featureJson.name} (${featureJson.id})`
: `${featureJson.id}`)
.replace('#{featureDescription}', featureJson.description ? featureJson.description : '')
.replace('#{optionsTable}', generateOptionsMarkdown());
let urlToConfig = './devcontainer-feature.json';
const basePathTrimmed = basePath.startsWith('./') ? basePath.substring(2) : basePath;
if (srcInfo.owner && srcInfo.repo) {
urlToConfig = `https://github.com/${srcInfo.owner}/${srcInfo.repo}/blob/main/${basePathTrimmed}/${f}/devcontainer-feature.json`;
}
const newReadme = readmeTemplate
// Templates & Features
.replace('#{Id}', parsedJson.id)
.replace('#{Name}', parsedJson.name ? `${parsedJson.name} (${parsedJson.id})` : `${parsedJson.id}`)
.replace('#{Description}', (_a = parsedJson.description) !== null && _a !== void 0 ? _a : '')
.replace('#{OptionsTable}', generateOptionsMarkdown())
// Features Only
.replace('#{Nwo}', `${owner}/${repo}`)
.replace('#{VersionTag}', versionTag)
// Templates Only
.replace('#{ManifestName}', (_c = (_b = parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.image) === null || _b === void 0 ? void 0 : _b.manifest) !== null && _c !== void 0 ? _c : '')
.replace('#{RepoUrl}', urlToConfig);
// Remove previous readme
if (fs.existsSync(readmePath)) {
fs.unlinkSync(readmePath);
......@@ -106,31 +153,6 @@ function generateFeaturesDocumentation(basePath) {
// Write new readme
fs.writeFileSync(readmePath, newReadme);
}
})));
});
});
});
}
exports.generateFeaturesDocumentation = generateFeaturesDocumentation;
const README_TEMPLATE = `
# #{featureName}
#{featureDescription}
## Example Usage
\`\`\`json
"features": {
"#{nwo}/#{featureId}@#{versionTag}": {
"version": "latest"
}
}
\`\`\`
## Options
#{optionsTable}
---
_Note: This file was auto-generated from the [devcontainer-feature.json](./devcontainer-feature.json)._
`;
......@@ -44,42 +44,51 @@ function run() {
core.debug('Reading input parameters...');
// Read inputs
const shouldPublishFeatures = core.getInput('publish-features').toLowerCase() === 'true';
const shouldPublishTemplate = core.getInput('publish-templates').toLowerCase() === 'true';
const shouldPublishTemplates = core.getInput('publish-templates').toLowerCase() === 'true';
const shouldGenerateDocumentation = core.getInput('generate-docs').toLowerCase() === 'true';
// Experimental
const shouldTagIndividualFeatures = core.getInput('tag-individual-features').toLowerCase() === 'true';
const shouldPublishToNPM = core.getInput('publish-to-npm').toLowerCase() === 'true';
const shouldPublishReleaseArtifacts = core.getInput('publish-release-artifacts').toLowerCase() === 'true';
const shouldPublishToOCI = core.getInput('publish-to-oci').toLowerCase() === 'true';
const opts = {
shouldTagIndividualFeatures,
shouldPublishToNPM,
shouldPublishReleaseArtifacts,
shouldPublishToOCI
};
const featuresBasePath = core.getInput('base-path-to-features');
const templatesBasePath = core.getInput('base-path-to-templates');
let featuresMetadata = undefined;
let templatesMetadata = undefined;
// -- Package Release Artifacts
if (shouldPublishFeatures) {
core.info('Publishing features...');
const featuresBasePath = core.getInput('base-path-to-features');
featuresMetadata = yield packageFeatures(featuresBasePath);
featuresMetadata = yield packageFeatures(featuresBasePath, opts);
}
if (shouldPublishTemplate) {
if (shouldPublishTemplates) {
core.info('Publishing template...');
const basePathToDefinitions = core.getInput('base-path-to-templates');
templatesMetadata = undefined; // TODO
yield packageTemplates(basePathToDefinitions);
templatesMetadata = yield packageTemplates(templatesBasePath);
}
if (shouldGenerateDocumentation) {
core.info('Generating documentation...');
const featuresBasePath = core.getInput('base-path-to-features');
if (featuresBasePath) {
// -- Generate Documentation
if (shouldGenerateDocumentation && featuresBasePath) {
core.info('Generating documentation for features...');
yield (0, generateDocs_1.generateFeaturesDocumentation)(featuresBasePath);
}
else {
core.error("'base-path-to-features' input is required to generate documentation");
}
// TODO: base-path-to-templates
if (shouldGenerateDocumentation && templatesBasePath) {
core.info('Generating documentation for templates...');
yield (0, generateDocs_1.generateTemplateDocumentation)(templatesBasePath);
}
// TODO: Programatically add feature/template fino with relevant metadata for UX clients.
core.info('Generation metadata file: devcontainer-collection.json');
yield (0, utils_1.addCollectionsMetadataFile)(featuresMetadata, templatesMetadata);
// -- Programatically add feature/template metadata to collections file.
core.info('Generating metadata file: devcontainer-collection.json');
yield (0, utils_1.addCollectionsMetadataFile)(featuresMetadata, templatesMetadata, opts);
});
}
function packageFeatures(basePath) {
function packageFeatures(basePath, opts) {
return __awaiter(this, void 0, void 0, function* () {
try {
core.info(`Archiving all features in ${basePath}`);
const metadata = yield (0, utils_1.getFeaturesAndPackage)(basePath);
const metadata = yield (0, utils_1.getFeaturesAndPackage)(basePath, opts);
core.info('Packaging features has finished.');
return metadata;
}
......@@ -94,14 +103,17 @@ function packageFeatures(basePath) {
function packageTemplates(basePath) {
return __awaiter(this, void 0, void 0, function* () {
try {
core.info(`Archiving all templated in ${basePath}`);
yield (0, utils_1.getTemplatesAndPackage)(basePath);
core.info(`Archiving all templates in ${basePath}`);
const metadata = yield (0, utils_1.getTemplatesAndPackage)(basePath);
core.info('Packaging templates has finished.');
return metadata;
}
catch (error) {
if (error instanceof Error)
if (error instanceof Error) {
core.setFailed(error.message);
}
}
return;
});
}
run();
......@@ -35,11 +35,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTemplatesAndPackage = exports.getFeaturesAndPackage = exports.addCollectionsMetadataFile = exports.tarDirectory = exports.renameLocal = exports.mkdirLocal = exports.writeLocalFile = exports.readLocalFile = void 0;
exports.getTemplatesAndPackage = exports.getFeaturesAndPackage = exports.pushCollectionsMetadataToOCI = exports.addCollectionsMetadataFile = exports.getGitHubMetadata = exports.tarDirectory = exports.renameLocal = exports.mkdirLocal = exports.writeLocalFile = exports.readLocalFile = void 0;
const github = __importStar(require("@actions/github"));
const tar = __importStar(require("tar"));
const fs = __importStar(require("fs"));
const core = __importStar(require("@actions/core"));
const child_process = __importStar(require("child_process"));
const util_1 = require("util");
const path_1 = __importDefault(require("path"));
exports.readLocalFile = (0, util_1.promisify)(fs.readFile);
......@@ -62,13 +63,10 @@ function tarDirectory(path, tgzName) {
});
}
exports.tarDirectory = tarDirectory;
function addCollectionsMetadataFile(featuresMetadata, templatesMetadata) {
return __awaiter(this, void 0, void 0, function* () {
const p = path_1.default.join('.', 'devcontainer-collection.json');
function getGitHubMetadata() {
// Insert github repo metadata
const ref = github.context.ref;
let sourceInformation = {
source: 'github',
owner: github.context.repo.owner,
repo: github.context.repo.repo,
ref,
......@@ -79,6 +77,69 @@ function addCollectionsMetadataFile(featuresMetadata, templatesMetadata) {
const tag = ref.replace('refs/tags/', '');
sourceInformation = Object.assign(Object.assign({}, sourceInformation), { tag });
}
return sourceInformation;
}
exports.getGitHubMetadata = getGitHubMetadata;
function tagFeatureAtVersion(featureMetaData) {
return __awaiter(this, void 0, void 0, function* () {
const featureId = featureMetaData.id;
const featureVersion = featureMetaData.version;
const tagName = `${featureId}_v${featureVersion}`;
// Get GITHUB_TOKEN from environment
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
core.setFailed('GITHUB_TOKEN environment variable is not set.');
return;
}
// Setup Octokit client
const octokit = github.getOctokit(githubToken);
// Use octokit to get all tags for this repo
const tags = yield octokit.rest.repos.listTags({
owner: github.context.repo.owner,
repo: github.context.repo.repo
});
// See if tags for this release was already created.
const tagExists = tags.data.some(tag => tag.name === tagName);
if (tagExists) {
core.info(`Tag ${tagName} already exists. Skipping...`);
return;
}
// Create tag
const createdTag = yield octokit.rest.git.createTag({
tag: tagName,
message: `Feature ${featureId} version ${featureVersion}`,
object: github.context.sha,
type: 'commit',
owner: github.context.repo.owner,
repo: github.context.repo.repo
});
if (createdTag.status === 201) {
core.info(`Tagged '${tagName}'`);
}
else {
core.setFailed(`Failed to tag '${tagName}'`);
return;
}
// Create reference to tag
const createdRef = yield octokit.rest.git.createRef({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
ref: `refs/tags/${tagName}`,
sha: createdTag.data.sha
});
if (createdRef.status === 201) {
core.info(`Created reference for '${tagName}'`);
}
else {
core.setFailed(`Failed to reference of tag '${tagName}'`);
return;
}
});
}
function addCollectionsMetadataFile(featuresMetadata, templatesMetadata, opts) {
return __awaiter(this, void 0, void 0, function* () {
const p = path_1.default.join('.', 'devcontainer-collection.json');
const sourceInformation = getGitHubMetadata();
const metadata = {
sourceInformation,
features: featuresMetadata || [],
......@@ -86,27 +147,148 @@ function addCollectionsMetadataFile(featuresMetadata, templatesMetadata) {
};
// Write to the file
yield (0, exports.writeLocalFile)(p, JSON.stringify(metadata, undefined, 4));
if (opts.shouldPublishToOCI) {
pushCollectionsMetadataToOCI(p);
}
});
}
exports.addCollectionsMetadataFile = addCollectionsMetadataFile;
function getFeaturesAndPackage(basePath) {
function pushArtifactToOCI(version, featureName, artifactPath) {
return __awaiter(this, void 0, void 0, function* () {
const exec = (0, util_1.promisify)(child_process.exec);
const versions = [version, '1.0', '1', 'latest']; // TODO: Generate semantic versions from 'version'
const sourceInfo = getGitHubMetadata();
yield Promise.all(versions.map((v) => __awaiter(this, void 0, void 0, function* () {
const ociRepo = `${sourceInfo.owner}/${sourceInfo.repo}/${featureName}:${v}`;
try {
const cmd = `oras push ghcr.io/${ociRepo} \
--manifest-config /dev/null:application/vnd.devcontainers \
./${artifactPath}:application/vnd.devcontainers.layer.v1+tar`;
yield exec(cmd);
core.info(`Pushed artifact to '${ociRepo}'`);
}
catch (error) {
if (error instanceof Error)
core.setFailed(`Failed to push '${ociRepo}': ${error.message}`);
}
})));
});
}
function pushCollectionsMetadataToOCI(collectionJsonPath) {
return __awaiter(this, void 0, void 0, function* () {
const exec = (0, util_1.promisify)(child_process.exec);
const sourceInfo = getGitHubMetadata();
const ociRepo = `${sourceInfo.owner}/${sourceInfo.repo}:latest`;
try {
const cmd = `oras push ghcr.io/${ociRepo} \
--manifest-config /dev/null:application/vnd.devcontainers \
./${collectionJsonPath}:application/vnd.devcontainers.collection.layer.v1+json`;
yield exec(cmd);
core.info(`Pushed collection metadata to '${ociRepo}'`);
}
catch (error) {
if (error instanceof Error)
core.setFailed(`Failed to push collection metadata '${ociRepo}': ${error.message}`);
}
});
}
exports.pushCollectionsMetadataToOCI = pushCollectionsMetadataToOCI;
function loginToGHCR() {
return __awaiter(this, void 0, void 0, function* () {
const exec = (0, util_1.promisify)(child_process.exec);
// Get GITHUB_TOKEN from environment
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
core.setFailed('GITHUB_TOKEN environment variable is not set.');
return;
}
try {
yield exec(`oras login ghcr.io -u USERNAME -p ${githubToken}`);
core.info('Oras logged in successfully!');
}
catch (error) {
if (error instanceof Error)
core.setFailed(` Oras login failed!`);
}
});
}
function getFeaturesAndPackage(basePath, opts) {
return __awaiter(this, void 0, void 0, function* () {
const { shouldPublishToNPM, shouldTagIndividualFeatures, shouldPublishReleaseArtifacts, shouldPublishToOCI } = opts;
const featureDirs = fs.readdirSync(basePath);
let metadatas = [];
const exec = (0, util_1.promisify)(child_process.exec);
if (shouldPublishToOCI) {
yield loginToGHCR();
}
yield Promise.all(featureDirs.map((f) => __awaiter(this, void 0, void 0, function* () {
var _a;
core.info(`feature ==> ${f}`);
if (f !== '.' && f !== '..') {
if (!f.startsWith('.')) {
const featureFolder = path_1.default.join(basePath, f);
const archiveName = `${f}.tgz`;
yield tarDirectory(`${basePath}/${f}`, archiveName);
const featureJsonPath = path_1.default.join(featureFolder, 'devcontainer-feature.json');
if (!fs.existsSync(featureJsonPath)) {
core.error(`Feature ${f} is missing a devcontainer-feature.json`);
core.error(`Feature '${f}' is missing a devcontainer-feature.json`);
core.setFailed('All features must have a devcontainer-feature.json');
return;
}
const featureMetadata = JSON.parse(fs.readFileSync(featureJsonPath, 'utf8'));
if (!featureMetadata.id || !featureMetadata.version) {
core.error(`Feature '${f}' is must defined an id and version`);
core.setFailed('Incomplete devcontainer-feature.json');
}
metadatas.push(featureMetadata);
const sourceInfo = getGitHubMetadata();
if (!sourceInfo.owner) {
core.setFailed('Could not determine repository owner.');
return;
}
const archiveName = `${f}.tgz`;
// ---- PUBLISH RELEASE ARTIFACTS (classic method) ----
if (shouldPublishReleaseArtifacts || shouldPublishToOCI) {
core.info(`** Tar'ing feature`);
yield tarDirectory(featureFolder, archiveName);
}
// ---- PUBLISH TO NPM ----
if (shouldPublishToOCI) {
core.info(`** Publishing to OCI`);
// TODO: CHECK IF THE FEATURE IS ALREADY PUBLISHED UNDER GIVEN TAG
yield pushArtifactToOCI(featureMetadata.version, f, archiveName);
}
// ---- TAG INDIVIDUAL FEATURES ----
if (shouldTagIndividualFeatures) {
core.info(`** Tagging individual feature`);
yield tagFeatureAtVersion(featureMetadata);
}
// ---- PUBLISH TO NPM ----
if (shouldPublishToNPM) {
core.info(`** Publishing to NPM`);
// Adds a package.json file to the feature folder
const packageJsonPath = path_1.default.join(featureFolder, 'package.json');
// if (!sourceInfo.tag) {
// core.error(`Feature ${f} is missing a tag! Cannot publish to NPM.`);
// core.setFailed('All features published to NPM must be tagged with a version');
// }
const packageJsonObject = {
name: `@${sourceInfo.owner}/${f}`,
version: featureMetadata.version,
description: `${(_a = featureMetadata.description) !== null && _a !== void 0 ? _a : 'My cool feature'}`,
author: `${sourceInfo.owner}`,
keywords: ['devcontainer-features']
};
yield (0, exports.writeLocalFile)(packageJsonPath, JSON.stringify(packageJsonObject, undefined, 4));
core.info(`Feature Folder is: ${featureFolder}`);
// Run npm pack, which 'tars' the folder
const packageName = yield exec(`npm pack ./${featureFolder}`);
if (packageName.stderr) {
core.error(`${packageName.stderr.toString()}`);
}
const publishOutput = yield exec(`npm publish --access public "${packageName.stdout.trim()}"`);
core.info(publishOutput.stdout);
if (publishOutput.stderr) {
core.error(`${publishOutput.stderr}`);
}
}
}
})));
if (metadatas.length === 0) {
......@@ -119,23 +301,29 @@ function getFeaturesAndPackage(basePath) {
exports.getFeaturesAndPackage = getFeaturesAndPackage;
function getTemplatesAndPackage(basePath) {
return __awaiter(this, void 0, void 0, function* () {
let archives = [];
fs.readdir(basePath, (err, files) => {
if (err) {
core.error(err.message);
core.setFailed(`failed to get list of templates: ${err.message}`);
const templateDirs = fs.readdirSync(basePath);
let metadatas = [];
yield Promise.all(templateDirs.map((t) => __awaiter(this, void 0, void 0, function* () {
core.info(`template ==> ${t}`);
if (!t.startsWith('.')) {
const templateFolder = path_1.default.join(basePath, t);
const archiveName = `devcontainer-template-${t}.tgz`;
// await tarDirectory(templateFolder, archiveName);
const templateJsonPath = path_1.default.join(templateFolder, 'devcontainer-template.json');
if (!fs.existsSync(templateJsonPath)) {
core.error(`Template '${t}' is missing a devcontainer-template.json`);
core.setFailed('All templates must have a devcontainer-template.json');
return;
}
files.forEach(file => {
core.info(`template ==> ${file}`);
if (file !== '.' && file !== '..') {
const archiveName = `devcontainer-definition-${file}.tgz`;
tarDirectory(`${basePath}/${file}`, archiveName);
archives.push(archiveName);
const templateMetadata = JSON.parse(fs.readFileSync(templateJsonPath, 'utf8'));
metadatas.push(templateMetadata);
}
});
});
return archives;
})));
if (metadatas.length === 0) {
core.setFailed('No templates found');
return;
}
return metadatas;
});
}
exports.getTemplatesAndPackage = getTemplatesAndPackage;
name: "(Release) Release dev container features (v2)"
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
deploy:
if: ${{ github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Generate tgz
- name: Install Oras
run: |
curl -LO https://github.com/oras-project/oras/releases/download/v0.13.0/oras_0.13.0_linux_amd64.tar.gz
mkdir -p oras-install/
tar -zxf oras_0.13.0_*.tar.gz -C oras-install/
mv oras-install/oras /usr/local/bin/
rm -rf oras_0.13.0_*.tar.gz oras-install/
- name: "Publish features to OCI"
uses: ./.github/devcontainers-action # TODO: Once 'devcontainers/action' is published, use that.
with:
publish-features: "true"
publish-to-oci: "true"
base-path-to-features: "./src"
- name: Remove temporary devcontainer-cli # TODO: Temporary
run: rm -rf ./devcontainer-cli-0*
- name: Get or Create Release at current tag
uses: ncipollo/release-action@v1
with:
allowUpdates: true # Lets us upload our own artifact from previous step
artifactErrorsFailBuild: true
artifacts: "./*.tgz,devcontainer-collection.json"
token: ${{ secrets.GITHUB_TOKEN }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
{
"id": "anaconda",
"version": "1.0.0",
"name": "Anaconda",
"options": {
"version": {
......@@ -14,9 +15,5 @@
"containerEnv": {
"CONDA_DIR": "/usr/local/conda",
"PATH": "${PATH}:${CONDA_DIR}/bin:"
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "aws-cli",
"version": "1.0.0",
"name": "AWS CLI",
"description": "Installs the AWS CLI along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.",
"options": {
......@@ -14,9 +15,5 @@
},
"extensions": [
"AmazonWebServices.aws-toolkit-vscode"
],
"install": {
"app": "",
"file": "install.sh"
}
]
}
{
"id": "azure-cli",
"version": "1.0.0",
"name": "Azure CLI",
"description": "Installs the Azure CLI along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.",
"options": {
......@@ -14,9 +15,5 @@
},
"extensions": [
"ms-vscode.azurecli"
],
"install": {
"app": "",
"file": "install.sh"
}
]
}
{
"id": "common-utils",
"name": "Common Debian Utilities",
"version": "1.0.0",
"description": "Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.",
"options": {
"install_Zsh": {
......@@ -55,9 +56,5 @@
},
"extensions": [
"ms-dotnettools.csharp"
],
"install": {
"app": "",
"file": "install.sh"
}
]
}
{
"id": "desktop-lite",
"version": "1.0.0",
"name": "Light-weight Desktop",
"description": "Adds a lightweight Fluxbox based desktop to the container that can be accessed using a VNC viewer or the web. GUI-based commands executed from the built-in VS code terminal will open on the desktop automatically.",
"options": {
......@@ -50,9 +51,5 @@
"entrypoint": "/usr/local/share/desktop-init.sh",
"containerEnv": {
"DISPLAY": ":1"
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "docker-from-docker",
"version": "1.0.0",
"name": "Docker (Docker-from-Docker)",
"descripton": "Re-use the host docker socket, adding the Docker CLI to a container. Feature invokes a script to enable using a forwarded Docker socket within a container to run Docker commands.",
"options": {
......@@ -41,9 +42,5 @@
"target": "/var/run/docker-host.sock",
"type": "bind"
}
],
"install": {
"app": "",
"file": "install.sh"
}
]
}
{
"id": "docker-in-docker",
"version": "1.0.0",
"name": "Docker (Docker-in-Docker)",
"description": "Create child containers *inside* a container, independent from the host's docker instance. Installs Docker extension in the container along with needed CLIs.",
"options": {
......@@ -42,9 +43,5 @@
"target": "/var/lib/docker",
"type": "volume"
}
],
"install": {
"app": "",
"file": "install.sh"
}
]
}
{
"id": "dotnet",
"version": "1.0.0",
"name": "Dotnet CLI",
"description": "Installs the .NET CLI. Provides option of installing sdk or runtime, and option of versions to install. Uses latest version of .NET sdk as defaults to install.",
"options": {
......
{
"id": "git-lfs",
"version": "1.0.0",
"name": "Git Large File Support (LFS)",
"description": "Installs Git Large File Support (Git LFS) along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like git and curl.",
"options": {
......@@ -12,9 +13,5 @@
"default": "latest",
"description": "Select version of Git LFS to install"
}
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "git",
"version": "1.0.0",
"name": "Git (from source)",
"description": "Install an up-to-date version of Git, built from source as needed. Useful for when you want the latest and greatest features. Auto-detects latest stable version and installs needed dependencies.",
"options": {
......@@ -17,9 +18,5 @@
"default": true,
"description": "Install from PPA if available"
}
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "github-cli",
"version": "1.0.0",
"name": "GitHub CLI",
"description": "Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies.",
"options": {
......@@ -12,9 +13,5 @@
"default": "latest",
"description": "Select version of the GitHub CLI, if not latest."
}
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "go",
"version": "1.0.0",
"name": "Go",
"description": "Installs Go and common Go utilities. Auto-detects latest version and installs needed dependencies.",
"options": {
......@@ -28,9 +29,5 @@
],
"securityOpt": [
"seccomp=unconfined"
],
"install": {
"app": "",
"file": "install.sh"
}
]
}
{
"id": "hugo",
"version": "1.0.0",
"name": "Hugo",
"options": {
"version": {
......@@ -14,9 +15,5 @@
"containerEnv": {
"HUGO_DIR": "/usr/local/hugo",
"PATH": "${HUGO_DIR}/bin:${PATH}"
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "java",
"version": "1.0.0",
"name": "Java (via SDKMAN!)",
"description": "Installs Java, SDKMAN! (if not installed), and needed dependencies.",
"options": {
......
{
"id": "kubectl-helm-minikube",
"version": "1.0.0",
"name": "Kubectl, Helm, and Minkube",
"description": "Installs latest version of kubectl, Helm, and optionally minikube. Auto-detects latest versions and installs needed dependencies.",
"options": {
......@@ -41,9 +42,5 @@
"target": "/home/vscode/.minikube",
"type": "volume"
}
],
"install": {
"app": "",
"file": "install.sh"
}
]
}
{
"id": "node",
"version": "1.0.0",
"name": "Node.js (via nvm) and yarn",
"description": "Installs Node.js, nvm, yarn, and needed dependencies.",
"options": {
......
{
"id": "oryx",
"version": "1.0.0",
"name": "Oryx",
"description": "Installs the oryx CLI",
"containerEnv": {
......
{
"id": "php",
"version": "1.0.0",
"name": "PHP",
"options": {
"version": {
......
{
"id": "powershell",
"version": "1.0.0",
"name": "PowerShell",
"description": "Installs PowerShell along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like gpg.",
"options": {
......@@ -13,9 +14,5 @@
"default": "latest",
"description": "Select or enter a version of PowerShell."
}
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "python",
"version": "1.0.0",
"name": "Python",
"description": "Installs the provided version of Python, as well as PIPX, and other common Python utilities. JupyterLab is conditionally installed with the python feature. Note: May require source code compilation.",
"options": {
......
{
"id": "ruby",
"version": "1.0.0",
"name": "Ruby (via rvm)",
"description": "Installs Ruby, rvm, rbenv, common Ruby utilities, and needed dependencies.",
"options": {
......
{
"id": "rust",
"version": "1.0.0",
"name": "Rust",
"description": "Installs Rust, common Rust utilities, and their required dependencies",
"options": {
......@@ -50,9 +51,5 @@
"**/target/**": true
},
"rust-analyzer.checkOnSave.command": "clippy"
},
"install": {
"app": "",
"file": "install.sh"
}
}
{
"id": "sshd",
"version": "1.0.0",
"name": "SSH server",
"description": "Adds a SSH server into a container so that you can use an external terminal, sftp, or SSHFS to interact with it.",
"options": {
......@@ -12,9 +13,5 @@
"description": "Currently unused."
}
},
"entrypoint": "/usr/local/share/ssh-init.sh",
"install": {
"app": "",
"file": "install.sh"
}
"entrypoint": "/usr/local/share/ssh-init.sh"
}
{
"id": "terraform",
"version": "1.0.0",
"name": "Terraform, tflint, and TFGrunt",
"description": "Installs the Terraform CLI and optionally TFLint and Terragrunt. Auto-detects latest version and installs needed dependencies.",
"options": {
......@@ -42,9 +43,5 @@
"args": []
},
"azureTerraform.terminal": "integrated"
},
"install": {
"app": "",
"file": "install.sh"
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment