UNPKG

tencentcloud-edgeone-migration-nodejs-v2

Version:

tencentcloud cdn config copy to edgeone

1,046 lines (964 loc) 31.4 kB
const _ = require("lodash"); const utils = require("./utils"); const transfer = require("./transfer"); const logger = require("./logger"); const ObjectsToCsv = require("objects-to-csv"); const isStandard = require("../src/getPlansUtils"); const { t } = require("../i18n/trans"); const { reportTitle, ZONE_GLOBAL_ENTITY, USE_DEFAULT } = require("./const"); const { AppId, isPrivate, isDev } = require("../config.json"); const genLog = require("../logGenerator") const capi = require("./api") const Aegis = require("../lib/@tencent/aegis-node-sdk"); const aegis = new Aegis({ id: "lJ7dDFybL1wqa79Po5", // 项目上报ID ...(!isPrivate && { selector: { type: "host", }, }), }); // 新版源站组接口未上线,暂时屏蔽后两个配置 const defaultConfigs = [ "IpFilter", "StatusCodeCache", "Compression", "BandwidthAlert", "RangeOriginPull", "FollowRedirect", "ErrorPage", "RequestHeader", "ResponseHeader", "CacheKey", "Cache", "Authentication", "ForceRedirect", "Referer", "MaxAge", "UrlRedirect", "UserAgentFilter", "OfflineCache", "PostMaxSize", "Quic", "WebSocket", "PathBasedOrigin", "PathRules", "OriginPullTimeout", "HostHeaderRewrite" ]; const ruleConfigs = [ "StatusCodeCache", "Compression", "RangeOriginPull", "FollowRedirect", "ErrorPage", "RequestHeader", "ResponseHeader", "CacheKey", "Cache", "Authentication", "MaxAge", "UrlRedirect", "OfflineCache", "PostMaxSize", "WebSocket", "PathBasedOrigin", "PathRules", "OriginPullTimeout", "HostHeaderRewrite" ]; const otherConfigs = ["IpFilterRefererUserAgentFilter", "BandwidthAlert"]; const httpsConfigs = [ "ForceRedirect", "Quic", "Http2", "OcspStapling", "Hsts", "TlsVersion", ]; // SCDN 迁移名单 const SCDNDefaultConfigs = ["Acl", "Bot", "CC"]; const SCDN2EO_MAP = { Acl: "AclConfig", CC: "RateLimitConfig", Bot: "BotConfig", }; /** * 任务入参 * @param {Object} options * @param {Array} options.DomainZoneMaps CDN域名跟EO站点的映射关系数组,必须 * @param {Array} options.DomainZoneMaps[i].Domains CDN域名,可批量传入,要求跟站点匹配,必须 * @param {String} options.DomainZoneMaps[i].ZoneId EO站点唯一ID,必须 * @param {Array} options.Configs 指定要迁移的配置,默认值见readme说明,非必须 * @param {Boolean} options.NeedCreateDomain 是否需要创建域名,不传默认true,传false的话,只迁移配置,不创建域名,非必须 */ async function runTasks(options) { const reportDatas = []; try { const domainMaps = options.DomainZoneMaps; const needCreateDomain = options.NeedCreateDomain === undefined ? true : options.NeedCreateDomain; let needConfigsKeys = options.Configs && options.Configs.length > 0 ? options.Configs.filter((config) => { return defaultConfigs.includes(config); }) : defaultConfigs; if (needConfigsKeys.length === 0) { needConfigsKeys = defaultConfigs; } if (!domainMaps || domainMaps.length === 0) { genLog.throwError(t("参数 DomainZoneMaps 不存在或为空数组")); } const tasks = []; domainMaps.forEach((map) => { if ( !map.Domains || !map.Domains.length || map.Domains.length === 0 || !map.ZoneId ) { return; } map.Domains.forEach((domain) => { tasks.push({ domain, zoneId: map.ZoneId, }); }); }); if (tasks.length === 0) { genLog.throwError(t("不存在有效的任务,请检查参数")); } genLog.defaultLog(t("待执行的任务数:{{taskLength}}", { taskLength: tasks.length })); for (let task of tasks) { const report = { Domain: task.domain, ZoneId: task.zoneId, AppId, Details: [], }; genLog.defaultLog(t("开始任务,Domain: {{taskDomain}},ZoneId: {{taskZoneId}}", { taskDomain: task.domain || null, taskZoneId: task.zoneId || null, })); const res = await singleTask( task.domain, task.zoneId, needConfigsKeys, report, needCreateDomain ); if(!isDev) { try { // 日志上报 aegis.report(report); } catch (e) { logger.error(e); } } report.Details.forEach((log) => { reportDatas.push({ [reportTitle.DOMAIN]: report.Domain, [reportTitle.ZONEID]: report.ZoneId, [reportTitle.CONFIG]: log.config, [reportTitle.RESULT]: log.result, [reportTitle.DETAIL]: log.detail, }); }); // 一个域名空一行 reportDatas.push({ [reportTitle.DOMAIN]: "", [reportTitle.ZONEID]: "", [reportTitle.CONFIG]: "", [reportTitle.RESULT]: "", [reportTitle.DETAIL]: "", }); } genLog.successLog(t("所有任务迁移完成")) const csv = new ObjectsToCsv(reportDatas); await csv.toDisk(`./report-${new Date().getTime()}.csv`); logger.success(t("已生成迁移报告")); } catch (e) { logger.error(e); } finally { genLog.writeLogFile(`${new Date().toISOString()}-${AppId}-task`) utils.exitTerminal(); } } /** * 任务入参 * @param {Object} options * @param {Array} options.DomainZoneMaps SCDN域名跟EO站点的映射关系数组,必须 * @param {Array} options.DomainZoneMaps[i].Domains SCDN域名唯一ID,可批量传入,要求跟站点匹配,必须 * @param {String} options.DomainZoneMaps[i].ZoneId EO站点唯一ID,必须 * @param {Array} options.Configs 指定要迁移的配置,默认值见readme说明,非必须 */ async function runScdnTasks(options) { const reportDatas = []; try { const domainMaps = options.DomainZoneMaps; if (!domainMaps || domainMaps.length === 0) { genLog.throwError(t("参数 DomainZoneMaps 不存在或为空数组")) } let tasks = []; domainMaps.forEach((map) => { if ( !map.Domains || !map.Domains.length || map.Domains.length === 0 || !map.ZoneId ) { return; } map.Domains.forEach((domain) => { tasks.push({ domain, zoneId: map.ZoneId, }); }); }); if (tasks.length === 0) { genLog.throwError(t(`不存在有效的任务,请检查参数`)) } const successDomainsInfo = await checkDomains(options, false); const successDomains = tasks.filter((task) => { return successDomainsInfo.some((successDomain) => { return task.domain === successDomain.DomainName; }); }); const unSuccessDomains = tasks.filter((task) => { return !successDomains.some((successDomain) => { return task.domain === successDomain.domain; }); }); if (unSuccessDomains.length > 0) { const regex = /^\[\{|\{|\}|\}|(\])$/g; const unSuccessMessage = JSON.stringify(unSuccessDomains).replace( regex, "" ); genLog.warnLog(t("以下站点下域名未部署成功:{{unSuccessMessage}}", {unSuccessMessage})) } tasks = successDomains; if (tasks.length === 0) { unSuccessDomains.forEach((item) => { reportDatas.push({ Domain: item.domain, ZoneId: item.zoneId, Detail: t("以上站点迁移失败"), }); }); } genLog.defaultLog(t("待迁移的任务数:{{taskLength}}", { taskLength: tasks.length })) for (let task of tasks) { const report = { Domain: task.domain, ZoneId: task.zoneId, AppId, Details: [], }; genLog.defaultLog(t( "开始SCDN配置迁移任务,ResourceId: {{taskDomain}},ZoneId: {{taskZoneId}}", { taskDomain: task.domain || null, taskZoneId: task.zoneId || null } )) const res = await singleScdnTask( task.domain, task.zoneId, report, options ); if(!isDev) { try { // 日志上报 aegis.report(report); } catch (e) { logger.error(e); } } report.Details.forEach((log) => { reportDatas.push({ [reportTitle.DOMAIN]: report.Domain, [reportTitle.ZONEID]: report.ZoneId, [reportTitle.CONFIG]: log.config, [reportTitle.RESULT]: log.result, [reportTitle.DETAIL]: log.detail, }); }); if (tasks.length > 1) { // 一个域名空一行 reportDatas.push({ [reportTitle.DOMAIN]: "", [reportTitle.ZONEID]: "", [reportTitle.CONFIG]: "", [reportTitle.RESULT]: "", [reportTitle.DETAIL]: "", }); } } genLog.successLog(t(`所有任务迁移完成`)) const csv = new ObjectsToCsv(reportDatas); await csv.toDisk(`./report-scdn-${new Date().getTime()}.csv`); logger.success(t(`已生成迁移报告`)); } catch (e) { logger.error(e); } finally { utils.exitTerminal(); genLog.writeLogFile(`${new Date().toISOString()}-${AppId}-scdnTask`) } } /** * 任务入参 * @param {Object} options * @param {String} options.DomainZoneMaps[i].ZoneId EO站点唯一ID */ async function checkDomains(options, isExitProcess = true) { const successDomains = []; try { for (let task of options.DomainZoneMaps) { const limit = 20; // 单次最多查询20个域名 const taskDomains = _.uniq(task.Domains); const fetchTimes = Math.ceil(taskDomains.length / limit); const getDomains = []; for (let i = 0; i < fetchTimes; i++) { const domains = taskDomains.slice(i * limit, (i + 1) * limit); const { AccelerationDomains } = await capi("DescribeAccelerationDomains", utils.TEO, { ZoneId: task.ZoneId, Filters: [{ Name: "domain-name", Values: domains }], }) getDomains.push(...AccelerationDomains); } if (getDomains.length > 0) { if (getDomains.length < task.Domains.length) { const notExistDomains = task.Domains.filter( (taskDomain) => !getDomains.some( (existDomain) => existDomain.DomainName === taskDomain ) ); genLog.warnLog(t("以下域名不存在于站点{{zoneId}}中:\n{{domains}}", { zoneId: task.ZoneId, domains: notExistDomains.join("\n"), })) } const check = getDomains.filter( (item) => item.DomainStatus !== "online" ); const successDomain = getDomains.filter( (item) => item.DomainStatus === "online" ); successDomains.push(...successDomain); if (check.length > 0) { genLog.warnLog(t("站点ID:{{task}}以下域名未部署成功:", { task: task.ZoneId })) check.forEach((item) => genLog.defaultLog(item.DomainName)); } else { genLog.successLog(t("所有域名均已成功部署")) } } else { genLog.warnLog(t("该站点(站点ID:{{task}})下未查询到域名,请核对站点ID是否正确", { task: task.ZoneId, })) } } return successDomains; } catch (e) { genLog.errorLog(e) } finally { isExitProcess && utils.exitTerminal(); isExitProcess && genLog.writeLogFile(`${new Date().toISOString()}-${AppId}-checkDomains`) } } /** * 任务入参 * @param {Object} options * @param {String} options.DomainZoneMaps[i].ZoneId EO站点唯一ID */ async function accessTask(options) { let tasks = []; const reportDatas = []; const domainMaps = options.DomainZoneMaps; if (!domainMaps || domainMaps.length === 0) { genLog.throwError(t(`参数 DomainZoneMaps 不存在或为空数组`)) } domainMaps.forEach((map) => { if ( !map.Domains || !map.Domains.length || map.Domains.length === 0 || !map.ZoneId ) { return; } map.Domains.forEach((domain) => { tasks.push({ domain, zoneId: map.ZoneId, }); }); }); if (tasks.length === 0) { genLog.throwError(t(`不存在有效的任务,请检查参数`)) } const successDomainsInfo = await checkDomains(options, false); const successDomains = tasks.filter((task) => { return successDomainsInfo.some((successDomain) => { return task.domain === successDomain.DomainName; }); }); const unSuccessDomains = tasks.filter((task) => { return !successDomains.some((successDomain) => { return task.domain === successDomain.domain; }); }); if (unSuccessDomains.length > 0) { const regex = /^\[\{|\{|\}|\}|(\])$/g; const unSuccessMessage = JSON.stringify(unSuccessDomains).replace( regex, "" ); genLog.warnLog(t("以下站点下域名未部署成功:{{unSuccessMessage}}", { unSuccessMessage })) } tasks = successDomains; if (tasks.length === 0) { unSuccessDomains.forEach((item) => { reportDatas.push({ Domain: item.domain, ZoneId: item.zoneId, Detail: t("以上站点迁移失败"), }); }); } genLog.defaultLog(t("待迁移的任务数:{{taskLength}}", { taskLength: tasks.length })) try { for (let task of tasks) { const report = { Domain: task.domain, ZoneId: task.zoneId, AppId, Details: [], }; await accessSingleTask(task.domain, task.zoneId, report, options); if(!isDev) { try { // 日志上报 aegis.report(report); } catch (e) { logger.error(e); } } report.Details.forEach((log) => { reportDatas.push({ [reportTitle.DOMAIN]: report.Domain, [reportTitle.ZONEID]: report.ZoneId, [reportTitle.CONFIG]: log.config, [reportTitle.RESULT]: log.result, [reportTitle.DETAIL]: log.detail, }); }); if (tasks.length > 1) { // 一个域名空一行 reportDatas.push({ [reportTitle.DOMAIN]: "", [reportTitle.ZONEID]: "", [reportTitle.CONFIG]: "", [reportTitle.RESULT]: "", [reportTitle.DETAIL]: "", }); } } genLog.successLog(t(`所有任务迁移完成`)) const csv = new ObjectsToCsv(reportDatas); await csv.toDisk(`./report-access-${new Date().getTime()}.csv`); logger.success(t(`已生成迁移报告`)); } catch (e) { genLog.errorLog(e) } finally { utils.exitTerminal(); genLog.writeLogFile(`${new Date().toISOString()}-${AppId}-access`) } } async function accessSingleTask(domain, zoneId, report) { // 创建其他配置 // 创建IpFilter,RefererUser,AgentFilter配置 try { genLog.defaultLog(t(`获取加速域名配置...`)); const { Domains: [domainConfig], } = await capi("DescribeDomainsConfig", utils.CDN, { Filters: [ { Name: "domain", Value: [domain], }, ], }) await transfer.otherTransfer["IpFilterRefererUserAgentFilter"]( zoneId, domain, domainConfig, report ); } catch (e) { report.Details.push({ config: t( "安全配置创建(IP黑白名单:IpFilter, 防盗链:Referer, UA黑白名单:UserAgentFilter)" ), result: t("失败"), detail: `${e.toString()}`, }); logger.error(e); } // 创建IBandwidthAlert配置,CreateUsageCappingStrategy接口未更新到云API的SDK,暂时屏蔽 // await transfer.otherTransfer['BandwidthAlert'](zoneId, domainName, domainConfig); genLog.successLog(t("{{domain}} 访问控制迁移完成", { domain })) } async function singleTask( domain, zoneId, needConfigsKeys, report, needCreateDomain ) { if (!domain) { const emptyDomainMsg = t(`参数 Domain 为空,跳过任务`) genLog.errorLog(emptyDomainMsg) report.Details.push({ config: t("所有配置"), result: t("失败"), detail: emptyDomainMsg, }); return; } if (!zoneId) { const emptyZoneIdMsg = t(`参数 zoneId 为空,跳过任务`); genLog.errorLog(emptyZoneIdMsg) report.Details.push({ config: t("所有配置"), result: t("失败"), detail: emptyZoneIdMsg, }); return; } genLog.defaultLog(t(`获取 CDN 域名配置...`)) try { const { Domains: [domainConfig], } = await capi("DescribeDomainsConfig", utils.CDN, { Filters: [ { Name: "domain", Value: [domain], }, ], }) if (!domainConfig) { genLog.throwError(t(`Domain: {{domain}} 对应的域名不存在,跳过任务`, { domain })) } genLog.successLog(t("配置获取成功")); // EO创建域名 const domainName = domainConfig.Domain; const origins = domainConfig.Origin.Origins; const originType = domainConfig.Origin.OriginType; const ipv6Status = domainConfig.Ipv6Access.Switch || "follow"; let groupId = ""; let originPort = null; if (domainConfig.Origin?.BackupOrigins?.length) { logger.warn( t( "暂不支持迁移热备源站, 请前往 EdgeOne 负载均衡(需加白申请)进行配置。" ) ); } if (domainConfig?.IpFreqLimit?.Switch === "on") { logger.warn( t( "暂不支持迁移 IP 访问限频配置, 请前往 EdgeOne web 防护速率限制进行设置。" ) ); } if (domainConfig?.RemoteAuthentication?.Switch === "on") { logger.warn(t("暂不支持迁移远程鉴权, 请前往 EdgeOne 边缘函数进行配置。")); } let originInfo = { // 非 'cos', 'image', 'igtm', 'third_party' 则认为是自有源站,此时不需要填写 HostHeader ...(!['cos', 'image', 'igtm', 'third_party'].includes(originType) && {HostHeader: domainConfig.Origin.ServerName}), }; if (needCreateDomain) { if (originType === "igtm") { originInfo["OriginType"] = "IP_DOMAIN"; originInfo["Origin"] = origins[0]; } else if (originType === "cos") { originInfo["OriginType"] = "COS"; originInfo["PrivateAccess"] = domainConfig.Origin.CosPrivateAccess; originInfo["Origin"] = origins[0]; } else if (["domain", "domainv6", "ip", "ipv6", "ip_domain"].includes(originType)) { if ( origins.length > 1 || utils.testIpAndWeight(origins[0]) || utils.testIpAndPortAndWeight(origins[0]) || utils.testDomainAndWeight(origins[0]) || utils.testDomainAndPortAndWeight(origins[0]) ) { const validResult = utils.validateOriginsPort(origins); if (!validResult[0]) { throw new Error(validResult[1]); } originPort = +validResult[1]; const originGroupName = domainName.replaceAll(".", "_"); groupId = await createOriginGroup({ zoneId, origins: origins, name: `CDN_migrate_domain_${originGroupName}`, }); originInfo["OriginType"] = "ORIGIN_GROUP"; originInfo["Origin"] = groupId; delete originInfo["HostHeader"]; // 自有源站 + 源站组 不需要填写 HostHeader } else { const [ip, port] = origins[0].split(":"); originInfo["OriginType"] = "IP_DOMAIN"; originInfo["Origin"] = ip; if (port) { originPort = +port; } } } else if ( ["third_party"].includes(originType) && domainConfig.Origin.OriginCompany === "aws_s3" ) { originInfo["OriginType"] = "IP_DOMAIN"; originInfo["Origin"] = origins[0]; originInfo["PrivateAccess"] = domainConfig.AwsPrivateAccess && domainConfig.AwsPrivateAccess.Switch ? domainConfig.AwsPrivateAccess.Switch : "off"; if (originInfo["PrivateAccess"] === "off") { originInfo["PrivateParameters"] = [ { Name: "AccessKeyId", Value: "", }, { Name: "SecretAccessKey", Value: "", }, { Name: "SignatureVersion", Value: "v4", }, { Name: "Region", Value: "", }, ]; } else { originInfo["PrivateParameters"] = [ { Name: "AccessKeyId", Value: domainConfig.AwsPrivateAccess.AccessKey, }, { Name: "SecretAccessKey", Value: domainConfig.AwsPrivateAccess.SecretKey, }, { Name: "SignatureVersion", Value: domainConfig.AwsPrivateAccess.Bucket, }, { Name: "Region", Value: domainConfig.AwsPrivateAccess.Region, }, ]; } } else { genLog.throwError(t("暂不支持该源站类型的域名迁移")); } } if (needCreateDomain) { logger.info(t(`调用 CreateAccelerationDomain 创建 EO 域名...`)); const param = { ZoneId: zoneId, DomainName: domainName, OriginInfo: originInfo, IPv6Status: ipv6Status, }; if (domainConfig.Origin.OriginPullProtocol) { param["OriginProtocol"] = domainConfig.Origin.OriginPullProtocol.toUpperCase(); } if (originPort) { param["HttpOriginPort"] = originPort; param["HttpsOriginPort"] = originPort; } await capi("CreateAccelerationDomain", utils.TEO, param) genLog.successLog(t(`域名创建成功`)); } // 分类配置,EO的配置主要分三部分接口组成,1.添加域名时源站配置,2.规则引擎配置,3.安全/用量封顶等其他配置 // 规则引擎配置 let ruleConfigKeys = needConfigsKeys.filter((key) => { return ruleConfigs.includes(key); }); // 判断是否需要迁移证书配置 if ( domainConfig.Https && domainConfig.Https.Switch === "on" && domainConfig.Https.CertInfo && domainConfig.Https.CertInfo.CertId ) { const expireTime = new Date( domainConfig.Https.CertInfo.ExpireTime.replace(/-/g, "/") ).getTime(); const now = new Date().getTime(); if (now > expireTime) { const expireMsg = t("证书已过期,无法迁移 HTTPS 相关配置"); genLog.errorLog(expireMsg); report.Details.push({ config: t("HTTPS配置(Https)"), result: t("失败"), detail: expireMsg, }); } else { // 获取ssl 证书列表 try { const { TotalCount, Certificates } = await capi("DescribeCertificates", utils.SSL, { Offset: 0, Limit: 1000, SearchKey: domainConfig.Https.CertInfo.CertId, }) if (TotalCount === 0) { genLog.throwError(t(`获取不到相关证书信息,无法迁移 HTTPS 相关配置`)) } // 创建证书配置 await capi("ModifyHostsCertificate", utils.TEO, { ZoneId: zoneId, Hosts: [domainName], Mode: 'sslcert', ServerCertInfo: [ { CertId: domainConfig.Https.CertInfo.CertId, }, ], }) // 证书创建完毕后,把证书相关配置加入到规则引擎中 ruleConfigKeys = [].concat(ruleConfigKeys, httpsConfigs); } catch (e) { genLog.errorLog(`${t("HTTPS配置(Https)")} ${t("失败")}: ${JSON.stringify(e)}`) report.Details.push({ config: t("HTTPS配置(Https)"), result: t("失败"), detail: `${e.toString()}`, }); } } } else { report.Details.push({ config: t("HTTPS配置(Https)"), result: t("未配置"), detail: "", }); } const cdnRuleConfigs = {}; const eoRuleConfigs = {}; genLog.defaultLog(t(`迁移 CDN 配置到规则引擎...`)) // CDN配置转成规则引擎配置 if (ruleConfigKeys.length > 0) { // 记录每个配置转成规则的日志 let ruleTransferLog = []; ruleConfigKeys.forEach((key) => { cdnRuleConfigs[key] = domainConfig[key]; eoRuleConfigs[key] = transfer.ruleTransfer[key](domainName, domainConfig, ruleTransferLog); }); // 规则引擎配置拼接 const {Cache, ...res} = eoRuleConfigs const rules = utils.rulesGenerator(domainName, res); const groupByRules = utils.copyConditionToOtherRule(rules); const requestMap = groupByRules.map((rule, index) => { return async () => { return capi("CreateRule", utils.TEO, { ZoneId: zoneId, RuleName: t(`[CDN迁移EO] {{domainName}}`, { domainName: `${domainName}_${index}` }), Status: 'enable', Rules: [rule], }); }; }); for (const request of requestMap) { try { await request(); // 规则创建成功,则将日志写到报告里 report.Details = report.Details.concat(ruleTransferLog); genLog.successLog(t(`规则创建成功`)) } catch (e) { report.Details.push({ config: t("规则引擎相关配置"), result: t("失败"), detail: `${e.toString()}`, }); genLog.errorLog(`${t("规则引擎相关配置")} ${t("失败")}: ${JSON.stringify(e)}`) } } if (Cache) { const configRules = utils.rulesGenerator(domainName, { Cache }); const groupByTTLRules = utils.copyConditionToOtherRule(configRules); const requestTTLMap = groupByTTLRules.map((ttlRule, index) => { return async () => { return capi("CreateRule", utils.TEO, { ZoneId: zoneId, RuleName: t(`[CDN迁移EO-节点缓存 TTL] {{domainName}}`, { domainName: `${domainName}_${index}` }), Status: 'enable', Rules: [ttlRule], }); }; }); for (const request of requestTTLMap) { try { await request(); // 规则创建成功,则将日志写到报告里 report.Details = report.Details.concat(ruleTransferLog); genLog.successLog(t(`节点缓存 TTL 配置迁移成功`)); } catch (e) { report.Details.push({ config: t('节点缓存 TTL'), result: t('失败'), detail: `${e.toString()}` }); genLog.errorLog(`${t('节点缓存 TTL')} ${t('失败')}: ${JSON.stringify(e)}`); } } } } else { genLog.warnLog(t(`无可迁移配置,跳过步骤`)) } genLog.successLog(t(`{{domainName}} 迁移完成`, { domainName })) } catch (e) { report.Details.push({ config: t("所有配置"), result: t("失败"), detail: `${e?.stack || e}`, }); genLog.errorLog(`${t("所有配置")} ${t("失败")}`); genLog.errorLog(e?.stack || e); } } async function singleScdnTask(domain, zoneId, report, options) { if (!domain) { const emptyDomainMsg = t(`参数 Domain 为空,跳过任务`); genLog.errorLog(emptyDomainMsg); report.Details.push({ config: t("所有配置"), result: t("失败"), detail: emptyDomainMsg, }); return; } if (!zoneId) { const emptyZoneIdMsg = t(`参数 ZoneId 为空,跳过任务`); genLog.errorLog(emptyZoneIdMsg); report.Details.push({ config: t("所有配置"), result: t("失败"), detail: emptyZoneIdMsg, }); return; } genLog.defaultLog(t(`获取 SCDN 加速域名安全配置...`)); try { const scdnConfig = await capi("DescribeScdnConfig", utils.CDN, { Domain: domain, }) if (!scdnConfig) { genLog.defaultLog(t("当前域名未配置安全防护,条过此步骤")); return; } const { Zones } = await capi("DescribeZones", utils.TEO, { Filters: [ { Name: "zone-id", Values: [zoneId], }, ], }) const eoPlanType = isStandard(Zones[0].Resources || [], Zones[0]); genLog.successLog(t(`SCDN 加速域名安全配置获取成功`)); genLog.defaultLog(t(`迁移 SCDN 加速域名安全配置到安全防护...`)); // 1、获取 scdnConfig 中需要导出的属性,判断当前拉取配置项是否在迁移名单 SCDNDefaultConfigs 中 const filterKeys = Object.keys(scdnConfig); const whiteList = filterKeys.filter((item) => { return SCDNDefaultConfigs.includes(item); }); // 2、数据结构转换 const eoSecurityConfig = {}; // 迁移日志 let ruleTransferLog = []; whiteList.map((key) => { eoSecurityConfig[SCDN2EO_MAP[key]] = transfer.scdnTransfer[ SCDN2EO_MAP[key] ](domain, scdnConfig[key], eoPlanType, ruleTransferLog); }); // 规则创建成功,则将日志写到报告里 report.Details = report.Details.concat(ruleTransferLog); // 新站点,新增域名修改安全配置步骤:解绑到空模板 try { await capi("BindSecurityTemplateToEntity", utils.TEO, { Entities: [domain], Operate: USE_DEFAULT, OverWrite: true, TemplateId: ZONE_GLOBAL_ENTITY, ZoneId: zoneId, }) } catch (e) { genLog.defaultLog(e.message); } // 3、创建步骤 await capi("ModifySecurityPolicy", utils.TEO, { ZoneId: zoneId, Entity: domain, SecurityConfig: eoSecurityConfig, TemplateId: "", }) genLog.successLog(t(`{{domain}} 迁移完成`, { domain })) } catch (e) { report.Details.push({ config: t("所有配置"), result: t("失败"), detail: `${e.toString()}`, }); genLog.errorLog(`${t("所有配置")} ${t("失败")}`); genLog.errorLog(e); } } async function createOriginGroup(params) { const { zoneId, origins, name } = params; const records = []; origins.forEach((origin) => { const [ipDomain, port, weight] = origin.split(":"); let record = { Record: ipDomain, Type: "IP_DOMAIN", Weight: Number(weight), }; if (weight === "" || weight === undefined) { delete record.Weight; } records.push(record); }); const { OriginGroupId } = await capi("CreateOriginGroup", utils.TEO, { ZoneId: zoneId, Name: name, Type: "GENERAL", Records: records, }) return OriginGroupId; } const API_MAP = { runTasks: runTasks, runScdnTasks: runScdnTasks, checkDomains: checkDomains, accessTask: accessTask, }; module.exports.init = function (CDN2EO, task) { _.each(API_MAP, function (fn, apiName) { CDN2EO.prototype[apiName] = fn; }); };