import path from 'path' import os from 'os' import * as uuid from 'uuid' import fs from 'fs' import log from '../logging' import platform from '../platform' import * as download from './download' import * as cache from './cache' import * as extract from './extract' import * as which from './which' import logging from '../logging' import process from 'process' import exec from '../exec' export const flowToolRegistryServerPort = 11516 export async function install( toolName: string, version: string, toolSuffix?: string ): Promise { const toolRegistryUrl = process.env['FLOW_TOOL_REGISTRY_URL'] if(!toolRegistryUrl) { throw new Error('env var "FLOW_TOOL_REGISTRY_URL" must be set') } const existCachedDir = await which.which(toolName, version) if(!!existCachedDir) { log.info(`Found cached tool ${toolName} ${version} in ${existCachedDir}`) return Promise.resolve(existCachedDir) } const copied = await tryCopyFromLocalCache(toolName, version) if (copied) { const existCachedDir = await which.which(toolName, version) log.info(`Copied ${toolName} ${version} from local cache server. Path is ${existCachedDir}`) return Promise.resolve(existCachedDir) } const downloadUrl = `${toolRegistryUrl}/${toolName}/${toolName}-${version}-${platform.getPlatform()}-${platform.getArch()}` + (toolSuffix ? `.${toolSuffix}` : '') logging.info(`Downloading from ${downloadUrl}`) const downloadedTmpFilePath = await download.download(downloadUrl, {}) logging.info(`Downloaded to ${downloadedTmpFilePath}`) const cachedDir = await cacheExtractedTool(downloadedTmpFilePath, toolName, version, toolSuffix) fs.rmSync(downloadedTmpFilePath) return Promise.resolve(cachedDir); } async function cacheExtractedTool(downloadedTmpFilePath: string, toolName: string, version: string, toolSuffix?: string): Promise { if (toolSuffix === 'tar.gz' || toolSuffix === 'zip') { const extractedDir = path.join(os.tmpdir(), uuid.v4()) logging.info(`Extracting ${downloadedTmpFilePath} to ${extractedDir}`) // 公共构建集群操作系统为 linux,尝试先使用 tar 解压 if (process.env['NODEGROUP_TYPE'] === 'K8S' && toolSuffix === 'tar.gz') { const tarCommand = `mkdir -p ${extractedDir}; tar --strip-components=1 -zxvf ${downloadedTmpFilePath} -C ${extractedDir} --warning=no-unknown-keyword >/dev/null` logging.info(`Try to exec: /bin/bash -c ${tarCommand}`) const resultCode = await exec.call('/bin/bash', ['-c', tarCommand], { listener: { stdoutLine: (line: string) => log.info(line), stderrLine: (line: string) => log.warning(line) } }) if (resultCode !== 0) { logging.error('Failed to use tar to extract, fallback to use decompress instead.') await extract.extract(downloadedTmpFilePath, extractedDir, { strip: 1 }) } } else { // 私有构建集群有缓存机制,直接使用 decompress 解压 await extract.extract(downloadedTmpFilePath, extractedDir, { strip: 1 }) } logging.info(`Caching ${extractedDir}`) const cachedDir = await cache.cacheDir(extractedDir, toolName, version) logging.info(`Cached ${extractedDir} to ${cachedDir}`) return Promise.resolve(cachedDir) } logging.info(`Caching ${downloadedTmpFilePath}`) const cachedDir = await cache.cacheFile(downloadedTmpFilePath, `${toolName}-${version}`, toolName, version) logging.info(`Cached ${downloadedTmpFilePath} to ${cachedDir}`) return Promise.resolve(cachedDir) } async function tryCopyFromLocalCache( toolName: string, version: string ): Promise { const cpToolUrl = `http://localhost:${flowToolRegistryServerPort}/restore?toolname=${toolName}&version=${version}&arch=${platform.getArch()}` const timeoutMillSeconds = 10 * 1000 try { const result = await download.requestURL(cpToolUrl, timeoutMillSeconds) log.info(`Get result from tool registry sidecar: ${result}`) return Promise.resolve(true) } catch (error) { if (`${error}`.includes('ECONNREFUSED')) { // tool registry sidecar is not ready, skip cp from tool registry sidecar operation } else { log.warning(`Get result from tool registry sidecar failed, ${error}`) } return Promise.resolve(false) } }