Compare commits

..

1 Commits

Author SHA1 Message Date
GitHub Action
b7246b12e7 build 2024-03-01 16:46:38 +00:00
8 changed files with 203 additions and 396 deletions

View File

@@ -13,6 +13,6 @@ jobs:
permissions: permissions:
actions: read actions: read
contents: write contents: write
uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@v1 uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@a705b2ab6a3ee889f2b0d925ad0bd2f9eb733ce6
with: with:
changelogPath: ./CHANGELOG.md changelogPath: ./CHANGELOG.md

View File

@@ -1,9 +1,5 @@
# Change Log # Change Log
## [4.2.0] - 2024-04-15
- #124 Fix OS detection and download OS-native archive extension
## [4.1.0] - 2024-03-01 ## [4.1.0] - 2024-03-01
- #130 switches to use Helm published file to read latest version instead of using GitHub releases - #130 switches to use Helm published file to read latest version instead of using GitHub releases

View File

@@ -7,7 +7,7 @@ Install a specific version of helm binary on the runner.
Acceptable values are latest or any semantic version string like v3.5.0 Use this action in workflow to define which version of helm will be used. v2+ of this action only support Helm3. Acceptable values are latest or any semantic version string like v3.5.0 Use this action in workflow to define which version of helm will be used. v2+ of this action only support Helm3.
```yaml ```yaml
- uses: azure/setup-helm@v4.2.0 - uses: azure/setup-helm@v4.1.0
with: with:
version: '<version>' # default is latest (stable) version: '<version>' # default is latest (stable)
id: install id: install

View File

