UNPKG

@yuntools/ali-ecs

Version:

阿里云 ECS 模块封装,支持 ESM,CJS 导入,提供 TypeScript 类型定义

271 lines (264 loc) 9.51 kB
/** * @yuntools/ali-ecs * 阿里云 ECS 模块封装,支持 ESM,CJS 导入,提供 TypeScript 类型定义 * * @version 11.0.0 * @author waiting * @license MIT * @link https://github.com/waitingsong/yuntools#readme */ 'use strict'; var assert = require('node:assert'); var Ecs = require('@alicloud/ecs20140526'); var openapiClient = require('@alicloud/openapi-client'); /* eslint-disable @typescript-eslint/no-unsafe-assignment */ // for ESM module // export const _Client = (Ecs as any).default as typeof Ecs const _Client = typeof module === 'object' ? Ecs : Ecs.default; exports.Action = void 0; (function (Action) { /** 获取指定 Ecs 节点信息 */ Action["DescribeInstances"] = "DescribeInstances"; })(exports.Action || (exports.Action = {})); exports.EcsStatusKey = void 0; (function (EcsStatusKey) { EcsStatusKey["deviceAvailable"] = "deviceAvailable"; EcsStatusKey["expiredTime"] = "expiredTime"; EcsStatusKey["instanceId"] = "instanceId"; EcsStatusKey["instanceName"] = "instanceName"; EcsStatusKey["regionId"] = "regionId"; EcsStatusKey["startTime"] = "startTime"; EcsStatusKey["status"] = "status"; })(exports.EcsStatusKey || (exports.EcsStatusKey = {})); exports.EcsInfoKey = void 0; (function (EcsInfoKey) { EcsInfoKey["deviceAvailable"] = "deviceAvailable"; EcsInfoKey["expiredTime"] = "expiredTime"; EcsInfoKey["instanceId"] = "instanceId"; EcsInfoKey["instanceName"] = "instanceName"; EcsInfoKey["regionId"] = "regionId"; EcsInfoKey["startTime"] = "startTime"; EcsInfoKey["status"] = "status"; EcsInfoKey["autoReleaseTime"] = "autoReleaseTime"; EcsInfoKey["clusterId"] = "clusterId"; EcsInfoKey["cpu"] = "cpu"; EcsInfoKey["creationTime"] = "creationTime"; EcsInfoKey["description"] = "description"; EcsInfoKey["eipAddress"] = "eipAddress"; EcsInfoKey["hostName"] = "hostName"; EcsInfoKey["innerIpAddress"] = "innerIpAddress"; EcsInfoKey["instanceChargeType"] = "instanceChargeType"; EcsInfoKey["instanceNetworkType"] = "instanceNetworkType"; EcsInfoKey["instanceType"] = "instanceType"; EcsInfoKey["instanceTypeFamily"] = "instanceTypeFamily"; EcsInfoKey["internetChargeType"] = "internetChargeType"; EcsInfoKey["internetMaxBandwidthIn"] = "internetMaxBandwidthIn"; EcsInfoKey["internetMaxBandwidthOut"] = "internetMaxBandwidthOut"; EcsInfoKey["memory"] = "memory"; EcsInfoKey["OSName"] = "OSName"; EcsInfoKey["OSType"] = "OSType"; EcsInfoKey["publicIpAddress"] = "publicIpAddress"; EcsInfoKey["resourceGroupId"] = "resourceGroupId"; EcsInfoKey["serialNumber"] = "serialNumber"; EcsInfoKey["zoneId"] = "zoneId"; })(exports.EcsInfoKey || (exports.EcsInfoKey = {})); /** * 阿里云 ECS 服务接口 * 最多支持 100 个实例 */ class EcsClient { id; secret; endpoint; client; debug = false; nextToken = ''; /** ip -> instanceId */ nodeIp2IdCache = new Map(); /** instanceId -> node */ id2NodeCache = new Map(); cacheTime; cacheTTLSec = 30; constructor(id, secret, endpoint = 'ecs-cn-hangzhou.aliyuncs.com') { this.id = id; this.secret = secret; this.endpoint = endpoint; this.client = this.createClient(id, secret); } /** 根据公网 IP 数组获取 Ecs 实例状态信息 */ async getNodeStatusByIps(ips, regionId = 'cn-hangzhou') { const ret = new Map(); const nodes = await this.getInstancesByIps(ips, regionId); nodes.forEach((row, ip) => { if (typeof row === 'undefined') { return; } const info = {}; Object.values(exports.EcsStatusKey).forEach((key) => { const value = row[key]; if (typeof value === 'undefined') { return; } Object.defineProperty(info, key, { enumerable: true, value, }); }); assert(Object.keys(info).length, 'info is empty'); ret.set(ip, info); }); return ret; } /** 根据公网 IP 数组获取 Ecs 实例信息 */ async getNodeInfoByIps(ips, regionId = 'cn-hangzhou') { const ret = new Map(); const nodes = await this.getInstancesByIps(ips, regionId); nodes.forEach((row, ip) => { if (typeof row === 'undefined') { return; } const info = {}; Object.values(exports.EcsInfoKey).forEach((key) => { const value = row[key]; if (typeof value === 'undefined') { return; } Object.defineProperty(info, key, { enumerable: true, value, }); }); assert(Object.keys(info).length, 'info is empty'); ret.set(ip, info); }); return ret; } /** 根据公网 IP 获取 Ecs 实例 ID */ async getInstanceIdByIp(ip) { assert(ip, 'ip is required'); this.cleanCache(); const nodeId = this.nodeIp2IdCache.get(ip); if (nodeId) { this.debug && console.info(`getInstanceIdByIp from cache: ${ip} -> ${nodeId}`); return nodeId; } const inst = await this.getInstanceByIp(ip); return inst?.instanceId; } /** 根据公网 IP 数组获取 Ecs 实例信息 */ async getInstancesByIps(ips, regionId = 'cn-hangzhou') { assert(Array.isArray(ips), 'ips must be an array'); this.cleanCache(); const ret = new Map(); for await (const ip of ips) { assert(ip, 'ip is required'); assert(typeof ip === 'string', 'ip must be a string'); const inst = await this.getInstanceByIp(ip, regionId); if (!inst) { continue; } ret.set(ip, inst); } return ret; } /** 根据公网 IP 获取 Ecs 实例信息 */ async getInstanceByIp(ip, regionId = 'cn-hangzhou') { assert(typeof ip === 'string', 'ip must be a string'); this.cleanCache(); const node = this._getInstanceByIpFromCache(ip.trim()); if (node) { this.debug && console.log(`getInstanceByIp from cache: ${ip}`); return node; } const opts = { action: exports.Action.DescribeInstances, nextToken: this.nextToken, regionId, publicIpAddresses: [ip], eipAddresses: [ip], // pageNumber: 0, pageSize: 100, totalCount: 100, }; const req = new Ecs.DescribeInstancesRequest(opts); this.debug && console.info({ req }); const resp = await this.client.describeInstances(req); /* c8 ignore next 3 */ if (resp.body.nextToken) { this.nextToken = resp.body.nextToken; } const insts = resp.body.instances?.instance; assert(insts && Array.isArray(insts), 'insts is empty'); this.debug && console.log(`${ip} found ${insts.length}`); this.updateInstancedCache(insts); this.debug && console.info({ insts }); for (const inst of insts) { const ips = inst.publicIpAddress?.ipAddress; if (ips?.includes(ip)) { return inst; } } } cleanCache(force = false) { const now = Date.now(); if (force) { this._cleanCache(); return; } const { cacheTime, cacheTTLSec } = this; assert(typeof cacheTime === 'number' || typeof cacheTime === 'undefined'); assert(typeof cacheTTLSec === 'number', 'cacheTTLSec must be a number'); if (cacheTime && ((now - cacheTime) > cacheTTLSec * 1000)) { console.log('cache expired'); this._cleanCache(); } // void else } updateInstancedCache(instances) { this.saveNodesToCache(instances); this.cacheTime = Date.now(); } _cleanCache() { this.nodeIp2IdCache.clear(); this.id2NodeCache.clear(); this.cacheTime = 0; } _getInstanceByIpFromCache(ip) { if (!ip.length) { console.warn('_getInstanceByIpFromCache: ip is empty'); return; } const id = this.nodeIp2IdCache.get(ip); if (!id) { return; } const node = this.id2NodeCache.get(id); return node; } createClient(accessKeyId, accessKeySecret) { const config = new openapiClient.Config({ accessKeyId, accessKeySecret }); config.endpoint = this.endpoint; const client = new _Client(config); this.debug && console.info({ client }); return client; } saveNodesToCache(nodes) { nodes.forEach((node) => { const { instanceId } = node; if (!instanceId) { return; } if (node.eipAddress?.ipAddress) { this.nodeIp2IdCache.set(node.eipAddress.ipAddress, instanceId); } node.publicIpAddress?.ipAddress?.forEach((ip) => { ip && this.nodeIp2IdCache.set(ip, instanceId); }); this.id2NodeCache.set(instanceId, node); }); } } exports.EcsClient = EcsClient; //# sourceMappingURL=index.cjs.map