UNPKG

@yuntools/ali-ecs

Version:

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

202 lines 6.95 kB
import assert from 'node:assert'; import { DescribeInstancesRequest } from '@alicloud/ecs20140526'; import { Config as ApiConfig } from '@alicloud/openapi-client'; import { _Client } from './client.js'; import { Action, EcsStatusKey, EcsInfoKey, } from './types.js'; /** * 阿里云 ECS 服务接口 * 最多支持 100 个实例 */ export 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(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(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: Action.DescribeInstances, nextToken: this.nextToken, regionId, publicIpAddresses: [ip], eipAddresses: [ip], // pageNumber: 0, pageSize: 100, totalCount: 100, }; const req = new 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 ApiConfig({ 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); }); } } //# sourceMappingURL=ecs.js.map