Compare commits

...

15 Commits

Author SHA1 Message Date
1c2a9c2c70 Update tsconfig.json 2021-04-01 11:47:28 +05:30
af5ec937fe Update package.json 2021-04-01 10:36:16 +05:30
62aeada586 Added trigger for L2 tests. (#26) 2021-03-31 17:23:12 +05:30
edc063ca5f adding graph ql for Helm versions - master (#22)
* Adding graphql to find out latest helm version of specified type

* Updating package-loc.json

* Updating js file

* Fixing PR comments

* Adding feature flag as environment variable

* Changing feature flag name

* Updating package-loc.json
2021-03-30 22:43:44 +05:30
e77e49c8f8 Added L0 tests and fixed some minor issues. (#20) 2021-03-26 13:08:55 +05:30
d315e938e1 Merge pull request #17 from Azure/dependabot/npm_and_yarn/actions/core-1.2.6
Bump @actions/core from 1.0.0 to 1.2.6
2020-10-05 12:26:52 +05:30
509333604f Bump @actions/core from 1.0.0 to 1.2.6
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.0.0 to 1.2.6.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 17:24:52 +00:00
91b7d21636 Changed the documentation to specify v before 'version' and code changes to append 'v' if it not provided in version (#11) (#15)
* Chnaged the documentation to sepcify v before 'version'

* append v to version incase it does not already include

* review comments fix
2020-05-14 12:16:24 +05:30
b1c4524a5f Users/shigupt/fixing helm latest releases master (#14)
* Updating the logic for downloading latest Helm version

* Adding semver to package.json
2020-05-13 21:53:04 +05:30
f404b8932e Update README.md 2020-02-27 14:58:14 +05:30
1de6b70ec6 Use correct version string format in setup instructions #7
Use correct version string format in setup instructions
2020-02-04 18:12:33 +05:30
d0d63812ab Use correct version string format in setup instructions
If somebody uses `1.15.0` instead of `v1.15.0` the installation will fail, this adjusts instructions to be slightly clearer.
2019-12-17 16:21:45 -08:00
87a2cd59a0 Merge pull request #5 from Azure/users/ansheno/helminstallfix
Added helm binary to the Path environment variable
2019-11-26 18:05:34 +05:30
fe2c706cad Added helm binary to the Path environment variable 2019-11-26 17:55:31 +05:30
11c996f4c7 Merge pull request #1 from Azure/users/ansheno/addedSetup-helm
Added setup-helm : Install helm binary
2019-10-16 01:47:26 +05:30
13 changed files with 6643 additions and 89 deletions

View File

@ -0,0 +1,33 @@
token=$1
commit=$2
repository=$3
prNumber=$4
frombranch=$5
tobranch=$6
patUser=$7
getPayLoad() {
cat <<EOF
{
"event_type": "SetupHelmActionPR",
"client_payload":
{
"action": "SetupHelm",
"commit": "$commit",
"repository": "$repository",
"prNumber": "$prNumber",
"tobranch": "$tobranch",
"frombranch": "$frombranch"
}
}
EOF
}
response=$(curl -u $patUser:$token -X POST https://api.github.com/repos/Azure/azure-actions-integration-tests/dispatches --data "$(getPayLoad)")
if [ "$response" == "" ]; then
echo "Integration tests triggered successfully"
else
echo "Triggering integration tests failed with: '$response'"
exit 1
fi

19
.github/workflows/integration-tests.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: "Trigger Integration tests"
on:
pull_request:
branches:
- master
- 'releases/*'
jobs:
trigger-integration-tests:
name: Trigger Integration tests
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v2
with:
path: IntegrationTests
- name: Trigger Test run
run: |
bash ./IntegrationTests/.github/workflows/TriggerIntegrationTests.sh ${{ secrets.L2_REPO_TOKEN }} ${{ github.event.pull_request.head.sha }} ${{ github.repository }} ${{ github.event.pull_request.number }} ${{ github.event.pull_request.head.ref }} ${{ github.event.pull_request.base.ref }} ${{ secrets.L2_REPO_USER }}

21
.github/workflows/unit-tests.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: "Run unit tests."
on: # rebuild any PRs and main branch changes
pull_request:
branches:
- master
- 'releases/*'
push:
branches:
- master
- 'releases/*'
jobs:
build: # make sure build/ci works properly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run L0 tests.
run: |
npm install
npm test

3
.gitignore vendored
View File

@ -59,3 +59,6 @@ typings/
# next.js build output # next.js build output
.next .next
node_modules
coverage

View File

@ -1,7 +1,7 @@
# Setup Helm # Setup Helm
#### Install a specific version of helm binary on the runner. #### Install a specific version of helm binary on the runner.
Acceptable values are latest or any semantic version string like 1.15.0. Use this action in workflow to define which version of helm will be used. Acceptable values are latest or any semantic version string like v2.16.7 Use this action in workflow to define which version of helm will be used.
```yaml ```yaml
- uses: azure/setup-helm@v1 - uses: azure/setup-helm@v1
@ -9,6 +9,8 @@ Acceptable values are latest or any semantic version string like 1.15.0. Use thi
version: '<version>' # default is latest stable version: '<version>' # default is latest stable
id: install id: install
``` ```
The cached helm binary path is prepended to the PATH environment variable as well as stored in the helm-path output variable.
Refer to the action metadata file for details about all the inputs https://github.com/Azure/setup-helm/blob/master/action.yml Refer to the action metadata file for details about all the inputs https://github.com/Azure/setup-helm/blob/master/action.yml
# Contributing # Contributing

195
__tests__/run.test.ts Normal file
View File

@ -0,0 +1,195 @@
import * as run from '../src/run'
import * as os from 'os';
import * as toolCache from '@actions/tool-cache';
import * as fs from 'fs';
import * as path from 'path';
import * as core from '@actions/core';
describe('run.ts', () => {
test('getExecutableExtension() - return .exe when os is Windows', () => {
jest.spyOn(os, 'type').mockReturnValue('Windows_NT');
expect(run.getExecutableExtension()).toBe('.exe');
expect(os.type).toBeCalled();
});
test('getExecutableExtension() - return empty string for non-windows OS', () => {
jest.spyOn(os, 'type').mockReturnValue('Darwin');
expect(run.getExecutableExtension()).toBe('');
expect(os.type).toBeCalled();
});
test('getHelmDownloadURL() - return the URL to download helm for Linux', () => {
jest.spyOn(os, 'type').mockReturnValue('Linux');
const kubectlLinuxUrl = 'https://get.helm.sh/helm-v3.2.1-linux-amd64.zip'
expect(run.getHelmDownloadURL('v3.2.1')).toBe(kubectlLinuxUrl);
expect(os.type).toBeCalled();
});
test('getHelmDownloadURL() - return the URL to download helm for Darwin', () => {
jest.spyOn(os, 'type').mockReturnValue('Darwin');
const kubectlDarwinUrl = 'https://get.helm.sh/helm-v3.2.1-darwin-amd64.zip'
expect(run.getHelmDownloadURL('v3.2.1')).toBe(kubectlDarwinUrl);
expect(os.type).toBeCalled();
});
test('getHelmDownloadURL() - return the URL to download helm for Windows', () => {
jest.spyOn(os, 'type').mockReturnValue('Windows_NT');
const kubectlWindowsUrl = 'https://get.helm.sh/helm-v3.2.1-windows-amd64.zip'
expect(run.getHelmDownloadURL('v3.2.1')).toBe(kubectlWindowsUrl);
expect(os.type).toBeCalled();
});
test('getStableHelmVersion() - download stable version file, read version and return it', async () => {
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool');
const response = JSON.stringify(
[
{
'tag_name': 'v4.0.0'
}, {
'tag_name': 'v3.0.0'
}, {
'tag_name': 'v2.0.0'
}
]
);
jest.spyOn(fs, 'readFileSync').mockReturnValue(response);
expect(await run.getStableHelmVersion()).toBe('v4.0.0');
expect(toolCache.downloadTool).toBeCalled();
expect(fs.readFileSync).toBeCalledWith('pathToTool', 'utf8');
});
test('getStableHelmVersion() - return default version if error occurs while getting latest version', async () => {
jest.spyOn(toolCache, 'downloadTool').mockRejectedValue('Unable to download');
jest.spyOn(core, 'warning').mockImplementation();
expect(await run.getStableHelmVersion()).toBe('v3.2.1');
expect(toolCache.downloadTool).toBeCalled();
expect(core.warning).toBeCalledWith("Cannot get the latest Helm info from https://api.github.com/repos/helm/helm/releases. Error Unable to download. Using default Helm version v3.2.1.");
});
test('walkSync() - return path to the all files matching fileToFind in dir', () => {
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
if (file == 'mainFolder') return ['file1' as unknown as fs.Dirent, 'file2' as unknown as fs.Dirent, 'folder1' as unknown as fs.Dirent, 'folder2' as unknown as fs.Dirent];
if (file == path.join('mainFolder', 'folder1')) return ['file11' as unknown as fs.Dirent, 'file12' as unknown as fs.Dirent];
if (file == path.join('mainFolder', 'folder2')) return ['file21' as unknown as fs.Dirent, 'file22' as unknown as fs.Dirent];
});
jest.spyOn(core, 'debug').mockImplementation();
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory = (file as string).toLowerCase().indexOf('file') == -1 ? true: false
return { isDirectory: () => isDirectory } as fs.Stats;
});
expect(run.walkSync('mainFolder', null, 'file21')).toEqual([path.join('mainFolder', 'folder2', 'file21')]);
expect(fs.readdirSync).toBeCalledTimes(3);
expect(fs.statSync).toBeCalledTimes(8);
});
test('walkSync() - return empty array if no file with name fileToFind exists', () => {
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
if (file == 'mainFolder') return ['file1' as unknown as fs.Dirent, 'file2' as unknown as fs.Dirent, 'folder1' as unknown as fs.Dirent, 'folder2' as unknown as fs.Dirent];
if (file == path.join('mainFolder', 'folder1')) return ['file11' as unknown as fs.Dirent, 'file12' as unknown as fs.Dirent];
if (file == path.join('mainFolder', 'folder2')) return ['file21' as unknown as fs.Dirent, 'file22' as unknown as fs.Dirent];
});
jest.spyOn(core, 'debug').mockImplementation();
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory = (file as string).toLowerCase().indexOf('file') == -1 ? true: false
return { isDirectory: () => isDirectory } as fs.Stats;
});
expect(run.walkSync('mainFolder', null, 'helm.exe')).toEqual([]);
expect(fs.readdirSync).toBeCalledTimes(3);
expect(fs.statSync).toBeCalledTimes(8);
});
test('findHelm() - change access permissions and find the helm in given directory', () => {
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {});
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
if (file == 'mainFolder') return ['helm.exe' as unknown as fs.Dirent];
});
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory = (file as string).indexOf('folder') == -1 ? false: true
return { isDirectory: () => isDirectory } as fs.Stats;
});
jest.spyOn(os, 'type').mockReturnValue('Windows_NT');
expect(run.findHelm('mainFolder')).toBe(path.join('mainFolder', 'helm.exe'));
});
test('findHelm() - throw error if executable not found', () => {
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {});
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
if (file == 'mainFolder') return [];
});
jest.spyOn(fs, 'statSync').mockImplementation((file) => { return { isDirectory: () => true } as fs.Stats});
jest.spyOn(os, 'type').mockReturnValue('Windows_NT');
expect(() => run.findHelm('mainFolder')).toThrow('Helm executable not found in path mainFolder');
});
test('downloadHelm() - download helm and return path to it', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('');
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool');
const response = JSON.stringify([{'tag_name': 'v4.0.0'}]);
jest.spyOn(fs, 'readFileSync').mockReturnValue(response);
jest.spyOn(os, 'type').mockReturnValue('Windows_NT');
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {});
jest.spyOn(toolCache, 'extractZip').mockResolvedValue('pathToUnzippedHelm');
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir');
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => ['helm.exe' as unknown as fs.Dirent]);
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory = (file as string).indexOf('folder') == -1 ? false: true
return { isDirectory: () => isDirectory } as fs.Stats;
});
expect(await run.downloadHelm(null)).toBe(path.join('pathToCachedDir', 'helm.exe'));
expect(toolCache.find).toBeCalledWith('helm', 'v4.0.0');
expect(toolCache.downloadTool).toBeCalledWith('https://get.helm.sh/helm-v4.0.0-windows-amd64.zip');
expect(fs.chmodSync).toBeCalledWith('pathToTool', '777');
expect(toolCache.extractZip).toBeCalledWith('pathToTool');
expect(fs.chmodSync).toBeCalledWith(path.join('pathToCachedDir', 'helm.exe'), '777');
});
test('downloadHelm() - throw error if unable to download', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('');
jest.spyOn(toolCache, 'downloadTool').mockImplementation(async () => { throw 'Unable to download'});
jest.spyOn(os, 'type').mockReturnValue('Windows_NT');
await expect(run.downloadHelm('v3.2.1')).rejects.toThrow('Failed to download Helm from location https://get.helm.sh/helm-v3.2.1-windows-amd64.zip');
expect(toolCache.find).toBeCalledWith('helm', 'v3.2.1');
expect(toolCache.downloadTool).toBeCalledWith('https://get.helm.sh/helm-v3.2.1-windows-amd64.zip');
});
test('downloadHelm() - return path to helm tool with same version from toolCache', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('pathToCachedDir');
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {});
expect(await run.downloadHelm('v3.2.1')).toBe(path.join('pathToCachedDir', 'helm.exe'));
expect(toolCache.find).toBeCalledWith('helm', 'v3.2.1');
expect(fs.chmodSync).toBeCalledWith(path.join('pathToCachedDir', 'helm.exe'), '777');
});
test('downloadHelm() - throw error is helm is not found in path', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('');
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool');
jest.spyOn(os, 'type').mockReturnValue('Windows_NT');
jest.spyOn(fs, 'chmodSync').mockImplementation();
jest.spyOn(toolCache, 'extractZip').mockResolvedValue('pathToUnzippedHelm');
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir');
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => []);
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory = (file as string).indexOf('folder') == -1 ? false: true
return { isDirectory: () => isDirectory } as fs.Stats;
});
await expect(run.downloadHelm('v3.2.1')).rejects.toThrow('Helm executable not found in path pathToCachedDir');
expect(toolCache.find).toBeCalledWith('helm', 'v3.2.1');
expect(toolCache.downloadTool).toBeCalledWith('https://get.helm.sh/helm-v3.2.1-windows-amd64.zip');
expect(fs.chmodSync).toBeCalledWith('pathToTool', '777');
expect(toolCache.extractZip).toBeCalledWith('pathToTool');
});
});

