import * as semver from 'semver' import {debug} from '@actions/core' // needs to be require for core node modules to be mocked /* eslint @typescript-eslint/no-require-imports: 0 */ import os = require('os') import cp = require('child_process') import fs = require('fs') /* NOTE: versions must be sorted descending by version in the manifest this library short circuits on first semver spec match platform_version is an optional filter and can be a semver spec or range [ { "version": "1.2.3", "stable": true, "release_url": "https://github.com/actions/sometool/releases/tag/1.2.3-20200402.6", "files": [ { "filename": "sometool-1.2.3-linux-x64.zip", "arch": "x64", "platform": "linux", "platform_version": "18.04" "download_url": "https://github.com/actions/sometool/releases/tag/1.2.3-20200402.6/sometool-1.2.3-linux-x64.zip" }, ... ] }, ... ] */ export interface IToolReleaseFile { filename: string // 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', // 'sunos', and 'win32' // platform_version is an optional semver filter // TODO: do we need distribution (e.g. ubuntu). // not adding yet but might need someday. // right now, 16.04 and 18.04 work platform: string platform_version?: string // 'arm', 'arm64', 'ia32', 'mips', 'mipsel', // 'ppc', 'ppc64', 's390', 's390x', // 'x32', and 'x64'. arch: string download_url: string } export interface IToolRelease { version: string stable: boolean release_url: string files: IToolReleaseFile[] } export async function _findMatch( versionSpec: string, stable: boolean, candidates: IToolRelease[], archFilter: string ): Promise { const platFilter = os.platform() let result: IToolRelease | undefined let match: IToolRelease | undefined let file: IToolReleaseFile | undefined for (const candidate of candidates) { const version = candidate.version debug(`check ${version} satisfies ${versionSpec}`) if ( semver.satisfies(version, versionSpec) && (!stable || candidate.stable === stable) ) { file = candidate.files.find(item => { debug( `${item.arch}===${archFilter} && ${item.platform}===${platFilter}` ) let chk = item.arch === archFilter && item.platform === platFilter if (chk && item.platform_version) { const osVersion = module.exports._getOsVersion() if (osVersion === item.platform_version) { chk = true } else { chk = semver.satisfies(osVersion, item.platform_version) } } return chk }) if (file) { debug(`matched ${candidate.version}`) match = candidate break } } } if (match && file) { // clone since we're mutating the file list to be only the file that matches result = Object.assign({}, match) result.files = [file] } return result } export function _getOsVersion(): string { // TODO: add windows and other linux, arm variants // right now filtering on version is only an ubuntu and macos scenario for tools we build for hosted (python) const plat = os.platform() let version = '' if (plat === 'darwin') { version = cp.execSync('sw_vers -productVersion').toString() } else if (plat === 'linux') { // lsb_release process not in some containers, readfile // Run cat /etc/lsb-release // DISTRIB_ID=Ubuntu // DISTRIB_RELEASE=18.04 // DISTRIB_CODENAME=bionic // DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS" const lsbContents = module.exports._readLinuxVersionFile() if (lsbContents) { const lines = lsbContents.split('\n') for (const line of lines) { const parts = line.split('=') if ( parts.length === 2 && (parts[0].trim() === 'VERSION_ID' || parts[0].trim() === 'DISTRIB_RELEASE') ) { version = parts[1].trim().replace(/^"/, '').replace(/"$/, '') break } } } } return version } export function _readLinuxVersionFile(): string { const lsbReleaseFile = '/etc/lsb-release' const osReleaseFile = '/etc/os-release' let contents = '' if (fs.existsSync(lsbReleaseFile)) { contents = fs.readFileSync(lsbReleaseFile).toString() } else if (fs.existsSync(osReleaseFile)) { contents = fs.readFileSync(osReleaseFile).toString() } return contents }