From eba997bc80d51e2f57e0dc47f5fcd6fd1959c5d8 Mon Sep 17 00:00:00 2001 From: John Bristowe Date: Tue, 3 May 2022 22:48:21 +1000 Subject: [PATCH] feat: incorporated SemVer support --- src/octopus-cli.ts | 152 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 29 deletions(-) diff --git a/src/octopus-cli.ts b/src/octopus-cli.ts index fb3b1f64..9938e355 100644 --- a/src/octopus-cli.ts +++ b/src/octopus-cli.ts @@ -7,17 +7,16 @@ import { extractZip } from '@actions/tool-cache' import {debug, info, setFailed} from '@actions/core' -import {Downloads} from './download' import {HttpClient} from '@actions/http-client' import {join, dirname} from 'path' import {v4} from 'uuid' +import {OctopusCLIVersionFetcher} from './octopusCLIVersionFetcher' const osPlatform: string = os.platform() -const platform: string = - osPlatform === 'win32' ? 'win' : osPlatform === 'darwin' ? 'osx' : 'linux' const ext: string = osPlatform === 'win32' ? 'zip' : 'tar.gz' -const octopusTools = `https://download.octopusdeploy.com/octopus-tools` -const latestUrl = `${octopusTools}/latest.json` +const baseUrl = `https://g.octopushq.com/` +const versionsUrl = `${baseUrl}/OctopusCLIVersions` +const latestToolsUrl = `${baseUrl}/LatestTools` const http: HttpClient = new HttpClient( 'action-install-octopus-cli', undefined, @@ -26,40 +25,128 @@ const http: HttpClient = new HttpClient( } ) -interface OctopusCliDownload { +interface LatestToolsResponse { + latest: string + downloads: DownloadOption[] +} + +type Primitive = undefined | null | boolean | number | string + +interface Dictionary { + [key: string]: Primitive +} + +type DownloadOption = { version: string - url: string + template: string + location: string + extension: string + platform?: string + architecture?: string } -const getLatest = async (): Promise => { - return (await http.getJson(latestUrl)).result +export interface Endpoint { + downloadUrl: string + version: string +} + +interface VersionsResponse { + versions: string[] } -const getDownloadUrl = async (version: string): Promise => { - let versionToDownload: string = version - if (version === 'latest') { - try { - const downloads: Downloads | null = await getLatest() - if (downloads !== null) { - versionToDownload = downloads.latest - } - } catch (e: unknown) { - if (e instanceof Error) { - setFailed(e) - } +const getVersions = async (): Promise => { + return (await http.getJson(versionsUrl)).result +} + +const getDownloadUrl = async (versionSpec: string): Promise => { + if (versionSpec === 'latest') { + versionSpec = '*' + } + + const versionsResponse: VersionsResponse | null = await getVersions() + if (versionsResponse === null) { + setFailed(`✕ Unable to get versions...`) + throw new Error(`✕ Unable to get versions...`) + } + + let version: string | null = versionSpec + try { + version = new OctopusCLIVersionFetcher( + versionsResponse.versions + ).getVersion(versionSpec) + info(`Latest version: ${version}`) + } catch (e: unknown) { + if (e instanceof Error) { + setFailed(e) } } - const downloadUrl = `${octopusTools}/${versionToDownload}/OctopusTools.${versionToDownload}.${platform}-x64.${ext}` + if (version === null) { + setFailed( + `✕ The version specified (${version}) is not available to download.` + ) + throw new Error( + `✕ The version specified (${version}) is not available to download.` + ) + } + + debug(`Attempting to find Octopus CLI version ${version}`) + + const latestToolsResponse = await http.getJson( + latestToolsUrl + ) + + if ( + latestToolsResponse.result === null || + latestToolsResponse.result === undefined + ) { + throw Error( + `Failed to resolve Octopus CLI version ${version}. Endpoint returned ${latestToolsResponse.statusCode} status code.` + ) + } + + let platform = 'linux' + switch (osPlatform) { + case 'darwin': + platform = 'osx' + break + case 'win32': + platform = 'win' + break + } + + let downloadUrl: string | undefined + + for (const download of latestToolsResponse.result.downloads) { + if (download.platform === platform) { + const result = {...download, version} + downloadUrl = applyTemplate(result, download.template) + } + } + + if (downloadUrl === undefined || downloadUrl === null) { + throw Error(`Failed to resolve endpoint URL to download: ${downloadUrl}`) + } const statusCode = (await http.head(downloadUrl)).message.statusCode if (statusCode !== 200) { - setFailed(`✕ Octopus CLI version not found: ${versionToDownload}`) - throw new Error(`Octopus CLI version not found: ${versionToDownload}`) + setFailed(`✕ Octopus CLI version not found: ${version}`) + throw new Error(`Octopus CLI version not found: ${version}`) } - info(`✓ Octopus CLI version found: ${versionToDownload}`) - return {version: versionToDownload, url: downloadUrl} + info(`✓ Octopus CLI version found: ${version}`) + return {downloadUrl, version} +} + +function applyTemplate(dictionary: Dictionary, template: string): string { + return Object.keys(dictionary).reduce( + (result, key) => + result.replace( + new RegExp(`{${key}}`, 'g'), + dictionary[key] ? String(dictionary[key]) : '' + ), + template + ) } export async function installOctopusCli(version: string): Promise { @@ -68,19 +155,26 @@ export async function installOctopusCli(version: string): Promise { info(`⬇️ Downloading Octopus CLI ${octopusCliDownload.version}...`) const dest = join(process.env['RUNNER_TEMP'] || '', `${v4()}.${ext}`) await fs.mkdir(dirname(dest), {recursive: true}) - const downloadPath: string = await downloadTool(octopusCliDownload.url, dest) + const downloadPath: string = await downloadTool( + octopusCliDownload.downloadUrl, + dest + ) debug(`Downloaded to ${downloadPath}`) info(`📦 Extracting Octopus CLI ${octopusCliDownload.version}...`) let extPath = '' if (osPlatform === 'win32') { extPath = await extractZip(downloadPath) - } else if (octopusCliDownload.url.endsWith('.gz')) { + } else if (octopusCliDownload.downloadUrl.endsWith('.gz')) { extPath = await extractTar(downloadPath) } debug(`Extracted to ${extPath}`) - const cachePath: string = await cacheDir(extPath, 'octo', version) + const cachePath: string = await cacheDir( + extPath, + 'octo', + octopusCliDownload.version + ) debug(`Cached to ${cachePath}`) const exePath: string = join(