diff --git a/action.yml b/action.yml index b67b5fb..4df0392 100644 --- a/action.yml +++ b/action.yml @@ -14,6 +14,10 @@ inputs: description: 'Set the download base URL' required: false default: 'https://get.helm.sh' + downloadBaseURLFallback: + description: 'Fallback base URL if primary download fails' + required: false + default: '' outputs: helm-path: description: 'Path to the cached helm binary' diff --git a/src/run.test.ts b/src/run.test.ts index 6e10689..08e35a4 100644 --- a/src/run.test.ts +++ b/src/run.test.ts @@ -228,7 +228,7 @@ describe('run.ts', () => { return {isDirectory: () => isDirectory} as fs.Stats }) - expect(await run.downloadHelm(downloadBaseURL, 'v4.0.0')).toBe( + expect(await run.downloadHelm(downloadBaseURL, 'v4.0.0', '')).toBe( path.join('pathToCachedDir', 'helm.exe') ) expect(toolCache.find).toHaveBeenCalledWith('helm', 'v4.0.0') @@ -252,9 +252,9 @@ describe('run.ts', () => { jest.spyOn(os, 'arch').mockReturnValue('x64') const downloadUrl = 'https://test.tld/helm-v3.2.1-windows-amd64.zip' - await expect(run.downloadHelm(downloadBaseURL, 'v3.2.1')).rejects.toThrow( - `Failed to download Helm from location ${downloadUrl}` - ) + await expect( + run.downloadHelm(downloadBaseURL, 'v3.2.1', '') + ).rejects.toThrow(`Failed to download Helm from location ${downloadUrl}`) expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1') expect(toolCache.downloadTool).toHaveBeenCalledWith(`${downloadUrl}`) }) @@ -278,7 +278,7 @@ describe('run.ts', () => { return {isDirectory: () => isDirectory} as fs.Stats }) - expect(await run.downloadHelm(downloadBaseURL, 'v3.2.1')).toBe( + expect(await run.downloadHelm(downloadBaseURL, 'v3.2.1', '')).toBe( path.join('pathToCachedDir', 'helm.exe') ) expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1') @@ -304,9 +304,9 @@ describe('run.ts', () => { return {isDirectory: () => isDirectory} as fs.Stats }) - await expect(run.downloadHelm(downloadBaseURL, 'v3.2.1')).rejects.toThrow( - 'Helm executable not found in path pathToCachedDir' - ) + await expect( + run.downloadHelm(downloadBaseURL, 'v3.2.1', '') + ).rejects.toThrow('Helm executable not found in path pathToCachedDir') expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1') expect(toolCache.downloadTool).toHaveBeenCalledWith( 'https://test.tld/helm-v3.2.1-windows-amd64.zip' @@ -314,4 +314,86 @@ describe('run.ts', () => { expect(fs.chmodSync).toHaveBeenCalledWith('pathToTool', '777') expect(toolCache.extractZip).toHaveBeenCalledWith('pathToTool') }) + + test('downloadHelm() - use fallback URL when primary download fails', async () => { + const fallbackBaseURL = 'https://fallback.tld' + jest.spyOn(toolCache, 'find').mockReturnValue('') + jest + .spyOn(toolCache, 'downloadTool') + .mockRejectedValueOnce(new Error('Primary download failed')) + .mockResolvedValueOnce('pathToTool') + jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath') + jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir') + jest.spyOn(os, 'platform').mockReturnValue('win32') + jest.spyOn(os, 'arch').mockReturnValue('x64') + jest.spyOn(fs, 'chmodSync').mockImplementation() + jest.spyOn(core, 'warning').mockImplementation() + 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(downloadBaseURL, 'v3.2.1', fallbackBaseURL) + ).toBe(path.join('pathToCachedDir', 'helm.exe')) + expect(toolCache.downloadTool).toHaveBeenCalledTimes(2) + expect(toolCache.downloadTool).toHaveBeenNthCalledWith( + 1, + 'https://test.tld/helm-v3.2.1-windows-amd64.zip' + ) + expect(toolCache.downloadTool).toHaveBeenNthCalledWith( + 2, + 'https://fallback.tld/helm-v3.2.1-windows-amd64.zip' + ) + expect(core.warning).toHaveBeenCalled() + }) + + test('downloadHelm() - throw error if both primary and fallback downloads fail', async () => { + const fallbackBaseURL = 'https://fallback.tld' + jest.spyOn(toolCache, 'find').mockReturnValue('') + jest + .spyOn(toolCache, 'downloadTool') + .mockRejectedValueOnce(new Error('Primary download failed')) + .mockRejectedValueOnce(new Error('Fallback download failed')) + jest.spyOn(os, 'platform').mockReturnValue('win32') + jest.spyOn(os, 'arch').mockReturnValue('x64') + + await expect( + run.downloadHelm(downloadBaseURL, 'v3.2.1', fallbackBaseURL) + ).rejects.toThrow( + 'Failed to download Helm from location https://test.tld/helm-v3.2.1-windows-amd64.zip or https://fallback.tld/helm-v3.2.1-windows-amd64.zip' + ) + expect(toolCache.downloadTool).toHaveBeenCalledTimes(2) + }) + + test('downloadHelm() - work without fallback URL (backwards compatibility)', async () => { + jest.spyOn(toolCache, 'find').mockReturnValue('') + jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool') + jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath') + jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir') + jest.spyOn(os, 'platform').mockReturnValue('win32') + jest.spyOn(os, 'arch').mockReturnValue('x64') + jest.spyOn(fs, 'chmodSync').mockImplementation() + 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(downloadBaseURL, 'v3.2.1')).toBe( + path.join('pathToCachedDir', 'helm.exe') + ) + expect(toolCache.downloadTool).toHaveBeenCalledTimes(1) + }) }) diff --git a/src/run.ts b/src/run.ts index 7e30c13..90daa60 100644 --- a/src/run.ts +++ b/src/run.ts @@ -24,9 +24,16 @@ export async function run() { } const downloadBaseURL = core.getInput('downloadBaseURL', {required: false}) + const downloadBaseURLFallback = core.getInput('downloadBaseURLFallback', { + required: false + }) core.startGroup(`Installing ${version}`) - const cachedPath = await downloadHelm(downloadBaseURL, version) + const cachedPath = await downloadHelm( + downloadBaseURL, + version, + downloadBaseURLFallback + ) core.endGroup() try { @@ -84,7 +91,8 @@ export function getHelmDownloadURL(baseURL: string, version: string): string { export async function downloadHelm( baseURL: string, - version: string + version: string, + fallbackBaseURL?: string ): Promise { let cachedToolpath = toolCache.find(helmToolName, version) if (cachedToolpath) { @@ -97,12 +105,33 @@ export async function downloadHelm( getHelmDownloadURL(baseURL, version) ) } catch (exception) { - throw new Error( - `Failed to download Helm from location ${getHelmDownloadURL( - baseURL, - version - )}` - ) + if (fallbackBaseURL) { + core.warning( + `Failed to download Helm from location ${getHelmDownloadURL( + baseURL, + version + )}. Attempting to download from fallback URL: ${fallbackBaseURL}` + ) + try { + helmDownloadPath = await toolCache.downloadTool( + getHelmDownloadURL(fallbackBaseURL, version) + ) + } catch (fallbackException) { + throw new Error( + `Failed to download Helm from location ${getHelmDownloadURL( + baseURL, + version + )} or ${getHelmDownloadURL(fallbackBaseURL, version)}` + ) + } + } else { + throw new Error( + `Failed to download Helm from location ${getHelmDownloadURL( + baseURL, + version + )}` + ) + } } fs.chmodSync(helmDownloadPath, '777')