View File

@ -1,10 +1,14 @@
name: 'Helm tool installer' name: 'Helm tool installer'
description: 'Install a specific version of helm binary. Acceptable values are latest or any semantic version string like 1.15.0' description: 'Install a specific version of helm binary. Acceptable values are latest or any semantic version string like 1.15.0'
inputs: inputs:
version: version:
description: 'Version of helm' description: 'Version of helm'
required: true required: true
default: 'latest' default: 'latest'
token:
description: 'Github token'
default: ${{ github.token }}
required: true
outputs: outputs:
helm-path: helm-path:
description: 'Path to the cached helm binary' description: 'Path to the cached helm binary'

18
jest.config.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true,
coverageThreshold: {
"global": {
"branches": 0,
"functions": 14,
"lines": 27,
"statements": 27
}
}
}

View File

@ -1,6 +1,26 @@
"use strict"; "use strict";
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license. // Licensed under the MIT license.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) { return new (P || (P = Promise))(function (resolve, reject) {
@ -10,23 +30,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getStableHelmVersion = void 0;
const os = __importStar(require("os")); const os = __importStar(require("os"));
const path = __importStar(require("path")); const path = __importStar(require("path"));
const util = __importStar(require("util")); const util = __importStar(require("util"));
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const toolCache = __importStar(require("../node_modules/@actions/tool-cache")); const semver = __importStar(require("semver"));
const core = __importStar(require("../node_modules/@actions/core")); const toolCache = __importStar(require("@actions/tool-cache"));
const core = __importStar(require("@actions/core"));
const graphql_1 = require("@octokit/graphql");
const helmToolName = 'helm'; const helmToolName = 'helm';
const stableHelmVersion = 'v2.14.1'; const stableHelmVersion = 'v3.2.1';
const helmLatestReleaseUrl = 'https://api.github.com/repos/helm/helm/releases/latest'; const stableHelm3Version = 'v3.5.3';
const stableHelm2Version = 'v2.17.0';
const LATEST_HELM2_VERSION = '2.*';
const LATEST_HELM3_VERSION = '3.*';
const helmAllReleasesUrl = 'https://api.github.com/repos/helm/helm/releases';
function getExecutableExtension() { function getExecutableExtension() {
if (os.type().match(/^Win/)) { if (os.type().match(/^Win/)) {
return '.exe'; return '.exe';
@ -46,19 +66,31 @@ function getHelmDownloadURL(version) {
} }
function getStableHelmVersion() { function getStableHelmVersion() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
return toolCache.downloadTool(helmLatestReleaseUrl).then((downloadPath) => { try {
const response = JSON.parse(fs.readFileSync(downloadPath, 'utf8').toString().trim()); const downloadPath = yield toolCache.downloadTool(helmAllReleasesUrl);
if (!response.tag_name) { const responseArray = JSON.parse(fs.readFileSync(downloadPath, 'utf8').toString().trim());
return stableHelmVersion; let latestHelmVersion = semver.clean(stableHelmVersion);
} responseArray.forEach(response => {
return response.tag_name; if (response && response.tag_name) {
}, (error) => { let currentHelmVerison = semver.clean(response.tag_name.toString());
core.debug(error); if (currentHelmVerison) {
core.warning(util.format("Failed to read latest kubectl version from stable.txt. From URL %s. Using default stable version %s", helmLatestReleaseUrl, stableHelmVersion)); if (currentHelmVerison.toString().indexOf('rc') == -1 && semver.gt(currentHelmVerison, latestHelmVersion)) {
return stableHelmVersion; //If current helm version is not a pre release and is greater than latest helm version
}); latestHelmVersion = currentHelmVerison;
}
}
}
});
latestHelmVersion = "v" + latestHelmVersion;
return latestHelmVersion;
}
catch (error) {
core.warning(util.format("Cannot get the latest Helm info from %s. Error %s. Using default Helm version %s.", helmAllReleasesUrl, error, stableHelmVersion));
}
return stableHelmVersion;
}); });
} }
exports.getStableHelmVersion = getStableHelmVersion;
var walkSync = function (dir, filelist, fileToFind) { var walkSync = function (dir, filelist, fileToFind) {
var files = fs.readdirSync(dir); var files = fs.readdirSync(dir);
filelist = filelist || []; filelist = filelist || [];
@ -78,7 +110,7 @@ var walkSync = function (dir, filelist, fileToFind) {
function downloadHelm(version) { function downloadHelm(version) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (!version) { if (!version) {
version = yield getStableHelmVersion(); version = yield getLatestHelmVersionFor("v3");
} }
let cachedToolpath = toolCache.find(helmToolName, version); let cachedToolpath = toolCache.find(helmToolName, version);
if (!cachedToolpath) { if (!cachedToolpath) {
@ -101,6 +133,45 @@ function downloadHelm(version) {
return helmpath; return helmpath;
}); });
} }
function getLatestHelmVersionFor(type) {
return __awaiter(this, void 0, void 0, function* () {
const token = core.getInput('token', { 'required': true });
try {
const { repository } = yield graphql_1.graphql(`{
repository(name: "helm", owner: "helm") {
releases(last: 100) {
nodes {
tagName
}
}
}
}`, {
headers: {
authorization: `token ${token}`
}
});
const releases = repository.releases.nodes.reverse();
let latestValidRelease = releases.find(release => isValidVersion(release.tagName, type));
if (latestValidRelease)
return latestValidRelease.tagName;
}
catch (err) {
core.warning(util.format("Error while fetching the latest Helm %s release. Error: %s. Using default Helm version %s.", type, err.toString(), getStableHelmVersionFor(type)));
return getStableHelmVersionFor(type);
}
core.warning(util.format("Could not find stable release for Helm %s. Using default Helm version %s.", type, getStableHelmVersionFor(type)));
return getStableHelmVersionFor(type);
});
}
function getStableHelmVersionFor(type) {
return type === "v2" ? stableHelm2Version : stableHelm3Version;
}
// isValidVersion checks if verison matches the specified type and is a stable release
function isValidVersion(version, type) {
if (!version.toLocaleLowerCase().startsWith(type))
return false;
return version.indexOf('rc') == -1;
}
function findHelm(rootFolder) { function findHelm(rootFolder) {
fs.chmodSync(rootFolder, '777'); fs.chmodSync(rootFolder, '777');
var filelist = []; var filelist = [];
@ -115,10 +186,35 @@ function findHelm(rootFolder) {
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
let version = core.getInput('version', { 'required': true }); let version = core.getInput('version', { 'required': true });
if (version.toLocaleLowerCase() === 'latest') { if (process.env['HELM_INSTALLER_LEGACY_VERSIONING'] == 'true') {
version = yield getStableHelmVersion(); if (version.toLocaleLowerCase() === 'latest') {
version = yield getStableHelmVersion();
}
else if (!version.toLocaleLowerCase().startsWith('v')) {
version = 'v' + version;
}
} }
else {
if (version.toLocaleLowerCase() === 'latest' || version === LATEST_HELM3_VERSION) {
version = yield getLatestHelmVersionFor("v3");
}
else if (version === LATEST_HELM2_VERSION) {
version = yield getLatestHelmVersionFor("v2");
}
else if (!version.toLocaleLowerCase().startsWith('v')) {
version = 'v' + version;
}
}
core.debug(util.format("Downloading %s", version));
let cachedPath = yield downloadHelm(version); let cachedPath = yield downloadHelm(version);
try {
if (!process.env['PATH'].startsWith(path.dirname(cachedPath))) {
core.addPath(path.dirname(cachedPath));
}
}
catch (_a) {
//do nothing, set as output variable
}
console.log(`Helm tool version: '${version}' has been cached at ${cachedPath}`); console.log(`Helm tool version: '${version}' has been cached at ${cachedPath}`);
core.setOutput('helm-path', cachedPath); core.setOutput('helm-path', cachedPath);
}); });

