import Plugin from "../Plugin" ; import * as deepEqual from "fast-deep-equal" ; import { readFile , statSync } from "fs" ; import { ServiceFunction , ServiceProvider } from "../types" ; import { Function , UpdateFunctionConfigurationRequest , Trigger , CreateTriggerRequest , Environment , Variable , VpcConfig , Code } from "../api/scf/scf" ; import { TriggerImpl , TriggerType } from "../triggers/Base" ; import { getTrigger } from "../triggers" ; import { MAX_DIRECTLY_POST_PACKAGE_SIZE , SCF_CAM_POLICY_COS } from "../constants" ; export default class TencentDeploy extends Plugin { private getAllFunctionsTask : Promise < Function [ ] > ; deployJob = async ( ) => { try { await this . doDeploy ( ) ; this . json ( { success : true } ) ; } catch ( e ) { this . json ( { success : false , error : e } ) ; throw e ; } } ; hooks = { "deploy:deploy" : this . deployJob , "deploy:function:packageFunction" : ( ) => this . serverless . pluginManager . spawn ( "package:function" ) , "deploy:function:deploy" : this . deployJob } ; async doDeploy ( ) { const functions = await this . cacheGetAllFunctions ( ) ; const map = new Map < string , Function > ( ) ; for ( const f of functions ) { map . set ( f . FunctionName , f ) ; } const tasks = this . getNeedDeployFunctions ( ) . map ( async ( f ) => await this . deployFunction ( f , map . get ( f . name ) ) ) ; await Promise . all ( tasks ) ; } async deployFunction ( f , r ) { if ( r ) { return this . deployExistsFunction ( f ) ; } return this . deployNewFunction ( f ) ; } async deployNewFunction ( f ) { this . log ( `开始创建函数 ${ f . name }` ) ; await this . provider . api . scf . CreateFunction ( { FunctionName : f . name , Handler : f . handler , MemorySize : f . memorySize , Description : f . description , Timeout : f . timeout , Environment : parseEnvironment ( f . environment ) , Runtime : this . serverless . service . provider . runtime , VpcConfig : parseVpcConfig ( f . vpc ) , Code : await this . getServiceCode ( f ) } ) ; this . log ( `创建函数 ${ f . name } 完成` ) ; await this . deployTriggers ( f , [ ] ) ; } async deployExistsFunction ( f ) { const fetchTask = this . provider . api . scf . GetFunction ( { FunctionName : f . name } ) ; this . log ( `开始更新函数 ${ f . name } 代码` ) ; await this . provider . api . scf . UpdateFunctionCode ( { FunctionName : f . name , Handler : f . handler , ... ( await this . getServiceCode ( f ) ) } ) ; this . log ( `更新函数 ${ f . name } 代码 完成` ) ; const r = await fetchTask ; let updates = { } as any ; if ( "memorySize" in f && f . memorySize !== r . MemorySize ) { updates . MemorySize = f . memorySize ; } if ( "timeout" in f && f . timeout !== r . Timeout ) { updates . Timeout = f . timeout ; } if ( "description" in f && f . description !== r . Description ) { updates . Description = f . description ; } const environment = parseEnvironment ( f . environment ) ; if ( ! deepEqual ( environment , r . Environment ) ) { updates . Environment = environment ; } const vpc = parseVpcConfig ( f . vpc ) ; if ( ! deepEqual ( vpc , r . VpcConfig ) ) { updates . VpcConfig = vpc ; } if ( Object . keys ( updates ) . length > 0 ) { this . log ( `开始更新函数 ${ f . name } 配置` ) ; await this . provider . api . scf . UpdateFunctionConfiguration ( { FunctionName : f . name , ... updates } ) ; this . log ( `更新函数 ${ f . name } 配置成功` ) ; } await this . deployTriggers ( f , r . Triggers ) ; } async deployTriggers ( f , triggers ) { this . log ( `开始更新函数 ${ f . name } 触发器` ) ; const existsTriggers = new Map < string , TriggerImpl < any > > ( ) ; for ( const trigger of triggers ) { const impl = getTrigger ( f , trigger . Type , null , trigger ) ; existsTriggers . set ( impl . hash , impl ) ; } const needCreateTriggers = [ ] ; for ( const wrap of f . events ) { const type = Object . keys ( wrap ) [ 0 ] as TriggerType ; const trigger = wrap [ type ] ; const local = getTrigger ( f , type as TriggerType , trigger ) ; if ( ! local ) { this . log ( `无法识别函数 ${ f . name } 触发器类型: ${ type }` ) ; continue ; } const remote = existsTriggers . get ( local . hash ) ; if ( ! remote ) { needCreateTriggers . push ( local ) ; } else { existsTriggers . delete ( local . hash ) ; } } for ( const [ type , remote ] of existsTriggers ) { this . log ( `开始删除函数 ${ f . name } 触发器 ${ remote . Type }:${ remote . TriggerName }` ) ; await this . provider . api . scf . DeleteTrigger ( { FunctionName : f . name , Type : remote . Type , TriggerName : remote . TriggerName , TriggerDesc : remote . TriggerDesc } ) ; await remote . afterDelete ( this . provider ) ; this . log ( `删除函数 ${ f . name } 触发器 ${ remote . Type }:${ remote . TriggerName } 完成` ) ; } for ( const trigger of needCreateTriggers ) { const params = { FunctionName : f . name , Type : trigger . Type , TriggerName : trigger . TriggerName , TriggerDesc : trigger . TriggerDescForCreation } ; this . log ( `开始创建函数 ${ f . name } 触发器 ${ params . Type }:${ params . TriggerName }` ) ; await trigger . beforeCreate ( this . provider ) ; await this . provider . api . scf . CreateTrigger ( params ) ; this . log ( `创建函数 ${ f . name } 触发器 ${ params . Type }:${ params . TriggerName } 完成` ) ; } if ( ! existsTriggers . size && ! needCreateTriggers . length ) { this . log ( `函数 ${ f . name } 触发器无需更新` ) ; } else { this . log ( `函数 ${ f . name } 触发器更新完成` ) ; } } async getServiceCode ( func ) { const filePath = func . package . artifact || this . serverless . service . package . artifact ; const size = statSync ( filePath ) . size ; if ( size < MAX_DIRECTLY_POST_PACKAGE_SIZE ) { return { ZipFile : await fileToBase64 ( filePath ) } ; } this . debug ( `函数 ${ func . name } 发布包超过 ${ MAX_DIRECTLY_POST_PACKAGE_SIZE } 字节，使用cos方式上传` ) ; await this . provider . api . cam . assureScfPolicy ( SCF_CAM_POLICY_COS ) ; const result = await this . provider . api . scf . upload ( filePath , func ) ; return { CosBucketName : result . Bucket . replace ( /\-\d+$/ , "" ) , CosObjectName : "/" + result . Key , CosBucketRegion : this . serverless . service . provider . region } ; } getNeedDeployFunctions ( ) { const functions = this . serverless . service . functions ; return Object . keys ( functions ) . filter ( name => { const specifiedFunction = this . options [ "function" ] ; return ! specifiedFunction || specifiedFunction === name ; } ) . map < ServiceFunction > ( name => { return functions [ name ] ; } ) ; } async cacheGetAllFunctions ( ) { if ( ! this . getAllFunctionsTask ) { this . getAllFunctionsTask = this . getAllFunctions ( ) ; } return await this . getAllFunctionsTask ; } async getAllFunctions ( ) { const maxPageSize = 100 ; let list = [ ] ; let total = 0 ; do { const { Functions , TotalCount } = await this . ListFunctions ( list . length , maxPageSize ) ; total = TotalCount ; list = list . concat ( Functions ) ; } while ( list . length < total ) ; return list ; } ListFunctions ( offset , limit ) { return this . provider . api . scf . ListFunctions ( { Offset : offset , Limit : limit } ) ; } } param env function parseEnvironment ( env ) { return { Variables : Object . keys ( env ) . map < Variable > ( k => ( { Key : k , Value : env [ k ] } ) ) } ; } function parseVpcConfig ( vpc = "" ) { const [ vpcId , subnetId ] = vpc . split ( "/" ) ; return { VpcId : vpc ? vpcId : null , SubnetId : vpc ? subnetId : null } ; } async function fileToBase64 ( filePath ) { const data = await new Promise < Buffer > ( ( resolve , reject ) => { readFile ( filePath , ( err , data ) => { if ( err ) { return reject ( err ) ; } resolve ( data ) ; } ) ; } ) ; return data . toString ( "base64" ) ; }