From 5789f54deaece468ce52d103a353a0e6f49da595 Mon Sep 17 00:00:00 2001 From: Reinhard Naegele Date: Fri, 29 Nov 2019 14:29:37 +0100 Subject: [PATCH] Remove TypeScript This removes TypeScript and all dependencies and only keeps a simple Javascript file that does not have any dependencies and can be run directly. There is not need for an NPM build any more and we just call a shell script via Javascript. With this, we no longer have to commit node_modules and Javascript compiled from TypeScript code. Also, the shell script can easily be run directly for local testing. Signed-off-by: Reinhard Naegele --- action.yml | 18 ++-- cr.sh | 232 ++++++++++++++++++++++++++++++++++++++++++++++ main.js | 30 ++++++ main.sh | 37 ++++++++ package-lock.json | 77 --------------- package.json | 22 ----- src/cr.ts | 87 ----------------- src/main.ts | 90 ------------------ src/tools.ts | 61 ------------ tsconfig.json | 24 ----- 10 files changed, 309 insertions(+), 369 deletions(-) create mode 100755 cr.sh create mode 100644 main.js create mode 100755 main.sh delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 src/cr.ts delete mode 100644 src/main.ts delete mode 100644 src/tools.ts delete mode 100644 tsconfig.json diff --git a/action.yml b/action.yml index 5d3e5dd..8823fc5 100644 --- a/action.yml +++ b/action.yml @@ -1,14 +1,16 @@ -name: "chart-releaser action" -description: "Run the chart-releaser tool" -author: "unguiculus" +name: "GitHub Action for Helm Chart Releasing" +description: "Release your Helm chart to a charts repo on GitHub Pages" +author: "The Helm authors" +branding: + color: blue + icon: check-circle inputs: - charts-dir: + charts_dir: description: The charts directory default: charts - charts-repo-url: + charts_repo_url: description: "The GitHub Pages URL to the charts repo (default: https://.github.io/)" - token: - description: The GitHub token + required: true runs: using: "node12" - main: "lib/main.js" + main: "main.js" diff --git a/cr.sh b/cr.sh new file mode 100755 index 0000000..a5d0190 --- /dev/null +++ b/cr.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +# Copyright The Helm Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +DEFAULT_CHART_RELEASER_VERSION=v0.2.3 + +: "${CR_TOKEN:?Environment variable CR_TOKEN must be set}" + +show_help() { +cat << EOF +Usage: $(basename "$0") + + -h, --help Display help + -v, --version The kind version to use (default: v0.2.3)" + -d, --charts-dir The charts directory (defaut: charts) + -u, --charts-repo-url The GitHub Pages URL to the charts repo (default: https://.github.io/) + -o, --owner The repo owner + -r, --repo The repo name +EOF +} + +main() { + local version="$DEFAULT_CHART_RELEASER_VERSION" + local charts_dir=charts + local owner= + local repo= + local charts_repo_url= + + parse_command_line "$@" + + echo "$repo" + local repo_root + repo_root=$(git rev-parse --show-toplevel) + pushd "$repo_root" > /dev/null + + echo 'Looking up latest tag...' + local latest_tag + latest_tag=$(lookup_latest_tag) + + echo "Discovering changed charts since '$latest_tag'..." + local changed_charts=() + readarray -t changed_charts <<< "$(lookup_changed_charts "$latest_tag")" + + if [[ -n "${changed_charts[*]}" ]]; then + install_chart_releaser + + rm -rf .cr-release-packages + mkdir -p .cr-release-packages + + rm -rf .cr-index + mkdir -p .cr-index + + for chart in "${changed_charts[@]}"; do + package_chart "$chart" + done + + release_charts + update_index + else + echo "Nothing to do. No chart changes detected." + fi + + popd > /dev/null +} + +parse_command_line() { + while :; do + case "${1:-}" in + -h|--help) + show_help + exit + ;; + -v|--version) + if [[ -n "${2:-}" ]]; then + version="$2" + shift + else + echo "ERROR: '-v|--version' cannot be empty." >&2 + show_help + exit 1 + fi + ;; + -d|--charts-dir) + if [[ -n "${2:-}" ]]; then + charts_dir="$2" + shift + else + echo "ERROR: '-d|--charts-dir' cannot be empty." >&2 + show_help + exit 1 + fi + ;; + -u|--charts-repo-url) + if [[ -n "${2:-}" ]]; then + charts_repo_url="$2" + shift + else + echo "ERROR: '-u|--charts-repo-url' cannot be empty." >&2 + show_help + exit 1 + fi + ;; + -o|--owner) + if [[ -n "${2:-}" ]]; then + owner="$2" + shift + else + echo "ERROR: '--owner' cannot be empty." >&2 + show_help + exit 1 + fi + ;; + -r|--repo) + if [[ -n "${2:-}" ]]; then + repo="$2" + shift + else + echo "ERROR: '--repo' cannot be empty." >&2 + show_help + exit 1 + fi + ;; + *) + break + ;; + esac + + shift + done + + if [[ -z "$owner" ]]; then + echo "ERROR: '-o|--owner' is required." >&2 + show_help + exit 1 + fi + + if [[ -z "$repo" ]]; then + echo "ERROR: '-r|--repo' is required." >&2 + show_help + exit 1 + fi + + if [[ -z "$charts_repo_url" ]]; then + charts_repo_url="https://$owner.github.io/$repo" + fi +} + +install_chart_releaser() { + echo "Installing chart-releaser..." + + curl -sSLo cr.tar.gz "https://github.com/helm/chart-releaser/releases/download/$version/chart-releaser_${version#v}_linux_amd64.tar.gz" + tar -xzf cr.tar.gz + sudo mv cr /usr/local/bin/cr +} + +lookup_latest_tag() { + git fetch --tags > /dev/null 2>&1 + + if ! git describe --tags --abbrev=0 2> /dev/null; then + git rev-list --max-parents=0 --first-parent HEAD + fi +} + +lookup_changed_charts() { + local commit="$1" + + local changed_files + changed_files=$(git diff --find-renames --name-only "$commit" -- "$charts_dir") + + local fields + if [[ "$charts_dir" == '.' ]]; then + fields='1' + else + fields='1,2' + fi + + cut -d '/' -f "$fields" <<< "$changed_files" | uniq +} + +package_chart() { + local chart="$1" + + echo "Packaging chart '$chart'..." + helm package "$chart" --destination .cr-release-packages --dependency-update --save=false +} + +release_charts() { + echo 'Releasing charts...' + cr upload -o "$owner" -r "$repo" +} + +update_index() { + echo 'Updating charts repo index...' + + set -x + + cr index -o "$owner" -r "$repo" -c "$charts_repo_url" + + gh_pages_worktree=$(mktemp -d) + + git worktree add "$gh_pages_worktree" gh-pages + + cp --force .cr-index/index.yaml "$gh_pages_worktree/index.yaml" + + pushd "$gh_pages_worktree" > /dev/null + + git add index.yaml + git commit --message="Update index.yaml" --signoff + + local repo_url="https://x-access-token:$CR_TOKEN@github.com/$owner/$repo" + git push "$repo_url" gh-pages + + popd > /dev/null +} + +main "$@" diff --git a/main.js b/main.js new file mode 100644 index 0000000..ee0628a --- /dev/null +++ b/main.js @@ -0,0 +1,30 @@ +// Copyright The Helm Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// # You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// # See the License for the specific language governing permissions and +// limitations under the License. + +const spawn = require('child_process').spawn +const path = require("path"); + +const main = async () => { + await new Promise((resolve, reject) => { + const proc = spawn('bash', [path.join(__dirname, 'main.sh')], {stdio: 'inherit'}) + proc.on('close', resolve) + proc.on('error', reject) + }) +} + +main().catch(err => { + console.error(err) + console.error(err.stack) + process.exit(-1) +}) diff --git a/main.sh b/main.sh new file mode 100755 index 0000000..64e03ef --- /dev/null +++ b/main.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Copyright The Helm Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR=$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}" || realpath "${BASH_SOURCE[0]}")") + +main() { + owner=$(cut -d '/' -f 1 <<< "$GITHUB_REPOSITORY") + repo=$(cut -d '/' -f 2 <<< "$GITHUB_REPOSITORY") + + args=(--owner "$owner" --repo "$repo") + args+=(--charts-dir "${INPUT_CHARTS_DIR?Input 'charts_dir' is required}") + + if [[ -n "${INPUT_CHARTS_REPO_URL:-}" ]]; then + args+=(--charts-repo-url "${INPUT_CHARTS_REPO_URL}") + fi + + "$SCRIPT_DIR/cr.sh" "${args[@]}" +} + +main diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 4141a07..0000000 --- a/package-lock.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "chart-testing", - "version": "0.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@actions/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.0.tgz", - "integrity": "sha512-ZKdyhlSlyz38S6YFfPnyNgCDZuAF2T0Qv5eHflNWytPS8Qjvz39bZFMry9Bb/dpSnqWcNeav5yM2CTYpJeY+Dw==" - }, - "@actions/exec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.1.tgz", - "integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ==" - }, - "@actions/io": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.1.tgz", - "integrity": "sha512-rhq+tfZukbtaus7xyUtwKfuiCRXd1hWSfmJNEpFgBQJ4woqPEpsBw04awicjwz9tyG2/MVhAEMfVn664Cri5zA==" - }, - "@actions/tool-cache": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.1.2.tgz", - "integrity": "sha512-IJczPaZr02ECa3Lgws/TJEVco9tjOujiQSZbO3dHuXXjhd5vrUtfOgGwhmz3/f97L910OraPZ8SknofUk6RvOQ==", - "requires": { - "@actions/core": "^1.1.0", - "@actions/exec": "^1.0.1", - "@actions/io": "^1.0.1", - "semver": "^6.1.0", - "typed-rest-client": "^1.4.0", - "uuid": "^3.3.2" - } - }, - "@types/node": { - "version": "12.12.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.8.tgz", - "integrity": "sha512-XLla8N+iyfjvsa0KKV+BP/iGSoTmwxsu5Ci5sM33z9TjohF72DEz95iNvD6pPmemvbQgxAv/909G73gUn8QR7w==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "tunnel": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", - "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=" - }, - "typed-rest-client": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.5.0.tgz", - "integrity": "sha512-DVZRlmsfnTjp6ZJaatcdyvvwYwbWvR4YDNFDqb+qdTxpvaVP99YCpBkA8rxsLtAPjBVoDe4fNsnMIdZTiPuKWg==", - "requires": { - "tunnel": "0.0.4", - "underscore": "1.8.3" - } - }, - "typescript": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", - "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", - "dev": true - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 623d536..0000000 --- a/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "chart-testing", - "version": "0.0.0", - "description": "kind GitHub action", - "main": "lib/main.js", - "scripts": { - "build": "tsc", - "test": "jest" - }, - "author": "The Helm Authors", - "license": "Apache", - "dependencies": { - "@actions/core": "^1.2.0", - "@actions/tool-cache": "^1.1.2", - "@actions/exec": "^1.0.1", - "@actions/io": "^1.0.1" - }, - "devDependencies": { - "@types/node": "^12.12.8", - "typescript": "^3.7.2" - } -} diff --git a/src/cr.ts b/src/cr.ts deleted file mode 100644 index 0e37659..0000000 --- a/src/cr.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright The Helm Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as exec from '@actions/exec'; -import * as io from '@actions/io'; -import {cr, git, helm} from "./tools"; - -export class ChartReleaser { - constructor(private readonly owner: string, private readonly repository: string, - private readonly chartRepoUrl: string, private readonly token: string) { - if (owner === "") { - throw new Error("owner is required") - } - if (repository === "") { - throw new Error("repository is required") - } - if (chartRepoUrl == "") { - this.chartRepoUrl = `https://${this.owner}.github.io/${this.repository}` - } - if (token === "") { - throw new Error("token is required") - } - } - - async execute(charts: Set) { - if (charts.size === 0) { - console.log("Charts set is emtpy. Nothing to do.") - return - } - - await io.rmRF(".cr-release-packages"); - await io.mkdirP(".cr-release-packages"); - - await io.rmRF(".cr-index"); - await io.mkdirP(".cr-index"); - - for (const chart of charts) { - await this.packageChart(chart) - } - - await this.releaseCharts(); - await this.updateIndex(); - } - - private async packageChart(chart: string) { - console.log(`Packacking chart '${chart}'...`); - - await helm("package", chart, "--destination", ".cr-release-packages", "--save=false", "--dependency-update"); - } - - private async releaseCharts() { - console.log("Releasing charts..."); - - await cr(this.token, "upload", "-o", this.owner, "-r", this.repository); - } - - private async updateIndex() { - console.log("Updating repo index..."); - - await cr(this.token, "index", "-o", this.owner, "-r", this.repository, "-c", this.chartRepoUrl); - - await git("checkout", "gh-pages"); - - await exec.exec("cp", ["--force", ".cr-index/index.yaml", "index.yaml"]); - - const actor = process.env["GITHUB_ACTOR"] || ""; - await git("config", "--local", "user.name", actor); - await git("config", "--local", "user.email", "noreply@github.com"); - - await git("add", "index.yaml"); - await git("commit", "--message='Update index.yaml'", "--signoff"); - - const repoUrl = `https://x-access-token:${this.token}@github.com/${this.owner}/${this.repository}`; - await git("push", repoUrl, "gh-pages"); - } -} diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 3446301..0000000 --- a/src/main.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright The Helm Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as core from '@actions/core'; -import {ChartReleaser} from "./cr"; -import {git} from "./tools"; - -const ChartsDirInput = "charts-dir"; -const ChartsRepoUrlInput = "charts-repo-url"; -const TokenInput = "token"; - -export function createChartReleaser(): ChartReleaser { - const ownerRepo = process.env["GITHUB_REPOSITORY"] || ""; - const split = ownerRepo.split("/"); - const owner = split[0]; - const repo = split[1]; - - const chartsRepoUrl = core.getInput(ChartsRepoUrlInput); - const token = core.getInput(TokenInput); - return new ChartReleaser(owner, repo, chartsRepoUrl, token) -} - -async function run() { - try { - const workspace = process.env["GITHUB_WORKSPACE"]; - if (workspace !== process.cwd()) { - core.setFailed("action must be run in the workspace root"); - return - } - - let chartsDir = core.getInput(ChartsDirInput); - if (chartsDir === "") { - chartsDir = "charts" - } - - console.log("Looking up latest tag..."); - const tag = await findLatestTag(); - - console.log(`Identifying changed charts since ${tag}...`) - const charts = await findChangedCharts(chartsDir, tag); - - if (charts.size == 0) { - console.log("Nothing to do. No chart changes detected.") - return - } - - const cr = createChartReleaser(); - await cr.execute(charts) - } catch (error) { - core.setFailed(error.message); - } -} - -async function findLatestTag(): Promise { - let tag: string; - try { - tag = await git("describe", "--tags", "--abbrev=0"); - } catch (ex) { - console.log("No tag found in repo. Getting first commit instead..."); - tag = await git("rev-list", "--max-parents=0", "--first-parent", "HEAD"); - } - return tag.trim() -} - -export async function findChangedCharts(chartsDir: string, ref: string): Promise> { - let changes = await git("diff", "--find-renames", "--name-only", ref, "--", chartsDir); - const set = new Set(); - - changes = changes.trim(); - if (changes) { - for (let change of changes.split("\n")) { - const split = change.split("/"); - set.add(split[0] + "/" + split[1]) - } - } - return set -} - -run(); diff --git a/src/tools.ts b/src/tools.ts deleted file mode 100644 index 0bc2ffd..0000000 --- a/src/tools.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as exec from '@actions/exec'; -import * as io from '@actions/io'; -import fs from 'fs'; - -export async function git(...args: string[]): Promise { - let output = ''; - - const options = {}; - // @ts-ignore - options.listeners = { - stdout: (data: Buffer) => { - output += data.toString(); - } - }; - - await exec.exec("git", args, options); - return output -} - -export async function helm(...args: string[]) { - const home = process.env["HOME"]; - const workspace = process.env["GITHUB_WORKSPACE"]; - - const dockerArgs = [ - "run", - "--interactive", - "--rm", - "--volume", `${home}/.helm:/root/.helm`, - "--volume", `${workspace}:/workdir`, - "--workdir", "/workdir", - "lachlanevenson/k8s-helm:v2.16.1" - ]; - - if (!fs.existsSync(`${home}/.helm`)) { - await io.mkdirP(`${home}/.helm`); - await exec.exec("docker", dockerArgs.concat("init", "--client-only")) - } - await exec.exec("docker", dockerArgs.concat(args)); -} - -export async function cr(token: string, ...args: string[]) { - const workspace = process.env["GITHUB_WORKSPACE"]; - - const options = {}; - // @ts-ignore - options.env = { - CR_TOKEN: token - }; - - const dockerArgs = [ - "run", - "--interactive", - "--rm", - "--env", "CR_TOKEN", - "--volume", `${workspace}:/workdir`, - "--workdir", "/workdir", - "quay.io/helmpack/chart-releaser:v0.2.3" - ]; - - await exec.exec("docker", dockerArgs.concat("cr", args), options); -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 746b873..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "outDir": "./lib", - "rootDir": "./src", - "strict": true, - "noImplicitAny": false, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "esModuleInterop": true - }, - "exclude": [ - "node_modules" - ] -}