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,95 +32,21 @@ 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"));
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 !== '..') {
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}'`);
return;
}
let featureJson = undefined;
try {
featureJson = JSON.parse(fs.readFileSync(featureJsonPath, 'utf8'));
}
catch (err) {
core.error(`Failed to parse ${featureJsonPath}: ${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'`);
return;
}
const ref = github.context.ref;
const owner = github.context.repo.owner;
const repo = github.context.repo.repo;
// Add tag if parseable
let versionTag = 'latest';
if (ref.includes('refs/tags/')) {
versionTag = ref.replace('refs/tags/', '');
}
const generateOptionsMarkdown = () => {
const options = featureJson === null || featureJson === void 0 ? void 0 : featureJson.options;
if (!options) {
return '';
}
const keys = Object.keys(options);
const contents = keys
.map(k => {
const val = options[k];
return `| ${k} | ${val.description || '-'} | ${val.type || '-'} | ${val.default || '-'} |`;
})
.join('\n');
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());
// Remove previous readme
if (fs.existsSync(readmePath)) {
fs.unlinkSync(readmePath);
}
// Write new readme
fs.writeFileSync(readmePath, newReadme);
}
});
});
});
}
exports.generateFeaturesDocumentation = generateFeaturesDocumentation;
const README_TEMPLATE = `
# #{featureName}
const utils_1 = require("./utils");
const FEATURES_README_TEMPLATE = `
# #{Name}
#{featureDescription}
#{Description}
## Example Usage
\`\`\`json
"features": {
"#{nwo}/#{featureId}@#{versionTag}": {
"#{Nwo}/#{Id}@#{VersionTag}": {
"version": "latest"
}
}
......@@ -128,9 +54,105 @@ const README_TEMPLATE = `
## Options
#{optionsTable}
#{OptionsTable}
---
_Note: This file was auto-generated from the [devcontainer-feature.json](./devcontainer-feature.json)._
_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* () {
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 jsonPath = path.join(basePath, f, metadataFile);
if (!fs.existsSync(jsonPath)) {
core.error(`${metadataFile} not found at path '${jsonPath}'`);
return;
}
let parsedJson = undefined;
try {
parsedJson = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
}
catch (err) {
core.error(`Failed to parse ${jsonPath}: ${err}`);
return;
}
if (!parsedJson || !(parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.id)) {
core.error(`${metadataFile} for '${f}' does not contain an 'id'`);
return;
}
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 && ref.includes('refs/tags/')) {
versionTag = ref.replace('refs/tags/', '');
}
const generateOptionsMarkdown = () => {
const options = parsedJson === null || parsedJson === void 0 ? void 0 : parsedJson.options;
if (!options) {
return '';
}
const keys = Object.keys(options);
const contents = keys
.map(k => {
const val = options[k];
return `| ${k} | ${val.description || '-'} | ${val.type || '-'} | ${val.default || '-'} |`;
})
.join('\n');
return '| Options Id | Description | Type | Default Value |\n' + '|-----|-----|-----|-----|\n' + contents;
};
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);
}
// Write new readme
fs.writeFileSync(readmePath, newReadme);
}
})));
});
}
......@@ -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) {
yield (0, generateDocs_1.generateFeaturesDocumentation)(featuresBasePath);
}
else {
core.error("'base-path-to-features' input is required to generate documentation");
}
// TODO: base-path-to-templates
// -- Generate Documentation
if (shouldGenerateDocumentation && featuresBasePath) {
core.info('Generating documentation for features...');
yield (0, generateDocs_1.generateFeaturesDocumentation)(featuresBasePath);
}
// 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);
if (shouldGenerateDocumentation && templatesBasePath) {
core.info('Generating documentation for templates...');
yield (0, generateDocs_1.generateTemplateDocumentation)(templatesBasePath);
}
// -- 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,23 +63,83 @@ function tarDirectory(path, tgzName) {
});
}
exports.tarDirectory = tarDirectory;
function addCollectionsMetadataFile(featuresMetadata, templatesMetadata) {
function getGitHubMetadata() {
// Insert github repo metadata
const ref = github.context.ref;
let sourceInformation = {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
ref,
sha: github.context.sha
};
// Add tag if parseable
if (ref.includes('refs/tags/')) {
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 p = path_1.default.join('.', 'devcontainer-collection.json');
// Insert github repo metadata
const ref = github.context.ref;
let sourceInformation = {
source: 'github',
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,
sha: github.context.sha
};
// Add tag if parseable
if (ref.includes('refs/tags/')) {
const tag = ref.replace('refs/tags/', '');
sourceInformation = Object.assign(Object.assign({}, sourceInformation), { tag });
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}`);
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 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;
}
});
});
return archives;
const templateMetadata = JSON.parse(fs.readFileSync(templateJsonPath, 'utf8'));
metadatas.push(templateMetadata);
}
})));
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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
]
}
{
"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"
}
}
\ No newline at end of file
]
}
{
"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"
}
}
\ No newline at end of file
]
}
{
"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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
]
}
{
"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"
}
}
\ No newline at end of file
]
}
{
"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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
]
}
{
"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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
]
}
{
"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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
}
{
"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"
}
}
\ No newline at end of file
"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"
}
}
\ No newline at end of file
}
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