@@ -12308,132 +12308,6 @@ function onConnectTimeout (socket) {
module.exports = buildConnector module.exports = buildConnector
/***/ }),
/***/ 4462:
/***/ ((module) => {
"use strict";
/** @type {Record<string, string | undefined>} */
const headerNameLowerCasedRecord = {}
// https://developer.mozilla.org/docs/Web/HTTP/Headers
const wellknownHeaderNames = [
'Accept',
'Accept-Encoding',
'Accept-Language',
'Accept-Ranges',
'Access-Control-Allow-Credentials',
'Access-Control-Allow-Headers',
'Access-Control-Allow-Methods',
'Access-Control-Allow-Origin',
'Access-Control-Expose-Headers',
'Access-Control-Max-Age',
'Access-Control-Request-Headers',
'Access-Control-Request-Method',
'Age',
'Allow',
'Alt-Svc',
'Alt-Used',
'Authorization',
'Cache-Control',
'Clear-Site-Data',
'Connection',
'Content-Disposition',
'Content-Encoding',
'Content-Language',
'Content-Length',
'Content-Location',
'Content-Range',
'Content-Security-Policy',
'Content-Security-Policy-Report-Only',
'Content-Type',
'Cookie',
'Cross-Origin-Embedder-Policy',
'Cross-Origin-Opener-Policy',
'Cross-Origin-Resource-Policy',
'Date',
'Device-Memory',
'Downlink',
'ECT',
'ETag',
'Expect',
'Expect-CT',
'Expires',
'Forwarded',
'From',
'Host',
'If-Match',
'If-Modified-Since',
'If-None-Match',
'If-Range',
'If-Unmodified-Since',
'Keep-Alive',
'Last-Modified',
'Link',
'Location',
'Max-Forwards',
'Origin',
'Permissions-Policy',
'Pragma',
'Proxy-Authenticate',
'Proxy-Authorization',
'RTT',
'Range',
'Referer',
'Referrer-Policy',
'Refresh',
'Retry-After',
'Sec-WebSocket-Accept',
'Sec-WebSocket-Extensions',
'Sec-WebSocket-Key',
'Sec-WebSocket-Protocol',
'Sec-WebSocket-Version',
'Server',
'Server-Timing',
'Service-Worker-Allowed',
'Service-Worker-Navigation-Preload',
'Set-Cookie',
'SourceMap',
'Strict-Transport-Security',
'Supports-Loading-Mode',
'TE',
'Timing-Allow-Origin',
'Trailer',
'Transfer-Encoding',
'Upgrade',
'Upgrade-Insecure-Requests',
'User-Agent',
'Vary',
'Via',
'WWW-Authenticate',
'X-Content-Type-Options',
'X-DNS-Prefetch-Control',
'X-Frame-Options',
'X-Permitted-Cross-Domain-Policies',
'X-Powered-By',
'X-Requested-With',
'X-XSS-Protection'
]
for (let i = 0; i < wellknownHeaderNames.length; ++i) {
const key = wellknownHeaderNames[i]
const lowerCasedKey = key.toLowerCase()
headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] =
lowerCasedKey
}
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
Object.setPrototypeOf(headerNameLowerCasedRecord, null)
module.exports = {
wellknownHeaderNames,
headerNameLowerCasedRecord
}
/***/ }), /***/ }),
/***/ 8045: /***/ 8045:
@@ -13266,7 +13140,6 @@ const { InvalidArgumentError } = __nccwpck_require__(8045)
const { Blob } = __nccwpck_require__(4300) const { Blob } = __nccwpck_require__(4300)
const nodeUtil = __nccwpck_require__(3837) const nodeUtil = __nccwpck_require__(3837)
const { stringify } = __nccwpck_require__(3477) const { stringify } = __nccwpck_require__(3477)
const { headerNameLowerCasedRecord } = __nccwpck_require__(4462)
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
@@ -13476,15 +13349,6 @@ function parseKeepAliveTimeout (val) {
return m ? parseInt(m[1], 10) * 1000 : null return m ? parseInt(m[1], 10) * 1000 : null
} }
/**
* Retrieves a header name and returns its lowercase value.
* @param {string | Buffer} value Header name
* @returns {string}
*/
function headerNameToString (value) {
return headerNameLowerCasedRecord[value] || value.toLowerCase()
}
function parseHeaders (headers, obj = {}) { function parseHeaders (headers, obj = {}) {
// For H2 support // For H2 support
if (!Array.isArray(headers)) return headers if (!Array.isArray(headers)) return headers
@@ -13756,7 +13620,6 @@ module.exports = {
isIterable, isIterable,
isAsyncIterable, isAsyncIterable,
isDestroyed, isDestroyed,
headerNameToString,
parseRawHeaders, parseRawHeaders,
parseHeaders, parseHeaders,
parseKeepAliveTimeout, parseKeepAliveTimeout,
@@ -17893,9 +17756,6 @@ function httpRedirectFetch (fetchParams, response) {
// https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name
request.headersList.delete('authorization') request.headersList.delete('authorization')
// https://fetch.spec.whatwg.org/#authentication-entries
request.headersList.delete('proxy-authorization', true)
// "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement. // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement.
request.headersList.delete('cookie') request.headersList.delete('cookie')
request.headersList.delete('host') request.headersList.delete('host')
@@ -20404,18 +20264,14 @@ const { isBlobLike, toUSVString, ReadableStreamFrom } = __nccwpck_require__(3983
const assert = __nccwpck_require__(9491) const assert = __nccwpck_require__(9491)
const { isUint8Array } = __nccwpck_require__(9830) const { isUint8Array } = __nccwpck_require__(9830)
let supportedHashes = []
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable // https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
/** @type {import('crypto')|undefined} */ /** @type {import('crypto')|undefined} */
let crypto let crypto
try { try {
crypto = __nccwpck_require__(6113) crypto = __nccwpck_require__(6113)
const possibleRelevantHashes = ['sha256', 'sha384', 'sha512']
supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash))
/* c8 ignore next 3 */
} catch { } catch {
} }
function responseURL (response) { function responseURL (response) {
@@ -20943,56 +20799,66 @@ function bytesMatch (bytes, metadataList) {
return true return true
} }
// 3. If response is not eligible for integrity validation, return false. // 3. If parsedMetadata is the empty set, return true.
// TODO
// 4. If parsedMetadata is the empty set, return true.
if (parsedMetadata.length === 0) { if (parsedMetadata.length === 0) {
return true return true
} }
// 5. Let metadata be the result of getting the strongest // 4. Let metadata be the result of getting the strongest
// metadata from parsedMetadata. // metadata from parsedMetadata.
const strongest = getStrongestMetadata(parsedMetadata) const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest) // get the strongest algorithm
const strongest = list[0].algo
// get all entries that use the strongest algorithm; ignore weaker
const metadata = list.filter((item) => item.algo === strongest)
// 6. For each item in metadata: // 5. For each item in metadata:
for (const item of metadata) { for (const item of metadata) {
// 1. Let algorithm be the alg component of item. // 1. Let algorithm be the alg component of item.
const algorithm = item.algo const algorithm = item.algo
// 2. Let expectedValue be the val component of item. // 2. Let expectedValue be the val component of item.
const expectedValue = item.hash let expectedValue = item.hash
// See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e
// "be liberal with padding". This is annoying, and it's not even in the spec. // "be liberal with padding". This is annoying, and it's not even in the spec.
if (expectedValue.endsWith('==')) {
expectedValue = expectedValue.slice(0, -2)
}
// 3. Let actualValue be the result of applying algorithm to bytes. // 3. Let actualValue be the result of applying algorithm to bytes.
let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
if (actualValue[actualValue.length - 1] === '=') { if (actualValue.endsWith('==')) {
if (actualValue[actualValue.length - 2] === '=') {
actualValue = actualValue.slice(0, -2) actualValue = actualValue.slice(0, -2)
} else {
actualValue = actualValue.slice(0, -1)
}
} }
// 4. If actualValue is a case-sensitive match for expectedValue, // 4. If actualValue is a case-sensitive match for expectedValue,
// return true. // return true.
if (compareBase64Mixed(actualValue, expectedValue)) { if (actualValue === expectedValue) {
return true
}
let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url')
if (actualBase64URL.endsWith('==')) {
actualBase64URL = actualBase64URL.slice(0, -2)
}
if (actualBase64URL === expectedValue) {
return true return true
} }
} }
// 7. Return false. // 6. Return false.
return false return false
} }
// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
// https://www.w3.org/TR/CSP2/#source-list-syntax // https://www.w3.org/TR/CSP2/#source-list-syntax
// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1 // https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-((?<hash>[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i const parseHashWithOptions = /((?<algo>sha256|sha384|sha512)-(?<hash>[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i
/** /**
* @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
@@ -21006,6 +20872,8 @@ function parseMetadata (metadata) {
// 2. Let empty be equal to true. // 2. Let empty be equal to true.
let empty = true let empty = true
const supportedHashes = crypto.getHashes()
// 3. For each token returned by splitting metadata on spaces: // 3. For each token returned by splitting metadata on spaces:
for (const token of metadata.split(' ')) { for (const token of metadata.split(' ')) {
// 1. Set empty to false. // 1. Set empty to false.
@@ -21015,11 +20883,7 @@ function parseMetadata (metadata) {
const parsedToken = parseHashWithOptions.exec(token) const parsedToken = parseHashWithOptions.exec(token)
// 3. If token does not parse, continue to the next token. // 3. If token does not parse, continue to the next token.
if ( if (parsedToken === null || parsedToken.groups === undefined) {
parsedToken === null ||
parsedToken.groups === undefined ||
parsedToken.groups.algo === undefined
) {
// Note: Chromium blocks the request at this point, but Firefox // Note: Chromium blocks the request at this point, but Firefox
// gives a warning that an invalid integrity was given. The // gives a warning that an invalid integrity was given. The
// correct behavior is to ignore these, and subsequently not // correct behavior is to ignore these, and subsequently not
@@ -21028,11 +20892,11 @@ function parseMetadata (metadata) {
} }
// 4. Let algorithm be the hash-algo component of token. // 4. Let algorithm be the hash-algo component of token.
const algorithm = parsedToken.groups.algo.toLowerCase() const algorithm = parsedToken.groups.algo
// 5. If algorithm is a hash function recognized by the user // 5. If algorithm is a hash function recognized by the user
// agent, add the parsed token to result. // agent, add the parsed token to result.
if (supportedHashes.includes(algorithm)) { if (supportedHashes.includes(algorithm.toLowerCase())) {
result.push(parsedToken.groups) result.push(parsedToken.groups)
} }
} }
@@ -21045,82 +20909,6 @@ function parseMetadata (metadata) {
return result return result
} }
/**
* @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList
*/
function getStrongestMetadata (metadataList) {
// Let algorithm be the algo component of the first item in metadataList.
// Can be sha256
let algorithm = metadataList[0].algo
// If the algorithm is sha512, then it is the strongest
// and we can return immediately
if (algorithm[3] === '5') {
return algorithm
}
for (let i = 1; i < metadataList.length; ++i) {
const metadata = metadataList[i]
// If the algorithm is sha512, then it is the strongest
// and we can break the loop immediately
if (metadata.algo[3] === '5') {
algorithm = 'sha512'
break
// If the algorithm is sha384, then a potential sha256 or sha384 is ignored
} else if (algorithm[3] === '3') {
continue
// algorithm is sha256, check if algorithm is sha384 and if so, set it as
// the strongest
} else if (metadata.algo[3] === '3') {
algorithm = 'sha384'
}
}
return algorithm
}
function filterMetadataListByAlgorithm (metadataList, algorithm) {
if (metadataList.length === 1) {
return metadataList
}
let pos = 0
for (let i = 0; i < metadataList.length; ++i) {
if (metadataList[i].algo === algorithm) {
metadataList[pos++] = metadataList[i]
}
}
metadataList.length = pos
return metadataList
}
/**
* Compares two base64 strings, allowing for base64url
* in the second string.
*
* @param {string} actualValue always base64
* @param {string} expectedValue base64 or base64url
* @returns {boolean}
*/
function compareBase64Mixed (actualValue, expectedValue) {
if (actualValue.length !== expectedValue.length) {
return false
}
for (let i = 0; i < actualValue.length; ++i) {
if (actualValue[i] !== expectedValue[i]) {
if (
(actualValue[i] === '+' && expectedValue[i] === '-') ||
(actualValue[i] === '/' && expectedValue[i] === '_')
) {
continue
}
return false
}
}
return true
}
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request // https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
// TODO // TODO
@@ -21536,8 +21324,7 @@ module.exports = {
urlHasHttpsScheme, urlHasHttpsScheme,
urlIsHttpHttpsScheme, urlIsHttpHttpsScheme,
readAllBytes, readAllBytes,
normalizeMethodRecord, normalizeMethodRecord
parseMetadata
} }
@@ -23624,17 +23411,12 @@ function parseLocation (statusCode, headers) {
// https://tools.ietf.org/html/rfc7231#section-6.4.4 // https://tools.ietf.org/html/rfc7231#section-6.4.4
function shouldRemoveHeader (header, removeContent, unknownOrigin) { function shouldRemoveHeader (header, removeContent, unknownOrigin) {
if (header.length === 4) { return (
return util.headerNameToString(header) === 'host' (header.length === 4 && header.toString().toLowerCase() === 'host') ||
} (removeContent && header.toString().toLowerCase().indexOf('content-') === 0) ||
if (removeContent && util.headerNameToString(header).startsWith('content-')) { (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') ||
return true (unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie')
} )
if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
const name = util.headerNameToString(header)
return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
}
return false
} }
// https://tools.ietf.org/html/rfc7231#section-6.4 // https://tools.ietf.org/html/rfc7231#section-6.4
@@ -28800,7 +28582,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}); });
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.walkSync = exports.findHelm = exports.downloadHelm = exports.getHelmDownloadURL = exports.getExecutableExtension = exports.getArchiveExtension = exports.getPlatform = exports.getArch = exports.getLatestHelmVersion = exports.getValidVersion = exports.run = void 0; exports.walkSync = exports.findHelm = exports.downloadHelm = exports.getHelmDownloadURL = exports.getExecutableExtension = exports.getLatestHelmVersion = exports.getValidVersion = exports.run = void 0;
const os = __nccwpck_require__(2037); const os = __nccwpck_require__(2037);
const path = __nccwpck_require__(1017); const path = __nccwpck_require__(1017);
const util = __nccwpck_require__(3837); const util = __nccwpck_require__(3837);
@@ -28856,24 +28638,38 @@ function getLatestHelmVersion() {
}); });
} }
exports.getLatestHelmVersion = getLatestHelmVersion; exports.getLatestHelmVersion = getLatestHelmVersion;
function getArch() {
return os.arch() === 'x64' ? 'amd64' : os.arch();
}
exports.getArch = getArch;
function getPlatform() {
return os.platform() === 'win32' ? 'windows' : os.platform();
}
exports.getPlatform = getPlatform;
function getArchiveExtension() {
return os.platform() === 'win32' ? 'zip' : 'tar.gz';
}
exports.getArchiveExtension = getArchiveExtension;
function getExecutableExtension() { function getExecutableExtension() {
return os.platform() === 'win32' ? '.exe' : ''; if (os.type().match(/^Win/)) {
return '.exe';
}
return '';
} }
exports.getExecutableExtension = getExecutableExtension; exports.getExecutableExtension = getExecutableExtension;
const LINUX = 'Linux';
const MAC_OS = 'Darwin';
const WINDOWS = 'Windows_NT';
const ARM64 = 'arm64';
function getHelmDownloadURL(baseURL, version) { function getHelmDownloadURL(baseURL, version) {
const urlPath = `helm-${version}-${getPlatform()}-${getArch()}.${getArchiveExtension()}`; const arch = os.arch();
const operatingSystem = os.type();
let urlPath = '';
switch (true) {
case operatingSystem == LINUX && arch == ARM64:
urlPath = util.format(`/helm-%s-linux-arm64.zip`, version);
break;
case operatingSystem == LINUX:
urlPath = util.format(`/helm-%s-linux-amd64.zip`, version);
break;
case operatingSystem == MAC_OS && arch == ARM64:
urlPath = util.format(`/helm-%s-darwin-arm64.zip`, version);
break;
case operatingSystem == MAC_OS:
urlPath = util.format(`/helm-%s-darwin-amd64.zip`, version);
break;
case operatingSystem == WINDOWS:
default:
urlPath = util.format(`/helm-%s-windows-amd64.zip`, version);
}
const url = new URL(urlPath, baseURL); const url = new URL(urlPath, baseURL);
return url.toString(); return url.toString();
} }
@@ -28890,10 +28686,8 @@ function downloadHelm(baseURL, version) {
throw new Error(`Failed to download Helm from location ${getHelmDownloadURL(baseURL, version)}`); throw new Error(`Failed to download Helm from location ${getHelmDownloadURL(baseURL, version)}`);
} }
fs.chmodSync(helmDownloadPath, '777'); fs.chmodSync(helmDownloadPath, '777');
const extractedPath = getPlatform() === 'windows' const unzipedHelmPath = yield toolCache.extractZip(helmDownloadPath);
? yield toolCache.extractZip(helmDownloadPath) cachedToolpath = yield toolCache.cacheDir(unzipedHelmPath, helmToolName, version);
: yield toolCache.extractTar(helmDownloadPath);
cachedToolpath = yield toolCache.cacheDir(extractedPath, helmToolName, version);
} }
const helmpath = findHelm(cachedToolpath); const helmpath = findHelm(cachedToolpath);
if (!helmpath) { if (!helmpath) {

19
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "setuphelm", "name": "setuphelm",
"version": "4.2.0", "version": "4.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "setuphelm", "name": "setuphelm",
"version": "4.2.0", "version": "4.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
@@ -1117,9 +1117,12 @@
} }
}, },
"node_modules/@octokit/action/node_modules/undici": { "node_modules/@octokit/action/node_modules/undici": {
"version": "6.11.1", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.5.0.tgz",
"integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==", "integrity": "sha512-/MUmPb2ptTvp1j7lPvdMSofMdqPxcOhAaKZi4k55sqm6XMeKI3n1dZJ5cnD4gLjpt2l7CIlthR1IXM59xKhpxw==",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": { "engines": {
"node": ">=18.0" "node": ">=18.0"
} }
@@ -3837,9 +3840,9 @@
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "5.28.4", "version": "5.28.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz",
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==",
"dependencies": { "dependencies": {
"@fastify/busboy": "^2.0.0" "@fastify/busboy": "^2.0.0"
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "setuphelm", "name": "setuphelm",
"version": "4.2.0", "version": "4.1.0",
"private": true, "private": true,
"description": "Setup helm", "description": "Setup helm",
"author": "Anumita Shenoy", "author": "Anumita Shenoy",

View File

@@ -6,74 +6,84 @@ import * as path from 'path'
import * as core from '@actions/core' import * as core from '@actions/core'
describe('run.ts', () => { describe('run.ts', () => {
const downloadBaseURL = 'https://test.tld'
// Cleanup mocks after each test to ensure that subsequent tests are not affected by the mocks.
afterEach(() => {
jest.restoreAllMocks()
})
test('getExecutableExtension() - return .exe when os is Windows', () => { test('getExecutableExtension() - return .exe when os is Windows', () => {
jest.spyOn(os, 'platform').mockReturnValue('win32') jest.spyOn(os, 'type').mockReturnValue('Windows_NT')
expect(run.getExecutableExtension()).toBe('.exe') expect(run.getExecutableExtension()).toBe('.exe')
expect(os.platform).toHaveBeenCalled() expect(os.type).toHaveBeenCalled()
}) })
test('getExecutableExtension() - return empty string for non-windows OS', () => { test('getExecutableExtension() - return empty string for non-windows OS', () => {
jest.spyOn(os, 'platform').mockReturnValue('darwin') jest.spyOn(os, 'type').mockReturnValue('Darwin')
expect(run.getExecutableExtension()).toBe('') expect(run.getExecutableExtension()).toBe('')
expect(os.platform).toHaveBeenCalled() expect(os.type).toHaveBeenCalled()
}) })
test('getHelmDownloadURL() - return the URL to download helm for Linux amd64', () => { test('getHelmDownloadURL() - return the URL to download helm for Linux', () => {
jest.spyOn(os, 'platform').mockReturnValue('linux') const downloadBaseURL = 'https://test.tld'
jest.spyOn(os, 'arch').mockReturnValue('x64')
const expected = 'https://test.tld/helm-v3.8.0-linux-amd64.tar.gz'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected) jest.spyOn(os, 'type').mockReturnValue('Linux')
expect(os.platform).toHaveBeenCalled() jest.spyOn(os, 'arch').mockReturnValueOnce('unknown')
const helmLinuxUrl = 'https://test.tld/helm-v3.8.0-linux-amd64.zip'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(
helmLinuxUrl
)
expect(os.type).toHaveBeenCalled()
expect(os.arch).toHaveBeenCalled()
// arm64
jest.spyOn(os, 'type').mockReturnValue('Linux')
jest.spyOn(os, 'arch').mockReturnValueOnce('arm64')
const helmLinuxArm64Url = 'https://test.tld/helm-v3.8.0-linux-arm64.zip'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(
helmLinuxArm64Url
)
expect(os.type).toHaveBeenCalled()
expect(os.arch).toHaveBeenCalled() expect(os.arch).toHaveBeenCalled()
}) })
test('getHelmDownloadURL() - return the URL to download helm for Linux arm64', () => { test('getHelmDownloadURL() - return the URL to download helm for Darwin', () => {
jest.spyOn(os, 'platform').mockReturnValue('linux') const downloadBaseURL = 'https://test.tld'
jest.spyOn(os, 'arch').mockReturnValue('arm64')
const expected = 'https://test.tld/helm-v3.8.0-linux-arm64.tar.gz'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected) jest.spyOn(os, 'type').mockReturnValue('Darwin')
expect(os.platform).toHaveBeenCalled() jest.spyOn(os, 'arch').mockReturnValueOnce('unknown')
const helmDarwinUrl = 'https://test.tld/helm-v3.8.0-darwin-amd64.zip'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(
helmDarwinUrl
)
expect(os.type).toHaveBeenCalled()
expect(os.arch).toHaveBeenCalled()
// arm64
jest.spyOn(os, 'type').mockReturnValue('Darwin')
jest.spyOn(os, 'arch').mockReturnValueOnce('arm64')
const helmDarwinArm64Url = 'https://test.tld/helm-v3.8.0-darwin-arm64.zip'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(
helmDarwinArm64Url
)
expect(os.type).toHaveBeenCalled()
expect(os.arch).toHaveBeenCalled() expect(os.arch).toHaveBeenCalled()
}) })
test('getHelmDownloadURL() - return the URL to download helm for Darwin x64', () => { test('getValidVersion() - return version with v prepended', () => {
jest.spyOn(os, 'platform').mockReturnValue('darwin') expect(run.getValidVersion('3.8.0')).toBe('v3.8.0')
jest.spyOn(os, 'arch').mockReturnValue('x64')
const expected = 'https://test.tld/helm-v3.8.0-darwin-amd64.tar.gz'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
expect(os.platform).toHaveBeenCalled()
expect(os.arch).toHaveBeenCalled()
})
test('getHelmDownloadURL() - return the URL to download helm for Darwin arm64', () => {
jest.spyOn(os, 'platform').mockReturnValue('darwin')
jest.spyOn(os, 'arch').mockReturnValue('arm64')
const expected = 'https://test.tld/helm-v3.8.0-darwin-arm64.tar.gz'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
expect(os.platform).toHaveBeenCalled()
expect(os.arch).toHaveBeenCalled()
}) })
test('getHelmDownloadURL() - return the URL to download helm for Windows', () => { test('getHelmDownloadURL() - return the URL to download helm for Windows', () => {
jest.spyOn(os, 'platform').mockReturnValue('win32') const downloadBaseURL = 'https://test.tld'
jest.spyOn(os, 'arch').mockReturnValue('x64')
const expected = 'https://test.tld/helm-v3.8.0-windows-amd64.zip' jest.spyOn(os, 'type').mockReturnValue('Windows_NT')
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
expect(os.platform).toHaveBeenCalled() const helmWindowsUrl = 'https://test.tld/helm-v3.8.0-windows-amd64.zip'
expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(
helmWindowsUrl
)
expect(os.type).toHaveBeenCalled()
}) })
test('getLatestHelmVersion() - return the latest version of HELM', async () => { test('getLatestHelmVersion() - return the latest version of HELM', async () => {
@@ -91,10 +101,6 @@ describe('run.ts', () => {
expect(await run.getLatestHelmVersion()).toBe('v3.13.3') expect(await run.getLatestHelmVersion()).toBe('v3.13.3')
}) })
test('getValidVersion() - return version with v prepended', () => {
expect(run.getValidVersion('3.8.0')).toBe('v3.8.0')
})
test('walkSync() - return path to the all files matching fileToFind in dir', () => { test('walkSync() - return path to the all files matching fileToFind in dir', () => {
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => { jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
if (file == 'mainFolder') if (file == 'mainFolder')
@@ -114,7 +120,6 @@ describe('run.ts', () => {
'file21' as unknown as fs.Dirent, 'file21' as unknown as fs.Dirent,
'file22' as unknown as fs.Dirent 'file22' as unknown as fs.Dirent
] ]
return []
}) })
jest.spyOn(core, 'debug').mockImplementation() jest.spyOn(core, 'debug').mockImplementation()
jest.spyOn(fs, 'statSync').mockImplementation((file) => { jest.spyOn(fs, 'statSync').mockImplementation((file) => {
@@ -149,7 +154,6 @@ describe('run.ts', () => {
'file21' as unknown as fs.Dirent, 'file21' as unknown as fs.Dirent,
'file22' as unknown as fs.Dirent 'file22' as unknown as fs.Dirent
] ]
return []
}) })
jest.spyOn(core, 'debug').mockImplementation() jest.spyOn(core, 'debug').mockImplementation()
jest.spyOn(fs, 'statSync').mockImplementation((file) => { jest.spyOn(fs, 'statSync').mockImplementation((file) => {
@@ -167,14 +171,13 @@ describe('run.ts', () => {
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {}) jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => { jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
if (file == 'mainFolder') return ['helm.exe' as unknown as fs.Dirent] if (file == 'mainFolder') return ['helm.exe' as unknown as fs.Dirent]
return []
}) })
jest.spyOn(fs, 'statSync').mockImplementation((file) => { jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory = const isDirectory =
(file as string).indexOf('folder') == -1 ? false : true (file as string).indexOf('folder') == -1 ? false : true
return {isDirectory: () => isDirectory} as fs.Stats return {isDirectory: () => isDirectory} as fs.Stats
}) })
jest.spyOn(os, 'platform').mockReturnValue('win32') jest.spyOn(os, 'type').mockReturnValue('Windows_NT')
expect(run.findHelm('mainFolder')).toBe( expect(run.findHelm('mainFolder')).toBe(
path.join('mainFolder', 'helm.exe') path.join('mainFolder', 'helm.exe')
@@ -185,13 +188,11 @@ describe('run.ts', () => {
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {}) jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => { jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
if (file == 'mainFolder') return [] if (file == 'mainFolder') return []
return []
}) })
jest.spyOn(fs, 'statSync').mockImplementation((file) => { jest.spyOn(fs, 'statSync').mockImplementation((file) => {
return {isDirectory: () => true} as fs.Stats return {isDirectory: () => true} as fs.Stats
}) })
jest.spyOn(os, 'platform').mockReturnValue('win32') jest.spyOn(os, 'type').mockReturnValue('Windows_NT')
expect(() => run.findHelm('mainFolder')).toThrow( expect(() => run.findHelm('mainFolder')).toThrow(
'Helm executable not found in path mainFolder' 'Helm executable not found in path mainFolder'
) )
@@ -202,9 +203,11 @@ describe('run.ts', () => {
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool') jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
const response = JSON.stringify([{tag_name: 'v4.0.0'}]) const response = JSON.stringify([{tag_name: 'v4.0.0'}])
jest.spyOn(fs, 'readFileSync').mockReturnValue(response) jest.spyOn(fs, 'readFileSync').mockReturnValue(response)
jest.spyOn(os, 'platform').mockReturnValue('win32') jest.spyOn(os, 'type').mockReturnValue('Windows_NT')
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {}) jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath') jest
.spyOn(toolCache, 'extractZip')
.mockResolvedValue('pathToUnzippedHelm')
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir') jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir')
jest jest
.spyOn(fs, 'readdirSync') .spyOn(fs, 'readdirSync')
@@ -215,7 +218,9 @@ describe('run.ts', () => {
return {isDirectory: () => isDirectory} as fs.Stats return {isDirectory: () => isDirectory} as fs.Stats
}) })
expect(await run.downloadHelm(downloadBaseURL, 'v4.0.0')).toBe( const baseURL = 'https://test.tld'
expect(await run.downloadHelm(baseURL, 'v4.0.0')).toBe(
path.join('pathToCachedDir', 'helm.exe') path.join('pathToCachedDir', 'helm.exe')
) )
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v4.0.0') expect(toolCache.find).toHaveBeenCalledWith('helm', 'v4.0.0')
@@ -235,33 +240,26 @@ describe('run.ts', () => {
jest.spyOn(toolCache, 'downloadTool').mockImplementation(async () => { jest.spyOn(toolCache, 'downloadTool').mockImplementation(async () => {
throw 'Unable to download' throw 'Unable to download'
}) })
jest.spyOn(os, 'platform').mockReturnValue('win32') jest.spyOn(os, 'type').mockReturnValue('Windows_NT')
const downloadUrl = 'https://test.tld/helm-v3.2.1-windows-amd64.zip' const baseURL = 'https://test.tld'
await expect(run.downloadHelm(downloadBaseURL, 'v3.2.1')).rejects.toThrow(
`Failed to download Helm from location ${downloadUrl}` await expect(run.downloadHelm(baseURL, 'v3.2.1')).rejects.toThrow(
'Failed to download Helm from location https://test.tld/helm-v3.2.1-windows-amd64.zip'
) )
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1') expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1')
expect(toolCache.downloadTool).toHaveBeenCalledWith(`${downloadUrl}`) expect(toolCache.downloadTool).toHaveBeenCalledWith(
'https://test.tld/helm-v3.2.1-windows-amd64.zip'
)
}) })
test('downloadHelm() - return path to helm tool with same version from toolCache', async () => { test('downloadHelm() - return path to helm tool with same version from toolCache', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('pathToCachedDir') jest.spyOn(toolCache, 'find').mockReturnValue('pathToCachedDir')
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir')
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath')
jest.spyOn(os, 'platform').mockReturnValue('win32')
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {}) jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
jest
.spyOn(fs, 'readdirSync')
.mockReturnValue(['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(downloadBaseURL, 'v3.2.1')).toBe( const baseURL = 'https://test.tld'
expect(await run.downloadHelm(baseURL, 'v3.2.1')).toBe(
path.join('pathToCachedDir', 'helm.exe') path.join('pathToCachedDir', 'helm.exe')
) )
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1') expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1')
@@ -274,11 +272,12 @@ describe('run.ts', () => {
test('downloadHelm() - throw error is helm is not found in path', async () => { test('downloadHelm() - throw error is helm is not found in path', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('') jest.spyOn(toolCache, 'find').mockReturnValue('')
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool') jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir') jest.spyOn(os, 'type').mockReturnValue('Windows_NT')
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath')
jest.spyOn(os, 'platform').mockReturnValue('win32')
jest.spyOn(fs, 'chmodSync').mockImplementation() 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, 'readdirSync').mockImplementation((file, _) => [])
jest.spyOn(fs, 'statSync').mockImplementation((file) => { jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory = const isDirectory =
@@ -286,7 +285,9 @@ describe('run.ts', () => {
return {isDirectory: () => isDirectory} as fs.Stats return {isDirectory: () => isDirectory} as fs.Stats
}) })
await expect(run.downloadHelm(downloadBaseURL, 'v3.2.1')).rejects.toThrow( const baseURL = 'https://test.tld'
await expect(run.downloadHelm(baseURL, 'v3.2.1')).rejects.toThrow(
'Helm executable not found in path pathToCachedDir' 'Helm executable not found in path pathToCachedDir'
) )
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1') expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1')

View File

@@ -61,24 +61,41 @@ export async function getLatestHelmVersion(): Promise<string> {
} }
} }
export function getArch(): string {
return os.arch() === 'x64' ? 'amd64' : os.arch()
}
export function getPlatform(): string {
return os.platform() === 'win32' ? 'windows' : os.platform()
}
export function getArchiveExtension(): string {
return os.platform() === 'win32' ? 'zip' : 'tar.gz'
}
export function getExecutableExtension(): string { export function getExecutableExtension(): string {
return os.platform() === 'win32' ? '.exe' : '' if (os.type().match(/^Win/)) {
return '.exe'
}
return ''
} }
const LINUX = 'Linux'
const MAC_OS = 'Darwin'
const WINDOWS = 'Windows_NT'
const ARM64 = 'arm64'
export function getHelmDownloadURL(baseURL: string, version: string): string { export function getHelmDownloadURL(baseURL: string, version: string): string {
const urlPath = `helm-${version}-${getPlatform()}-${getArch()}.${getArchiveExtension()}` const arch = os.arch()
const operatingSystem = os.type()
let urlPath = ''
switch (true) {
case operatingSystem == LINUX && arch == ARM64:
urlPath = util.format(`/helm-%s-linux-arm64.zip`, version)
break
case operatingSystem == LINUX:
urlPath = util.format(`/helm-%s-linux-amd64.zip`, version)
break
case operatingSystem == MAC_OS && arch == ARM64:
urlPath = util.format(`/helm-%s-darwin-arm64.zip`, version)
break
case operatingSystem == MAC_OS:
urlPath = util.format(`/helm-%s-darwin-amd64.zip`, version)
break
case operatingSystem == WINDOWS:
default:
urlPath = util.format(`/helm-%s-windows-amd64.zip`, version)
}
const url = new URL(urlPath, baseURL) const url = new URL(urlPath, baseURL)
return url.toString() return url.toString()
} }
@@ -104,13 +121,9 @@ export async function downloadHelm(
} }
fs.chmodSync(helmDownloadPath, '777') fs.chmodSync(helmDownloadPath, '777')
const extractedPath = const unzipedHelmPath = await toolCache.extractZip(helmDownloadPath)
getPlatform() === 'windows'
? await toolCache.extractZip(helmDownloadPath)
: await toolCache.extractTar(helmDownloadPath)
cachedToolpath = await toolCache.cacheDir( cachedToolpath = await toolCache.cacheDir(
extractedPath, unzipedHelmPath,
helmToolName, helmToolName,
version version
) )