2019-06-20 13:28:39 -04:00
|
|
|
import * as tc from '@actions/tool-cache';
|
2020-06-29 18:41:13 +03:00
|
|
|
import * as core from '@actions/core';
|
2019-06-20 13:28:39 -04:00
|
|
|
import * as path from 'path';
|
2019-08-19 19:28:37 +07:00
|
|
|
import * as semver from 'semver';
|
2020-02-09 00:29:21 -05:00
|
|
|
import * as httpm from '@actions/http-client';
|
|
|
|
import * as sys from './system';
|
2022-05-12 17:04:39 +09:00
|
|
|
import fs from 'fs';
|
2020-06-29 18:41:13 +03:00
|
|
|
import os from 'os';
|
2022-12-12 10:58:49 +01:00
|
|
|
import {StableReleaseAlias} from './utils';
|
2019-11-20 15:24:28 -05:00
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
type InstallationType = 'dist' | 'manifest';
|
2019-06-20 13:28:39 -04:00
|
|
|
|
2020-02-09 00:21:39 -05:00
|
|
|
export interface IGoVersionFile {
|
2020-02-09 00:29:21 -05:00
|
|
|
filename: string;
|
2020-02-09 00:21:39 -05:00
|
|
|
// darwin, linux, windows
|
2020-02-09 00:29:21 -05:00
|
|
|
os: string;
|
|
|
|
arch: string;
|
2019-06-20 13:28:39 -04:00
|
|
|
}
|
|
|
|
|
2020-02-09 00:21:39 -05:00
|
|
|
export interface IGoVersion {
|
|
|
|
version: string;
|
|
|
|
stable: boolean;
|
|
|
|
files: IGoVersionFile[];
|
2019-06-20 13:28:39 -04:00
|
|
|
}
|
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
export interface IGoVersionInfo {
|
|
|
|
type: InstallationType;
|
|
|
|
downloadUrl: string;
|
|
|
|
resolvedVersion: string;
|
|
|
|
fileName: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getGo(
|
|
|
|
versionSpec: string,
|
2022-02-09 14:59:04 +03:00
|
|
|
checkLatest: boolean,
|
2022-08-12 12:29:48 +02:00
|
|
|
auth: string | undefined,
|
|
|
|
arch = os.arch()
|
2020-06-29 18:41:13 +03:00
|
|
|
) {
|
2022-12-12 10:58:49 +01:00
|
|
|
let manifest: tc.IToolRelease[] | undefined;
|
2020-06-29 18:41:13 +03:00
|
|
|
let osPlat: string = os.platform();
|
|
|
|
|
2022-12-12 10:58:49 +01:00
|
|
|
if (
|
|
|
|
versionSpec === StableReleaseAlias.Stable ||
|
|
|
|
versionSpec === StableReleaseAlias.OldStable
|
|
|
|
) {
|
|
|
|
manifest = await getManifest(auth);
|
|
|
|
let stableVersion = await resolveStableVersionInput(
|
|
|
|
versionSpec,
|
|
|
|
arch,
|
|
|
|
osPlat,
|
|
|
|
manifest
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!stableVersion) {
|
|
|
|
stableVersion = await resolveStableVersionDist(versionSpec, arch);
|
|
|
|
if (!stableVersion) {
|
|
|
|
throw new Error(
|
|
|
|
`Unable to find Go version '${versionSpec}' for platform ${osPlat} and architecture ${arch}.`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-12 15:45:36 +01:00
|
|
|
core.info(`${versionSpec} version resolved as ${stableVersion}`);
|
|
|
|
|
2022-12-12 10:58:49 +01:00
|
|
|
versionSpec = stableVersion;
|
|
|
|
}
|
|
|
|
|
2022-02-09 14:59:04 +03:00
|
|
|
if (checkLatest) {
|
|
|
|
core.info('Attempting to resolve the latest version from the manifest...');
|
|
|
|
const resolvedVersion = await resolveVersionFromManifest(
|
|
|
|
versionSpec,
|
2022-02-28 10:16:32 +03:00
|
|
|
true,
|
2022-08-12 12:29:48 +02:00
|
|
|
auth,
|
2022-12-12 10:58:49 +01:00
|
|
|
arch,
|
|
|
|
manifest
|
2022-02-09 14:59:04 +03:00
|
|
|
);
|
|
|
|
if (resolvedVersion) {
|
|
|
|
versionSpec = resolvedVersion;
|
|
|
|
core.info(`Resolved as '${versionSpec}'`);
|
|
|
|
} else {
|
|
|
|
core.info(`Failed to resolve version ${versionSpec} from manifest`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
// check cache
|
|
|
|
let toolPath: string;
|
2022-08-12 12:29:48 +02:00
|
|
|
toolPath = tc.find('go', versionSpec, arch);
|
2020-06-29 18:41:13 +03:00
|
|
|
// If not found in cache, download
|
|
|
|
if (toolPath) {
|
|
|
|
core.info(`Found in cache @ ${toolPath}`);
|
|
|
|
return toolPath;
|
|
|
|
}
|
|
|
|
core.info(`Attempting to download ${versionSpec}...`);
|
|
|
|
let downloadPath = '';
|
|
|
|
let info: IGoVersionInfo | null = null;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Try download from internal distribution (popular versions only)
|
|
|
|
//
|
|
|
|
try {
|
2022-12-12 10:58:49 +01:00
|
|
|
info = await getInfoFromManifest(versionSpec, true, auth, arch, manifest);
|
2020-06-29 18:41:13 +03:00
|
|
|
if (info) {
|
2022-08-12 12:29:48 +02:00
|
|
|
downloadPath = await installGoVersion(info, auth, arch);
|
2020-06-29 18:41:13 +03:00
|
|
|
} else {
|
|
|
|
core.info(
|
|
|
|
'Not found in manifest. Falling back to download directly from Go'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
if (
|
|
|
|
err instanceof tc.HTTPError &&
|
|
|
|
(err.httpStatusCode === 403 || err.httpStatusCode === 429)
|
|
|
|
) {
|
|
|
|
core.info(
|
|
|
|
`Received HTTP status code ${err.httpStatusCode}. This usually indicates the rate limit has been exceeded`
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
core.info(err.message);
|
|
|
|
}
|
|
|
|
core.debug(err.stack);
|
|
|
|
core.info('Falling back to download directly from Go');
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Download from storage.googleapis.com
|
|
|
|
//
|
|
|
|
if (!downloadPath) {
|
2022-08-12 12:29:48 +02:00
|
|
|
info = await getInfoFromDist(versionSpec, arch);
|
2020-06-29 18:41:13 +03:00
|
|
|
if (!info) {
|
|
|
|
throw new Error(
|
2022-08-12 12:29:48 +02:00
|
|
|
`Unable to find Go version '${versionSpec}' for platform ${osPlat} and architecture ${arch}.`
|
2020-06-29 18:41:13 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
core.info('Install from dist');
|
2022-08-12 12:29:48 +02:00
|
|
|
downloadPath = await installGoVersion(info, undefined, arch);
|
2020-06-29 18:41:13 +03:00
|
|
|
} catch (err) {
|
|
|
|
throw new Error(`Failed to download version ${versionSpec}: ${err}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return downloadPath;
|
|
|
|
}
|
|
|
|
|
2022-02-09 14:59:04 +03:00
|
|
|
async function resolveVersionFromManifest(
|
|
|
|
versionSpec: string,
|
|
|
|
stable: boolean,
|
2022-08-12 12:29:48 +02:00
|
|
|
auth: string | undefined,
|
2022-12-12 10:58:49 +01:00
|
|
|
arch: string,
|
|
|
|
manifest: tc.IToolRelease[] | undefined
|
2022-02-09 14:59:04 +03:00
|
|
|
): Promise<string | undefined> {
|
|
|
|
try {
|
2022-12-12 10:58:49 +01:00
|
|
|
const info = await getInfoFromManifest(
|
|
|
|
versionSpec,
|
|
|
|
stable,
|
|
|
|
auth,
|
|
|
|
arch,
|
|
|
|
manifest
|
|
|
|
);
|
2022-02-09 14:59:04 +03:00
|
|
|
return info?.resolvedVersion;
|
|
|
|
} catch (err) {
|
|
|
|
core.info('Unable to resolve a version from the manifest...');
|
|
|
|
core.debug(err.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
async function installGoVersion(
|
|
|
|
info: IGoVersionInfo,
|
2022-08-12 12:29:48 +02:00
|
|
|
auth: string | undefined,
|
|
|
|
arch: string
|
2020-06-29 18:41:13 +03:00
|
|
|
): Promise<string> {
|
|
|
|
core.info(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`);
|
2022-07-28 17:22:06 -05:00
|
|
|
|
|
|
|
// Windows requires that we keep the extension (.zip) for extraction
|
|
|
|
const isWindows = os.platform() === 'win32';
|
|
|
|
const tempDir = process.env.RUNNER_TEMP || '.';
|
|
|
|
const fileName = isWindows ? path.join(tempDir, info.fileName) : undefined;
|
|
|
|
|
|
|
|
const downloadPath = await tc.downloadTool(info.downloadUrl, fileName, auth);
|
2020-06-29 18:41:13 +03:00
|
|
|
|
|
|
|
core.info('Extracting Go...');
|
|
|
|
let extPath = await extractGoArchive(downloadPath);
|
|
|
|
core.info(`Successfully extracted go to ${extPath}`);
|
|
|
|
if (info.type === 'dist') {
|
|
|
|
extPath = path.join(extPath, 'go');
|
|
|
|
}
|
|
|
|
|
|
|
|
core.info('Adding to the cache ...');
|
|
|
|
const cachedDir = await tc.cacheDir(
|
|
|
|
extPath,
|
|
|
|
'go',
|
2022-08-12 12:29:48 +02:00
|
|
|
makeSemver(info.resolvedVersion),
|
|
|
|
arch
|
2020-06-29 18:41:13 +03:00
|
|
|
);
|
|
|
|
core.info(`Successfully cached go to ${cachedDir}`);
|
|
|
|
return cachedDir;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function extractGoArchive(archivePath: string): Promise<string> {
|
2021-08-19 21:19:21 +02:00
|
|
|
const platform = os.platform();
|
2020-06-29 18:41:13 +03:00
|
|
|
let extPath: string;
|
|
|
|
|
2021-08-19 21:19:21 +02:00
|
|
|
if (platform === 'win32') {
|
2020-06-29 18:41:13 +03:00
|
|
|
extPath = await tc.extractZip(archivePath);
|
|
|
|
} else {
|
|
|
|
extPath = await tc.extractTar(archivePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
return extPath;
|
|
|
|
}
|
|
|
|
|
2022-12-12 10:58:49 +01:00
|
|
|
export async function getManifest(auth: string | undefined) {
|
|
|
|
return tc.getManifestFromRepo('actions', 'go-versions', auth, 'main');
|
|
|
|
}
|
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
export async function getInfoFromManifest(
|
|
|
|
versionSpec: string,
|
|
|
|
stable: boolean,
|
2022-08-12 12:29:48 +02:00
|
|
|
auth: string | undefined,
|
2022-12-12 10:58:49 +01:00
|
|
|
arch = os.arch(),
|
|
|
|
manifest?: tc.IToolRelease[] | undefined
|
2020-06-29 18:41:13 +03:00
|
|
|
): Promise<IGoVersionInfo | null> {
|
|
|
|
let info: IGoVersionInfo | null = null;
|
2022-12-12 10:58:49 +01:00
|
|
|
if (!manifest) {
|
|
|
|
core.debug('No manifest cached');
|
|
|
|
manifest = await getManifest(auth);
|
|
|
|
}
|
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
core.info(`matching ${versionSpec}...`);
|
2022-12-12 10:58:49 +01:00
|
|
|
|
|
|
|
const rel = await tc.findFromManifest(versionSpec, stable, manifest, arch);
|
2020-06-29 18:41:13 +03:00
|
|
|
|
|
|
|
if (rel && rel.files.length > 0) {
|
|
|
|
info = <IGoVersionInfo>{};
|
|
|
|
info.type = 'manifest';
|
|
|
|
info.resolvedVersion = rel.version;
|
|
|
|
info.downloadUrl = rel.files[0].download_url;
|
|
|
|
info.fileName = rel.files[0].filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getInfoFromDist(
|
2022-08-12 12:29:48 +02:00
|
|
|
versionSpec: string,
|
|
|
|
arch: string
|
2020-06-29 18:41:13 +03:00
|
|
|
): Promise<IGoVersionInfo | null> {
|
|
|
|
let version: IGoVersion | undefined;
|
2022-08-12 12:29:48 +02:00
|
|
|
version = await findMatch(versionSpec, arch);
|
2020-06-29 18:41:13 +03:00
|
|
|
if (!version) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let downloadUrl: string = `https://storage.googleapis.com/golang/${version.files[0].filename}`;
|
|
|
|
|
|
|
|
return <IGoVersionInfo>{
|
|
|
|
type: 'dist',
|
|
|
|
downloadUrl: downloadUrl,
|
|
|
|
resolvedVersion: version.version,
|
|
|
|
fileName: version.files[0].filename
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-02-09 00:29:21 -05:00
|
|
|
export async function findMatch(
|
2022-08-12 12:29:48 +02:00
|
|
|
versionSpec: string,
|
|
|
|
arch = os.arch()
|
2020-02-09 00:29:21 -05:00
|
|
|
): Promise<IGoVersion | undefined> {
|
2022-08-12 12:29:48 +02:00
|
|
|
let archFilter = sys.getArch(arch);
|
2020-02-09 00:21:39 -05:00
|
|
|
let platFilter = sys.getPlatform();
|
2019-06-20 13:28:39 -04:00
|
|
|
|
2020-02-09 18:09:15 -05:00
|
|
|
let result: IGoVersion | undefined;
|
2020-02-09 00:29:21 -05:00
|
|
|
let match: IGoVersion | undefined;
|
2019-06-20 13:28:39 -04:00
|
|
|
|
2020-02-09 09:25:20 -05:00
|
|
|
const dlUrl: string = 'https://golang.org/dl/?mode=json&include=all';
|
2020-06-29 18:41:13 +03:00
|
|
|
let candidates: IGoVersion[] | null = await module.exports.getVersionsDist(
|
|
|
|
dlUrl
|
|
|
|
);
|
2020-02-09 00:21:39 -05:00
|
|
|
if (!candidates) {
|
2020-02-09 18:48:40 -05:00
|
|
|
throw new Error(`golang download url did not return results`);
|
2019-08-19 19:28:37 +07:00
|
|
|
}
|
2020-02-09 00:29:21 -05:00
|
|
|
|
2020-02-09 00:21:39 -05:00
|
|
|
let goFile: IGoVersionFile | undefined;
|
2020-02-09 00:29:21 -05:00
|
|
|
for (let i = 0; i < candidates.length; i++) {
|
2020-02-09 00:21:39 -05:00
|
|
|
let candidate: IGoVersion = candidates[i];
|
2020-02-10 19:18:01 -05:00
|
|
|
let version = makeSemver(candidate.version);
|
2020-02-09 00:29:21 -05:00
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug(`check ${version} satisfies ${versionSpec}`);
|
2022-02-28 10:16:32 +03:00
|
|
|
if (semver.satisfies(version, versionSpec)) {
|
2020-02-09 00:21:39 -05:00
|
|
|
goFile = candidate.files.find(file => {
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug(
|
|
|
|
`${file.arch}===${archFilter} && ${file.os}===${platFilter}`
|
|
|
|
);
|
2020-02-09 00:21:39 -05:00
|
|
|
return file.arch === archFilter && file.os === platFilter;
|
|
|
|
});
|
2019-08-19 19:28:37 +07:00
|
|
|
|
2020-02-09 00:21:39 -05:00
|
|
|
if (goFile) {
|
2020-06-29 18:41:13 +03:00
|
|
|
core.debug(`matched ${candidate.version}`);
|
2020-02-09 00:21:39 -05:00
|
|
|
match = candidate;
|
|
|
|
break;
|
|
|
|
}
|
2019-08-19 19:28:37 +07:00
|
|
|
}
|
2020-02-09 00:29:21 -05:00
|
|
|
}
|
2019-08-19 19:28:37 +07:00
|
|
|
|
2020-02-09 00:21:39 -05:00
|
|
|
if (match && goFile) {
|
2020-02-09 18:09:15 -05:00
|
|
|
// clone since we're mutating the file list to be only the file that matches
|
|
|
|
result = <IGoVersion>Object.assign({}, match);
|
|
|
|
result.files = [goFile];
|
2019-08-19 19:28:37 +07:00
|
|
|
}
|
|
|
|
|
2020-02-09 18:09:15 -05:00
|
|
|
return result;
|
2019-08-19 19:28:37 +07:00
|
|
|
}
|
2020-02-09 09:25:20 -05:00
|
|
|
|
2020-06-29 18:41:13 +03:00
|
|
|
export async function getVersionsDist(
|
|
|
|
dlUrl: string
|
|
|
|
): Promise<IGoVersion[] | null> {
|
2020-02-09 09:25:20 -05:00
|
|
|
// this returns versions descending so latest is first
|
2020-06-29 18:41:13 +03:00
|
|
|
let http: httpm.HttpClient = new httpm.HttpClient('setup-go', [], {
|
|
|
|
allowRedirects: true,
|
|
|
|
maxRedirects: 3
|
|
|
|
});
|
2020-02-09 18:48:40 -05:00
|
|
|
return (await http.getJson<IGoVersion[]>(dlUrl)).result;
|
2020-02-09 09:25:20 -05:00
|
|
|
}
|
2020-02-10 19:18:01 -05:00
|
|
|
|
|
|
|
//
|
|
|
|
// Convert the go version syntax into semver for semver matching
|
|
|
|
// 1.13.1 => 1.13.1
|
|
|
|
// 1.13 => 1.13.0
|
2022-02-28 10:16:32 +03:00
|
|
|
// 1.10beta1 => 1.10.0-beta.1, 1.10rc1 => 1.10.0-rc.1
|
|
|
|
// 1.8.5beta1 => 1.8.5-beta.1, 1.8.5rc1 => 1.8.5-rc.1
|
2020-02-10 19:18:01 -05:00
|
|
|
export function makeSemver(version: string): string {
|
|
|
|
version = version.replace('go', '');
|
2022-02-28 10:16:32 +03:00
|
|
|
version = version.replace('beta', '-beta.').replace('rc', '-rc.');
|
2020-02-10 19:18:01 -05:00
|
|
|
let parts = version.split('-');
|
|
|
|
|
2022-02-28 10:16:32 +03:00
|
|
|
let semVersion = semver.coerce(parts[0])?.version;
|
|
|
|
if (!semVersion) {
|
|
|
|
throw new Error(
|
|
|
|
`The version: ${version} can't be changed to SemVer notation`
|
|
|
|
);
|
|
|
|
}
|
2020-02-10 19:18:01 -05:00
|
|
|
|
2022-02-28 10:16:32 +03:00
|
|
|
if (!parts[1]) {
|
|
|
|
return semVersion;
|
2020-02-10 19:18:01 -05:00
|
|
|
}
|
|
|
|
|
2022-02-28 10:16:32 +03:00
|
|
|
const fullVersion = semver.valid(`${semVersion}-${parts[1]}`);
|
|
|
|
|
|
|
|
if (!fullVersion) {
|
|
|
|
throw new Error(
|
|
|
|
`The version: ${version} can't be changed to SemVer notation`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return fullVersion;
|
2020-02-10 19:18:01 -05:00
|
|
|
}
|
2022-05-12 17:04:39 +09:00
|
|
|
|
|
|
|
export function parseGoVersionFile(versionFilePath: string): string {
|
|
|
|
const contents = fs.readFileSync(versionFilePath).toString();
|
|
|
|
|
2022-11-01 19:13:57 +09:00
|
|
|
if (
|
|
|
|
path.basename(versionFilePath) === 'go.mod' ||
|
|
|
|
path.basename(versionFilePath) === 'go.work'
|
|
|
|
) {
|
2022-05-12 17:04:39 +09:00
|
|
|
const match = contents.match(/^go (\d+(\.\d+)*)/m);
|
|
|
|
return match ? match[1] : '';
|
|
|
|
}
|
|
|
|
|
|
|
|
return contents.trim();
|
|
|
|
}
|
2022-12-12 10:58:49 +01:00
|
|
|
|
|
|
|
async function resolveStableVersionDist(versionSpec: string, arch: string) {
|
|
|
|
let archFilter = sys.getArch(arch);
|
|
|
|
let platFilter = sys.getPlatform();
|
|
|
|
const dlUrl: string = 'https://golang.org/dl/?mode=json&include=all';
|
|
|
|
let candidates: IGoVersion[] | null = await module.exports.getVersionsDist(
|
|
|
|
dlUrl
|
|
|
|
);
|
|
|
|
if (!candidates) {
|
|
|
|
throw new Error(`golang download url did not return results`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const fixedCandidates = candidates.map(item => {
|
|
|
|
return {
|
|
|
|
...item,
|
|
|
|
version: makeSemver(item.version)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const stableVersion = await resolveStableVersionInput(
|
|
|
|
versionSpec,
|
|
|
|
archFilter,
|
|
|
|
platFilter,
|
|
|
|
fixedCandidates
|
|
|
|
);
|
|
|
|
|
|
|
|
return stableVersion;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function resolveStableVersionInput(
|
|
|
|
versionSpec: string,
|
|
|
|
arch: string,
|
|
|
|
platform: string,
|
|
|
|
manifest: tc.IToolRelease[] | IGoVersion[]
|
|
|
|
) {
|
|
|
|
const releases = manifest
|
|
|
|
.map(item => {
|
|
|
|
const index = item.files.findIndex(
|
|
|
|
item => item.arch === arch && item.filename.includes(platform)
|
|
|
|
);
|
|
|
|
if (index === -1) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return item.version;
|
|
|
|
})
|
|
|
|
.filter(item => !!item && !semver.prerelease(item));
|
|
|
|
|
|
|
|
if (versionSpec === StableReleaseAlias.Stable) {
|
|
|
|
return releases[0];
|
|
|
|
} else {
|
|
|
|
const versions = releases.map(
|
|
|
|
release => `${semver.major(release)}.${semver.minor(release)}`
|
|
|
|
);
|
|
|
|
const uniqueVersions = Array.from(new Set(versions));
|
|
|
|
|
|
|
|
const oldstableVersion = releases.find(item =>
|
|
|
|
item.startsWith(uniqueVersions[1])
|
|
|
|
);
|
|
|
|
|
|
|
|
return oldstableVersion;
|
|
|
|
}
|
|
|
|
}
|