From c5252f1b64e7f35a33f7ca5f3811e5435178835a Mon Sep 17 00:00:00 2001 From: Stephanie Closson Date: Thu, 2 Apr 2020 13:57:33 -0600 Subject: [PATCH] Toolkit: simplify the plugin ci docker image (#23267) * removing src dir on publish * Moved from binary to native typescript 1. Moved to a native typescrpt github publish using the existing github client. 2. Change dist.js to detect running in a linked environment. Todo: Optimize docker image for build size. * Optimized build of docker container Much smaller. From 5.47 gb to 2.88 * Feedback from discussion with Ryan - Added gget for getting grafana versions - Added infrastructure for testing - Uploaded new docker image * Fixed typo... Not sure what happened there :) * Added command to download canary * small fix for displaying versions in help * Removed --dev option Should really just rename version to (ex: 1.2.0-dev) * removing src dir on publish * Moved from binary to native typescript 1. Moved to a native typescrpt github publish using the existing github client. 2. Change dist.js to detect running in a linked environment. Todo: Optimize docker image for build size. * Optimized build of docker container Much smaller. From 5.47 gb to 2.88 * Feedback from discussion with Ryan - Added gget for getting grafana versions - Added infrastructure for testing - Uploaded new docker image * Fixed typo... Not sure what happened there :) * Added command to download canary * small fix for displaying versions in help * Removed --dev option Should really just rename version to (ex: 1.2.0-dev) --- packages/grafana-data/src/types/plugin.ts | 1 + .../bin/grafana-toolkit.dist.js | 21 ++- .../docker/grafana-plugin-ci/Dockerfile | 53 +------- .../docker/grafana-plugin-ci/README.md | 14 +- .../docker/grafana-plugin-ci/build.sh | 8 ++ .../docker/grafana-plugin-ci/common.sh | 7 + .../docker/grafana-plugin-ci/install/gget | 63 +++++++++ .../scripts/deploy-common.sh | 38 ++++++ .../grafana-plugin-ci/scripts/deploy-user.sh | 3 + .../grafana-plugin-ci/scripts/deploy.sh | 43 ++++++ .../grafana-plugin-ci/test/docker-compose.yml | 8 ++ .../docker/grafana-plugin-ci/test/start.sh | 4 + packages/grafana-toolkit/src/cli/index.ts | 4 +- .../src/cli/tasks/plugin.utils.ts | 36 ++--- .../src/cli/utils/githubClient.ts | 7 +- .../src/cli/utils/githubRelease.ts | 127 +++++++++--------- 16 files changed, 298 insertions(+), 139 deletions(-) create mode 100755 packages/grafana-toolkit/docker/grafana-plugin-ci/build.sh create mode 100755 packages/grafana-toolkit/docker/grafana-plugin-ci/common.sh create mode 100755 packages/grafana-toolkit/docker/grafana-plugin-ci/install/gget create mode 100755 packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-common.sh create mode 100755 packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-user.sh create mode 100755 packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy.sh create mode 100644 packages/grafana-toolkit/docker/grafana-plugin-ci/test/docker-compose.yml create mode 100755 packages/grafana-toolkit/docker/grafana-plugin-ci/test/start.sh diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index 2f33b290851..98c4b03c1e3 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -34,6 +34,7 @@ export interface PluginMeta { enabled?: boolean; defaultNavUrl?: string; hasUpdate?: boolean; + enterprise?: boolean; latestVersion?: string; pinned?: boolean; } diff --git a/packages/grafana-toolkit/bin/grafana-toolkit.dist.js b/packages/grafana-toolkit/bin/grafana-toolkit.dist.js index d6498f3d3a4..a1cff04187d 100755 --- a/packages/grafana-toolkit/bin/grafana-toolkit.dist.js +++ b/packages/grafana-toolkit/bin/grafana-toolkit.dist.js @@ -1,5 +1,22 @@ #!/usr/bin/env node -// This bin is used for cli installed from npm +const fs = require('fs'); -require('../src/cli/index.js').run(); +entrypoint = () => { + const defaultEntryPoint = '../src/cli/index.js'; + // We are running in dev mode. Don't use compiled binaries, rather use the dev entrypoint. + if (fs.existsSync(`${process.env['HOME']}/.config/yarn/link/@grafana/toolkit`)) { + console.log('Running in linked mode'); + return `${__dirname}/grafana-toolkit.js`; + } + + // We are using npx, and a relative path does not find index.js + if (!fs.existsSync(defaultEntryPoint) && fs.existsSync(`${__dirname}/../dist/src/cli/index.js`)) { + return `${__dirname}/../dist/src/cli/index.js`; + } + + // The default entrypoint must exist, return it now. + return defaultEntryPoint; +}; + +require(entrypoint()).run(); diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/Dockerfile b/packages/grafana-toolkit/docker/grafana-plugin-ci/Dockerfile index f2cdd43c7a9..52c32f3f984 100644 --- a/packages/grafana-toolkit/docker/grafana-plugin-ci/Dockerfile +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/Dockerfile @@ -1,51 +1,6 @@ FROM circleci/node:12-browsers USER root -WORKDIR /tmp - -# Install Go -ADD https://dl.google.com/go/go1.14.linux-amd64.tar.gz /tmp -RUN echo 08df79b46b0adf498ea9f320a0f23d6ec59e9003660b4c9c1ce8e5e2c6f823ca go1.14.linux-amd64.tar.gz | sha256sum --check --status -RUN tar -C /usr/local -xf go1.14.linux-amd64.tar.gz - -# Install golangci-lint -ADD https://github.com/golangci/golangci-lint/releases/download/v1.23.7/golangci-lint-1.23.7-linux-amd64.tar.gz /tmp -RUN echo 34df1794a2ea8e168b3c98eed3cc0f3e13ed4cba735e4e40ef141df5c41bc086 golangci-lint-1.23.7-linux-amd64.tar.gz | sha256sum --check --status -RUN tar xf golangci-lint-1.23.7-linux-amd64.tar.gz -RUN mv golangci-lint-1.23.7-linux-amd64/golangci-lint /usr/local/bin -RUN ln -s /usr/local/go/bin/go /usr/local/bin/go -RUN ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt -RUN chmod 755 /usr/local/bin/golangci-lint - -# Install dependencies -RUN apt-get update -y && apt-get install -y adduser libfontconfig1 locate && /bin/rm -rf /var/lib/apt/lists/* - -# Install code climate -ADD https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 /usr/local/bin/cc-test-reporter -RUN echo 38f2442892027f61a07f52c845818750261b2ba58bffb043a582495339d37c05 /usr/local/bin/cc-test-reporter | sha256sum --check --status -RUN chmod +x /usr/local/bin/cc-test-reporter - -# Download, but don't install previous grafana releases -RUN mkdir -pv /usr/local/grafana/deb -ADD https://dl.grafana.com/oss/release/grafana_6.6.2_amd64.deb /usr/local/grafana/deb -ADD https://dl.grafana.com/oss/release/grafana_6.5.3_amd64.deb /usr/local/grafana/deb -ADD https://dl.grafana.com/oss/release/grafana_6.4.5_amd64.deb /usr/local/grafana/deb -ADD https://dl.grafana.com/oss/release/grafana_6.3.7_amd64.deb /usr/local/grafana/deb - -# Perform user specific initialization -USER circleci -RUN mkdir -pv ${HOME}/plugin ${HOME}/go/bin ${HOME}/bin ${HOME}/src ${HOME}/tmp - -# Install grafana release with yarn -RUN git clone https://github.com/grafana/grafana.git ${HOME}/src/grafana -WORKDIR /home/circleci/src/grafana -RUN git checkout tags/v$(curl -s https://raw.githubusercontent.com/grafana/grafana/master/latest.json | jq -r '.stable') -RUN yarn cache clean && yarn install --frozen-lockfile - -# Install Mage -RUN git clone https://github.com/magefile/mage.git ${HOME}/src/mage -WORKDIR /home/circleci/src/mage -RUN go run bootstrap.go - -ENV PATH /home/circleci/go/bin:/usr/local/go/bin:/home/circleci/.local/bin:/home/circleci/bin:${PATH} -WORKDIR /home/circleci/plugin - +ADD scripts scripts +WORKDIR scripts +RUN ./deploy.sh +ADD install/gget /usr/local/bin/gget diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/README.md b/packages/grafana-toolkit/docker/grafana-plugin-ci/README.md index 55b9f9f1aef..caf5eadbf5d 100644 --- a/packages/grafana-toolkit/docker/grafana-plugin-ci/README.md +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/README.md @@ -20,7 +20,7 @@ The home directory will be `/home/circleci` All of the above directories are in the path, so there is no need to specify fully qualified paths. -## Grafana source +## Grafana - Installed in `/home/circleci/src/grafana` - `yarn install` has been run @@ -47,5 +47,15 @@ To test, your CircleCI config will need a run section with something similar to # Building To build, cd to `/packages/grafana-toolkit/docker/grafana-plugin-ci` ``` -docker build . +./build.sh ``` + +# Developing/Testing +To test, you should have docker-compose installed. +``` +cd test +./start.sh +``` + +You will be in /home/circleci/test with the buildscripts installed to the local directory. +Do your edits/run tests. When saving, your edits will be available in the container immediately. \ No newline at end of file diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/build.sh b/packages/grafana-toolkit/docker/grafana-plugin-ci/build.sh new file mode 100755 index 00000000000..152a546fee6 --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash +source ./common.sh + +output=$(docker build . | tee /dev/tty) +hash=$(echo "$output" | tail -1 | sed -ne "s/^Successfully built \(.*\)/\1/p") +docker tag "$hash" $DOCKER_IMAGE_NAME:latest +docker push $DOCKER_IMAGE_NAME:latest + diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/common.sh b/packages/grafana-toolkit/docker/grafana-plugin-ci/common.sh new file mode 100755 index 00000000000..c1fbccb4bde --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/common.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +## +## Common variable declarations +## + +DOCKER_IMAGE_NAME="srclosson/grafana-plugin-ci" \ No newline at end of file diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/install/gget b/packages/grafana-toolkit/docker/grafana-plugin-ci/install/gget new file mode 100755 index 00000000000..955eb47f9df --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/install/gget @@ -0,0 +1,63 @@ +#!/bin/bash +## +# gget +# A script to get and install grafana versions +# for usage information see "show_help" below. +# + +latest=$(curl -s 'https://raw.githubusercontent.com/grafana/grafana/master/latest.json' | jq -r '.stable') +canary=$(curl -s "https://grafana.com/api/grafana/versions" | jq ".items[0].version" | tr -d '"') + +show_help() { + echo "Usage: gget " + echo "" + echo "where can be:" + echo " 1) A version from https://grafana.com/grafana/download (ex x.y.z)" + echo " 2) latest (currently $latest)" + echo " 3) canary (currently $canary)" + echo "" + echo " -h, --help: Display this help message" + echo "" + exit 0 +} + +opts=$(getopt -o h --long help -n 'gget' -- "$@") +[ $? -eq 0 ] || { + show_help +} + +eval set -- "$opts" +while true; do + case "$1" in + -h | --help) + show_help + ;; + --) + shift + break + ;; + *) + break + ;; + esac + shift +done + +[ -z "$1" ] && show_help + +# Make sure the script is being run as root +if [ $EUID -ne 0 ]; then + echo "This script must be run as root" + exit 1 +fi + +## +# MAIN +# +# Enough setup, let's actually do something +# +version=$1 +[ "$version" == "latest" ] && version="$latest" +[ "$version" == "canary" ] && version="$canary" +wget "https://dl.grafana.com/oss/release/grafana_${version}_amd64.deb" -O "/tmp/grafana_${version}_amd64.deb" +dpkg -i "/tmp/grafana_${version}_amd64.deb" && /bin/rm -rfv "/tmp/grafana_${version}_amd64.deb" diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-common.sh b/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-common.sh new file mode 100755 index 00000000000..524bb5e4ec6 --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-common.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +## +# Script to deploy a docker image. Must return exit code 0 +# +do_exit() { + message="$1" + exit_code="$2" + + echo "$message" + exit $exit_code +} + + +## +# Get file, get's a file, validates the SHA +# @param filename +# @param expected sha value +# @returns 0 if successful, -1 of checksum validation failed. +# +get_file () { + [ -n "$1" ] && url=$1 || do_exit "url required" -1 + [ -n "$2" ] && dest=$2 || do_exit "destination required" -2 + sha=$3 + file=$(basename $dest) + + wget "$url" -O "$dest" + if [ -n "$sha" ]; then + echo "$sha $dest" | sha256sum --check --status || do_exit "Checksum validation failed for $file. Exiting" -1 + fi +} + +untar_file () { + [ -n "$1" ] && src=$1 || do_exit "src required" -1 + [ -n "$2" ] && dest=$2 || dest="/usr/local" + + tar -C "$dest" -xf "$src" && /bin/rm -rf "$src" +} \ No newline at end of file diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-user.sh b/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-user.sh new file mode 100755 index 00000000000..08f2b7b950a --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy-user.sh @@ -0,0 +1,3 @@ +#!/bin/bash +source "./deploy-common.sh" + diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy.sh b/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy.sh new file mode 100755 index 00000000000..1bdb094137e --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/scripts/deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash +source "./deploy-common.sh" + +# Install Go +filename="go1.14.linux-amd64.tar.gz" +get_file "https://dl.google.com/go/$filename" "/tmp/$filename" "08df79b46b0adf498ea9f320a0f23d6ec59e9003660b4c9c1ce8e5e2c6f823ca" +untar_file "/tmp/$filename" + + +# Install golangci-lint +filename="golangci-lint-1.23.7-linux-amd64.tar.gz" +get_file "https://github.com/golangci/golangci-lint/releases/download/v1.23.7/$filename" \ + "/tmp/$filename" \ + "34df1794a2ea8e168b3c98eed3cc0f3e13ed4cba735e4e40ef141df5c41bc086" +untar_file "/tmp/$filename" +chmod 755 /usr/local/bin/golangci-lint +ln -s /usr/local/golangci-lint-1.23.7-linux-amd64/golangci-lint /usr/local/bin/golangci-lint +ln -s /usr/local/go/bin/go /usr/local/bin/go +ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt + +# Install dependencies +apt-get update -y && apt-get install -y adduser libfontconfig1 locate && /bin/rm -rf /var/lib/apt/lists/* + +# Install code climate +get_file "https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64" \ + "/usr/local/bin/cc-test-reporter" \ + "38f2442892027f61a07f52c845818750261b2ba58bffb043a582495339d37c05" +chmod +x /usr/local/bin/cc-test-reporter + +# Install Mage +mkdir -pv /tmp/mage $HOME/go/bin +git clone https://github.com/magefile/mage.git /tmp/mage +pushd /tmp/mage && go run bootstrap.go && popd +mv $HOME/go/bin/mage /usr/local/bin +# Cleanup after yourself +/bin/rm -rf /tmp/mage +/bin/rm -rf $HOME/go + +# Perform user specific initialization +sudo -u circleci ./deploy-user.sh + +# Get the size down +/bin/rm -rf /var/lib/apt/lists \ No newline at end of file diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/test/docker-compose.yml b/packages/grafana-toolkit/docker/grafana-plugin-ci/test/docker-compose.yml new file mode 100644 index 00000000000..7dc66924f78 --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/test/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' +services: + citest: + image: "circleci/node:12-browsers" + user: root + volumes: + - ../scripts:/home/circleci/scripts + - ../install:/home/circleci/install diff --git a/packages/grafana-toolkit/docker/grafana-plugin-ci/test/start.sh b/packages/grafana-toolkit/docker/grafana-plugin-ci/test/start.sh new file mode 100755 index 00000000000..486ff5a6994 --- /dev/null +++ b/packages/grafana-toolkit/docker/grafana-plugin-ci/test/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Enter the docker container +docker-compose run citest bash -c "cd /home/circleci; exec bash --login -i" diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts index c876b814b49..4fe7fd3e463 100644 --- a/packages/grafana-toolkit/src/cli/index.ts +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -200,14 +200,12 @@ export const run = (includeInternalScripts = false) => { .option('--dryrun', 'Do a dry run only', false) .option('--verbose', 'Print verbose', false) .option('--commitHash ', 'Specify the commit hash') - .option('--recreate', 'Recreate the release if already present') - .description('Publish to github ... etc etc etc') + .description('Publish to github') .action(async cmd => { await execTask(githubPublishTask)({ dryrun: cmd.dryrun, verbose: cmd.verbose, commitHash: cmd.commitHash, - recreate: cmd.recreate, }); }); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts index 74b4ba10170..63076ffb498 100755 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.utils.ts @@ -4,7 +4,6 @@ import { GitHubRelease } from '../utils/githubRelease'; import { getPluginId } from '../../config/utils/getPluginId'; import { getCiFolder } from '../../plugins/env'; import { useSpinner } from '../utils/useSpinner'; - import path = require('path'); // @ts-ignore @@ -20,10 +19,11 @@ const releaseNotes = async (): Promise => { const checkoutBranch = async (branchName: string): Promise => { const currentBranch = await execa.shell(`git rev-parse --abbrev-ref HEAD`); const branchesAvailable = await execa.shell( - `(git branch -a | grep ${branchName} | grep -v remote) || echo 'No release found'` + `(git branch -a | grep "${branchName}$" | grep -v remote) || echo 'No release found'` ); if (currentBranch.stdout !== branchName) { + console.log('available', branchesAvailable.stdout.trim()); if (branchesAvailable.stdout.trim() === branchName) { return ['git', ['checkout', branchName]]; } else { @@ -61,27 +61,28 @@ const prepareRelease = useSpinner('Preparing release', async ({ dryrun, ver const distDir = path.resolve(ciDir, 'dist'); const distContentDir = path.resolve(distDir, getPluginId()); const pluginJsonFile = path.resolve(distContentDir, 'plugin.json'); - const pluginVersion = getPluginJson(pluginJsonFile).info.version; + const pluginJson = getPluginJson(pluginJsonFile); const GIT_EMAIL = 'eng@grafana.com'; const GIT_USERNAME = 'CircleCI Automation'; const githubPublishScript: Command = [ ['git', ['config', 'user.email', GIT_EMAIL]], ['git', ['config', 'user.name', GIT_USERNAME]], - await checkoutBranch(`release-${pluginVersion}`), - ['cp', ['-rf', distContentDir, 'dist'], { dryrun }], + await checkoutBranch(`release-${pluginJson.info.version}`), + ['cp', ['-rf', distContentDir, 'dist']], ['git', ['add', '--force', distDir], { dryrun }], ['git', ['add', '--force', 'dist'], { dryrun }], + ['/bin/rm', ['-rf', 'src'], { enterprise: true }], [ 'git', - ['commit', '-m', `automated release ${pluginVersion} [skip ci]`], + ['commit', '-m', `automated release ${pluginJson.info.version} [skip ci]`], { dryrun, okOnError: [/nothing to commit/g, /nothing added to commit/g, /no changes added to commit/g], }, ], - ['git', ['tag', '-f', pluginVersion]], - ['git', ['push', '-f', 'origin', `release-${pluginVersion}`], { dryrun }], + ['git', ['tag', '-f', pluginJson.info.version]], + ['git', ['push', '-f', 'origin', `release-${pluginJson.info.version}`], { dryrun }], ]; for (let line of githubPublishScript) { @@ -98,6 +99,11 @@ const prepareRelease = useSpinner('Preparing release', async ({ dryrun, ver if (opts['dryrun']) { line[1].push('--dry-run'); } + + if (pluginJson.enterprise && !opts['enterprise']) { + continue; + } + const { stdout } = await execa(command, args); if (verbose) { console.log(stdout); @@ -129,19 +135,18 @@ const prepareRelease = useSpinner('Preparing release', async ({ dryrun, ver } }); -interface GithubPluglishReleaseOptions { +interface GithubPublishReleaseOptions { commitHash?: string; - recreate?: boolean; githubToken: string; gitRepoOwner: string; gitRepoName: string; } -const createRelease = useSpinner( +const createRelease = useSpinner( 'Creating release', - async ({ commitHash, recreate, githubToken, gitRepoName, gitRepoOwner }) => { + async ({ commitHash, githubToken, gitRepoName, gitRepoOwner }) => { const gitRelease = new GitHubRelease(githubToken, gitRepoOwner, gitRepoName, await releaseNotes(), commitHash); - return gitRelease.release(recreate || false); + return gitRelease.release(); } ); @@ -149,10 +154,10 @@ export interface GithubPublishOptions { dryrun?: boolean; verbose?: boolean; commitHash?: string; - recreate?: boolean; + dev?: boolean; } -const githubPublishRunner: TaskRunner = async ({ dryrun, verbose, commitHash, recreate }) => { +const githubPublishRunner: TaskRunner = async ({ dryrun, verbose, commitHash }) => { if (!process.env['CIRCLE_REPOSITORY_URL']) { throw `The release plugin requires you specify the repository url as environment variable CIRCLE_REPOSITORY_URL`; } @@ -172,7 +177,6 @@ const githubPublishRunner: TaskRunner = async ({ dryrun, v await createRelease({ commitHash, - recreate, githubToken, gitRepoOwner: parsedUrl.owner, gitRepoName: parsedUrl.name, diff --git a/packages/grafana-toolkit/src/cli/utils/githubClient.ts b/packages/grafana-toolkit/src/cli/utils/githubClient.ts index 22207984ac8..d737010d94a 100644 --- a/packages/grafana-toolkit/src/cli/utils/githubClient.ts +++ b/packages/grafana-toolkit/src/cli/utils/githubClient.ts @@ -1,6 +1,6 @@ import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; -const grafanaURL = 'https://api.github.com/repos/grafana/grafana'; +const grafanaURL = (repo: string) => `https://api.github.com/repos/grafana/${repo}`; const enterpriseURL = 'https://api.github.com/repos/grafana/grafana-enterprise'; // Encapsulates the creation of a client for the Github API @@ -14,17 +14,18 @@ const enterpriseURL = 'https://api.github.com/repos/grafana/grafana-enterprise'; interface GithubClientProps { required?: boolean; enterprise?: boolean; + repo?: string; } class GithubClient { client: AxiosInstance; - constructor({ required = false, enterprise = false }: GithubClientProps = {}) { + constructor({ required = false, enterprise = false, repo = 'grafana' }: GithubClientProps = {}) { const username = process.env.GITHUB_USERNAME; const token = process.env.GITHUB_ACCESS_TOKEN; const clientConfig: AxiosRequestConfig = { - baseURL: enterprise ? enterpriseURL : grafanaURL, + baseURL: enterprise ? enterpriseURL : grafanaURL(repo), timeout: 10000, }; diff --git a/packages/grafana-toolkit/src/cli/utils/githubRelease.ts b/packages/grafana-toolkit/src/cli/utils/githubRelease.ts index e1ab8a67881..976060e2469 100644 --- a/packages/grafana-toolkit/src/cli/utils/githubRelease.ts +++ b/packages/grafana-toolkit/src/cli/utils/githubRelease.ts @@ -2,19 +2,22 @@ import { getPluginId } from '../../config/utils/getPluginId'; import { getPluginJson } from '../../config/utils/pluginValidation'; import { getCiFolder } from '../../plugins/env'; import path = require('path'); +import fs = require('fs'); // @ts-ignore -import execa = require('execa'); +// import execa = require('execa'); +import GithubClient from './githubClient'; +import { AxiosResponse } from 'axios'; -const ghrPlatform = (): string => { - switch (process.platform) { - case 'win32': - return 'windows'; - case 'darwin': - return 'darwin'; - case 'linux': - return 'linux'; +const resolveContentType = (extension: string): string => { + switch (extension) { + case 'zip': + return 'application/zip'; + case 'json': + return 'application/json'; + case 'sha1': + return 'text/plain'; default: - return process.platform; + return 'application/octet-stream'; } }; @@ -24,6 +27,7 @@ class GitHubRelease { repository: string; releaseNotes: string; commitHash?: string; + git: GithubClient; constructor(token: string, username: string, repository: string, releaseNotes: string, commitHash?: string) { this.token = token; @@ -31,36 +35,37 @@ class GitHubRelease { this.repository = repository; this.releaseNotes = releaseNotes; this.commitHash = commitHash; + + this.git = new GithubClient({ + repo: repository, + }); } - /** - * Get the ghr binary to perform the release - */ - private async getGhr(): Promise { - const GHR_VERSION = '0.13.0'; - const GHR_ARCH = process.arch === 'x64' ? 'amd64' : '386'; - const GHR_PLATFORM = ghrPlatform(); - const GHR_EXTENSION = process.platform === 'linux' ? 'tar.gz' : 'zip'; - const outName = `./ghr.${GHR_EXTENSION}`; - const archiveName = `ghr_v${GHR_VERSION}_${GHR_PLATFORM}_${GHR_ARCH}`; - const exeName = process.platform === 'linux' ? 'ghr' : 'ghr.exe'; - const exeNameFullPath = path.resolve(process.cwd(), archiveName, exeName); - const ghrUrl = `https://github.com/tcnksm/ghr/releases/download/v${GHR_VERSION}/${archiveName}.${GHR_EXTENSION}`; - await execa('wget', [ghrUrl, `--output-document=${outName}`]); - if (GHR_EXTENSION === 'tar.gz') { - await execa('tar', ['zxvf', outName]); - } else { - await execa('unzip', ['-p', outName]); - } + async publishAssets(srcLocation: string, destUrl: string) { + // Add the assets. Loop through files in the ci/dist folder and upload each asset. + fs.readdir(srcLocation, (err: NodeJS.ErrnoException | null, files: string[]) => { + if (err) { + throw err; + } - if (process.platform === 'linux') { - await execa('chmod', ['755', exeNameFullPath]); - } - - return exeNameFullPath; + files.forEach(async (file: string) => { + const fileStat = fs.statSync(`${srcLocation}/${file}`); + const fileData = fs.readFileSync(`${srcLocation}/${file}`); + try { + await this.git.client.post(`${destUrl}?name=${file}`, fileData, { + headers: { + 'Content-Type': resolveContentType(path.extname(file)), + 'Content-Length': fileStat.size, + }, + }); + } catch (reason) { + console.log('Could not post', reason); + } + }); + }); } - async release(recreate: boolean) { + async release() { const ciDir = getCiFolder(); const distDir = path.resolve(ciDir, 'dist'); const distContentDir = path.resolve(distDir, getPluginId()); @@ -69,37 +74,31 @@ class GitHubRelease { const PUBLISH_DIR = path.resolve(getCiFolder(), 'packages'); const commitHash = this.commitHash || pluginInfo.build?.hash; - // Get the ghr binary according to platform - const ghrExe = await this.getGhr(); + try { + const latestRelease: AxiosResponse = await this.git.client.get('releases/latest'); - if (!commitHash) { - throw 'The release plugin was not able to locate a commithash for release. Either build using the ci, or specify the commit hash with --commitHash '; + // Re-release if the version is the same as an existing release + if (latestRelease.data.tag_name === `v${pluginInfo.version}`) { + await this.git.client.delete(`releases/${latestRelease.data.id}`); + } + + // Now make the release + const newReleaseResponse = await this.git.client.post('releases', { + tag_name: `v${pluginInfo.version}`, + target_commitish: commitHash, + name: `v${pluginInfo.version}`, + body: this.releaseNotes, + draft: false, + prerelease: false, + }); + + this.publishAssets( + PUBLISH_DIR, + `https://uploads.github.com/repos/${this.username}/${this.repository}/releases/${newReleaseResponse.data.id}/assets` + ); + } catch (reason) { + console.error('error', reason); } - - const args = [ - '-t', - this.token, - '-u', - this.username, - '-r', - this.repository, // should override --- may not be the same - '-c', - commitHash, - '-n', - `${this.repository}_v${pluginInfo.version}`, - '-b', - this.releaseNotes, - `v${pluginInfo.version}`, - PUBLISH_DIR, - ]; - - if (recreate) { - args.splice(12, 0, '-recreate'); - } - - const { stdout } = await execa(ghrExe, args); - - console.log(stdout); } }