0
1
Fork 0
setup-go/src/installer.ts

430 lines
11 KiB
TypeScript
Raw Normal View History

2019-06-20 19:28:39 +02:00
import * as tc from '@actions/tool-cache';
import * as core from '@actions/core';
2019-06-20 19:28:39 +02:00
import * as path from 'path';
import * as semver from 'semver';
2020-02-09 06:29:21 +01:00
import * as httpm from '@actions/http-client';
import * as sys from './system';
2022-05-12 10:04:39 +02:00
import fs from 'fs';
import os from 'os';
import {StableReleaseAlias} from './utils';
type InstallationType = 'dist' | 'manifest';
2019-06-20 19:28:39 +02:00
2020-02-09 06:21:39 +01:00
export interface IGoVersionFile {
2020-02-09 06:29:21 +01:00
filename: string;
2020-02-09 06:21:39 +01:00
// darwin, linux, windows
2020-02-09 06:29:21 +01:00
os: string;
arch: string;
2019-06-20 19:28:39 +02:00
}
2020-02-09 06:21:39 +01:00
export interface IGoVersion {
version: string;
stable: boolean;
files: IGoVersionFile[];
2019-06-20 19:28:39 +02:00
}
export interface IGoVersionInfo {
type: InstallationType;
downloadUrl: string;
resolvedVersion: string;
fileName: string;
}
export async function getGo(
versionSpec: string,
checkLatest: boolean,
2022-08-12 12:29:48 +02:00
auth: string | undefined,
arch = os.arch()
) {
let manifest: tc.IToolRelease[] | undefined;
const osPlat: string = os.platform();
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}`);
versionSpec = stableVersion;
}
if (checkLatest) {
core.info('Attempting to resolve the latest version from the manifest...');
const resolvedVersion = await resolveVersionFromManifest(
versionSpec,
true,
2022-08-12 12:29:48 +02:00
auth,
arch,
manifest
);
if (resolvedVersion) {
versionSpec = resolvedVersion;
core.info(`Resolved as '${versionSpec}'`);
} else {
core.info(`Failed to resolve version ${versionSpec} from manifest`);
}
}
// check cache
const toolPath = tc.find('go', versionSpec, arch);
// 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 {
info = await getInfoFromManifest(versionSpec, true, auth, arch, manifest);
if (info) {
2022-08-12 12:29:48 +02:00
downloadPath = await installGoVersion(info, auth, arch);
} 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);
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}.`
);
}
try {
core.info('Install from dist');
2022-08-12 12:29:48 +02:00
downloadPath = await installGoVersion(info, undefined, arch);
} catch (err) {
throw new Error(`Failed to download version ${versionSpec}: ${err}`);
}
}
return downloadPath;
}
async function resolveVersionFromManifest(
versionSpec: string,
stable: boolean,
2022-08-12 12:29:48 +02:00
auth: string | undefined,
arch: string,
manifest: tc.IToolRelease[] | undefined
): Promise<string | undefined> {
try {
const info = await getInfoFromManifest(
versionSpec,
stable,
auth,
arch,
manifest
);
return info?.resolvedVersion;
} catch (err) {
core.info('Unable to resolve a version from the manifest...');
core.debug(err.message);
}
}
async function installGoVersion(
info: IGoVersionInfo,
2022-08-12 12:29:48 +02:00
auth: string | undefined,
arch: string
): Promise<string> {
core.info(`Acquiring ${info.resolvedVersion} from ${info.downloadUrl}`);
// 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);
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
);
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();
let extPath: string;
2021-08-19 21:19:21 +02:00
if (platform === 'win32') {
extPath = await tc.extractZip(archivePath);
} else {
extPath = await tc.extractTar(archivePath);
}
return extPath;
}
export async function getManifest(auth: string | undefined) {
return tc.getManifestFromRepo('actions', 'go-versions', auth, 'main');
}
export async function getInfoFromManifest(
versionSpec: string,
stable: boolean,
2022-08-12 12:29:48 +02:00
auth: string | undefined,
arch = os.arch(),
manifest?: tc.IToolRelease[] | undefined
): Promise<IGoVersionInfo | null> {
let info: IGoVersionInfo | null = null;
if (!manifest) {
core.debug('No manifest cached');
manifest = await getManifest(auth);
}
core.info(`matching ${versionSpec}...`);
const rel = await tc.findFromManifest(versionSpec, stable, manifest, arch);
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
): Promise<IGoVersionInfo | null> {
const version: IGoVersion | undefined = await findMatch(versionSpec, arch);
if (!version) {
return null;
}
const downloadUrl = `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 06:29:21 +01:00
export async function findMatch(
2022-08-12 12:29:48 +02:00
versionSpec: string,
arch = os.arch()
2020-02-09 06:29:21 +01:00
): Promise<IGoVersion | undefined> {
const archFilter = sys.getArch(arch);
const platFilter = sys.getPlatform();
2019-06-20 19:28:39 +02:00
2020-02-10 00:09:15 +01:00
let result: IGoVersion | undefined;
2020-02-09 06:29:21 +01:00
let match: IGoVersion | undefined;
2019-06-20 19:28:39 +02:00
const dlUrl = 'https://golang.org/dl/?mode=json&include=all';
const candidates: IGoVersion[] | null = await module.exports.getVersionsDist(
dlUrl
);
2020-02-09 06:21:39 +01:00
if (!candidates) {
2020-02-10 00:48:40 +01:00
throw new Error(`golang download url did not return results`);
}
2020-02-09 06:29:21 +01:00
2020-02-09 06:21:39 +01:00
let goFile: IGoVersionFile | undefined;
2020-02-09 06:29:21 +01:00
for (let i = 0; i < candidates.length; i++) {
const candidate: IGoVersion = candidates[i];
const version = makeSemver(candidate.version);
2020-02-09 06:29:21 +01:00
core.debug(`check ${version} satisfies ${versionSpec}`);
if (semver.satisfies(version, versionSpec)) {
2020-02-09 06:21:39 +01:00
goFile = candidate.files.find(file => {
core.debug(
`${file.arch}===${archFilter} && ${file.os}===${platFilter}`
);
2020-02-09 06:21:39 +01:00
return file.arch === archFilter && file.os === platFilter;
});
2020-02-09 06:21:39 +01:00
if (goFile) {
core.debug(`matched ${candidate.version}`);
2020-02-09 06:21:39 +01:00
match = candidate;
break;
}
}
2020-02-09 06:29:21 +01:00
}
2020-02-09 06:21:39 +01:00
if (match && goFile) {
2020-02-10 00:09:15 +01:00
// clone since we're mutating the file list to be only the file that matches
result = <IGoVersion>Object.assign({}, match);
result.files = [goFile];
}
2020-02-10 00:09:15 +01:00
return result;
}
2020-02-09 15:25:20 +01:00
export async function getVersionsDist(
dlUrl: string
): Promise<IGoVersion[] | null> {
2020-02-09 15:25:20 +01:00
// this returns versions descending so latest is first
const http: httpm.HttpClient = new httpm.HttpClient('setup-go', [], {
allowRedirects: true,
maxRedirects: 3
});
2020-02-10 00:48:40 +01:00
return (await http.getJson<IGoVersion[]>(dlUrl)).result;
2020-02-09 15:25:20 +01:00
}
2020-02-11 01:18:01 +01:00
//
// Convert the go version syntax into semver for semver matching
// 1.13.1 => 1.13.1
// 1.13 => 1.13.0
// 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-11 01:18:01 +01:00
export function makeSemver(version: string): string {
version = version.replace('go', '');
version = version.replace('beta', '-beta.').replace('rc', '-rc.');
const parts = version.split('-');
2020-02-11 01:18:01 +01:00
const semVersion = semver.coerce(parts[0])?.version;
if (!semVersion) {
throw new Error(
`The version: ${version} can't be changed to SemVer notation`
);
}
2020-02-11 01:18:01 +01:00
if (!parts[1]) {
return semVersion;
2020-02-11 01:18:01 +01: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-11 01:18:01 +01:00
}
2022-05-12 10:04:39 +02:00
export function parseGoVersionFile(versionFilePath: string): string {
const contents = fs.readFileSync(versionFilePath).toString();
2022-11-01 11:13:57 +01:00
if (
path.basename(versionFilePath) === 'go.mod' ||
path.basename(versionFilePath) === 'go.work'
) {
2022-05-12 10:04:39 +02:00
const match = contents.match(/^go (\d+(\.\d+)*)/m);
return match ? match[1] : '';
}
return contents.trim();
}
async function resolveStableVersionDist(versionSpec: string, arch: string) {
const archFilter = sys.getArch(arch);
const platFilter = sys.getPlatform();
const dlUrl = 'https://golang.org/dl/?mode=json&include=all';
const 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;
}
}