6095
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,18 +6,24 @@
"author": "Anumita Shenoy", "author": "Anumita Shenoy",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/tool-cache": "1.1.2", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0",
"@actions/io": "^1.0.0", "@actions/io": "^1.0.0",
"@actions/core": "^1.0.0", "@actions/tool-cache": "1.1.2",
"@actions/exec": "^1.0.0" "@octokit/graphql": "^4.6.1",
"semver": "^6.1.0"
}, },
"main": "lib/run.js", "main": "lib/run.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc --outDir ./lib --rootDir ./src",
"test": "echo \"Error: no test specified\" && exit 1" "test": "jest",
"test-coverage": "jest --coverage"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.10", "@types/node": "^12.0.10",
"typescript": "^3.5.2" "typescript": "^3.5.2",
"jest": "^26.0.1",
"@types/jest": "^25.2.2",
"ts-jest": "^25.5.1"
} }
} }

View File

@ -1,26 +1,33 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license. // Licensed under the MIT license.
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as util from 'util'; import * as util from 'util';
import * as fs from 'fs'; import * as fs from 'fs';
import * as semver from 'semver';
import * as toolCache from '@actions/tool-cache'; import * as toolCache from '@actions/tool-cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import { graphql } from '@octokit/graphql';
const helmToolName = 'helm'; const helmToolName = 'helm';
const stableHelmVersion = 'v2.14.1'; const stableHelmVersion = 'v3.2.1';
const helmLatestReleaseUrl = 'https://api.github.com/repos/helm/helm/releases/latest'; const stableHelm3Version = 'v3.5.3';
const stableHelm2Version = 'v2.17.0';
const LATEST_HELM2_VERSION = '2.*';
const LATEST_HELM3_VERSION = '3.*';
const helmAllReleasesUrl = 'https://api.github.com/repos/helm/helm/releases';
function getExecutableExtension(): string { export function getExecutableExtension(): string {
if (os.type().match(/^Win/)) { if (os.type().match(/^Win/)) {
return '.exe'; return '.exe';
} }
return ''; return '';
} }
function getHelmDownloadURL(version: string): string { export function getHelmDownloadURL(version: string): string {
switch (os.type()) { switch (os.type()) {
case 'Linux': case 'Linux':
return util.format('https://get.helm.sh/helm-%s-linux-amd64.zip', version); return util.format('https://get.helm.sh/helm-%s-linux-amd64.zip', version);
@ -31,46 +38,52 @@ function getHelmDownloadURL(version: string): string {
case 'Windows_NT': case 'Windows_NT':
default: default:
return util.format('https://get.helm.sh/helm-%s-windows-amd64.zip', version); return util.format('https://get.helm.sh/helm-%s-windows-amd64.zip', version);
} }
} }
async function getStableHelmVersion(): Promise<string> { export async function getStableHelmVersion(): Promise<string> {
return toolCache.downloadTool(helmLatestReleaseUrl).then((downloadPath) => { try {
const response = JSON.parse(fs.readFileSync(downloadPath, 'utf8').toString().trim()); const downloadPath = await toolCache.downloadTool(helmAllReleasesUrl);
if (!response.tag_name) const responseArray = JSON.parse(fs.readFileSync(downloadPath, 'utf8').toString().trim());
{ let latestHelmVersion = semver.clean(stableHelmVersion);
return stableHelmVersion; responseArray.forEach(response => {
} if (response && response.tag_name) {
let currentHelmVerison = semver.clean(response.tag_name.toString());
return response.tag_name; if (currentHelmVerison) {
}, (error) => { if (currentHelmVerison.toString().indexOf('rc') == -1 && semver.gt(currentHelmVerison, latestHelmVersion)) {
core.debug(error); //If current helm version is not a pre release and is greater than latest helm version
core.warning(util.format("Failed to read latest kubectl version from stable.txt. From URL %s. Using default stable version %s", helmLatestReleaseUrl, stableHelmVersion)); latestHelmVersion = currentHelmVerison;
return stableHelmVersion; }
}); }
}
});
latestHelmVersion = "v" + latestHelmVersion;
return latestHelmVersion;
} catch (error) {
core.warning(util.format("Cannot get the latest Helm info from %s. Error %s. Using default Helm version %s.", helmAllReleasesUrl, error, stableHelmVersion));
}
return stableHelmVersion;
} }
export var walkSync = function (dir, filelist, fileToFind) {
var walkSync = function(dir, filelist, fileToFind) {
var files = fs.readdirSync(dir); var files = fs.readdirSync(dir);
filelist = filelist || []; filelist = filelist || [];
files.forEach(function(file) { files.forEach(function (file) {
if (fs.statSync(path.join(dir, file)).isDirectory()) { if (fs.statSync(path.join(dir, file)).isDirectory()) {
filelist = walkSync(path.join(dir, file), filelist, fileToFind); filelist = walkSync(path.join(dir, file), filelist, fileToFind);
} }
else { else {
core.debug(file); core.debug(file);
if(file == fileToFind) if (file == fileToFind) {
{ filelist.push(path.join(dir, file));
filelist.push(path.join(dir, file)); }
} }
}
}); });
return filelist; return filelist;
}; };
async function downloadHelm(version: string): Promise<string> { export async function downloadHelm(version: string): Promise<string> {
if (!version) { version = await getStableHelmVersion(); } if (!version) { version = await getStableHelmVersion(); }
let cachedToolpath = toolCache.find(helmToolName, version); let cachedToolpath = toolCache.find(helmToolName, version);
if (!cachedToolpath) { if (!cachedToolpath) {
@ -78,7 +91,7 @@ async function downloadHelm(version: string): Promise<string> {
try { try {
helmDownloadPath = await toolCache.downloadTool(getHelmDownloadURL(version)); helmDownloadPath = await toolCache.downloadTool(getHelmDownloadURL(version));
} catch (exception) { } catch (exception) {
throw new Error(util.format("Failed to download Helm from location ", getHelmDownloadURL(version))); throw new Error(util.format("Failed to download Helm from location", getHelmDownloadURL(version)));
} }
fs.chmodSync(helmDownloadPath, '777'); fs.chmodSync(helmDownloadPath, '777');
@ -88,32 +101,100 @@ async function downloadHelm(version: string): Promise<string> {
const helmpath = findHelm(cachedToolpath); const helmpath = findHelm(cachedToolpath);
if (!helmpath) { if (!helmpath) {
throw new Error(util.format("Helm executable not found in path ", cachedToolpath)); throw new Error(util.format("Helm executable not found in path", cachedToolpath));
} }
fs.chmodSync(helmpath, '777'); fs.chmodSync(helmpath, '777');
return helmpath; return helmpath;
} }
function findHelm(rootFolder: string): string { async function getLatestHelmVersionFor(type: string): Promise<string> {
const token = core.getInput('token', { 'required': true });
try {
const { repository } = await graphql(
`{
repository(name: "helm", owner: "helm") {
releases(last: 100) {
nodes {
tagName
}
}
}
}`,
{
headers: {
authorization: `token ${token}`
}
}
);
const releases = repository.releases.nodes.reverse();
let latestValidRelease = releases.find(release => isValidVersion(release.tagName, type));
if (latestValidRelease)
return latestValidRelease.tagName;
} catch (err) {
core.warning(util.format("Error while fetching the latest Helm %s release. Error: %s. Using default Helm version %s.", type, err.toString(), getStableHelmVersionFor(type)));
return getStableHelmVersionFor(type);
}
core.warning(util.format("Could not find stable release for Helm %s. Using default Helm version %s.", type, getStableHelmVersionFor(type)));
return getStableHelmVersionFor(type);
}
function getStableHelmVersionFor(type: string) {
return type === "v2" ? stableHelm2Version : stableHelm3Version;
}
// isValidVersion checks if verison matches the specified type and is a stable release
function isValidVersion(version: string, type: string): boolean {
if (!version.toLocaleLowerCase().startsWith(type))
return false;
return version.indexOf('rc') == -1;
}
export function findHelm(rootFolder: string): string {
fs.chmodSync(rootFolder, '777'); fs.chmodSync(rootFolder, '777');
var filelist: string[] = []; var filelist: string[] = [];
walkSync(rootFolder, filelist, helmToolName + getExecutableExtension()); walkSync(rootFolder, filelist, helmToolName + getExecutableExtension());
if (!filelist) { if (!filelist || filelist.length == 0) {
throw new Error(util.format("Helm executable not found in path ", rootFolder)); throw new Error(util.format("Helm executable not found in path", rootFolder));
} }
else { else {
return filelist[0]; return filelist[0];
} }
} }
async function run() { export async function run() {
let version = core.getInput('version', { 'required': true }); let version = core.getInput('version', { 'required': true });
if (version.toLocaleLowerCase() === 'latest') {
version = await getStableHelmVersion(); if (process.env['HELM_INSTALLER_LEGACY_VERSIONING'] == 'true') {
if (version.toLocaleLowerCase() === 'latest') {
version = await getStableHelmVersion();
} else if (!version.toLocaleLowerCase().startsWith('v')) {
version = 'v' + version;
}
} else {
if (version.toLocaleLowerCase() === 'latest' || version === LATEST_HELM3_VERSION) {
version = await getLatestHelmVersionFor("v3");
} else if (version === LATEST_HELM2_VERSION) {
version = await getLatestHelmVersionFor("v2");
} else if (!version.toLocaleLowerCase().startsWith('v')) {
version = 'v' + version;
}
} }
core.debug(util.format("Downloading %s", version));
let cachedPath = await downloadHelm(version); let cachedPath = await downloadHelm(version);
try {
if (!process.env['PATH'].startsWith(path.dirname(cachedPath))) {
core.addPath(path.dirname(cachedPath));
}
}
catch {
//do nothing, set as output variable
}
console.log(`Helm tool version: '${version}' has been cached at ${cachedPath}`); console.log(`Helm tool version: '${version}' has been cached at ${cachedPath}`);
core.setOutput('helm-path', cachedPath); core.setOutput('helm-path', cachedPath);
} }

View File

@ -22,7 +22,7 @@
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */ /* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */ //"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */ // "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
@ -45,7 +45,7 @@
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
@ -58,6 +58,7 @@
/* Experimental Options */ /* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"skipLibCheck": true
}, },
"exclude": ["node_modules", "**/*.test.ts"] "exclude": ["node_modules", "**/*.test.ts"]
} }