From 282a81e1fc50840ce1b9522a53f29a601aaccd86 Mon Sep 17 00:00:00 2001 From: Gennady Trubach <10942688+gtrubach@users.noreply.github.com> Date: Mon, 15 Mar 2021 10:16:34 +0300 Subject: [PATCH] Added arch detection to kubectl download (#117) --- __tests__/run.test.ts | 28 +++++++++++++++++++++++++--- lib/utilities/kubectl-util.js | 26 ++++++++++++++++++++------ src/utilities/kubectl-util.ts | 26 ++++++++++++++++++++------ 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/__tests__/run.test.ts b/__tests__/run.test.ts index 766487ea..e161966f 100644 --- a/__tests__/run.test.ts +++ b/__tests__/run.test.ts @@ -6,6 +6,7 @@ import * as deployment from '../src/utilities/strategy-helpers/deployment-helper import * as fs from 'fs'; import * as io from '@actions/io'; import * as toolCache from '@actions/tool-cache'; +import * as util from 'util'; import * as fileHelper from '../src/utilities/files-helper'; import { getWorkflowAnnotationKeyLabel, getWorkflowAnnotationsJson } from '../src/constants'; import * as inputParam from '../src/input-parameters'; @@ -23,6 +24,7 @@ const os = require("os"); const coreMock = mocked(core, true); const ioMock = mocked(io, true); const inputParamMock = mocked(inputParam, true); +const osMock = mocked(os, true); const toolCacheMock = mocked(toolCache, true); const fileUtility = mocked(fs, true); @@ -91,10 +93,15 @@ beforeEach(() => { process.env['GITHUB_TOKEN'] = 'testToken'; }) -test("setKubectlPath() - install a particular version", async () => { +test.each([ + ['arm', 'arm'], + ['arm64', 'arm64'], + ['x64', 'amd64'] +])("setKubectlPath() - install a particular version on %s", async (osArch, kubectlArch) => { const kubectlVersion = 'v1.18.0' //Mocks coreMock.getInput = jest.fn().mockReturnValue(kubectlVersion); + osMock.arch = jest.fn().mockReturnValue(osArch); toolCacheMock.find = jest.fn().mockReturnValue(undefined); toolCacheMock.downloadTool = jest.fn().mockReturnValue('downloadpath'); toolCacheMock.cacheFile = jest.fn().mockReturnValue('cachepath'); @@ -103,7 +110,7 @@ test("setKubectlPath() - install a particular version", async () => { //Invoke and assert await expect(action.run()).resolves.not.toThrow(); expect(toolCacheMock.find).toBeCalledWith('kubectl', kubectlVersion); - expect(toolCacheMock.downloadTool).toBeCalledWith(getkubectlDownloadURL(kubectlVersion)); + expect(toolCacheMock.downloadTool).toBeCalledWith(getkubectlDownloadURL(kubectlVersion, kubectlArch)); }); test("setKubectlPath() - install a latest version", async () => { @@ -394,4 +401,19 @@ test("utility - getWorkflowFilePath() - Get workflow file path under API failure //Invoke and assert await expect(utility.getWorkflowFilePath(process.env.GITHUB_TOKEN)).resolves.not.toThrowError; await expect(utility.getWorkflowFilePath(process.env.GITHUB_TOKEN)).resolves.toBe(process.env.GITHUB_WORKFLOW); -}); \ No newline at end of file +}); + +test("action - run() - Throw kubectl error on 404 response", async () => { + const kubectlVersion = 'v1.18.0' + const arch = 'arm128'; + // Mock + coreMock.getInput = jest.fn().mockReturnValue(kubectlVersion); + osMock.arch = jest.fn().mockReturnValue(arch); + toolCacheMock.find = jest.fn().mockReturnValue(undefined); + toolCacheMock.downloadTool = jest.fn().mockImplementation(_ => { + throw new toolCache.HTTPError(httpClient.StatusCodes.NOT_FOUND); + }); + + //Invoke and assert + await expect(action.run()).rejects.toThrow(util.format("Kubectl '%s' for '%s' arch not found.", kubectlVersion, arch)); +}); diff --git a/lib/utilities/kubectl-util.js b/lib/utilities/kubectl-util.js index 7b4b0063..02fb3e1f 100644 --- a/lib/utilities/kubectl-util.js +++ b/lib/utilities/kubectl-util.js @@ -16,6 +16,7 @@ const os = require("os"); const path = require("path"); const toolCache = require("@actions/tool-cache"); const util = require("util"); +const httpClient_1 = require("./httpClient"); const kubectlToolName = 'kubectl'; const stableKubectlVersion = 'v1.15.0'; const stableVersionUrl = 'https://storage.googleapis.com/kubernetes-release/release/stable.txt'; @@ -26,15 +27,22 @@ function getExecutableExtension() { } return ''; } -function getkubectlDownloadURL(version) { +function getKubectlArch() { + let arch = os.arch(); + if (arch === 'x64') { + return 'amd64'; + } + return arch; +} +function getkubectlDownloadURL(version, arch) { switch (os.type()) { case 'Linux': - return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/linux/amd64/kubectl', version); + return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/linux/%s/kubectl', version, arch); case 'Darwin': - return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/darwin/amd64/kubectl', version); + return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/darwin/%s/kubectl', version, arch); case 'Windows_NT': default: - return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/windows/amd64/kubectl.exe', version); + return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/windows/%s/kubectl.exe', version, arch); } } exports.getkubectlDownloadURL = getkubectlDownloadURL; @@ -58,12 +66,18 @@ function downloadKubectl(version) { return __awaiter(this, void 0, void 0, function* () { let cachedToolpath = toolCache.find(kubectlToolName, version); let kubectlDownloadPath = ''; + let arch = getKubectlArch(); if (!cachedToolpath) { try { - kubectlDownloadPath = yield toolCache.downloadTool(getkubectlDownloadURL(version)); + kubectlDownloadPath = yield toolCache.downloadTool(getkubectlDownloadURL(version, arch)); } catch (exception) { - throw new Error('DownloadKubectlFailed'); + if (exception instanceof toolCache.HTTPError && exception.httpStatusCode === httpClient_1.StatusCodes.NOT_FOUND) { + throw new Error(util.format("Kubectl '%s' for '%s' arch not found.", version, arch)); + } + else { + throw new Error('DownloadKubectlFailed'); + } } cachedToolpath = yield toolCache.cacheFile(kubectlDownloadPath, kubectlToolName + getExecutableExtension(), kubectlToolName, version); } diff --git a/src/utilities/kubectl-util.ts b/src/utilities/kubectl-util.ts index 11b202ec..ce335ba1 100644 --- a/src/utilities/kubectl-util.ts +++ b/src/utilities/kubectl-util.ts @@ -6,6 +6,7 @@ import * as toolCache from '@actions/tool-cache'; import * as util from 'util'; import { Kubectl } from '../kubectl-object-model'; +import { StatusCodes } from "./httpClient" const kubectlToolName = 'kubectl'; const stableKubectlVersion = 'v1.15.0'; @@ -19,17 +20,25 @@ function getExecutableExtension(): string { return ''; } -export function getkubectlDownloadURL(version: string): string { +function getKubectlArch(): string { + let arch = os.arch(); + if (arch === 'x64') { + return 'amd64'; + } + return arch; +} + +export function getkubectlDownloadURL(version: string, arch: string): string { switch (os.type()) { case 'Linux': - return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/linux/amd64/kubectl', version); + return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/linux/%s/kubectl', version, arch); case 'Darwin': - return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/darwin/amd64/kubectl', version); + return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/darwin/%s/kubectl', version, arch); case 'Windows_NT': default: - return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/windows/amd64/kubectl.exe', version); + return util.format('https://storage.googleapis.com/kubernetes-release/release/%s/bin/windows/%s/kubectl.exe', version, arch); } } @@ -51,11 +60,16 @@ export async function getStableKubectlVersion(): Promise { export async function downloadKubectl(version: string): Promise { let cachedToolpath = toolCache.find(kubectlToolName, version); let kubectlDownloadPath = ''; + let arch = getKubectlArch(); if (!cachedToolpath) { try { - kubectlDownloadPath = await toolCache.downloadTool(getkubectlDownloadURL(version)); + kubectlDownloadPath = await toolCache.downloadTool(getkubectlDownloadURL(version, arch)); } catch (exception) { - throw new Error('DownloadKubectlFailed'); + if (exception instanceof toolCache.HTTPError && exception.httpStatusCode === StatusCodes.NOT_FOUND) { + throw new Error(util.format("Kubectl '%s' for '%s' arch not found.", version, arch)); + } else { + throw new Error('DownloadKubectlFailed'); + } } cachedToolpath = await toolCache.cacheFile(kubectlDownloadPath, kubectlToolName + getExecutableExtension(), kubectlToolName, version);