diff --git a/__tests__/e2e/ci.sh b/__tests__/e2e/ci.sh index c97bf90..dd1a9a6 100755 --- a/__tests__/e2e/ci.sh +++ b/__tests__/e2e/ci.sh @@ -20,7 +20,6 @@ s remove -y rm -rf ./code/target cd .. - echo "test java runtime" cd java export fc_component_function_name=java-$(uname)-$(uname -m)-$RANDSTR @@ -31,7 +30,6 @@ s remove -y rm -rf ./target cd .. - echo "test custom go runtime ..." cd custom rm -rf ./go/code/go.sum @@ -43,7 +41,6 @@ s remove -y -t ./go/s.yaml rm -rf ./go/code/target cd .. - echo "test nodejs runtime with auto ..." cd nodejs export fc_component_function_name=nodejs14-$(uname)-$(uname -m)-$RANDSTR @@ -53,6 +50,26 @@ s info -y -t ./s_auto.yaml s remove -y -t ./s_auto.yaml cd .. +echo "test deploy with alias" +cd nodejs +export fc_component_function_name=nodejs14-$(uname)-$(uname -m)-$RANDSTR +s deploy --function -t s2.yaml +versionId=$(s version publish -t s2.yaml --silent -o json | jq -r '."versionId"') +echo "latest version = $versionId" +if [[ "$versionId" -gt 1 ]]; then + mainVersion=$((versionId - 1)) + echo "main version = $mainVersion" + s alias publish --alias-name test --version-id $mainVersion --vw "{\"$versionId\": 0.2}" -t s2.yaml +else + s alias publish --alias-name test --version-id $versionId -t s2.yaml +fi + +s deploy --trigger -t s2.yaml +s deploy --async-invoke-config -t s2.yaml +s info -t s2.yaml +s remove -y -t s2.yaml +cd .. + echo "test http trigger with jwt ..." cd trigger/jwt export fc_component_function_name=nodejs16-$(uname)-$(uname -m)-$RANDSTR @@ -60,8 +77,8 @@ s deploy -y -t ./s.yaml s invoke -e '{"hello":"fc http trigger with jwt"}' -t ./s.yaml rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm yum install -y jq -url1=$(s info -y -t ./s.yaml --silent -o json | jq -r '.hello_world.url.system_url') -url2=$(s info -y -t ./s.yaml --silent -o json | jq -r '.hello_world_2.url.system_url') +url1=$(s info -y -t ./s.yaml --silent -o json | jq -r '.hello_world.url.system_url') +url2=$(s info -y -t ./s.yaml --silent -o json | jq -r '.hello_world_2.url.system_url') echo $url1 echo $url2 curl -XPOST $url1/black1/aa -d '{"test":"jwt"}' diff --git a/__tests__/e2e/nodejs/s.yaml b/__tests__/e2e/nodejs/s.yaml index 8c2f40a..16a1101 100644 --- a/__tests__/e2e/nodejs/s.yaml +++ b/__tests__/e2e/nodejs/s.yaml @@ -19,17 +19,4 @@ resources: instanceLifecycleConfig: initializer: handler: event.initializer - timeout: 10 - - # vpcConfig: auto - # nasConfig: auto - # logConfig: auto - - asyncInvokeConfig: - destinationConfig: - onFailure: - destination: acs:mns:${vars.region}::/topics/serverless-devs-fc3-ci-test/messages - onSuccess: - destination: acs:fc:${vars.region}::functions/serverless-devs-ci-async-invoke-config-succ - maxAsyncEventAgeInSeconds: 360 - maxAsyncRetryAttempts: 3 \ No newline at end of file + timeout: 10 \ No newline at end of file diff --git a/__tests__/e2e/nodejs/s2.yaml b/__tests__/e2e/nodejs/s2.yaml new file mode 100644 index 0000000..79c7450 --- /dev/null +++ b/__tests__/e2e/nodejs/s2.yaml @@ -0,0 +1,44 @@ +edition: 3.0.0 # 命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范 +name: test-node-app # 项目名称 +access: quanxi + +vars: + region: ${env('REGION', 'cn-huhehaote')} + +resources: + fcDemo: # 业务名称/模块名称 + component: ${env('fc_component_version', path('../../../'))} + props: # 组件的属性值 + region: ${vars.region} + functionName: fc3-event-part-${env('fc_component_function_name', 'nodejs14')} + runtime: ${env('fc_component_runtime', 'nodejs14')} + code: ./code + handler: event.handler + memorySize: 128 + timeout: 30 + instanceLifecycleConfig: + initializer: + handler: event.initializer + timeout: 10 + + triggers: + - triggerName: httpTrigger # 触发器名称 + triggerType: http # 触发器类型 + description: 'xxxx' + qualifier: test # 触发服务的版本 + triggerConfig: + authType: anonymous # 鉴权类型,可选值:anonymous、function + disableURLInternet: false # 是否禁用公网访问 URL + methods: # HTTP 触发器支持的访问方法,可选值:GET、POST、PUT、DELETE、HEAD + - GET + - POST + + asyncInvokeConfig: + destinationConfig: + onFailure: + destination: acs:mns:${vars.region}::/topics/serverless-devs-fc3-ci-test/messages + onSuccess: + destination: acs:fc:${vars.region}::functions/serverless-devs-ci-async-invoke-config-succ + maxAsyncEventAgeInSeconds: 360 + maxAsyncRetryAttempts: 3 + qualifier: test \ No newline at end of file diff --git a/__tests__/e2e/nodejs/test.sh b/__tests__/e2e/nodejs/test.sh new file mode 100755 index 0000000..d7be37a --- /dev/null +++ b/__tests__/e2e/nodejs/test.sh @@ -0,0 +1,17 @@ +#! /bin/bash +# s remove -y -t s2.yaml +s deploy --function -t s2.yaml +versionId=$(s version publish -t s2.yaml --silent -o json | jq -r '."versionId"') +echo "latest version = $versionId" +if [[ "$versionId" -gt 1 ]]; then + mainVersion=$((versionId - 1)) + echo "main version = $mainVersion" + s alias publish --alias-name test --version-id $mainVersion --vw "{\"$versionId\": 0.2}" -t s2.yaml +else + s alias publish --alias-name test --version-id $versionId -t s2.yaml +fi + +s deploy --trigger -t s2.yaml +s deploy --async-invoke-config -t s2.yaml +s info -t s2.yaml +s alias list -t s2.yaml diff --git a/src/interface/async_invoke_config.ts b/src/interface/async_invoke_config.ts index 39b3248..7bf66c5 100644 --- a/src/interface/async_invoke_config.ts +++ b/src/interface/async_invoke_config.ts @@ -10,4 +10,5 @@ export interface IAsyncInvokeConfig { destinationConfig?: IDestinationConfig; maxAsyncEventAgeInSeconds?: number; maxAsyncRetryAttempts?: number; + qualifier?: string; } diff --git a/src/subCommands/deploy/impl/async_invoke_config.ts b/src/subCommands/deploy/impl/async_invoke_config.ts index 759bc27..b49448a 100644 --- a/src/subCommands/deploy/impl/async_invoke_config.ts +++ b/src/subCommands/deploy/impl/async_invoke_config.ts @@ -20,8 +20,9 @@ export default class AsyncInvokeConfig extends Base { super(inputs, opts.yes); this.functionName = inputs.props?.functionName; - this.local = _.cloneDeep(_.get(inputs, 'props.asyncInvokeConfig', {})) as IAsyncInvokeConfig; - logger.debug(`need deploy asyncInvokeConfig: ${JSON.stringify(this.local)}`); + const asyncInvokeConfig = _.get(inputs, 'props.asyncInvokeConfig', {}); + this.local = _.cloneDeep(asyncInvokeConfig) as IAsyncInvokeConfig; + logger.debug(`need deploy asyncInvokeConfig: ${JSON.stringify(asyncInvokeConfig)}`); } async before() { @@ -35,16 +36,18 @@ export default class AsyncInvokeConfig extends Base { const localConfig = this.local; const id = `${this.functionName}/asyncInvokeConfig`; + const asyncInvokeConfig = _.get(this.inputs, 'props.asyncInvokeConfig', {}); + const qualifier = _.get(asyncInvokeConfig, 'qualifier', 'LATEST'); if (!_.isEmpty(localConfig)) { localConfig.destinationConfig = localConfig.destinationConfig || {}; if (this.needDeploy) { - await this.fcSdk.putAsyncInvokeConfig(this.functionName, 'LATEST', localConfig); + await this.fcSdk.putAsyncInvokeConfig(this.functionName, qualifier, localConfig); } else if (_.isEmpty(remoteConfig)) { // 如果不需要部署,但是远端资源不存在,则尝试创建一下 logger.debug( `Online asyncInvokeConfig does not exist, specified not to deploy, attempting to create ${id}`, ); - await this.fcSdk.putAsyncInvokeConfig(this.functionName, 'LATEST', localConfig); + await this.fcSdk.putAsyncInvokeConfig(this.functionName, qualifier, localConfig); } else { logger.debug( `Online asyncInvokeConfig exists, specified not to deploy, skipping deployment ${id}`, @@ -55,12 +58,17 @@ export default class AsyncInvokeConfig extends Base { } private async getRemote() { + const asyncInvokeConfig = _.get(this.inputs, 'props.asyncInvokeConfig', {}); + const qualifier = _.get(asyncInvokeConfig, 'qualifier', 'LATEST'); try { const result = await this.fcSdk.getAsyncInvokeConfig( this.functionName, - 'LATEST', + qualifier, GetApiType.simpleUnsupported, ); + if (result) { + result.qualifier = qualifier; + } this.remote = result; } catch (ex) { logger.debug(`Get remote asyncInvokeConfig of ${this.functionName} error: ${ex.message}`); diff --git a/src/subCommands/deploy/index.ts b/src/subCommands/deploy/index.ts index 4ec45c4..0d2df59 100644 --- a/src/subCommands/deploy/index.ts +++ b/src/subCommands/deploy/index.ts @@ -23,17 +23,23 @@ export default class Deploy { alias: { 'assume-yes': 'y', }, - boolean: ['skip-push'], + boolean: ['skip-push', 'async_invoke_config'], }); // TODO: 更完善的验证 verify(inputs.props); - const { function: type, trigger, 'assume-yes': yes, 'skip-push': skipPush } = this.opts; + const { + function: type, + trigger, + 'async-invoke-config': async_invoke_config, + 'assume-yes': yes, + 'skip-push': skipPush, + } = this.opts; logger.debug('parse argv:'); logger.debug(this.opts); // 初始化部署实例 - const deployAll = !type && !trigger; + const deployAll = !type && !trigger && !async_invoke_config; logger.debug(`Deploy all resources: ${deployAll}`); if (deployAll || type) { this.service = new Service(inputs, { type, yes, skipPush }); // function @@ -41,8 +47,10 @@ export default class Deploy { if (deployAll || trigger) { this.trigger = new Trigger(inputs, { yes, trigger }); } - if (deployAll) { + if (deployAll || async_invoke_config) { this.asyncInvokeConfig = new AsyncInvokeConfig(inputs, { yes }); + } + if (deployAll) { this.vpcBinding = new VpcBinding(inputs, { yes }); } } diff --git a/src/subCommands/info/index.ts b/src/subCommands/info/index.ts index cca37d0..86f347f 100644 --- a/src/subCommands/info/index.ts +++ b/src/subCommands/info/index.ts @@ -116,7 +116,17 @@ export default class Info { return {}; } try { - return await this.fcSdk.getAsyncInvokeConfig(this.functionName, 'LATEST', this.getApiType); + const asyncInvokeConfig = _.get(this.inputs, 'props.asyncInvokeConfig', {}); + const qualifier = _.get(asyncInvokeConfig, 'qualifier', 'LATEST'); + const result = await this.fcSdk.getAsyncInvokeConfig( + this.functionName, + qualifier, + this.getApiType, + ); + if (result) { + result.qualifier = qualifier; + } + return result; } catch (ex) { logger.debug(`Get AsyncInvokeConfig ${this.functionName} error: ${ex}`); return { diff --git a/src/subCommands/plan/index.ts b/src/subCommands/plan/index.ts index 09a2cd8..e781066 100644 --- a/src/subCommands/plan/index.ts +++ b/src/subCommands/plan/index.ts @@ -126,12 +126,17 @@ export default class Plan { private async planAsyncInvokeConfig() { let remote = {}; + const asyncInvokeConfig = _.get(this.inputs, 'props.asyncInvokeConfig', {}); + const qualifier = _.get(asyncInvokeConfig, 'qualifier', 'LATEST'); try { const result = await this.fcSdk.getAsyncInvokeConfig( this.functionName, - 'LATEST', + qualifier, GetApiType.simpleUnsupported, ); + if (result) { + result.qualifier = qualifier; + } remote = result; } catch (ex) { logger.debug( @@ -140,7 +145,10 @@ export default class Plan { } // eslint-disable-next-line prefer-const - let local = _.cloneDeep(_.get(this.inputs.props, 'asyncInvokeConfig', {})); + let local = _.cloneDeep(_.get(this.inputs.props, 'asyncInvokeConfig', {} as any)); + if (local) { + local.qualifier = qualifier; + } return diffConvertPlanYaml(remote, local, { deep: 1, complete: true }); } diff --git a/src/subCommands/remove/index.ts b/src/subCommands/remove/index.ts index 1838f2e..9682b5d 100644 --- a/src/subCommands/remove/index.ts +++ b/src/subCommands/remove/index.ts @@ -12,6 +12,7 @@ export default class Remove { private region: IRegion; private functionName: string; private yes = false; + private async_invoke_config: boolean; private resources: Record = {}; private fcSdk: FC; @@ -21,7 +22,7 @@ export default class Remove { alias: { 'assume-yes': 'y', }, - boolean: ['function'], + boolean: ['function', 'async_invoke_config'], string: ['function-name', 'region'], }); logger.debug(`parse argv: ${JSON.stringify(opts)}`); @@ -32,9 +33,11 @@ export default class Remove { trigger, 'assume-yes': yes, 'function-name': functionName, + 'async-invoke-config': async_invoke_config, } = opts; - const removeAll = !needRemoveFunction && !trigger; + const removeAll = !needRemoveFunction && !trigger && !async_invoke_config; + this.async_invoke_config = async_invoke_config; this.region = region || _.get(inputs, 'props.region'); logger.debug(`region: ${this.region}`); @@ -87,6 +90,7 @@ export default class Remove { return; } } + await this.removeAsyncInvokeConfig(); await this.removeTrigger(); await this.removeFunction(); } @@ -103,6 +107,12 @@ export default class Remove { await this.getTriggerResource(); logger.spin('got', 'trigger', `${this.region}/${this.functionName}`); } + + if (this.resources.function || this.async_invoke_config) { + logger.spin('getting', 'asyncInvokeConfig', `${this.region}/${this.functionName}`); + await this.getAsyncInvokeConfigResource(); + logger.spin('got', 'asyncInvokeConfig', `${this.region}/${this.functionName}`); + } } private async getFunctionResource() { @@ -143,26 +153,6 @@ export default class Remove { ); } - try { - const asyncInvokeConfigs = await this.fcSdk.listAsyncInvokeConfig(this.functionName); - if (!_.isEmpty(asyncInvokeConfigs)) { - this.resources.asyncInvokeConfigs = asyncInvokeConfigs.map((item) => ({ - functionArn: item.functionArn, - // acs:fc:cn-huhehaote:123456:functions/fc3-command-fc3-command/test - // acs:fc:cn-huhehaote:123456:functions/fc3-command-fc3-command - qualifier: item.functionArn.split('/')[2] || 'LATEST', - destinationConfig: item.destinationConfig, - })); - logger.write(`Remove function ${this.region}/${this.functionName} asyncInvokeConfigs:`); - logger.output(this.resources.asyncInvokeConfigs, 2); - console.log(); - } - } catch (ex) { - logger.debug( - `List function ${this.region}/${this.functionName} asyncInvokeConfigs error: ${ex.message}`, - ); - } - try { const provision = await this.fcSdk.listFunctionProvisionConfig(this.functionName); this.resources.provision = provision.map((item) => { @@ -232,6 +222,28 @@ export default class Remove { } } + private async getAsyncInvokeConfigResource() { + try { + const asyncInvokeConfigs = await this.fcSdk.listAsyncInvokeConfig(this.functionName); + if (!_.isEmpty(asyncInvokeConfigs)) { + this.resources.asyncInvokeConfigs = asyncInvokeConfigs.map((item) => ({ + functionArn: item.functionArn, + // acs:fc:cn-huhehaote:123456:functions/fc3-command-fc3-command/test + // acs:fc:cn-huhehaote:123456:functions/fc3-command-fc3-command + qualifier: item.functionArn.split('/')[2] || 'LATEST', + destinationConfig: item.destinationConfig, + })); + logger.write(`Remove function ${this.region}/${this.functionName} asyncInvokeConfigs:`); + logger.output(this.resources.asyncInvokeConfigs, 2); + console.log(); + } + } catch (ex) { + logger.debug( + `List function ${this.region}/${this.functionName} asyncInvokeConfigs error: ${ex.message}`, + ); + } + } + private async getTriggerResource() { let triggers = []; try { @@ -281,10 +293,29 @@ export default class Remove { } } + private async removeAsyncInvokeConfig() { + if (_.isEmpty(this.resources.asyncInvokeConfigs)) { + return; + } + + for (const { qualifier } of this.resources.asyncInvokeConfigs) { + logger.spin( + 'removing', + 'function asyncInvokeConfigs', + `${this.region}/${this.functionName}/${qualifier}`, + ); + await this.fcSdk.removeAsyncInvokeConfig(this.functionName, qualifier); + logger.spin( + 'removed', + 'function asyncInvokeConfigs', + `${this.region}/${this.functionName}/${qualifier}`, + ); + } + } + private async removeFunction() { /* 删除资源顺序 vpcBinding: {} - asyncInvokeConfig: {} provision: [ { qualifier: 'test', current: 2, target: 2 } ], // target / current concurrency: 80, aliases: [ 'test' ], @@ -311,22 +342,6 @@ export default class Remove { } } - if (!_.isEmpty(this.resources.asyncInvokeConfigs)) { - for (const { qualifier } of this.resources.asyncInvokeConfigs) { - logger.spin( - 'removing', - 'function asyncInvokeConfigs', - `${this.region}/${this.functionName}/${qualifier}`, - ); - await this.fcSdk.removeAsyncInvokeConfig(this.functionName, qualifier); - logger.spin( - 'removed', - 'function asyncInvokeConfigs', - `${this.region}/${this.functionName}/${qualifier}`, - ); - } - } - if (!_.isEmpty(this.resources.provision)) { for (const { qualifier } of this.resources.provision) { logger.spin(