From 8fc55f8189332aa84b1541a7a74fa0aa4f70a4b5 Mon Sep 17 00:00:00 2001 From: yugasun Date: Thu, 12 Nov 2020 17:14:12 +0800 Subject: [PATCH 1/6] feat: uniform configure --- README.md | 7 +- docs/configure.md | 307 ++++++++++++---------- example/serverless.yml | 16 +- serverless.component.yml | 197 +++++++++++++- src/config.js | 43 +++- src/package.json | 3 +- src/serverless.js | 414 ++++++++++++++++++------------ src/utils.js | 540 ++++++++++++++++++++++++--------------- 8 files changed, 1018 insertions(+), 509 deletions(-) diff --git a/README.md b/README.md index 0e8404a..773c056 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,11 @@ inputs: src: ./ # (optional) path to the source folder. default is a hello world app. exclude: - .env - functionName: expressDemo region: ap-guangzhou - runtime: Nodejs10.15 - apigatewayConf: + faas: + name: expressDemo + runtime: Nodejs10.15 + apigw: protocols: - http - https diff --git a/docs/configure.md b/docs/configure.md index ce276cd..0aa72c0 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -5,45 +5,48 @@ ```yml # serverless.yml -component: express # (必选) 组件名称,在该实例中为express -name: expressDemo # 必选) 组件实例名称. org: orgDemo # (可选) 用于记录组织信息,默认值为您的腾讯云账户 appid,必须为字符串 app: appDemo # (可选) 用于记录组织信息. 默认与name相同,必须为字符串 stage: dev # (可选) 用于区分环境信息,默认值是 dev +component: framework # (必选) 组件名称,在该实例中为express +name: frameworkDemo # 必选) 组件实例名称. + inputs: + framework: express region: ap-guangzhou # 云函数所在区域 - functionName: expressDemo # 云函数名称 - serviceName: mytest # api网关服务名称 - runtime: Nodejs10.15 # 运行环境 - serviceId: service-np1uloxw # api网关服务ID entryFile: sls.js # 自定义 server 的入口文件名,默认为 sls.js,如果不想修改文件名为 sls.js 可以自定义 - src: ./src # 第一种为string时,会打包src对应目录下的代码上传到默认cos上。 - # src: # 第二种,部署src下的文件代码,并打包成zip上传到bucket上 - # src: ./src # 本地需要打包的文件目录 - # bucket: bucket01 # bucket name,当前会默认在bucket name后增加 appid 后缀, 本例中为 bucket01-appid - # exclude: # 被排除的文件或目录 - # - .env - # - node_modules - # src: # 第三种,在指定存储桶bucket中已经存在了object代码,直接部署 - # bucket: bucket01 # bucket name,当前会默认在bucket name后增加 appid 后缀, 本例中为 bucket01-appid - # object: cos.zip # bucket key 指定存储桶内的文件 - layers: - - name: layerName # layer名称 - version: 1 # 版本 - functionConf: # 函数配置相关 + src: + src: ./ # 本地需要打包的文件目录 + exclude: # 被排除的文件或目录 + - .env + # - 'node_modules/**' + faas: # 函数配置相关 + name: expressDemo # 云函数名称 + runtime: Nodejs10.15 # 运行环境 timeout: 10 # 超时时间,单位秒 eip: false # 是否固定出口IP memorySize: 128 # 内存大小,单位MB - environment: # 环境变量 - variables: # 环境变量数组 - TEST: vale - vpcConfig: # 私有网络配置 - vpcId: '' # 私有网络的Id - subnetId: '' # 子网ID - apigatewayConf: # api网关配置 + environments: # 环境变量 + - envKey: TEST + envVal: 123 + vpc: # 私有网络配置 + vpcId: vpc-xxxx # 私有网络的Id + subnetId: subnet-xxxx # 子网ID + layers: + - name: layerName # layer名称 + version: 1 # 版本 + apigw: # api网关配置 isDisabled: false # 是否禁用自动创建 API 网关功能 - enableCORS: true # 允许跨域 + id: service-xxx # api网关服务ID + name: mytest # api网关服务名称 + description: mytest # api网关描述 + cors: true # 允许跨域 + protocols: + - http + - https + environment: test + timeout: 15 customDomains: # 自定义域名绑定 - domain: abc.com # 待绑定的自定义的域名 certificateId: abcdefg # 待绑定自定义域名的证书唯一 ID @@ -56,147 +59,195 @@ inputs: protocols: # 绑定自定义域名的协议类型,默认与服务的前端协议一致。 - http # 支持http协议 - https # 支持https协议 - protocols: - - http - - https - environment: test - serviceTimeout: 15 - usagePlan: # 用户使用计划 - usagePlanId: 1111 - usagePlanName: slscmp - usagePlanDesc: sls create - maxRequestNum: 1000 - auth: # 密钥 - secretName: secret - secretIds: - - xxx + + # 项目中静态资源自动托管到对象存储 + static: + cos: + bucket: static-bucket + acl: + permissions: public-read + sources: + - src: .next/static + targetDir: /_next/static + - src: public + targetDir: / + cdn: + area: mainland + domain: abc.com + autoRefresh: true + refreshType: delete + forceRedirect: + switch: on + redirectType: https + redirectStatusCode: 301 + https: + http2: on + certId: 'abc' ``` ## 配置描述 主要的参数 -| 参数名称 | 必选 | 默认值 | 描述 | -| ------------------------------------ | :--: | :-------------: | :------------------------------------------------------------------ | -| runtime | 否 | `Nodejs10.15` | 执行环境, 目前支持: Nodejs6.10, Nodejs8.9, Nodejs10.15, Nodejs12.16 | -| region | 否 | `ap-guangzhou` | 项目部署所在区域,默认广州区 | -| functionName | 否 | | 云函数名称 | -| serviceName | 否 | | API 网关服务名称, 默认创建一个新的服务名称 | -| serviceId | 否 | | API 网关服务 ID,如果存在将使用这个 API 网关服务 | -| entryFile | 否 | `sls.js` | 自定义 server 的入口文件名 | -| src | 否 | `process.cwd()` | 默认为当前目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) | -| layers | 否 | | 云函数绑定的 layer, 配置参数参考 [层配置](#层配置) | -| [functionConf](#函数配置) | 否 | | 函数配置 | -| [apigatewayConf](#API-网关配置) | 否 | | API 网关配置 | -| [cloudDNSConf](#DNS-配置) | 否 | | DNS 配置 | -| [Region special config](#指定区配置) | 否 | | 指定区配置 | +| 参数名称 | 必选 | 默认值 | 描述 | +| ---------------------------- | :--: | :------------: | :------------------------------------------------------- | +| framework | 是 | | 项目使用的 Web 框架 | +| src | 是 | | 代码目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) | +| region | 否 | `ap-guangzhou` | 项目部署所在区域 | +| entryFile | 否 | `sls.js` | 自定义 server 的入口文件名 | +| [faas](#函数配置) | 否 | | 函数配置 | +| [apigw](#API-网关配置) | 否 | | API 网关配置 | +| [static](#静态资源-CDN-配置) | 否 | | 静态资源 CDN 配置 | + +> 注意:目前 `framework` 支持 Web 框架有 `express`、`koa`、`egg`、`next`、`nuxt`、`nest`、`laravel`、`thinkphp`、`flask`。 ## 执行目录 -| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | -| -------- | :------: | :-------------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| src | 否 | String | | 代码路径。与 object 不能同时存在。 | -| exclude | 否 | Array of String | | 不包含的文件或路径, 遵守 [glob 语法](https://github.com/isaacs/node-glob) | -| bucket | 否 | String | | bucket 名称。如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 bucket-appid 对应的存储桶中;如果配置了 object,表示获取 bucket-appid 对应存储桶中 object 对应的代码进行部署。 | -| object | 否 | String | | 部署的代码在存储桶中的路径。 | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| -------- | :--: | :------: | :----: | :------------------------------------------------------------------------ | +| src | 否 | string | | 代码路径。与 obejct 不能同时存在。 | +| exclude | 否 | string[] | | 不包含的文件或路径, 遵守 [glob 语法](https://github.com/isaacs/node-glob) | +| bucket | 否 | string | | bucket 名称。 | +| obejct | 否 | string | | 部署的代码在存储桶中的路径。 | -## 层配置 +> **注意**:如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 bucket-appid 对应的存储桶中;如果配置了 obejct,表示获取 bucket-appid 对应存储桶中 obejct 对应的代码进行部署。 -| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | -| -------- | :------: | :----: | :----: | :------- | -| name | 否 | String | | 层名称 | -| version | 否 | String | | 层版本号 | +比如需要忽略项目的 `node_modules` 目录,可以配置如下: -### DNS 配置 +```yaml +exclude: + - 'node_modules/**' +``` -参考: https://cloud.tencent.com/document/product/302/8516 +## 层配置 -| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | -| ---------- | :------: | -------- | :----: | :---------------------------------------------- | -| ttl | 否 | Number | `600` | TTL 值,范围 1 - 604800,不同等级域名最小值不同 | -| recordLine | 否 | String[] | | 记录的线路名称 | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| -------- | :--: | :----: | :----: | :------- | +| name | 否 | string | | 层名称 | +| version | 否 | string | | 层版本号 | ### 指定区配置 -| 参数名称 | 是否必选 | 类型 | 默认值 | 函数 | -| ------------------------------- | :------: | ------ | ------ | ------------ | -| [functionConf](#函数配置) | 否 | Object | | 函数配置 | -| [apigatewayConf](#API-网关配置) | 否 | Object | | API 网关配置 | -| [cloudDNSConf](#DNS-配置) | 否 | Object | | DNS 配置 | +| 参数名称 | 必选 | 类型 | 默认值 | 函数 | +| ---------------------- | :--: | ------ | ------ | ------------ | +| [faas](#函数配置) | 否 | obejct | | 函数配置 | +| [apigw](#API-网关配置) | 否 | obejct | | API 网关配置 | ### 函数配置 参考: https://cloud.tencent.com/document/product/583/18586 -| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | -| ----------- | :------: | :-----: | :-----: | :------------------------------------------------------------------------------ | -| timeout | 否 | Number | `3` | 函数最长执行时间,单位为秒,可选值范围 1-900 秒,默认为 3 秒 | -| memorySize | 否 | Number | `128` | 函数运行时内存大小,默认为 128M,可选范围 64、128MB-3072MB,并且以 128MB 为阶梯 | -| environment | 否 | Object | | 函数的环境变量, 参考 [环境变量](#环境变量) | -| vpcConfig | 否 | Object | | 函数的 VPC 配置, 参考 [VPC 配置](#VPC-配置) | -| eip | 否 | Boolean | `false` | 是否固定出口 IP | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| ------------ | :--: | :------: | :-----------: | :------------------------------------------------------------------ | +| runtime | 否 | string | `Nodejs10.15` | 执行环境, 目前支持: Nodejs6.10, Nodejs8.9, Nodejs10.15, Nodejs12.16 | +| name | 否 | string | | 云函数名称 | +| timeout | 否 | number | `3` | 函数最长执行时间,单位为秒,可选值范围 1-900 秒,默认为 3 秒 | +| memorySize | 否 | number | `128` | 函数运行时内存大小,可选范围 64、128MB-3072MB,并且以 128MB 为阶梯 | +| environments | 否 | object[] | | 函数的环境变量, 参考 [环境变量](#环境变量) | +| vpc | 否 | obejct | | 函数的 VPC 配置, 参考 [VPC 配置](#VPC-配置) | +| eip | 否 | boolean | `false` | 是否固定出口 IP | +| layers | 否 | obejct[] | | 云函数绑定的 layer, 配置参数参考 [层配置](#层配置) | + +> 此处只是列举,`faas` 参数支持 [scf](https://github.com/serverless-components/tencent-scf/tree/master/docs/configure.md) 组件的所有基础配置( `events` 除外) ##### 环境变量 -| 参数名称 | 类型 | 描述 | -| --------- | ---- | :---------------------------------------- | -| variables | | 环境变量参数, 包含多对 key-value 的键值对 | +| 参数名称 | 类型 | 描述 | +| -------- | ------ | :------------- | +| envKey | string | 环境变量 key | +| envVal | string | 环境变量 value | ##### VPC 配置 | 参数名称 | 类型 | 描述 | | -------- | ------ | :------ | -| subnetId | String | 子网 ID | -| vpcId | String | VPC ID | +| vpcId | string | VPC ID | +| subnetId | string | 子网 ID | ### API 网关配置 -| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | -| -------------- | :------: | :------- | :--------- | :--------------------------------------------------------------------------------- | -| protocols | 否 | String[] | `['http']` | 前端请求的类型,如 http,https,http 与 https | -| environment | 否 | String | `release` | 发布环境. 目前支持三种发布环境: test(测试), prepub(预发布) 与 release(发布). | -| usagePlan | 否 | | | 使用计划配置, 参考 [使用计划](#使用计划) | -| auth | 否 | | | API 密钥配置, 参考 [API 密钥](#API-密钥配置) | -| customDomain | 否 | Object[] | | 自定义 API 域名配置, 参考 [自定义域名](#自定义域名) | -| enableCORS | 否 | Boolean | `false` | 开启跨域。默认值为否。 | -| serviceTimeout | 否 | Number | `15` | Api 超时时间,单位: 秒 | -| isDisabled | 否 | Boolean | `false` | 关闭自动创建 API 网关功能。默认值为否,即默认自动创建 API 网关。 | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| ------------- | :--: | :------- | :----------- | :--------------------------------------------------------------- | +| id | 否 | | | API 网关服务 ID,如果存在将使用这个 API 网关服务 | +| name | 否 | | `serverless` | API 网关服务名称, 默认创建一个新的服务名称 | +| description | 否 | | | API 网关服务描述 | +| protocols | 否 | string[] | `['http']` | 前端请求的类型,如 http,https,http 与 https | +| environment | 否 | string | `release` | 发布环境. 目前支持三种发布环境: test、prepub、release. | +| cors | 否 | boolean | `false` | 开启跨域。默认值为否。 | +| timeout | 否 | number | `15` | Api 超时时间,单位: 秒 | +| isDisabled | 否 | boolean | `false` | 关闭自动创建 API 网关功能。默认值为否,即默认自动创建 API 网关。 | +| customDomains | 否 | obejct[] | | 自定义 API 域名配置, 参考 [自定义域名](#自定义域名) | -##### 使用计划 +##### 自定义域名 -参考: https://cloud.tencent.com/document/product/628/14947 +Refer to: https://cloud.tencent.com/document/product/628/14906 -| 参数名称 | 是否必选 | 类型 | 描述 | -| ------------- | :------: | ------ | :------------------------------------------------------ | -| usagePlanId | 否 | String | 用户自定义使用计划 ID | -| usagePlanName | 否 | String | 用户自定义的使用计划名称 | -| usagePlanDesc | 否 | String | 用户自定义的使用计划描述 | -| maxRequestNum | 否 | Number | 请求配额总数,如果为空,将使用-1 作为默认值,表示不开启 | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| --------- | :--: | :------: | :-----: | :-------------------------------------------------------------------------- | +| domain | 是 | string | | 待绑定的自定义的域名。 | +| certId | 否 | string | | 待绑定自定义域名的证书唯一 ID,如果设置了 type 为 `https`,则为必选 | +| customMap | 否 | string | `false` | 是否自定义路径映射。为 `true` 时,表示自定义路径映射,此时 `pathMap` 必填。 | +| pathMap | 否 | object[] | `[]` | 自定义路径映射的路径。 参考 [自定义路径映射](#自定义路径映射) | +| protocol | 否 | string[] | | 绑定自定义域名的协议类型,默认与服务的前端协议一致。 | -##### API 密钥配置 +#### 自定义路径映射 -参考: https://cloud.tencent.com/document/product/628/14916 +| 参数名称 | 必选 | 类型 | Description | +| ----------- | :--: | :----- | :------------- | +| path | 是 | string | 自定义映射路径 | +| environment | 是 | string | 自定义映射环境 | -| 参数名称 | 类型 | 描述 | -| ---------- | :----- | :------- | -| secretName | String | 密钥名称 | -| secretIds | String | 密钥 ID | +> 使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 -##### 自定义域名 +### 静态资源 CDN 配置 -Refer to: https://cloud.tencent.com/document/product/628/14906 +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| -------- | :--: | :----: | :----: | :-------------------- | +| cos | 是 | obejct | | [COS 配置](#cos-配置) | +| cdn | 否 | obejct | | [CDN 配置](#cdn-配置) | + +##### COS 配置 + +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| -------- | :--: | :------: | :--------------------------------: | :------------------------------- | +| bucket | 是 | string | | COS 存储同名称,没有将自动创建 | +| acl | 否 | obejct | | 存储桶权限配置,参考 [acl](#acl) | +| sources | 否 | obejct[] | `` | 需要托管到 COS 的静态资源目录 | + +默认的 `sources`,也可以根据个人需要自定义需要托管到 COS 的静态资源目录: + +```json +[{ "src": "public", "targetDir": "/" }] +``` + +###### acl + +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| ----------- | :--: | :----: | :-----------: | :----------- | +| permissions | 是 | string | `public-read` | 公共权限配置 | + +##### CDN 配置 + +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| ------------- | :--: | :-----: | :--------: | :--------------------------------------------------------- | +| domain | 是 | string | | CDN 域名 | +| area | 否 | string | `mainland` | 加速区域,mainland: 大陆,overseas:海外,global:全球加速 | +| autoRefresh | 否 | boolean | `true` | 是否自动刷新 CDN | +| refreshType | 否 | boolean | `delete` | CDN 刷新类型,delete:刷新全部资源,flush:刷新变更资源 | +| forceRedirect | 否 | obejct | | 访问协议强制跳转配置,参考 [forceRedirect](#forceRedirect) | +| https | 否 | obejct | | https 配置,参考 [https](#https) | + +###### forceRedirect -| 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | -| ---------------- | :------: | :------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| domain | 是 | String | | 待绑定的自定义的域名。 | -| certificateId | 否 | String | | 待绑定自定义域名的证书唯一 ID,如果设置了 type 为 `https`,则为必选 | -| isDefaultMapping | 否 | String | `true` | 是否使用默认路径映射。为 `false` 时,表示自定义路径映射,此时 pathMappingSet 必填。 | -| pathMappingSet | 否 | Object[] | `[]` | 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 | -| protocol | 否 | String[] | | 绑定自定义域名的协议类型,默认与服务的前端协议一致。 | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| ------------------ | :--: | :----: | :----: | :------------------------------------------------------------- | +| switch | 是 | string | `on` | 访问强制跳转配置开关, on:开启,off:关闭 | +| redirectType | 是 | string | `http` | 访问强制跳转类型,http:强制 http 跳转,https:强制 https 跳转 | +| redirectStatusCode | 是 | number | `301` | 强制跳转时返回状态码,支持 301、302 | -- 自定义路径映射 +###### https -| 参数名称 | 是否必选 | 类型 | Description | -| ----------- | :------: | :----- | :------------- | -| path | 是 | String | 自定义映射路径 | -| environment | 是 | String | 自定义映射环境 | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| -------- | :--: | :----: | :----: | :------------------------------------ | +| certId | 是 | string | | 腾讯云托管域名证书 ID | +| http2 | 是 | string | | 是否开启 HTTP2,on: 开启,off: 关闭 | diff --git a/example/serverless.yml b/example/serverless.yml index fe13841..08b81f5 100644 --- a/example/serverless.yml +++ b/example/serverless.yml @@ -1,7 +1,7 @@ org: orgDemo app: appDemo stage: dev -component: express +component: express@dev name: expressDemo inputs: @@ -9,7 +9,19 @@ inputs: src: ./ exclude: - .env - apigatewayConf: + faas: + name: ${name} + timeout: 10 + memorySize: 128 + # layers: + # - name: test-layer + # version: 1 + environments: + - envKey: NODE_ENV + envVal: production + apigw: + name: express_demo + environment: release protocols: - http - https diff --git a/serverless.component.yml b/serverless.component.yml index 9d302b4..bbc784b 100644 --- a/serverless.component.yml +++ b/serverless.component.yml @@ -1,5 +1,5 @@ name: express -version: 0.2.0 +version: dev author: Tencent Cloud, Inc. org: Tencent Cloud, Inc. description: Deploy a serverless Express.js application on Tencent SCF and API Gateway. @@ -9,3 +9,198 @@ readme: https://github.com/serverless-components/tencent-express/tree/master/REA license: MIT main: ./src webDeployable: true + +actions: + deploy: + definition: Deploy web framework application + inputs: + src: + type: src + required: true + description: The folder containing the source code of your framework application. + entryFile: + type: string + faas: + type: object + keys: + name: + type: string + description: The name of faas + regex: '^[A-Za-z][\w-_]{0,58}[A-Za-z0-9]$' + region: + type: string + default: ap-guangzhou + description: Region for faas + allow: # The values that are allowed for this + - ap-guangzhou + - ap-shanghai + - ap-hongkong + - ap-beijing + - ap-chengdu + - ap-tokyo + - ap-mumbai + - ap-singapore + - na-siliconvalley + - na-toronto + runtime: + type: string + description: faas runtime + default: Nodejs10.15 + allow: + - Python2.7 + - Python3.6 + - Nodejs6.10 + - Nodejs8.9 + - Nodejs10.15 + - Nodejs12.16 + - Php5 + - Php7 + - Go1 + - Java8 + - CustomRuntime + layers: + type: array + items: + - type: object + keys: + name: + type: string + required: true + version: + type: number + required: true + handler: + type: string + default: 'index.main_handler' + description: The handler of faas + environment: + type: object + description: faas environment + keys: + variables: + type: object + vpc: + type: object + keys: + vpcId: + type: string + subnetId: + type: string + memorySize: + type: number + description: SCF memory size + default: 128 # The default value + min: 64 # Minimum number allowed + max: 3072 # Maximum number allowed + allow: # The values that are allowed for this + - 64 + - 128 + - 256 + - 384 + - 512 + - 640 + - 768 + - 896 + - 1024 + - 1152 + - 1280 + - 1408 + - 1536 + - 1664 + - 1792 + - 1920 + - 2048 + - 2176 + - 2304 + - 2432 + - 2560 + - 2688 + - 2816 + - 2944 + - 3072 + apigw: + type: object + keys: + id: + type: string + regex: '^service-(\w){8,}$' + name: + type: string + description: Name of API Gateway + default: serverless + regex: '^[a-zA-Z][a-zA-Z0-9(_)]{0,48}[a-zA-Z0-9]?$' + static: + type: object + keys: + cos: + type: object + keys: + bucket: + type: string + description: The name of cos + regex: '^[a-z][a-z0-9-]{0,48}[a-z0-9]$' + acl: + type: object + sources: + type: array + items: + - type: object + keys: + src: + type: string + required: true + targetDir: + type: string + required: true + cdn: + type: object + keys: + area: + type: string + allow: + - mainland + - overseas + - global + domain: + type: string + required: true + autoRefresh: + type: boolean + refreshType: + type: string + allow: + - delete + - flush + forceRedirect: + type: object + keys: + switch: + type: string + allow: + - on + - off + redirectType: + type: string + allow: + - http + - https + redirectStatusCode: + type: number + allow: + - 301 + - 302 + https: + type: object + keys: + http2: + type: string + allow: + - on + - off + certId: + type: string + required: true + remove: + definition: Remove web framework application + metrics: + definition: Get metrics of web framework application diff --git a/src/config.js b/src/config.js index 35901dd..84c7a12 100644 --- a/src/config.js +++ b/src/config.js @@ -1,15 +1,40 @@ +const frameworks = { + express: { + injectSlsSdk: true, + templateUrl: + 'https://serverless-templates-1300862921.cos.ap-beijing.myqcloud.com/express-demo.zip', + runtime: 'Nodejs10.15', + defaultEntryFile: 'sls.js', + defaultStatics: [{ src: 'public', targetDir: '/' }] + } +} + const CONFIGS = { - templateUrl: - 'https://serverless-templates-1300862921.cos.ap-beijing.myqcloud.com/express-demo.zip', - compName: 'express', - compFullname: 'Express.js', - defaultEntryFile: 'sls.js', + // support metrics frameworks + supportMetrics: ['express', 'next', 'nuxt'], + region: 'ap-guangzhou', + description: 'Created by Serverless Component', handler: 'sl_handler.handler', - runtime: 'Nodejs10.15', - timeout: 3, + timeout: 10, memorySize: 128, namespace: 'default', - description: 'Created by Serverless Component' + cos: { + lifecycle: [ + { + status: 'Enabled', + id: 'deleteObject', + filter: '', + expiration: { days: '10' }, + abortIncompleteMultipartUpload: { daysAfterInitiation: '10' } + } + ] + } } -module.exports = CONFIGS +module.exports = () => { + const frameworkConfigs = frameworks.express + return { + ...CONFIGS, + ...frameworkConfigs + } +} diff --git a/src/package.json b/src/package.json index d144f82..6b6a383 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,8 @@ { "dependencies": { + "adm-zip": "^0.4.16", "download": "^8.0.0", - "tencent-component-toolkit": "^1.17.0", + "tencent-component-toolkit": "^1.18.6", "type": "^2.0.0" } } diff --git a/src/serverless.js b/src/serverless.js index 530110f..d75001f 100644 --- a/src/serverless.js +++ b/src/serverless.js @@ -1,8 +1,15 @@ const { Component } = require('@serverless/core') -const { Scf, Apigw, Cns, Cam, Metrics } = require('tencent-component-toolkit') +const { Scf, Apigw, Cns, Cam, Metrics, Cos, Cdn } = require('tencent-component-toolkit') const { TypeError } = require('tencent-component-toolkit/src/utils/error') -const { uploadCodeToCos, getDefaultProtocol, prepareInputs, deepClone } = require('./utils') -const CONFIGS = require('./config') +const { + uploadCodeToCos, + getDefaultProtocol, + deepClone, + initializeInputs, + initializeStaticCosInputs, + initializeStaticCdnInputs +} = require('./utils') +const initConfigs = require('./config') class ServerlessComponent extends Component { getCredentials() { @@ -26,11 +33,18 @@ class ServerlessComponent extends Component { return this.credentials.tencent.tmpSecrets.appId } - async deployFunction(credentials, inputs, regionList) { + initialize(framework = 'express') { + const CONFIGS = initConfigs(framework) + this.CONFIGS = CONFIGS + this.framework = framework + this.__TmpCredentials = this.getCredentials() + } + + async deployFaas(credentials, inputs) { if (!inputs.role) { try { - const camClient = new Cam(credentials) - const roleExist = await camClient.CheckSCFExcuteRole() + const cam = new Cam(credentials) + const roleExist = await cam.CheckSCFExcuteRole() if (roleExist) { inputs.role = 'QCS_SCFExcuteRole' } @@ -39,56 +53,58 @@ class ServerlessComponent extends Component { } } - const outputs = {} const appId = this.getAppId() - - const funcDeployer = async (curRegion) => { - const code = await uploadCodeToCos(this, appId, credentials, inputs, curRegion) - const scf = new Scf(credentials, curRegion) + const { region } = inputs + const { state } = this + const instance = this + const funcDeployer = async () => { + const code = await uploadCodeToCos(instance, appId, credentials, inputs, region) + const scf = new Scf(credentials, region) const tempInputs = { ...inputs, code } const scfOutput = await scf.deploy(deepClone(tempInputs)) - outputs[curRegion] = { - functionName: scfOutput.FunctionName, + const outputs = { + name: scfOutput.FunctionName, runtime: scfOutput.Runtime, namespace: scfOutput.Namespace } - this.state[curRegion] = { - ...(this.state[curRegion] ? this.state[curRegion] : {}), - ...outputs[curRegion] + if (scfOutput.Layers && scfOutput.Layers.length > 0) { + outputs.layers = scfOutput.Layers.map((item) => ({ + name: item.LayerName, + version: item.LayerVersion + })) } // default version is $LATEST - outputs[curRegion].lastVersion = scfOutput.LastVersion + const faasState = state.faas || state.scf || {} + outputs.lastVersion = scfOutput.LastVersion ? scfOutput.LastVersion - : this.state.lastVersion || '$LATEST' + : faasState.lastVersion || '$LATEST' // default traffic is 1.0, it can also be 0, so we should compare to undefined - outputs[curRegion].traffic = + outputs.traffic = scfOutput.Traffic !== undefined ? scfOutput.Traffic - : this.state.traffic !== undefined - ? this.state.traffic + : faasState.traffic !== undefined + ? faasState.traffic : 1 - if (outputs[curRegion].traffic !== 1 && scfOutput.ConfigTrafficVersion) { - outputs[curRegion].configTrafficVersion = scfOutput.ConfigTrafficVersion - this.state.configTrafficVersion = scfOutput.ConfigTrafficVersion + if (outputs.traffic !== 1 && scfOutput.ConfigTrafficVersion) { + outputs.configTrafficVersion = scfOutput.ConfigTrafficVersion } - this.state.lastVersion = outputs[curRegion].lastVersion - this.state.traffic = outputs[curRegion].traffic + return outputs } - for (let i = 0; i < regionList.length; i++) { - const curRegion = regionList[i] - await funcDeployer(curRegion) - } - this.save() - return outputs + const faasOutputs = await funcDeployer(region) + + this.state.faas = faasOutputs + await this.save() + + return faasOutputs } // try to add dns record @@ -119,187 +135,262 @@ class ServerlessComponent extends Component { } } - async deployApigateway(credentials, inputs, regionList) { + async deployApigw(credentials, inputs) { if (inputs.isDisabled) { return {} } - const getServiceId = (instance, region) => { - const regionState = instance.state[region] - return inputs.serviceId || (regionState && regionState.serviceId) - } + const { region } = inputs + const { state } = this - const deployTasks = [] - const outputs = {} - regionList.forEach((curRegion) => { - const apigwDeployer = async () => { - const apigw = new Apigw(credentials, curRegion) - - const oldState = this.state[curRegion] || {} - const apigwInputs = { - ...inputs, - oldState: { - apiList: oldState.apiList || [], - customDomains: oldState.customDomains || [] - } - } - // different region deployment has different service id - apigwInputs.serviceId = getServiceId(this, curRegion) - const apigwOutput = await apigw.deploy(deepClone(apigwInputs)) - outputs[curRegion] = { - serviceId: apigwOutput.serviceId, - subDomain: apigwOutput.subDomain, - environment: apigwOutput.environment, - url: `${getDefaultProtocol(inputs.protocols)}://${apigwOutput.subDomain}/${ - apigwOutput.environment - }${apigwInputs.endpoints[0].path}` + const apigwDeployer = async () => { + const apigw = new Apigw(credentials, region) + + const oldState = state.apigw || {} + const apigwInputs = { + ...inputs, + oldState: { + apis: oldState.apis || [], + customDomains: oldState.customDomains || [] } + } + // different region deployment has different service id + const apigwOutput = await apigw.deploy(deepClone(apigwInputs)) + const outputs = { + created: true, + url: `${getDefaultProtocol(apigwInputs.protocols)}://${apigwOutput.subDomain}/${ + apigwOutput.environment + }${apigwInputs.endpoints[0].path}`, + id: apigwOutput.serviceId, + domain: apigwOutput.subDomain, + environment: apigwOutput.environment, + apis: apigwOutput.apiList + } - if (apigwOutput.customDomains) { - // TODO: need confirm add cns authentication - if (inputs.autoAddDnsRecord === true) { - // await this.tryToAddDnsRecord(credentials, apigwOutput.customDomains) - } - outputs[curRegion].customDomains = apigwOutput.customDomains + if (apigwOutput.customDomains) { + // TODO: need confirm add cns authentication + if (inputs.autoAddDnsRecord === true) { + // await this.tryToAddDnsRecord(credentials, apigwOutput.customDomains) } - this.state[curRegion] = { - created: true, - ...(this.state[curRegion] ? this.state[curRegion] : {}), - ...outputs[curRegion], - apiList: apigwOutput.apiList + outputs.customDomains = apigwOutput.customDomains + } + return outputs + } + + const apigwOutputs = await apigwDeployer() + + this.state.apigw = apigwOutputs + await this.save() + + return apigwOutputs + } + + // deploy static to cos, and setup cdn + async deployStatic(credentials, inputs, region) { + const { state, framework } = this + const { zipPath } = state + const appId = this.getAppId() + const deployStaticOutpus = {} + + if (zipPath) { + console.log(`Deploy static for ${framework} application`) + // 1. deploy to cos + const { staticCosInputs, bucket } = await initializeStaticCosInputs( + this, + inputs, + appId, + zipPath + ) + + const cos = new Cos(credentials, region) + const cosOutput = { + region + } + // flush bucket + if (inputs.cos.replace) { + await cos.flushBucketFiles(bucket) + } + for (let i = 0; i < staticCosInputs.length; i++) { + const curInputs = staticCosInputs[i] + console.log(`Starting deploy directory ${curInputs.src} to cos bucket ${curInputs.bucket}`) + const deployRes = await cos.deploy(curInputs) + cosOutput.origin = `${curInputs.bucket}.cos.${region}.myqcloud.com` + cosOutput.bucket = deployRes.bucket + cosOutput.url = `https://${curInputs.bucket}.cos.${region}.myqcloud.com` + console.log(`Deploy directory ${curInputs.src} to cos bucket ${curInputs.bucket} success`) + } + deployStaticOutpus.cos = cosOutput + + // 2. deploy cdn + if (inputs.cdn) { + const cdn = new Cdn(credentials) + const cdnInputs = await initializeStaticCdnInputs(this, inputs, cosOutput.cosOrigin) + console.log(`Starting deploy cdn ${cdnInputs.domain}`) + const cdnDeployRes = await cdn.deploy(cdnInputs) + const protocol = cdnInputs.https ? 'https' : 'http' + const cdnOutput = { + domain: cdnDeployRes.domain, + url: `${protocol}://${cdnDeployRes.domain}`, + cname: cdnDeployRes.cname } + deployStaticOutpus.cdn = cdnOutput + + console.log(`Deploy cdn ${cdnInputs.domain} success`) } - deployTasks.push(apigwDeployer()) - }) - await Promise.all(deployTasks) + console.log(`Deployed static for ${framework} application successfully`) - this.save() - return outputs + return deployStaticOutpus + } + + return null } async deploy(inputs) { - console.log(`Deploying ${CONFIGS.compFullname} App...`) + this.initialize('express') + const { __TmpCredentials, CONFIGS, framework } = this - const credentials = this.getCredentials() + console.log(`Deploying ${framework} Application`) - // 对Inputs内容进行标准化 - const { regionList, functionConf, apigatewayConf } = await prepareInputs( - this, - credentials, - inputs - ) + const { region, faasConfig, apigwConfig } = await initializeInputs(this, inputs) - // 部署函数 + API网关 - const outputs = {} - if (!functionConf.code.src) { + const outputs = { + region + } + if (!faasConfig.code.src) { outputs.templateUrl = CONFIGS.templateUrl } - const deployTasks = [this.deployFunction(credentials, functionConf, regionList, outputs)] - // support apigatewayConf.isDisabled - if (apigatewayConf.isDisabled !== true) { - deployTasks.push(this.deployApigateway(credentials, apigatewayConf, regionList, outputs)) + const deployTasks = [this.deployFaas(__TmpCredentials, faasConfig)] + // support apigw.isDisabled + if (apigwConfig.isDisabled !== true) { + deployTasks.push(this.deployApigw(__TmpCredentials, apigwConfig)) } else { - this.state.apigwDisabled = true + this.state.apigw.isDisabled = true } - const [functionOutputs, apigwOutputs = {}] = await Promise.all(deployTasks) - - // optimize outputs for one region - if (regionList.length === 1) { - const [oneRegion] = regionList - outputs.region = oneRegion - outputs['apigw'] = apigwOutputs[oneRegion] - outputs['scf'] = functionOutputs[oneRegion] - } else { - outputs['apigw'] = apigwOutputs - outputs['scf'] = functionOutputs + const [faasOutputs, apigwOutputs = {}] = await Promise.all(deployTasks) + + outputs['faas'] = faasOutputs + outputs['apigw'] = apigwOutputs + + // start deploy static cdn + const staticConfig = inputs.static || inputs.staticConf + if (staticConfig) { + staticConfig.cos = staticConfig.cos || staticConfig.cosConf + staticConfig.cdn = staticConfig.cdn || staticConfig.cdnConf + outputs.static = await this.deployStatic(__TmpCredentials, staticConfig, region) + this.state.static = outputs.static } - this.state.region = regionList[0] - this.state.regionList = regionList - this.state.lambdaArn = functionConf.name + // this config for online debug + this.state.region = region + this.state.namespace = faasConfig.namespace + this.state.lambdaArn = faasConfig.name return outputs } - async remove() { - console.log(`Removing ${CONFIGS.compFullname} App...`) - - const { state } = this - const { regionList = [] } = state - - const credentials = this.getCredentials() - - const removeHandlers = [] - for (let i = 0; i < regionList.length; i++) { - const curRegion = regionList[i] - const curState = state[curRegion] - const scf = new Scf(credentials, curRegion) - const apigw = new Apigw(credentials, curRegion) - const handler = async () => { - await scf.remove({ - functionName: curState.functionName, - namespace: curState.namespace - }) - // if disable apigw, no need to remove - if (state.apigwDisabled !== true) { - await apigw.remove({ - created: curState.created, - environment: curState.environment, - serviceId: curState.serviceId, - apiList: curState.apiList, - customDomains: curState.customDomains - }) + async removeStatic(credentials) { + // remove static + const { region } = this.state + const staticState = this.state.static || this.state.staticConf + if (staticState) { + console.log(`Removing static config`) + // 1. remove cos + if (staticState.cos) { + const cos = new Cos(credentials, region) + await cos.remove(staticState.cos) + } + // 2. remove cdn + if (staticState.cdn) { + const cdn = new Cdn(credentials) + try { + await cdn.remove(staticState.cdn) + } catch (e) { + // no op } } - removeHandlers.push(handler()) + console.log(`Remove static config success`) } + } - await Promise.all(removeHandlers) + async remove() { + this.initialize('express') + const { __TmpCredentials, framework, state } = this - if (this.state.cns) { - const cns = new Cns(credentials) - for (let i = 0; i < this.state.cns.length; i++) { - await cns.remove({ deleteList: this.state.cns[i].records }) - } + console.log(`Removing ${framework} App`) + + const { region } = state + + const apigwState = state.apigw + const faasState = state.faas || state.scf + const scf = new Scf(__TmpCredentials, region) + const apigw = new Apigw(__TmpCredentials, region) + await scf.remove({ + functionName: faasState.name || faasState.functionName, + namespace: faasState.namespace + }) + // if disable apigw, no need to remove + if (apigwState.isDisabled !== true) { + const serviceId = apigwState.id || apigwState.serviceId + let apis = apigwState.apis || apigwState.apiList || [] + apis = apis.map((item) => { + item.created = true + return item + }) + await apigw.remove({ + created: true, + serviceId: serviceId, + environment: apigwState.environment, + apiList: apis, + customDomains: apigwState.customDomains + }) } + // remove static + await this.removeStatic(__TmpCredentials) + this.state = {} + + return {} } async metrics(inputs = {}) { - console.log(`Get ${CONFIGS.compFullname} Metrics Datas...`) + this.initialize('express') + const { __TmpCredentials, state } = this + + console.log(`Get framework metrics data`) if (!inputs.rangeStart || !inputs.rangeEnd) { throw new TypeError( - `PARAMETER_${CONFIGS.compName.toUpperCase()}_METRICS`, + `PARAMETER_FRAMEWORK_METRICS`, 'rangeStart and rangeEnd are require inputs' ) } - const { region } = this.state + const { region } = state if (!region) { - throw new TypeError( - `PARAMETER_${CONFIGS.compName.toUpperCase()}_METRICS`, - 'No region property in state' - ) + throw new TypeError(`PARAMETER_FRAMEWORK_METRICS`, 'No region property in state') } - const { functionName, namespace, functionVersion } = this.state[region] || {} - if (functionName) { + + const faasState = state.faas || state.scf || {} + const { namespace, latestVersion } = faasState + const faasName = faasState.name || faasState.functionName + if (faasName) { const options = { - funcName: functionName, + funcName: faasName, namespace: namespace, - version: functionVersion, + version: latestVersion, region, timezone: inputs.tz } - const curState = this.state[region] - if (curState.serviceId) { - options.apigwServiceId = curState.serviceId - options.apigwEnvironment = curState.environment || 'release' + const apigwState = state.apigw + const serviceId = apigwState.id || apigwState.serviceId + if (serviceId) { + options.apigwServiceId = serviceId + options.apigwEnvironment = apigwState.environment || 'release' } - const credentials = this.getCredentials() - const mertics = new Metrics(credentials, options) + + const mertics = new Metrics(__TmpCredentials, options) const metricResults = await mertics.getDatas( inputs.rangeStart, inputs.rangeEnd, @@ -307,10 +398,7 @@ class ServerlessComponent extends Component { ) return metricResults } - throw new TypeError( - `PARAMETER_${CONFIGS.compName.toUpperCase()}_METRICS`, - 'Function name not define' - ) + throw new TypeError(`PARAMETER_FRAMEWORK_METRICS`, 'Function name not define') } } diff --git a/src/utils.js b/src/utils.js index 72c30b5..071991c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,11 +1,9 @@ const path = require('path') +const fs = require('fs') const { Cos } = require('tencent-component-toolkit') -const ensureObject = require('type/object/ensure') -const ensureIterable = require('type/iterable/ensure') -const ensureString = require('type/string/ensure') const download = require('download') const { TypeError } = require('tencent-component-toolkit/src/utils/error') -const CONFIGS = require('./config') +const AdmZip = require('adm-zip') /* * Generates a random id @@ -23,13 +21,6 @@ const getType = (obj) => { return Object.prototype.toString.call(obj).slice(8, -1) } -const mergeJson = (sourceJson, targetJson) => { - Object.entries(sourceJson).forEach(([key, val]) => { - targetJson[key] = deepClone(val) - }) - return targetJson -} - const capitalString = (str) => { if (str.length < 2) { return str.toUpperCase() @@ -38,12 +29,16 @@ const capitalString = (str) => { return `${str[0].toUpperCase()}${str.slice(1)}` } +const getTimestamp = () => { + return Math.floor(Date.now() / 1000) +} + const getDefaultProtocol = (protocols) => { return String(protocols).includes('https') ? 'https' : 'http' } -const getDefaultFunctionName = () => { - return `${CONFIGS.compName}_component_${generateId()}` +const getDefaultFunctionName = (framework) => { + return `${framework}-${generateId()}` } const getDefaultServiceName = () => { @@ -54,16 +49,39 @@ const getDefaultServiceDescription = () => { return 'Created by Serverless Component' } -const validateTraffic = (num) => { +const getDefaultBucketName = (region) => { + return `serverless-${region}-code` +} + +const getDefaultObjectName = (inputs) => { + return `${inputs.name}-${getTimestamp()}.zip` +} + +const getDirFiles = (dirPath) => { + const targetPath = path.resolve(dirPath) + const files = fs.readdirSync(targetPath) + const temp = {} + files.forEach((file) => { + temp[file] = path.join(targetPath, file) + }) + return temp +} + +const removeAppid = (str, appid) => { + const suffix = `-${appid}` + if (!str || str.indexOf(suffix) === -1) { + return str + } + return str.slice(0, -suffix.length) +} + +const validateTraffic = (framework, num) => { if (getType(num) !== 'Number') { - throw new TypeError( - `PARAMETER_${CONFIGS.compName.toUpperCase()}_TRAFFIC`, - 'traffic must be a number' - ) + throw new TypeError(`PARAMETER_${framework.toUpperCase()}_TRAFFIC`, 'traffic must be a number') } if (num < 0 || num > 1) { throw new TypeError( - `PARAMETER_${CONFIGS.compName.toUpperCase()}_TRAFFIC`, + `PARAMETER_${framework.toUpperCase()}_TRAFFIC`, 'traffic must be a number between 0 and 1' ) } @@ -71,7 +89,8 @@ const validateTraffic = (num) => { } const getCodeZipPath = async (instance, inputs) => { - console.log(`Packaging ${CONFIGS.compFullname} application...`) + const { CONFIGS, framework } = instance + console.log(`Packaging ${framework} application`) // unzip source zip file let zipPath @@ -80,7 +99,7 @@ const getCodeZipPath = async (instance, inputs) => { const downloadPath = `/tmp/${generateId()}` const filename = 'template' - console.log(`Installing Default ${CONFIGS.compFullname} App...`) + console.log(`Downloading default ${framework} application`) try { await download(CONFIGS.templateUrl, downloadPath, { filename: `${filename}.zip` @@ -96,6 +115,24 @@ const getCodeZipPath = async (instance, inputs) => { return zipPath } +// get files/dirs need to inject to project code +const getInjection = (instance) => { + const { CONFIGS } = instance + let injectFiles = {} + let injectDirs = {} + const shimPath = path.join(__dirname, '_shims') + if (CONFIGS.injectSlsSdk) { + injectFiles = instance.getSDKEntries(`_shims/handler.handler`) + injectDirs = { + _shims: shimPath + } + } else { + injectFiles = getDirFiles(shimPath) + } + + return { injectFiles, injectDirs } +} + /** * Upload code to COS * @param {Component} instance serverless component instance @@ -105,57 +142,42 @@ const getCodeZipPath = async (instance, inputs) => { * @param {string} region region */ const uploadCodeToCos = async (instance, appId, credentials, inputs, region) => { - const bucketName = inputs.code.bucket || `sls-cloudfunction-${region}-code` - const objectName = inputs.code.object || `${inputs.name}-${Math.floor(Date.now() / 1000)}.zip` - // if set bucket and object not pack code - if (!inputs.code.bucket || !inputs.code.object) { - const zipPath = await getCodeZipPath(instance, inputs) - console.log(`Code zip path ${zipPath}`) - - // save the zip path to state for lambda to use it - instance.state.zipPath = zipPath - - const cos = new Cos(credentials, region) - - if (!inputs.code.bucket) { - // create default bucket - await cos.deploy({ - bucket: bucketName + '-' + appId, - force: true, - lifecycle: [ - { - status: 'Enabled', - id: 'deleteObject', - filter: '', - expiration: { days: '10' }, - abortIncompleteMultipartUpload: { daysAfterInitiation: '10' } - } - ] - }) - } + const { CONFIGS, framework } = instance + const bucketName = inputs.code.bucket || getDefaultBucketName(region) + const objectName = inputs.code.object || getDefaultObjectName(inputs) + const bucket = `${bucketName}-${appId}` - // upload code to cos - if (!inputs.code.object) { - console.log(`Getting cos upload url for bucket ${bucketName}`) - const uploadUrl = await cos.getObjectUrl({ - bucket: bucketName + '-' + appId, - object: objectName, - method: 'PUT' - }) + const zipPath = await getCodeZipPath(instance, inputs) + console.log(`Code zip path ${zipPath}`) - // if shims and sls sdk entries had been injected to zipPath, no need to injected again - console.log(`Uploading code to bucket ${bucketName}`) - if (instance.codeInjected === true) { - await instance.uploadSourceZipToCOS(zipPath, uploadUrl, {}, {}) - } else { - const slsSDKEntries = instance.getSDKEntries('_shims/handler.handler') - await instance.uploadSourceZipToCOS(zipPath, uploadUrl, slsSDKEntries, { - _shims: path.join(__dirname, '_shims') - }) - instance.codeInjected = true - } - console.log(`Upload ${objectName} to bucket ${bucketName} success`) - } + // save the zip path to state for lambda to use it + instance.state.zipPath = zipPath + + const cos = new Cos(credentials, region) + + if (!inputs.code.bucket) { + // create default bucket + await cos.deploy({ + force: true, + bucket: bucketName + '-' + appId, + lifecycle: CONFIGS.cos.lifecycle + }) + } + if (!inputs.code.object) { + console.log(`Getting cos upload url for bucket ${bucketName}`) + const uploadUrl = await cos.getObjectUrl({ + bucket: bucket, + object: objectName, + method: 'PUT' + }) + + // if shims and sls sdk entries had been injected to zipPath, no need to injected again + console.log(`Uploading code to bucket ${bucketName}`) + + const { injectFiles, injectDirs } = getInjection(instance, framework) + + await instance.uploadSourceZipToCOS(zipPath, uploadUrl, injectFiles, injectDirs) + console.log(`Upload ${objectName} to bucket ${bucketName} success`) } // save bucket state @@ -168,158 +190,271 @@ const uploadCodeToCos = async (instance, appId, credentials, inputs, region) => } } -const prepareInputs = async (instance, credentials, inputs = {}) => { - // 对function inputs进行标准化 - const tempFunctionConf = inputs.functionConf - ? inputs.functionConf - : inputs.functionConfig - ? inputs.functionConfig - : {} - const fromClientRemark = `tencent-${CONFIGS.compName}` - const regionList = inputs.region - ? typeof inputs.region == 'string' - ? [inputs.region] - : inputs.region - : ['ap-guangzhou'] +const initializeStaticCosInputs = async (instance, inputs, appId, codeZipPath) => { + const { CONFIGS, framework } = instance + try { + const staticCosInputs = [] + const { cos: cosConfig } = inputs + const sources = cosConfig.sources || CONFIGS.defaultStatics + const { bucket } = cosConfig + // remove user append appid + const bucketName = removeAppid(bucket, appId) + const staticPath = `/tmp/${generateId()}` + const codeZip = new AdmZip(codeZipPath) + const entries = codeZip.getEntries() + + // traverse sources, generate static directory and deploy to cos + for (let i = 0; i < sources.length; i++) { + const curSource = sources[i] + const entryName = `${curSource.src}` + let exist = false + entries.forEach((et) => { + if (et.entryName.indexOf(entryName) === 0) { + codeZip.extractEntryTo(et, staticPath, true, true) + exist = true + } + }) + if (exist) { + const cosInputs = { + force: true, + protocol: cosConfig.protocol, + bucket: `${bucketName}-${appId}`, + src: `${staticPath}/${entryName}`, + keyPrefix: curSource.targetDir || '/', + acl: { + permissions: 'public-read', + grantRead: '', + grantWrite: '', + grantFullControl: '' + } + } - // chenck state function name - const stateFunctionName = - instance.state[regionList[0]] && instance.state[regionList[0]].functionName - const functionConf = Object.assign(tempFunctionConf, { - code: { - src: inputs.src, - bucket: inputs.srcOriginal && inputs.srcOriginal.bucket, - object: inputs.srcOriginal && inputs.srcOriginal.object - }, - name: - ensureString(inputs.functionName, { isOptional: true }) || - stateFunctionName || - getDefaultFunctionName(), - region: regionList, - role: ensureString(tempFunctionConf.role ? tempFunctionConf.role : inputs.role, { - default: '' - }), - handler: ensureString(tempFunctionConf.handler ? tempFunctionConf.handler : inputs.handler, { - default: CONFIGS.handler - }), - runtime: ensureString(tempFunctionConf.runtime ? tempFunctionConf.runtime : inputs.runtime, { - default: CONFIGS.runtime - }), - namespace: ensureString( - tempFunctionConf.namespace ? tempFunctionConf.namespace : inputs.namespace, - { default: CONFIGS.namespace } - ), - description: ensureString( - tempFunctionConf.description ? tempFunctionConf.description : inputs.description, - { - default: CONFIGS.description + if (cosConfig.acl) { + cosInputs.acl = { + permissions: cosConfig.acl.permissions || 'public-read', + grantRead: cosConfig.acl.grantRead || '', + grantWrite: cosConfig.acl.grantWrite || '', + grantFullControl: cosConfig.acl.grantFullControl || '' + } + } + + staticCosInputs.push(cosInputs) } - ), - fromClientRemark, - layers: ensureIterable(tempFunctionConf.layers ? tempFunctionConf.layers : inputs.layers, { - default: [] - }), - cfs: ensureIterable(tempFunctionConf.cfs ? tempFunctionConf.cfs : inputs.cfs, { - default: [] - }), - publish: inputs.publish, - traffic: inputs.traffic, - lastVersion: instance.state.lastVersion, - timeout: tempFunctionConf.timeout ? tempFunctionConf.timeout : CONFIGS.timeout, - memorySize: tempFunctionConf.memorySize ? tempFunctionConf.memorySize : CONFIGS.memorySize, - tags: ensureObject(tempFunctionConf.tags ? tempFunctionConf.tags : inputs.tag, { - default: null - }) - }) + } + return { + bucket: `${bucketName}-${appId}`, + staticCosInputs + } + } catch (e) { + throw new TypeError(`UTILS_${framework}_initializeStaticCosInputs`, e.message, e.stack) + } +} - // validate traffic - if (inputs.traffic !== undefined) { - validateTraffic(inputs.traffic) +const initializeStaticCdnInputs = async (instance, inputs, origin) => { + const { CONFIGS, framework } = instance + try { + const { cdn: cdnConfig } = inputs + const cdnInputs = { + async: true, + area: cdnConfig.area || 'mainland', + domain: cdnConfig.domain, + serviceType: 'web', + origin: { + origins: [origin], + originType: 'cos', + originPullProtocol: 'https' + }, + autoRefresh: true, + ...cdnConfig + } + if (cdnConfig.https) { + // using these default configs, for making user's config more simple + cdnInputs.forceRedirect = + cdnConfig.https.forceRedirect || CONFIGS.defaultcdnConfig.forceRedirect + if (!cdnConfig.https.certId) { + throw new TypeError(`PARAMETER_${framework}_HTTPS`, 'https.certId is required') + } + cdnInputs.https = { + ...CONFIGS.defaultcdnConfig.https, + ...{ + http2: cdnConfig.https.http2 || 'on', + certInfo: { + certId: cdnConfig.https.certId + } + } + } + } + if (cdnInputs.autoRefresh) { + cdnInputs.refreshCdn = { + flushType: cdnConfig.refreshType || 'delete', + urls: [`http://${cdnInputs.domain}`, `https://${cdnInputs.domain}`] + } + } + + return cdnInputs + } catch (e) { + throw new TypeError(`UTILS_${framework}_initializeStaticCdnInputs`, e.message, e.stack) } - functionConf.needSetTraffic = inputs.traffic !== undefined && functionConf.lastVersion +} - if (tempFunctionConf.environment) { - functionConf.environment = tempFunctionConf.environment - functionConf.environment.variables = functionConf.environment.variables || {} - functionConf.environment.variables.SERVERLESS = '1' - functionConf.environment.variables.SLS_ENTRY_FILE = inputs.entryFile || CONFIGS.defaultEntryFile +// compatible code for old configs +// transfer yaml config to sdk inputs +const yamlToSdkInputs = ({ instance, sourceInputs, faasConfig, apigwConfig }) => { + const { framework, state } = instance + // chenck state function name + const stateFaasName = state.faas && state.faas.name + const { functionConf = {}, apigatewayConf = {} } = sourceInputs + // transfer faas config + faasConfig.name = + faasConfig.name || + sourceInputs.functionName || + stateFaasName || + getDefaultFunctionName(framework) + + if (faasConfig.environments) { + // this is new config array to object + const environment = deepClone(faasConfig.environments) + if (getType(environment) === 'Array') { + faasConfig.environment = { + variables: { + SERVERLESS: '1', + SLS_ENTRY_FILE: instance.slsEntryFile + } + } + environment.forEach((item) => { + faasConfig.environment.variables[item.envKey] = item.envVal + }) + } else { + faasConfig.environment = { + variables: environment.variables || {} + } + faasConfig.environment.variables.SERVERLESS = '1' + faasConfig.environment.variables.SLS_ENTRY_FILE = instance.slsEntryFile + } } else { - functionConf.environment = { + faasConfig.environment = { variables: { SERVERLESS: '1', - SLS_ENTRY_FILE: inputs.entryFile || CONFIGS.defaultEntryFile + SLS_ENTRY_FILE: instance.slsEntryFile } } } - if (tempFunctionConf.vpcConfig) { - functionConf.vpcConfig = tempFunctionConf.vpcConfig + if (faasConfig.vpc || functionConf.vpcConfig) { + faasConfig.vpcConfig = faasConfig.vpc || functionConf.vpcConfig } - // 对apigw inputs进行标准化 - const tempApigwConf = inputs.apigatewayConf - ? inputs.apigatewayConf - : inputs.apigwConfig - ? inputs.apigwConfig - : {} - const apigatewayConf = Object.assign(tempApigwConf, { - serviceId: inputs.serviceId || tempApigwConf.serviceId, - region: regionList, - isDisabled: tempApigwConf.isDisabled === true, - fromClientRemark: fromClientRemark, - serviceName: inputs.serviceName || tempApigwConf.serviceName || getDefaultServiceName(instance), - serviceDesc: tempApigwConf.serviceDesc || getDefaultServiceDescription(instance), - protocols: tempApigwConf.protocols || ['http'], - environment: tempApigwConf.environment ? tempApigwConf.environment : 'release', - customDomains: tempApigwConf.customDomains || [] - }) - if (!apigatewayConf.endpoints) { - apigatewayConf.endpoints = [ - { - path: tempApigwConf.path || '/', - enableCORS: tempApigwConf.enableCORS, - serviceTimeout: tempApigwConf.serviceTimeout, - method: 'ANY', - apiName: tempApigwConf.apiName || 'index', - function: { - isIntegratedResponse: true, - functionName: functionConf.name, - functionNamespace: functionConf.namespace, - functionQualifier: - (tempApigwConf.function && tempApigwConf.function.functionQualifier) || '$LATEST' - } - } - ] - } - if (tempApigwConf.usagePlan) { - apigatewayConf.endpoints[0].usagePlan = { - usagePlanId: tempApigwConf.usagePlan.usagePlanId, - usagePlanName: tempApigwConf.usagePlan.usagePlanName, - usagePlanDesc: tempApigwConf.usagePlan.usagePlanDesc, - maxRequestNum: tempApigwConf.usagePlan.maxRequestNum + if (faasConfig.tags) { + const tags = deepClone(faasConfig.tags) + if (getType(tags) === 'Array') { + faasConfig.tags = {} + tags.forEach((item) => { + faasConfig.tags[item.tagKey] = item.tagVal + }) } } - if (tempApigwConf.auth) { - apigatewayConf.endpoints[0].auth = { - secretName: tempApigwConf.auth.secretName, - secretIds: tempApigwConf.auth.secretIds + + faasConfig.layers = faasConfig.layers || sourceInputs.layers || [] + + // transfer apigw config + const stateApigwId = state.apigw && (state.apigw.id || state.apigw.serviceId) + apigwConfig.serviceId = + apigwConfig.serviceId || apigatewayConf.serviceId || sourceInputs.serviceId || stateApigwId + apigwConfig.serviceName = + apigwConfig.serviceName || + apigatewayConf.serviceName || + sourceInputs.serviceName || + getDefaultServiceName(instance) + apigwConfig.serviceDesc = + apigwConfig.serviceDesc || apigatewayConf.serviceDesc || getDefaultServiceDescription(instance) + + apigwConfig.endpoints = [ + { + path: '/', + apiName: 'index', + method: 'ANY', + enableCORS: apigwConfig.cors || apigatewayConf.enableCORS, + serviceTimeout: apigwConfig.timeout || apigatewayConf.serviceTimeout, + function: { + isIntegratedResponse: true, + functionQualifier: apigwConfig.qualifier || '$DEFAULT', + functionName: faasConfig.name, + functionNamespace: faasConfig.namespace + } } + ] + + if (apigwConfig.customDomains && apigwConfig.customDomains.length > 0) { + apigwConfig.customDomains = apigwConfig.customDomains.map((item) => { + if (item.certificateId) { + // old config, directly return + return item + } + return { + domain: item.domain, + certificateId: item.certId, + isDefaultMapping: !item.customMap, + pathMappingSet: item.pathMap, + protocols: item.protocols + } + }) } - regionList.forEach((curRegion) => { - const curRegionConf = inputs[curRegion] - if (curRegionConf && curRegionConf.functionConf) { - functionConf[curRegion] = curRegionConf.functionConf - } - if (curRegionConf && curRegionConf.apigatewayConf) { - apigatewayConf[curRegion] = curRegionConf.apigatewayConf - } + return { faasConfig, apigwConfig } +} + +const initializeInputs = async (instance, inputs = {}) => { + const { CONFIGS, framework } = instance + const region = inputs.region || CONFIGS.region + + const tempFaasConfig = inputs.faas || inputs.functionConf || {} + const faasConfig = Object.assign(tempFaasConfig, { + region: region, + code: { + src: inputs.src, + bucket: inputs.srcOriginal && inputs.srcOriginal.bucket, + object: inputs.srcOriginal && inputs.srcOriginal.object + }, + name: tempFaasConfig.name, + role: tempFaasConfig.role || '', + handler: tempFaasConfig.handler || CONFIGS.handler, + runtime: tempFaasConfig.runtime || CONFIGS.runtime, + namespace: tempFaasConfig.namespace || CONFIGS.namespace, + description: tempFaasConfig.description || CONFIGS.description, + layers: tempFaasConfig.layers, + cfs: tempFaasConfig.cfs || [], + timeout: tempFaasConfig.timeout || CONFIGS.timeout, + memorySize: tempFaasConfig.memorySize || CONFIGS.memorySize, + environments: tempFaasConfig.environments || tempFaasConfig.environment, + tags: tempFaasConfig.tags || inputs.tags, + publish: inputs.publish, + traffic: inputs.traffic + }) + + // validate traffic + if (inputs.traffic !== undefined) { + validateTraffic(framework, inputs.traffic) + } + faasConfig.needSetTraffic = inputs.traffic !== undefined && faasConfig.lastVersion + + const slsEntryFile = inputs.entryFile || CONFIGS.defaultEntryFile + instance.slsEntryFile = slsEntryFile + + const tempApigwConfig = inputs.apigw || inputs.apigatewayConf || {} + const apigwConfig = Object.assign(tempApigwConfig, { + region, + isDisabled: tempApigwConfig.isDisabled === true, + serviceId: tempApigwConfig.id, + serviceName: tempApigwConfig.name, + serviceDesc: tempApigwConfig.description, + protocols: tempApigwConfig.protocols || ['http'], + environment: tempApigwConfig.environment || 'release', + customDomains: tempApigwConfig.customDomains }) return { - regionList, - functionConf, - apigatewayConf + region, + ...yamlToSdkInputs({ instance, sourceInputs: inputs, faasConfig, apigwConfig }) } } @@ -327,8 +462,9 @@ module.exports = { deepClone, generateId, uploadCodeToCos, - mergeJson, capitalString, getDefaultProtocol, - prepareInputs + initializeInputs, + initializeStaticCosInputs, + initializeStaticCdnInputs } From 9a260014c7aad4b3e656c3a688c4898edaf9f73b Mon Sep 17 00:00:00 2001 From: yugasun Date: Thu, 12 Nov 2020 17:29:48 +0800 Subject: [PATCH 2/6] fix: remove --- docs/configure.md | 9 ++++----- src/serverless.js | 41 +++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index 0aa72c0..801c5cd 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -9,11 +9,10 @@ org: orgDemo # (可选) 用于记录组织信息,默认值为您的腾讯云 app: appDemo # (可选) 用于记录组织信息. 默认与name相同,必须为字符串 stage: dev # (可选) 用于区分环境信息,默认值是 dev -component: framework # (必选) 组件名称,在该实例中为express -name: frameworkDemo # 必选) 组件实例名称. +component: express # (必选) 组件名称,在该实例中为express +name: expressDemo # 必选) 组件实例名称. inputs: - framework: express region: ap-guangzhou # 云函数所在区域 entryFile: sls.js # 自定义 server 的入口文件名,默认为 sls.js,如果不想修改文件名为 sls.js 可以自定义 src: @@ -91,7 +90,7 @@ inputs: | 参数名称 | 必选 | 默认值 | 描述 | | ---------------------------- | :--: | :------------: | :------------------------------------------------------- | -| framework | 是 | | 项目使用的 Web 框架 | +| express | 是 | | 项目使用的 Web 框架 | | src | 是 | | 代码目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) | | region | 否 | `ap-guangzhou` | 项目部署所在区域 | | entryFile | 否 | `sls.js` | 自定义 server 的入口文件名 | @@ -99,7 +98,7 @@ inputs: | [apigw](#API-网关配置) | 否 | | API 网关配置 | | [static](#静态资源-CDN-配置) | 否 | | 静态资源 CDN 配置 | -> 注意:目前 `framework` 支持 Web 框架有 `express`、`koa`、`egg`、`next`、`nuxt`、`nest`、`laravel`、`thinkphp`、`flask`。 +> 注意:目前 `express` 支持 Web 框架有 `express`、`koa`、`egg`、`next`、`nuxt`、`nest`、`laravel`、`thinkphp`、`flask`。 ## 执行目录 diff --git a/src/serverless.js b/src/serverless.js index d75001f..cd9d26a 100644 --- a/src/serverless.js +++ b/src/serverless.js @@ -322,29 +322,34 @@ class ServerlessComponent extends Component { const { region } = state - const apigwState = state.apigw - const faasState = state.faas || state.scf + const apigwState = state.apigw || {} + const faasState = state.faas || state.scf || {} const scf = new Scf(__TmpCredentials, region) const apigw = new Apigw(__TmpCredentials, region) - await scf.remove({ - functionName: faasState.name || faasState.functionName, - namespace: faasState.namespace - }) + const faasName = faasState.name || faasState.functionName + if (faasName) { + await scf.remove({ + functionName: faasState.name || faasState.functionName, + namespace: faasState.namespace + }) + } // if disable apigw, no need to remove if (apigwState.isDisabled !== true) { const serviceId = apigwState.id || apigwState.serviceId - let apis = apigwState.apis || apigwState.apiList || [] - apis = apis.map((item) => { - item.created = true - return item - }) - await apigw.remove({ - created: true, - serviceId: serviceId, - environment: apigwState.environment, - apiList: apis, - customDomains: apigwState.customDomains - }) + if (serviceId) { + let apis = apigwState.apis || apigwState.apiList || [] + apis = apis.map((item) => { + item.created = true + return item + }) + await apigw.remove({ + created: true, + serviceId: serviceId, + environment: apigwState.environment, + apiList: apis, + customDomains: apigwState.customDomains + }) + } } // remove static From 38495491982414bc0a50ae0296fcd059377803d5 Mon Sep 17 00:00:00 2001 From: yugasun Date: Fri, 13 Nov 2020 15:40:40 +0800 Subject: [PATCH 3/6] docs: update configure --- docs/configure.md | 1 - src/_shims/package.json | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index 801c5cd..e2be15e 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -90,7 +90,6 @@ inputs: | 参数名称 | 必选 | 默认值 | 描述 | | ---------------------------- | :--: | :------------: | :------------------------------------------------------- | -| express | 是 | | 项目使用的 Web 框架 | | src | 是 | | 代码目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) | | region | 否 | `ap-guangzhou` | 项目部署所在区域 | | entryFile | 否 | `sls.js` | 自定义 server 的入口文件名 | diff --git a/src/_shims/package.json b/src/_shims/package.json index 2140c5d..8790250 100644 --- a/src/_shims/package.json +++ b/src/_shims/package.json @@ -1,15 +1,4 @@ { - "name": "@serverless/express", - "main": "./handler.js", - "publishConfig": { - "access": "public" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "lint": "eslint . --fix --cache" - }, - "author": "Serverless, Inc.", - "license": "Apache", "dependencies": { "tencent-component-monitor": "^1.1.0", "tencent-serverless-http": "^1.2.0" From 561908e01c1a812f5f3e8d19ddf439f615213e3e Mon Sep 17 00:00:00 2001 From: yugasun Date: Fri, 13 Nov 2020 16:24:13 +0800 Subject: [PATCH 4/6] docs: update configure --- docs/configure.md | 4 +--- serverless.component.yml | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index e2be15e..79423f1 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -97,8 +97,6 @@ inputs: | [apigw](#API-网关配置) | 否 | | API 网关配置 | | [static](#静态资源-CDN-配置) | 否 | | 静态资源 CDN 配置 | -> 注意:目前 `express` 支持 Web 框架有 `express`、`koa`、`egg`、`next`、`nuxt`、`nest`、`laravel`、`thinkphp`、`flask`。 - ## 执行目录 | 参数名称 | 必选 | 类型 | 默认值 | 描述 | @@ -108,7 +106,7 @@ inputs: | bucket | 否 | string | | bucket 名称。 | | obejct | 否 | string | | 部署的代码在存储桶中的路径。 | -> **注意**:如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 bucket-appid 对应的存储桶中;如果配置了 obejct,表示获取 bucket-appid 对应存储桶中 obejct 对应的代码进行部署。 +> **注意**:如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 `-` 对应的存储桶中;如果配置了 obejct,表示获取 bucket-appid 对应存储桶中 obejct 对应的代码进行部署。 比如需要忽略项目的 `node_modules` 目录,可以配置如下: diff --git a/serverless.component.yml b/serverless.component.yml index bbc784b..0e9cda4 100644 --- a/serverless.component.yml +++ b/serverless.component.yml @@ -12,12 +12,12 @@ webDeployable: true actions: deploy: - definition: Deploy web framework application + definition: Deploy Express.js application inputs: src: type: src required: true - description: The folder containing the source code of your framework application. + description: The folder containing the source code of your Express.js application. entryFile: type: string faas: @@ -201,6 +201,6 @@ actions: type: string required: true remove: - definition: Remove web framework application + definition: Remove express application metrics: - definition: Get metrics of web framework application + definition: Get metrics of express application From 987f8013b5ca4fbca3d31769cbd3b080729c15b4 Mon Sep 17 00:00:00 2001 From: yugasun Date: Fri, 13 Nov 2020 17:42:01 +0800 Subject: [PATCH 5/6] fix: static cdn config --- docs/configure.md | 42 +++++++++++++++++++++------------------- serverless.component.yml | 36 +++++++++++++++++----------------- src/config.js | 12 ++++++++++++ src/utils.js | 10 +++++++--- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/docs/configure.md b/docs/configure.md index 79423f1..b4b71b3 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -75,13 +75,13 @@ inputs: domain: abc.com autoRefresh: true refreshType: delete - forceRedirect: - switch: on - redirectType: https - redirectStatusCode: 301 https: http2: on certId: 'abc' + forceRedirect: + switch: on + redirectType: https + redirectStatusCode: 301 ``` ## 配置描述 @@ -97,6 +97,8 @@ inputs: | [apigw](#API-网关配置) | 否 | | API 网关配置 | | [static](#静态资源-CDN-配置) | 否 | | 静态资源 CDN 配置 | +> 注意:目前 `express` 支持 Web 框架有 `express`、`express`、`egg`、`next`、`nuxt`、`nest`、`laravel`、`thinkphp`、`flask`。 + ## 执行目录 | 参数名称 | 必选 | 类型 | 默认值 | 描述 | @@ -106,7 +108,7 @@ inputs: | bucket | 否 | string | | bucket 名称。 | | obejct | 否 | string | | 部署的代码在存储桶中的路径。 | -> **注意**:如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 `-` 对应的存储桶中;如果配置了 obejct,表示获取 bucket-appid 对应存储桶中 obejct 对应的代码进行部署。 +> **注意**:如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 bucket-appid 对应的存储桶中;如果配置了 obejct,表示获取 bucket-appid 对应存储桶中 obejct 对应的代码进行部署。 比如需要忽略项目的 `node_modules` 目录,可以配置如下: @@ -224,14 +226,21 @@ Refer to: https://cloud.tencent.com/document/product/628/14906 ##### CDN 配置 -| 参数名称 | 必选 | 类型 | 默认值 | 描述 | -| ------------- | :--: | :-----: | :--------: | :--------------------------------------------------------- | -| domain | 是 | string | | CDN 域名 | -| area | 否 | string | `mainland` | 加速区域,mainland: 大陆,overseas:海外,global:全球加速 | -| autoRefresh | 否 | boolean | `true` | 是否自动刷新 CDN | -| refreshType | 否 | boolean | `delete` | CDN 刷新类型,delete:刷新全部资源,flush:刷新变更资源 | -| forceRedirect | 否 | obejct | | 访问协议强制跳转配置,参考 [forceRedirect](#forceRedirect) | -| https | 否 | obejct | | https 配置,参考 [https](#https) | +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| ----------- | :--: | :-----: | :--------: | :--------------------------------------------------------- | +| domain | 是 | string | | CDN 域名 | +| area | 否 | string | `mainland` | 加速区域,mainland: 大陆,overseas:海外,global:全球加速 | +| autoRefresh | 否 | boolean | `true` | 是否自动刷新 CDN | +| refreshType | 否 | boolean | `delete` | CDN 刷新类型,delete:刷新全部资源,flush:刷新变更资源 | +| https | 否 | obejct | | https 配置,参考 [https](#https) | + +###### https + +| 参数名称 | 必选 | 类型 | 默认值 | 描述 | +| ------------- | :--: | :----: | :----: | :--------------------------------------------------------- | +| certId | 是 | string | | 腾讯云托管域名证书 ID | +| http2 | 是 | string | | 是否开启 HTTP2,on: 开启,off: 关闭 | +| forceRedirect | 否 | obejct | | 访问协议强制跳转配置,参考 [forceRedirect](#forceRedirect) | ###### forceRedirect @@ -240,10 +249,3 @@ Refer to: https://cloud.tencent.com/document/product/628/14906 | switch | 是 | string | `on` | 访问强制跳转配置开关, on:开启,off:关闭 | | redirectType | 是 | string | `http` | 访问强制跳转类型,http:强制 http 跳转,https:强制 https 跳转 | | redirectStatusCode | 是 | number | `301` | 强制跳转时返回状态码,支持 301、302 | - -###### https - -| 参数名称 | 必选 | 类型 | 默认值 | 描述 | -| -------- | :--: | :----: | :----: | :------------------------------------ | -| certId | 是 | string | | 腾讯云托管域名证书 ID | -| http2 | 是 | string | | 是否开启 HTTP2,on: 开启,off: 关闭 | diff --git a/serverless.component.yml b/serverless.component.yml index 0e9cda4..39d8455 100644 --- a/serverless.component.yml +++ b/serverless.component.yml @@ -171,24 +171,6 @@ actions: allow: - delete - flush - forceRedirect: - type: object - keys: - switch: - type: string - allow: - - on - - off - redirectType: - type: string - allow: - - http - - https - redirectStatusCode: - type: number - allow: - - 301 - - 302 https: type: object keys: @@ -200,6 +182,24 @@ actions: certId: type: string required: true + forceRedirect: + type: object + keys: + switch: + type: string + allow: + - on + - off + redirectType: + type: string + allow: + - http + - https + redirectStatusCode: + type: number + allow: + - 301 + - 302 remove: definition: Remove express application metrics: diff --git a/src/config.js b/src/config.js index 84c7a12..fb31205 100644 --- a/src/config.js +++ b/src/config.js @@ -28,6 +28,18 @@ const CONFIGS = { abortIncompleteMultipartUpload: { daysAfterInitiation: '10' } } ] + }, + cdn: { + autoRefresh: true, + forceRedirect: { + switch: 'on', + redirectType: 'https', + redirectStatusCode: 301 + }, + https: { + switch: 'on', + http2: 'on' + } } } diff --git a/src/utils.js b/src/utils.js index 071991c..da9f745 100644 --- a/src/utils.js +++ b/src/utils.js @@ -269,13 +269,17 @@ const initializeStaticCdnInputs = async (instance, inputs, origin) => { } if (cdnConfig.https) { // using these default configs, for making user's config more simple - cdnInputs.forceRedirect = - cdnConfig.https.forceRedirect || CONFIGS.defaultcdnConfig.forceRedirect + cdnInputs.forceRedirect = { + ...{ + switch: 'on' + }, + ...(cdnConfig.https.forceRedirect || CONFIGS.cdn.forceRedirect) + } if (!cdnConfig.https.certId) { throw new TypeError(`PARAMETER_${framework}_HTTPS`, 'https.certId is required') } cdnInputs.https = { - ...CONFIGS.defaultcdnConfig.https, + ...CONFIGS.cdn.https, ...{ http2: cdnConfig.https.http2 || 'on', certInfo: { From 2ba95822cb14bfc1305e27c83962b6f87ee84fe2 Mon Sep 17 00:00:00 2001 From: yugasun Date: Fri, 13 Nov 2020 17:44:53 +0800 Subject: [PATCH 6/6] ci: update email --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4c476a4..11c8ac8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,6 +43,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GIT_AUTHOR_NAME: slsplus - GIT_AUTHOR_EMAIL: yuga.sun.bj@gmail.com + GIT_AUTHOR_EMAIL: slsplus.sz@gmail.com GIT_COMMITTER_NAME: slsplus - GIT_COMMITTER_EMAIL: yuga.sun.bj@gmail.com + GIT_COMMITTER_EMAIL: slsplus.sz@gmail.com