├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── release.yml │ ├── test.yml │ └── validate.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── index.test.js └── lib │ └── utils.js ├── commitlint.config.js ├── docs ├── configure.md ├── output.md └── upload.md ├── example ├── README.md ├── index.html ├── package.json ├── serverless.yml ├── sls.js └── sls.upload.js ├── jest.config.js ├── package.json ├── prettier.config.js ├── release.config.js ├── serverless.component.yml └── src ├── _shims ├── handler.js ├── package.json └── sls.js ├── config.js ├── package.json ├── serverless.js └── utils.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_size = 2 11 | indent_style = space 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | example 5 | *.test.js 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['prettier'], 4 | plugins: ['import', 'prettier'], 5 | env: { 6 | es6: true, 7 | jest: true, 8 | node: true 9 | }, 10 | parser: 'babel-eslint', 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: 'module', 14 | ecmaFeatures: { 15 | jsx: true 16 | } 17 | }, 18 | globals: { 19 | on: true // for the Socket file 20 | }, 21 | rules: { 22 | 'array-bracket-spacing': [ 23 | 'error', 24 | 'never', 25 | { 26 | objectsInArrays: false, 27 | arraysInArrays: false 28 | } 29 | ], 30 | 'arrow-parens': ['error', 'always'], 31 | 'arrow-spacing': ['error', { before: true, after: true }], 32 | 'comma-dangle': ['error', 'never'], 33 | curly: 'error', 34 | 'eol-last': 'error', 35 | 'func-names': 'off', 36 | 'id-length': [ 37 | 'error', 38 | { 39 | min: 1, 40 | max: 50, 41 | properties: 'never', 42 | exceptions: ['e', 'i', 'n', 't', 'x', 'y', 'z', '_', '$'] 43 | } 44 | ], 45 | 'no-alert': 'error', 46 | 'no-console': 'off', 47 | 'no-const-assign': 'error', 48 | 'no-else-return': 'error', 49 | 'no-empty': 'off', 50 | 'no-shadow': 'error', 51 | 'no-undef': 'error', 52 | 'no-unused-vars': 'error', 53 | 'no-use-before-define': 'error', 54 | 'no-useless-constructor': 'error', 55 | 'object-curly-newline': 'off', 56 | 'object-shorthand': 'off', 57 | 'prefer-const': 'error', 58 | 'prefer-destructuring': ['error', { object: true, array: false }], 59 | quotes: [ 60 | 'error', 61 | 'single', 62 | { 63 | allowTemplateLiterals: true, 64 | avoidEscape: true 65 | } 66 | ], 67 | semi: ['error', 'never'], 68 | 'spaced-comment': 'error', 69 | strict: ['error', 'global'], 70 | 'prettier/prettier': 'error' 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | env: 12 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | with: 17 | persist-credentials: false 18 | 19 | - name: Install Node.js and npm 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 14.x 23 | registry-url: https://registry.npmjs.org 24 | 25 | - name: Retrieve dependencies from cache 26 | id: cacheNpm 27 | uses: actions/cache@v2 28 | with: 29 | path: | 30 | ~/.npm 31 | node_modules 32 | key: npm-v14-${{ runner.os }}-refs/heads/master-${{ hashFiles('package.json') }} 33 | restore-keys: npm-v14-${{ runner.os }}-refs/heads/master- 34 | 35 | - name: Install dependencies 36 | if: steps.cacheNpm.outputs.cache-hit != 'true' 37 | run: | 38 | npm update --no-save 39 | npm update --save-dev --no-save 40 | - name: Releasing 41 | run: | 42 | npm run release 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 45 | GIT_AUTHOR_NAME: slsplus 46 | GIT_AUTHOR_EMAIL: slsplus.sz@gmail.com 47 | GIT_COMMITTER_NAME: slsplus 48 | GIT_COMMITTER_EMAIL: slsplus.sz@gmail.com 49 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v2 14 | with: 15 | # Ensure connection with 'master' branch 16 | fetch-depth: 2 17 | 18 | - name: Install Node.js and npm 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 14.x 22 | registry-url: https://registry.npmjs.org 23 | 24 | - name: Retrieve dependencies from cache 25 | id: cacheNpm 26 | uses: actions/cache@v2 27 | with: 28 | path: | 29 | ~/.npm 30 | node_modules 31 | key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} 32 | restore-keys: | 33 | npm-v14-${{ runner.os }}-${{ github.ref }}- 34 | npm-v14-${{ runner.os }}-refs/heads/master- 35 | 36 | - name: Install dependencies 37 | if: steps.cacheNpm.outputs.cache-hit != 'true' 38 | run: | 39 | npm update --no-save 40 | npm update --save-dev --no-save 41 | - name: Running tests 42 | run: npm run test 43 | env: 44 | SERVERLESS_PLATFORM_VENDOR: tencent 45 | GLOBAL_ACCELERATOR_NA: true 46 | TENCENT_SECRET_ID: ${{ secrets.TENCENT_SECRET_ID }} 47 | TENCENT_SECRET_KEY: ${{ secrets.TENCENT_SECRET_KEY }} 48 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | jobs: 8 | lintAndFormatting: 9 | name: Lint & Formatting 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v2 14 | with: 15 | # Ensure connection with 'master' branch 16 | fetch-depth: 2 17 | 18 | - name: Install Node.js and npm 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 14.x 22 | registry-url: https://registry.npmjs.org 23 | 24 | - name: Retrieve dependencies from cache 25 | id: cacheNpm 26 | uses: actions/cache@v2 27 | with: 28 | path: | 29 | ~/.npm 30 | node_modules 31 | key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} 32 | restore-keys: | 33 | npm-v14-${{ runner.os }}-${{ github.ref }}- 34 | npm-v14-${{ runner.os }}-refs/heads/master- 35 | 36 | - name: Install dependencies 37 | if: steps.cacheNpm.outputs.cache-hit != 'true' 38 | run: | 39 | npm update --no-save 40 | npm update --save-dev --no-save 41 | 42 | - name: Validate Formatting 43 | run: npm run prettier:fix 44 | - name: Validate Lint rules 45 | run: npm run lint:fix 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sublime-project 3 | *.sublime-workspace 4 | *.log 5 | .serverless 6 | v8-compile-cache-* 7 | jest/* 8 | coverage 9 | .serverless_plugins 10 | testProjects/*/package-lock.json 11 | testProjects/*/yarn.lock 12 | .serverlessUnzipped 13 | node_modules 14 | .vscode/ 15 | .eslintcache 16 | dist 17 | .idea 18 | build/ 19 | .env* 20 | env.js 21 | package-lock.json 22 | test 23 | yarn.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | example -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | CHANGELOG.md 5 | *.test.js 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.4.0](https://github.com/serverless-components/tencent-koa/compare/v0.3.2...v0.4.0) (2021-01-26) 2 | 3 | 4 | ### Features 5 | 6 | * support apigw base64 encode ([#46](https://github.com/serverless-components/tencent-koa/issues/46)) ([d18d9b7](https://github.com/serverless-components/tencent-koa/commit/d18d9b787438d5a9430f6b8d66296cf98fd4b31e)) 7 | 8 | ## [0.3.2](https://github.com/serverless-components/tencent-koa/compare/v0.3.1...v0.3.2) (2020-12-28) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * add sls initialize before creating server ([9ce2692](https://github.com/serverless-components/tencent-koa/commit/9ce26922f15c904e3b6e8fc7061b823c36cb0f33)) 14 | 15 | ## [0.3.1](https://github.com/serverless-components/tencent-koa/compare/v0.3.0...v0.3.1) (2020-12-15) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * update deploy and remove flow ([#42](https://github.com/serverless-components/tencent-koa/issues/42)) ([97e70ae](https://github.com/serverless-components/tencent-koa/commit/97e70aedf2818696d9409b180671e7fcb61a8169)) 21 | 22 | # [0.3.0](https://github.com/serverless-components/tencent-koa/compare/v0.2.0...v0.3.0) (2020-11-05) 23 | 24 | 25 | ### Features 26 | 27 | * add entryFile doc ([660d31c](https://github.com/serverless-components/tencent-koa/commit/660d31c7ffbbd909c2cfce291e22ea1ec5e89cd8)) 28 | 29 | # [0.2.0](https://github.com/serverless-components/tencent-koa/compare/v0.1.0...v0.2.0) (2020-10-12) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * support all parameters for apigw ([c3efa96](https://github.com/serverless-components/tencent-koa/commit/c3efa96be05122a62d31ece57cdcaa315fdfb228)) 35 | 36 | 37 | ### Features 38 | 39 | * support custom entry file ([38a93c4](https://github.com/serverless-components/tencent-koa/commit/38a93c44814f69f4f48a3512f617bb4a379a0515)) 40 | 41 | # [0.1.0](https://github.com/serverless-components/tencent-koa/compare/v0.0.12...v0.1.0) (2020-09-28) 42 | 43 | 44 | ### Features 45 | 46 | * support multi region and cls config ([3dd0c8f](https://github.com/serverless-components/tencent-koa/commit/3dd0c8fd4a0a074c858df1d151f9131ad5d14620)) 47 | 48 | ## [0.0.12](https://github.com/serverless-components/tencent-koa/compare/v0.0.11...v0.0.12) (2020-09-04) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * update deps ([9854a88](https://github.com/serverless-components/tencent-koa/commit/9854a889676e3558538bd6b30defe05600a3c3ec)) 54 | 55 | ## [0.0.11](https://github.com/serverless-components/tencent-koa/compare/v0.0.10...v0.0.11) (2020-09-03) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * update deps ([31dbc3e](https://github.com/serverless-components/tencent-koa/commit/31dbc3e321a14648de0ef0918f1a0a5af0905336)) 61 | * update tencnet-component-toolkit for api mark ([9c79124](https://github.com/serverless-components/tencent-koa/commit/9c79124a18a9da3dea18157d69c952ed0f1cdacc)) 62 | 63 | ## [0.0.10](https://github.com/serverless-components/tencent-koa/compare/v0.0.9...v0.0.10) (2020-09-01) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * support cfs ([b7f7d9e](https://github.com/serverless-components/tencent-koa/commit/b7f7d9e7070ebe3dd3e0190eb66f56e6686aa7a7)) 69 | 70 | ## [0.0.9](https://github.com/serverless-components/tencent-koa/compare/v0.0.8...v0.0.9) (2020-08-26) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * apigw custom domain update bug ([8d6de6f](https://github.com/serverless-components/tencent-koa/commit/8d6de6fe3fc9d4933fcce1cd052d46bc942e9908)) 76 | * l5 enable config ([cc7f2f8](https://github.com/serverless-components/tencent-koa/commit/cc7f2f82f4efa1c5304dde748ac80127fb758311)) 77 | * support eip config ([d629f87](https://github.com/serverless-components/tencent-koa/commit/d629f878a2a9f0e80eeceb9043045e34fb1e75a2)) 78 | * traffic zero display bug ([5c2c0cc](https://github.com/serverless-components/tencent-koa/commit/5c2c0cc9b72d8b0e26ca112184c2bdd0a15bde8d)) 79 | * update default function description ([e9a95b0](https://github.com/serverless-components/tencent-koa/commit/e9a95b0346fafec33ab08d95bd0dd9a0e9520cc1)) 80 | * update error message ([8d14b37](https://github.com/serverless-components/tencent-koa/commit/8d14b37e4805710f701385d0bd01ee4628d2b434)) 81 | * update get credential error message ([764287e](https://github.com/serverless-components/tencent-koa/commit/764287ee5cd977c23da047a11149c91a1086fb73)) 82 | * update toolkit verison ([d859fb6](https://github.com/serverless-components/tencent-koa/commit/d859fb677159e79b2eb535822fd40e252f437eb3)) 83 | * upgrade deps ([0142d69](https://github.com/serverless-components/tencent-koa/commit/0142d6938aeafa4c489b8def1119b50bef8553ef)) 84 | * upgrade deps ([eae66b2](https://github.com/serverless-components/tencent-koa/commit/eae66b231df8737a5c249072dd4dbb2f269639be)) 85 | * upgrade deps ([12bc6b0](https://github.com/serverless-components/tencent-koa/commit/12bc6b03b84a8c8e0ebd3f9802b9b66555d5db40)) 86 | * upgrade deps ([b0501fe](https://github.com/serverless-components/tencent-koa/commit/b0501fe5591e8aaba0d4fc95d3564a54400012cc)) 87 | * upgrade tencent-component-toolkit for delete compatibility ([05ce942](https://github.com/serverless-components/tencent-koa/commit/05ce9428e78b572219d00048316279c80628bba9)) 88 | * upgrade tencent-serverless-http ([ef8d10f](https://github.com/serverless-components/tencent-koa/commit/ef8d10fde3271d61d6cff9062742a803827377ad)) 89 | 90 | 91 | ### Features 92 | 93 | * optmize code zip flow ([6fa5126](https://github.com/serverless-components/tencent-koa/commit/6fa5126532f85d976007e1442fcf0198179ef273)) 94 | * support disable creating apigw ([5975396](https://github.com/serverless-components/tencent-koa/commit/5975396dcdff734c1ac4447696b3b305a5875f13)) 95 | * support scf publish version and traffic setup ([47522b4](https://github.com/serverless-components/tencent-koa/commit/47522b4edc6c14f6cc89bb379aed885899cfac46)) 96 | * update config & support usageplan+auth ([fa4eebd](https://github.com/serverless-components/tencent-koa/commit/fa4eebdbf24eb0d8f2dc949444f15da6176154ae)) 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tencent Cloud, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️⚠️⚠️ 所有框架组件项目迁移到 [tencent-framework-components](https://github.com/serverless-components/tencent-framework-components). 2 | 3 | [![Serverless Koa Tencent Cloud](https://img.serverlesscloud.cn/20191226/1577361724216-koajs_width.png)](http://serverless.com) 4 | 5 | # 腾讯云 Koa 组件 6 | 7 | ## 简介 8 | 9 | 使用腾讯云 Koa 组件,可快速的在腾讯云创建,配置和管理一个 [Koa 框架](https://koajs.com/) 服务。 10 | 11 | ## 目录 12 | 13 | 1. [安装](#1-安装) 14 | 2. [创建](#2-创建) 15 | 3. [配置](#3-配置) 16 | 4. [部署](#4-部署) 17 | 5. [移除](#5-移除) 18 | 19 | ### 1. 安装 20 | 21 | 通过 npm 全局安装 [serverless cli](https://github.com/serverless/serverless) 22 | 23 | ```bash 24 | $ npm install -g serverless 25 | ``` 26 | 27 | ### 2. 创建 28 | 29 | 通过如下命令和模板链接,快速创建一个 koa 应用: 30 | 31 | ```bash 32 | $ serverless init koa-starter --name example 33 | $ cd example 34 | ``` 35 | 36 | ### 3. 部署 37 | 38 | 在 `serverless.yml` 文件所在的项目根目录,运行以下指令,将会弹出二维码,直接扫码授权进行部署: 39 | 40 | ``` 41 | serverless deploy 42 | ``` 43 | 44 | > **说明**:如果鉴权失败,请参考 [权限配置](https://cloud.tencent.com/document/product/1154/43006) 进行授权。 45 | 46 | 部署完成后,控制台会打印相关的输出信息,您可以通过 `${output:${stage}:${app}:apigw.url}` 的形式在其他 `serverless` 组件中引用该组件的 API 网关访问链接(或通过类似的形式引用该组建其他输出结果),具体的,可以查看完成的输出文档: 47 | 48 | - [点击此处查看输出文档](https://github.com/serverless-components/tencent-koa/tree/master/docs/output.md) 49 | 50 | ### 4. 配置 51 | 52 | koa 组件支持 0 配置部署,也就是可以直接通过配置文件中的默认值进行部署。但你依然可以修改更多可选配置来进一步开发该 koa 项目。 53 | 54 | 以下是 koa 组件的 `serverless.yml`配置示例: 55 | 56 | ```yml 57 | # serverless.yml 58 | 59 | org: orgDemo # (optional) serverless dashboard org. default is the first org you created during signup. 60 | app: appDemo # (optional) serverless dashboard app. default is the same as the name property. 61 | stage: dev # (optional) serverless dashboard stage. default is dev. 62 | component: koa # (required) name of the component. In that case, it's koa. 63 | name: koaDemo # (required) name of your koa component instance. 64 | 65 | inputs: 66 | src: 67 | src: ./ # (optional) path to the source folder. default is a hello world app. 68 | exclude: 69 | - .env 70 | region: ap-guangzhou 71 | runtime: Nodejs10.15 72 | apigatewayConf: 73 | protocols: 74 | - http 75 | - https 76 | environment: release 77 | ``` 78 | 79 | - [点击此处查看配置文档](https://github.com/serverless-components/tencent-koa/tree/master/docs/configure.md) 80 | 81 | ### 5. 移除 82 | 83 | 通过以下命令移除部署的 Koa 服务。 84 | 85 | ``` 86 | $ serverless remove 87 | ``` 88 | 89 | ### 账号配置(可选) 90 | 91 | 当前默认支持 CLI 扫描二维码登录,如您希望配置持久的环境变量/秘钥信息,也可以本地创建 `.env` 文件 92 | 93 | ```bash 94 | $ touch .env # 腾讯云的配置信息 95 | ``` 96 | 97 | 在 `.env` 文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存 98 | 99 | 如果没有腾讯云账号,可以在此[注册新账号](https://cloud.tencent.com/register)。 100 | 101 | 如果已有腾讯云账号,可以在[API 密钥管理](https://console.cloud.tencent.com/cam/capi)中获取 `SecretId` 和`SecretKey`. 102 | 103 | ``` 104 | # .env 105 | TENCENT_SECRET_ID=123 106 | TENCENT_SECRET_KEY=123 107 | ``` 108 | 109 | ## 静态资源服务 110 | 111 | 如果想要支持返回静态资源,比如图片之类的,需要在入口文件 `sls.js` 中指定相关 `MIME` 类型的文件为二进制,这样云函数在返回请求结果给 API 网关是,会对指定类型进行 `Base64` 编码,最终返回给客户端才能正常显示。如下: 112 | 113 | ```js 114 | const koa = require('koa') 115 | const app = koa() 116 | 117 | // Routes 118 | // ... 119 | 120 | app.binaryTypes = ['*/*'] 121 | 122 | module.exports = app 123 | ``` 124 | 125 | `['*/*']` 代表所有文件类型将进行 `Base64` 编码,如果需要定制化,可以配置为 `['image/png']`,意思是指定 `png` 格式的图片类型。 126 | 127 | 更多文件类型的 `MIME` 类型,可参考 [mime-db](https://github.com/jshttp/mime-db/blob/master/db.json)。 128 | 129 | ### slsInitialize 应用初始化 130 | 131 | 有些时候,Koa 服务在启动前,需要进行一个初始化操作,比如数据库建连,就可以通过在 Koa 实例对象上添加 `slsInitialize` 函数来实现,如下: 132 | 133 | ```js 134 | const Koa = require('koa') 135 | const mysql = require('mysql2/promise') 136 | 137 | const app = new Koa() 138 | 139 | // ... 140 | 141 | app.slsInitialize = async () => { 142 | app.db = await mysql.createConnection({ 143 | host: 'localhost', 144 | user: 'root', 145 | database: 'test' 146 | }) 147 | } 148 | 149 | // don't forget to export! 150 | module.exports = app 151 | ``` 152 | 153 | 这样应用部署到云函数后,在函数服务逻辑执行前,会先执行 `slsInitialize()` 函数,来初始化数据库连接。 154 | 155 | ## 文件上传 156 | 157 | [文件上传教程](https://github.com/serverless-components/tencent-koa/tree/master/docs/upload.md) 158 | 159 | ## License 160 | 161 | MIT License 162 | 163 | Copyright (c) 2020 Tencent Cloud, Inc. 164 | -------------------------------------------------------------------------------- /__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | const { generateId, getServerlessSdk } = require('./lib/utils') 2 | const execSync = require('child_process').execSync 3 | const path = require('path') 4 | const axios = require('axios') 5 | 6 | const instanceYaml = { 7 | org: 'orgDemo', 8 | app: 'appDemo', 9 | component: 'koa@dev', 10 | name: `koa-integration-tests-${generateId()}`, 11 | stage: 'dev', 12 | inputs: { 13 | region: 'ap-guangzhou', 14 | runtime: 'Nodejs10.15', 15 | apigatewayConf: { environment: 'test' } 16 | } 17 | } 18 | 19 | const credentials = { 20 | tencent: { 21 | SecretId: process.env.TENCENT_SECRET_ID, 22 | SecretKey: process.env.TENCENT_SECRET_KEY, 23 | } 24 | } 25 | 26 | // get serverless construct sdk 27 | const sdk = getServerlessSdk(instanceYaml.org) 28 | 29 | it('deploy koa app by template', async () => { 30 | const instance = await sdk.deploy(instanceYaml, credentials) 31 | expect(instance).toBeDefined() 32 | expect(instance.instanceName).toEqual(instanceYaml.name) 33 | // get src from template by default 34 | expect(instance.outputs.templateUrl).toBeDefined() 35 | expect(instance.outputs.region).toEqual(instanceYaml.inputs.region) 36 | expect(instance.outputs.scf).toBeDefined() 37 | expect(instance.outputs.scf.runtime).toEqual(instanceYaml.inputs.runtime) 38 | expect(instance.outputs.apigw).toBeDefined() 39 | expect(instance.outputs.apigw.environment).toEqual(instanceYaml.inputs.apigatewayConf.environment) 40 | }) 41 | 42 | it('deploy with src code', async () => { 43 | const srcPath = path.join(__dirname, '..', 'example') 44 | execSync('npm install', { cwd: srcPath }) 45 | instanceYaml.inputs.src = srcPath 46 | 47 | const instance = await sdk.deploy(instanceYaml, credentials) 48 | const response = await axios.get(instance.outputs.apigw.url) 49 | 50 | expect(response.data).toContain('Serverless Framework') 51 | expect(instance.outputs.templateUrl).not.toBeDefined() 52 | }) 53 | 54 | it('remove koa app', async () => { 55 | await sdk.remove(instanceYaml, credentials) 56 | result = await sdk.getInstance(instanceYaml.org, instanceYaml.stage, instanceYaml.app, instanceYaml.name) 57 | 58 | expect(result.instance.instanceStatus).toEqual('inactive') 59 | }) 60 | -------------------------------------------------------------------------------- /__tests__/lib/utils.js: -------------------------------------------------------------------------------- 1 | const { ServerlessSDK } = require('@serverless/platform-client-china') 2 | 3 | /* 4 | * Generate random id 5 | */ 6 | const generateId = () => 7 | Math.random() 8 | .toString(36) 9 | .substring(6) 10 | 11 | /* 12 | * Initializes and returns an instance of the serverless sdk 13 | * @param ${string} orgName - the serverless org name. 14 | */ 15 | const getServerlessSdk = (orgName) => { 16 | const sdk = new ServerlessSDK({ 17 | context: { 18 | orgName 19 | } 20 | }) 21 | return sdk 22 | } 23 | 24 | module.exports = { generateId, getServerlessSdk } 25 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const Configuration = { 2 | /* 3 | * Resolve and load @commitlint/config-conventional from node_modules. 4 | * Referenced packages must be installed 5 | */ 6 | extends: ['@commitlint/config-conventional'] 7 | } 8 | 9 | module.exports = Configuration 10 | -------------------------------------------------------------------------------- /docs/configure.md: -------------------------------------------------------------------------------- 1 | # 配置文档 2 | 3 | ## 全部配置 4 | 5 | ```yml 6 | # serverless.yml 7 | 8 | component: koa # (必选) 组件名称,在该实例中为koa 9 | name: koaDemo # 必选) 组件实例名称. 10 | org: orgDemo # (可选) 用于记录组织信息,默认值为您的腾讯云账户 appid,必须为字符串 11 | app: appDemo # (可选) 用于记录组织信息. 默认与name相同,必须为字符串 12 | stage: dev # (可选) 用于区分环境信息,默认值是 dev 13 | 14 | inputs: 15 | region: ap-guangzhou # 云函数所在区域 16 | functionName: koaDemo # 云函数名称 17 | serviceName: mytest # api网关服务名称 18 | runtime: Nodejs10.15 # 运行环境 19 | serviceId: service-np1uloxw # api网关服务ID 20 | entryFile: sls.js # 自定义 server 的入口文件名,默认为 sls.js,如果不想修改文件名为 sls.js 可以自定义 21 | src: ./src # 第一种为string时,会打包src对应目录下的代码上传到默认cos上。 22 | # src: # 第二种,部署src下的文件代码,并打包成zip上传到bucket上 23 | # src: ./src # 本地需要打包的文件目录 24 | # bucket: bucket01 # bucket name,当前会默认在bucket name后增加 appid 后缀, 本例中为 bucket01-appid 25 | # exclude: # 被排除的文件或目录 26 | # - .env 27 | # - node_modules 28 | # src: # 第三种,在指定存储桶bucket中已经存在了object代码,直接部署 29 | # bucket: bucket01 # bucket name,当前会默认在bucket name后增加 appid 后缀, 本例中为 bucket01-appid 30 | # object: cos.zip # bucket key 指定存储桶内的文件 31 | layers: 32 | - name: layerName # layer名称 33 | version: 1 # 版本 34 | functionConf: # 函数配置相关 35 | timeout: 10 # 超时时间,单位秒 36 | eip: false # 是否固定出口IP 37 | cls: # CLS 日志投递配置 38 | logsetId: abcdef # 所属日志集ID 39 | topicId: abcdef # 日志主题ID 40 | memorySize: 128 # 内存大小,单位MB 41 | environment: # 环境变量 42 | variables: # 环境变量数组 43 | TEST: vale 44 | vpcConfig: # 私有网络配置 45 | vpcId: '' # 私有网络的Id 46 | subnetId: '' # 子网ID 47 | apigatewayConf: # api网关配置 48 | isDisabled: false # 是否禁用自动创建 API 网关功能 49 | enableCORS: true # 允许跨域 50 | customDomains: # 自定义域名绑定 51 | - domain: abc.com # 待绑定的自定义的域名 52 | certificateId: abcdefg # 待绑定自定义域名的证书唯一 ID 53 | # 如要设置自定义路径映射,请设置为 false 54 | isDefaultMapping: false 55 | # 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 56 | pathMappingSet: 57 | - path: / 58 | environment: release 59 | protocols: # 绑定自定义域名的协议类型,默认与服务的前端协议一致。 60 | - http # 支持http协议 61 | - https # 支持https协议 62 | protocols: 63 | - http 64 | - https 65 | environment: test 66 | serviceTimeout: 15 67 | usagePlan: # 用户使用计划 68 | usagePlanId: 1111 69 | usagePlanName: slscmp 70 | usagePlanDesc: sls create 71 | maxRequestNum: 1000 72 | auth: # 密钥 73 | secretName: secret 74 | secretIds: 75 | - xxx 76 | ``` 77 | 78 | ## 配置描述 79 | 80 | 主要的参数 81 | 82 | | 参数名称 | 是否必选 | 默认值 | 描述 | 83 | | ------------------------------------ | :------: | :-------------: | :------------------------------------------------------------------ | 84 | | runtime | 否 | Nodejs10.15 | 执行环境, 目前支持: Nodejs6.10, Nodejs8.9, Nodejs10.15, Nodejs12.16 | 85 | | region | 否 | ap-guangzhou | 项目部署所在区域,默认广州区 | 86 | | functionName | 否 | | 云函数名称 | 87 | | serviceName | 否 | | API 网关服务名称, 默认创建一个新的服务名称 | 88 | | serviceId | 否 | | API 网关服务 ID,如果存在将使用这个 API 网关服务 | 89 | | entryFile | 否 | `sls.js` | 自定义 server 的入口文件名 | 90 | | src | 否 | `process.cwd()` | 默认为当前目录, 如果是对象, 配置参数参考 [执行目录](#执行目录) | 91 | | layers | 否 | | 云函数绑定的 layer, 配置参数参考 [层配置](#层配置) | 92 | | [functionConf](#函数配置) | 否 | | 函数配置 | 93 | | [apigatewayConf](#API-网关配置) | 否 | | API 网关配置 | 94 | | [cloudDNSConf](#DNS-配置) | 否 | | DNS 配置 | 95 | | [Region special config](#指定区配置) | 否 | | 指定区配置 | 96 | 97 | ## 执行目录 98 | 99 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 100 | | -------- | :------: | :-------------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 101 | | src | 否 | String | | 代码路径。与 object 不能同时存在。 | 102 | | exclude | 否 | Array of String | | 不包含的文件或路径, 遵守 [glob 语法](https://github.com/isaacs/node-glob) | 103 | | bucket | 否 | String | | bucket 名称。如果配置了 src,表示部署 src 的代码并压缩成 zip 后上传到 bucket-appid 对应的存储桶中;如果配置了 object,表示获取 bucket-appid 对应存储桶中 object 对应的代码进行部署。 | 104 | | object | 否 | String | | 部署的代码在存储桶中的路径。 | 105 | 106 | ## 层配置 107 | 108 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 109 | | -------- | :------: | :----: | :----: | :------- | 110 | | name | 否 | String | | 层名称 | 111 | | version | 否 | String | | 层版本号 | 112 | 113 | ### DNS 配置 114 | 115 | 参考: https://cloud.tencent.com/document/product/302/8516 116 | 117 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 118 | | ---------- | :------: | -------- | :----: | :---------------------------------------------- | 119 | | ttl | 否 | Number | 600 | TTL 值,范围 1 - 604800,不同等级域名最小值不同 | 120 | | recordLine | 否 | String[] | | 记录的线路名称 | 121 | 122 | ### 指定区配置 123 | 124 | | 参数名称 | 是否必选 | 类型 | 默认值 | 函数 | 125 | | ------------------------------- | :------: | ------ | ------ | ------------ | 126 | | [functionConf](#函数配置) | 否 | Object | | 函数配置 | 127 | | [apigatewayConf](#API-网关配置) | 否 | Object | | API 网关配置 | 128 | | [cloudDNSConf](#DNS-配置) | 否 | Object | | DNS 配置 | 129 | 130 | ### 函数配置 131 | 132 | 参考: https://cloud.tencent.com/document/product/583/18586 133 | 134 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 135 | | ----------- | :------: | :-----: | :-----: | :------------------------------------------------------------------------------ | 136 | | timeout | 否 | Number | 3 | 函数最长执行时间,单位为秒,可选值范围 1-900 秒,默认为 3 秒 | 137 | | memorySize | 否 | Number | 128 | 函数运行时内存大小,默认为 128M,可选范围 64、128MB-3072MB,并且以 128MB 为阶梯 | 138 | | environment | 否 | Object | | 函数的环境变量, 参考 [环境变量](#环境变量) | 139 | | vpcConfig | 否 | Object | | 函数的 VPC 配置, 参考 [VPC 配置](#VPC-配置) | 140 | | eip | 否 | Boolean | `false` | 是否固定出口 IP | 141 | | cls | 否 | Object | | CLS 日志投递配置,参考 [CLS 配置](#CLS-配置) | 142 | 143 | ##### 环境变量 144 | 145 | | 参数名称 | 类型 | 描述 | 146 | | --------- | ---- | :---------------------------------------- | 147 | | variables | | 环境变量参数, 包含多对 key-value 的键值对 | 148 | 149 | ##### VPC 配置 150 | 151 | | 参数名称 | 类型 | 描述 | 152 | | -------- | ------ | :------ | 153 | | subnetId | String | 子网 ID | 154 | | vpcId | String | VPC ID | 155 | 156 | ##### CLS 配置 157 | 158 | | 参数名称 | 类型 | 描述 | 159 | | -------- | ------ | :------------ | 160 | | logsetId | String | 所属日志集 ID | 161 | | topicId | String | 日志主题 ID | 162 | 163 | ### API 网关配置 164 | 165 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 166 | | -------------- | :------: | :------- | :------- | :--------------------------------------------------------------------------------- | 167 | | protocols | 否 | String[] | ['http'] | 前端请求的类型,如 http,https,http 与 https | 168 | | environment | 否 | String | release | 发布环境. 目前支持三种发布环境: test(测试), prepub(预发布) 与 release(发布). | 169 | | usagePlan | 否 | | | 使用计划配置, 参考 [使用计划](#使用计划) | 170 | | auth | 否 | | | API 密钥配置, 参考 [API 密钥](#API-密钥配置) | 171 | | customDomain | 否 | Object[] | | 自定义 API 域名配置, 参考 [自定义域名](#自定义域名) | 172 | | enableCORS | 否 | Boolean | `false` | 开启跨域。默认值为否。 | 173 | | serviceTimeout | 否 | Number | `15` | Api 超时时间,单位: 秒 | 174 | | isDisabled | 否 | Boolean | `false` | 关闭自动创建 API 网关功能。默认值为否,即默认自动创建 API 网关。 | 175 | 176 | ##### 使用计划 177 | 178 | 参考: https://cloud.tencent.com/document/product/628/14947 179 | 180 | | 参数名称 | 是否必选 | 类型 | 描述 | 181 | | ------------- | :------: | ------ | :------------------------------------------------------ | 182 | | usagePlanId | 否 | String | 用户自定义使用计划 ID | 183 | | usagePlanName | 否 | String | 用户自定义的使用计划名称 | 184 | | usagePlanDesc | 否 | String | 用户自定义的使用计划描述 | 185 | | maxRequestNum | 否 | Int | 请求配额总数,如果为空,将使用-1 作为默认值,表示不开启 | 186 | 187 | ##### API 密钥配置 188 | 189 | 参考: https://cloud.tencent.com/document/product/628/14916 190 | 191 | | 参数名称 | 类型 | 描述 | 192 | | ---------- | :----- | :------- | 193 | | secretName | String | 密钥名称 | 194 | | secretIds | String | 密钥 ID | 195 | 196 | ##### 自定义域名 197 | 198 | Refer to: https://cloud.tencent.com/document/product/628/14906 199 | 200 | | 参数名称 | 是否必选 | 类型 | 默认值 | 描述 | 201 | | ---------------- | :------: | :------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 202 | | domain | 是 | String | | 待绑定的自定义的域名。 | 203 | | certificateId | 否 | String | | 待绑定自定义域名的证书唯一 ID,如果设置了 type 为 https,则为必选 | 204 | | isDefaultMapping | 否 | String | `true` | 是否使用默认路径映射,默认为 true。为 false 时,表示自定义路径映射,此时 pathMappingSet 必填。 | 205 | | pathMappingSet | 否 | Object[] | `[]` | 自定义路径映射的路径。使用自定义映射时,可一次仅映射一个 path 到一个环境,也可映射多个 path 到多个环境。并且一旦使用自定义映射,原本的默认映射规则不再生效,只有自定义映射路径生效。 | 206 | | protocol | 否 | String[] | | 绑定自定义域名的协议类型,默认与服务的前端协议一致。 | 207 | 208 | - 自定义路径映射 209 | 210 | | 参数名称 | 是否必选 | 类型 | Description | 211 | | ----------- | :------: | :----- | :------------- | 212 | | path | 是 | String | 自定义映射路径 | 213 | | environment | 是 | String | 自定义映射环境 | 214 | -------------------------------------------------------------------------------- /docs/output.md: -------------------------------------------------------------------------------- 1 | # 部署 output 参数介绍 2 | 3 | > 组件输出可以在别的组件中通过 `${output:${stage}:${app}:.}` 获取 4 | > 5 | > 例如,如果该组件名称是 `test_name`, ·且只部署于一个地域,则可以通过 `${output:${stage}:${app}:test_name.apigw.url}` 在别的组件中获取该组件的 API 网关的 `url`。 6 | 7 | | 名称 | 类型 | 描述 | 8 | | :---------- | :------------------------------------------------------------------------------: | :------------------------------- | 9 | | templateUrl | string | 未提供代码时的模板代码 url | 10 | | region | string | 地域信息(只有一个地域时才提供) | 11 | | scf | [`FunctionOutput | Record`](#云函数输出-`FunctionOutput`) | 云函数输出信息 | 12 | | apigw | [`ApigwOutput | Record`](#API-网关输出-`ApigwOutput`) | API 网关输出信息 | 13 | 14 | ## 云函数输出 `FunctionOutput` 15 | 16 | | 名称 | 类型 | 描述 | 17 | | :------------------- | :------------: | :--------------------- | 18 | | functionName | string | 云函数名称 | 19 | | runtime | string | 云运行环境 | 20 | | namespace | string | 云函数名称空间 | 21 | | lastVersion | string | 云函数版本 | 22 | | traffic | `number (0~1)` | 将多少流量导向该云函数 | 23 | | configTrafficVersion | string | | 24 | 25 | ## API 网关输出 `ApigwOutput` 26 | 27 | | 名称 | 类型 | 描述 | 28 | | :------------ | :------------------------------------------------------------------: | :------------------------- | 29 | | serviceId | string | API 网关 ID | 30 | | subDomain | string | API 网关子域名 | 31 | | enviroment | `"release" | "prepub" | "test"` | API 网关 | 32 | | url | string | API 网关对外的完整 URL | 33 | | traffic | number (0~1) | 将多少流量导向该云函数 | 34 | | customDomains | [CustomDomain[]](#API-网关自定义域名输出-`ApigwOutput.CustomDomain`) | API 网关自定义域名输出列表 | 35 | 36 | ## API 网关自定义域名输出 `ApigwOutput.CustomDomain` 37 | 38 | | 名称 | 类型 | 描述 | 39 | | :--------------- | :----------------------------------------------------------------: | :------------------------- | 40 | | domain | string | 自定义域名 | 41 | | certificateId | string | 域名证书 ID | 42 | | isDefaultMapping | boolean | 该自定义域名是否为默认域名 | 43 | | pathMappingSet | [PathMapping[]](#-API-网关域名映射规则-`CustomDomain.PathMapping`) | 该域名的路径映射规则列表 | 44 | | protocols | `"http" | "https"` | 启用的协议 | 45 | 46 | ## API 网关域名映射规则 `CustomDomain.PathMapping` 47 | 48 | | 名称 | 类型 | 描述 | 49 | | :--------- | :----: | :--------------- | 50 | | path | string | 路径 | 51 | | enviroment | string | 路径映射到的环境 | 52 | -------------------------------------------------------------------------------- /docs/upload.md: -------------------------------------------------------------------------------- 1 | ## 文件上传说明 2 | 3 | 项目中如果涉及到文件上传,需要依赖 API 网关提供的 [Base64 编码能力](https://cloud.tencent.com/document/product/628/51799),使用时只需要 `serverless.yml` 中配置 `isBase64Encoded` 为 `true`,如下: 4 | 5 | ```yaml 6 | app: appDemo 7 | stage: dev 8 | component: koa 9 | name: koaDemo 10 | 11 | inputs: 12 | # 省略... 13 | apigatewayConf: 14 | isBase64Encoded: true 15 | # 省略... 16 | # 省略... 17 | ``` 18 | 19 | 当前 API 网关支持上传最大文件大小为 `2M`,如果文件过大,请修改为前端直传对象存储方案。 20 | 21 | ## Base64 示例 22 | 23 | 此 Github 项目的 `example` 目录下存在模板文件: 24 | 25 | - [sls.upload.js](../example/sls.upload.js) 26 | 27 | 开发者可根据个人项目需要参考修改,使用时需要复制文件名为 `sls.js`。 28 | 29 | 文件中实现了文件上传接口 `POST /upload`,如果要支持文件上传,需要安装 `@koajs/multer` 和 `multer` 包。 30 | 31 | 同时需要在 `serverless.yml` 的 `apigatewayConf` 中配置 `isBase64Encoded` 为 `true`。 32 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | [![Serverless Koa Tencent Cloud](https://img.serverlesscloud.cn/20191226/1577361724216-koajs_width.png)](http://serverless.com) 2 | 3 | # 腾讯云 Koa 组件 4 | 5 | 简体中文 | [English](https://github.com/serverless-components/tencent-koa/tree/master/README.en.md) 6 | 7 | ## 简介 8 | 9 | 使用腾讯云 Koa 组件,可快速的在腾讯云创建,配置和管理一个 [Koa 框架](https://koajs.com/) 服务。 10 | 11 | ## 目录 12 | 13 | 1. [安装](#1-安装) 14 | 2. [创建](#2-创建) 15 | 3. [配置](#3-配置) 16 | 4. [部署](#4-部署) 17 | 5. [移除](#5-移除) 18 | 19 | ### 1. 安装 20 | 21 | 通过 npm 安装 serverless 22 | 23 | ```bash 24 | $ npm install -g serverless 25 | ``` 26 | 27 | ### 2. 创建 28 | 29 | 本地创建 `serverless.yml` 文件: 30 | 31 | ```bash 32 | $ touch serverless.yml 33 | ``` 34 | 35 | 初始化一个新的 npm 包,并安装 koa: 36 | 37 | ```bash 38 | # 创建后持续回车 39 | $ npm init 40 | 41 | # 安装 koa 42 | $ npm i --save koa 43 | ``` 44 | 45 | 创建一个 `sls.js`文件,并在其中创建您的 koa App: 46 | 47 | ```bash 48 | $ touch sls.js 49 | ``` 50 | 51 | ```js 52 | const koa = require('koa') 53 | const app = new koa() 54 | 55 | app.use(async (ctx, next) => { 56 | if (ctx.path !== '/') return next() 57 | ctx.body = 'Hello from Koa' 58 | }) 59 | 60 | // set binary types 61 | // app.binaryTypes = [*/*]; 62 | 63 | // don't forget to export! 64 | module.exports = app 65 | ``` 66 | 67 | ### 3. 配置 68 | 69 | 在 serverless.yml 中进行如下配置 70 | 71 | ```yml 72 | # serverless.yml 73 | 74 | org: orgDemo # (optional) serverless dashboard org. default is the first org you created during signup. 75 | app: appDemo # (optional) serverless dashboard app. default is the same as the name property. 76 | stage: dev # (optional) serverless dashboard stage. default is dev. 77 | component: koa # (required) name of the component. In that case, it's koa. 78 | name: koaDemo # (required) name of your koa component instance. 79 | 80 | inputs: 81 | src: 82 | src: ./ # (optional) path to the source folder. default is a hello world app. 83 | exclude: 84 | - .env 85 | region: ap-guangzhou 86 | runtime: Nodejs10.15 87 | apigatewayConf: 88 | protocols: 89 | - http 90 | - https 91 | environment: release 92 | ``` 93 | 94 | - [点击此处查看配置文档](https://github.com/serverless-components/tencent-koa/tree/master/docs/configure.md) 95 | 96 | ### 4. 部署 97 | 98 | 如您的账号未[登陆](https://cloud.tencent.com/login)或[注册](https://cloud.tencent.com/register)腾讯云,您可以直接通过`微信`扫描命令行中的二维码进行授权登陆和注册。 99 | 100 | 通过`sls`命令进行部署,并可以添加`--debug`参数查看部署过程中的信息 101 | 102 | > 注:`sls`命令是`serverless`命令的缩写 103 | 104 | ``` 105 | $ sls deploy 106 | ``` 107 | 108 | 部署完毕后,可以在浏览器中访问部署成功地 url。 109 | 110 | ### 5. 移除 111 | 112 | 通过以下命令移除部署的 Koa 服务。 113 | 114 | ``` 115 | $ sls remove 116 | ``` 117 | 118 | ### 账号配置(可选) 119 | 120 | 当前默认支持 CLI 扫描二维码登录,如您希望配置持久的环境变量/秘钥信息,也可以本地创建 `.env` 文件 121 | 122 | ```bash 123 | $ touch .env # 腾讯云的配置信息 124 | ``` 125 | 126 | 在 `.env` 文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存 127 | 128 | 如果没有腾讯云账号,可以在此[注册新账号](https://cloud.tencent.com/register)。 129 | 130 | 如果已有腾讯云账号,可以在[API 密钥管理](https://bash.cloud.tencent.com/cam/capi)中获取 `SecretId` 和`SecretKey`. 131 | 132 | ``` 133 | # .env 134 | TENCENT_SECRET_ID=123 135 | TENCENT_SECRET_KEY=123 136 | ``` 137 | 138 | ### 还支持哪些组件? 139 | 140 | 可以在 [Serverless Components](https://github.com/serverless/components) repo 中查询更多组件的信息。 141 | 142 | ## License 143 | 144 | MIT License 145 | 146 | Copyright (c) 2020 Tencent Cloud, Inc. 147 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Serverless Component - Express.js 7 | 14 | 15 | 16 |

17 | Welcome to Koa.js application created by 18 | Serverless Framework. 19 |

20 | 21 | 22 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "sls.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "yugasun", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@koa/router": "^10.0.0", 13 | "koa": "^2.13.1", 14 | "koa-sendfile": "^2.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/serverless.yml: -------------------------------------------------------------------------------- 1 | app: appDemo 2 | stage: dev 3 | component: koa 4 | name: koaDemo 5 | 6 | inputs: 7 | src: 8 | src: ./ # (optional) path to the source folder. default is a hello world app. 9 | exclude: 10 | - .env 11 | region: ap-guangzhou 12 | runtime: Nodejs10.15 13 | apigatewayConf: 14 | protocols: 15 | - http 16 | - https 17 | environment: release 18 | -------------------------------------------------------------------------------- /example/sls.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const KoaRouter = require('@koa/router') 3 | const sendFile = require('koa-sendfile') 4 | const path = require('path') 5 | 6 | const app = new Koa() 7 | const router = new KoaRouter() 8 | const isServerless = process.env.SERVERLESS 9 | const PORT = 3000 10 | 11 | // Routes 12 | router.get(`/`, async (ctx) => { 13 | await sendFile(ctx, path.join(__dirname, 'index.html')) 14 | }) 15 | 16 | app.use(router.allowedMethods()).use(router.routes()) 17 | 18 | // don't forget to export! 19 | if (isServerless) { 20 | module.exports = app 21 | } else { 22 | app.listen(PORT, () => { 23 | console.log(`Server start on http://localhost:${PORT}`) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /example/sls.upload.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const KoaRouter = require('@koa/router') 3 | const multer = require('@koa/multer') 4 | const sendFile = require('koa-sendfile') 5 | const path = require('path') 6 | 7 | const isServerless = process.env.SERVERLESS 8 | const app = new Koa() 9 | const router = new KoaRouter() 10 | const upload = multer({ dest: isServerless ? '/tmp/upload' : './upload' }) 11 | 12 | router.get(`/`, async (ctx) => { 13 | await sendFile(ctx, path.join(__dirname, 'index.html')) 14 | }) 15 | router.post('/upload', upload.single('file'), (ctx) => { 16 | ctx.body = { 17 | success: true, 18 | data: ctx.file 19 | } 20 | }) 21 | 22 | app.use(router.routes()).use(router.allowedMethods()) 23 | 24 | if (isServerless) { 25 | module.exports = app 26 | } else { 27 | app.listen(3000, () => { 28 | console.log(`Server start on http://localhost:3000`) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | require('dotenv').config({ path: join(__dirname, '.env.test') }) 3 | 4 | const config = { 5 | verbose: true, 6 | silent: false, 7 | testTimeout: 600000, 8 | testEnvironment: 'node', 9 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$', 10 | testPathIgnorePatterns: ['/node_modules/', '/__tests__/lib/'], 11 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] 12 | } 13 | 14 | module.exports = config 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@serverless/koa", 3 | "main": "src/serverless.js", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "scripts": { 8 | "test": "jest", 9 | "commitlint": "commitlint -f HEAD@{15}", 10 | "lint": "eslint --ext .js,.ts,.tsx .", 11 | "lint:fix": "eslint --fix --ext .js,.ts,.tsx .", 12 | "prettier": "prettier --check '**/*.{css,html,js,json,md,yaml,yml}'", 13 | "prettier:fix": "prettier --write '**/*.{css,html,js,json,md,yaml,yml}'", 14 | "release": "semantic-release", 15 | "release-local": "node -r dotenv/config node_modules/semantic-release/bin/semantic-release --no-ci --dry-run", 16 | "check-dependencies": "npx npm-check --skip-unused --update" 17 | }, 18 | "husky": { 19 | "hooks": { 20 | "pre-commit": "ygsec && lint-staged", 21 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 22 | "pre-push": "ygsec && npm run lint:fix && npm run prettier:fix" 23 | } 24 | }, 25 | "lint-staged": { 26 | "**/*.{js,ts,tsx}": [ 27 | "npm run lint:fix", 28 | "git add ." 29 | ], 30 | "**/*.{css,html,js,json,md,yaml,yml}": [ 31 | "npm run prettier:fix", 32 | "git add ." 33 | ] 34 | }, 35 | "author": "Tencent Cloud, Inc.", 36 | "license": "MIT", 37 | "dependencies": {}, 38 | "devDependencies": { 39 | "@commitlint/cli": "^8.3.5", 40 | "@commitlint/config-conventional": "^8.3.4", 41 | "@semantic-release/changelog": "^5.0.0", 42 | "@semantic-release/commit-analyzer": "^8.0.1", 43 | "@semantic-release/git": "^9.0.0", 44 | "@semantic-release/npm": "^7.0.4", 45 | "@semantic-release/release-notes-generator": "^9.0.1", 46 | "@serverless/platform-client-china": "^1.0.19", 47 | "@ygkit/secure": "0.0.3", 48 | "axios": "^0.19.2", 49 | "babel-eslint": "^10.1.0", 50 | "dotenv": "^8.2.0", 51 | "eslint": "^6.8.0", 52 | "eslint-config-prettier": "^6.10.0", 53 | "eslint-plugin-import": "^2.20.1", 54 | "eslint-plugin-prettier": "^3.1.2", 55 | "husky": "^4.2.5", 56 | "jest": "^25.0.1", 57 | "lint-staged": "^10.0.8", 58 | "prettier": "^1.19.1", 59 | "semantic-release": "^17.0.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | printWidth: 100, 4 | semi: false, 5 | singleQuote: true, 6 | tabWidth: 2, 7 | trailingComma: 'none' 8 | } 9 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verifyConditions: [ 3 | '@semantic-release/changelog', 4 | '@semantic-release/git', 5 | '@semantic-release/github' 6 | ], 7 | plugins: [ 8 | [ 9 | '@semantic-release/commit-analyzer', 10 | { 11 | preset: 'angular', 12 | parserOpts: { 13 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'] 14 | } 15 | } 16 | ], 17 | [ 18 | '@semantic-release/release-notes-generator', 19 | { 20 | preset: 'angular', 21 | parserOpts: { 22 | noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'] 23 | }, 24 | writerOpts: { 25 | commitsSort: ['subject', 'scope'] 26 | } 27 | } 28 | ], 29 | [ 30 | '@semantic-release/changelog', 31 | { 32 | changelogFile: 'CHANGELOG.md' 33 | } 34 | ], 35 | [ 36 | '@semantic-release/git', 37 | { 38 | assets: ['package.json', 'src/**', 'CHANGELOG.md'], 39 | message: 'chore(release): version ${nextRelease.version} \n\n${nextRelease.notes}' 40 | } 41 | ], 42 | [ 43 | '@semantic-release/github', 44 | { 45 | assets: ['!.env'] 46 | } 47 | ] 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /serverless.component.yml: -------------------------------------------------------------------------------- 1 | name: koa 2 | version: 0.4.0 3 | author: 'Tencent Cloud, Inc.' 4 | org: 'Tencent Cloud, Inc.' 5 | description: Deploy a serverless Koa.js application onto Tencent SCF and API Gateway. 6 | keywords: 'tencent, serverless, koa' 7 | repo: 'https://github.com/serverless-components/tencent-koa/' 8 | readme: 'https://github.com/serverless-components/tencent-koa/tree/master/README.md' 9 | license: MIT 10 | main: ./src 11 | webDeployable: true 12 | -------------------------------------------------------------------------------- /src/_shims/handler.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const { createServer, proxy } = require('tencent-serverless-http') 4 | 5 | let server 6 | let app 7 | 8 | exports.handler = async (event, context) => { 9 | const userSls = path.join(__dirname, '..', process.env.SLS_ENTRY_FILE) 10 | if (fs.existsSync(userSls)) { 11 | // eslint-disable-next-line 12 | console.log(`Using user custom entry file ${process.env.SLS_ENTRY_FILE}`) 13 | app = require(userSls) 14 | } else { 15 | app = require('./sls.js') 16 | } 17 | 18 | app.request.__SLS_EVENT__ = event 19 | app.request.__SLS_CONTEXT__ = context 20 | 21 | if (app.slsInitialize && typeof app.slsInitialize === 'function') { 22 | await app.slsInitialize() 23 | } 24 | 25 | // cache server, not create repeatly 26 | if (!server) { 27 | server = createServer(app.callback(), null, app.binaryTypes || []) 28 | } 29 | 30 | context.callbackWaitsForEmptyEventLoop = 31 | app.callbackWaitsForEmptyEventLoop === true ? true : false 32 | 33 | const result = await proxy(server, event, context, 'PROMISE') 34 | return result.promise 35 | } 36 | -------------------------------------------------------------------------------- /src/_shims/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "tencent-serverless-http": "^1.3.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/_shims/sls.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | 3 | const app = new Koa() 4 | 5 | app.use(async (ctx, next) => { 6 | if (ctx.path !== '/') { 7 | return next() 8 | } 9 | ctx.body = 'Hello from Koa' 10 | }) 11 | 12 | // don't forget to export! 13 | module.exports = app 14 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const CONFIGS = { 2 | templateUrl: 'https://serverless-templates-1300862921.cos.ap-beijing.myqcloud.com/koa-demo.zip', 3 | compName: 'koa', 4 | compFullname: 'Koa.js', 5 | defaultEntryFile: 'sls.js', 6 | handler: 'sl_handler.handler', 7 | runtime: 'Nodejs10.15', 8 | timeout: 3, 9 | memorySize: 128, 10 | namespace: 'default', 11 | description: 'Created by Serverless Component' 12 | } 13 | 14 | module.exports = CONFIGS 15 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "download": "^8.0.0", 4 | "tencent-component-toolkit": "1.20.10", 5 | "type": "^2.1.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/serverless.js: -------------------------------------------------------------------------------- 1 | const { Component } = require('@serverless/core') 2 | const { Scf, Apigw, Cns } = require('tencent-component-toolkit') 3 | const { TypeError } = require('tencent-component-toolkit/src/utils/error') 4 | const { uploadCodeToCos, getDefaultProtocol, prepareInputs, deepClone } = require('./utils') 5 | const CONFIGS = require('./config') 6 | 7 | class ServerlessComponent extends Component { 8 | getCredentials() { 9 | const { tmpSecrets } = this.credentials.tencent 10 | 11 | if (!tmpSecrets || !tmpSecrets.TmpSecretId) { 12 | throw new TypeError( 13 | 'CREDENTIAL', 14 | 'Cannot get secretId/Key, your account could be sub-account and does not have the access to use SLS_QcsRole, please make sure the role exists first, then visit https://cloud.tencent.com/document/product/1154/43006, follow the instructions to bind the role to your account.' 15 | ) 16 | } 17 | 18 | return { 19 | SecretId: tmpSecrets.TmpSecretId, 20 | SecretKey: tmpSecrets.TmpSecretKey, 21 | Token: tmpSecrets.Token 22 | } 23 | } 24 | 25 | getAppId() { 26 | return this.credentials.tencent.tmpSecrets.appId 27 | } 28 | 29 | async deployFunction(credentials, inputs, regionList) { 30 | const outputs = {} 31 | const appId = this.getAppId() 32 | 33 | const funcDeployer = async (curRegion) => { 34 | const code = await uploadCodeToCos(this, appId, credentials, inputs, curRegion) 35 | const scf = new Scf(credentials, curRegion) 36 | const tempInputs = { 37 | ...inputs, 38 | code 39 | } 40 | const scfOutput = await scf.deploy(deepClone(tempInputs)) 41 | outputs[curRegion] = { 42 | functionName: scfOutput.FunctionName, 43 | runtime: scfOutput.Runtime, 44 | namespace: scfOutput.Namespace 45 | } 46 | 47 | this.state[curRegion] = { 48 | ...(this.state[curRegion] ? this.state[curRegion] : {}), 49 | ...outputs[curRegion] 50 | } 51 | 52 | // default version is $LATEST 53 | outputs[curRegion].lastVersion = scfOutput.LastVersion 54 | ? scfOutput.LastVersion 55 | : this.state.lastVersion || '$LATEST' 56 | 57 | // default traffic is 1.0, it can also be 0, so we should compare to undefined 58 | outputs[curRegion].traffic = 59 | scfOutput.Traffic !== undefined 60 | ? scfOutput.Traffic 61 | : this.state.traffic !== undefined 62 | ? this.state.traffic 63 | : 1 64 | 65 | if (outputs[curRegion].traffic !== 1 && scfOutput.ConfigTrafficVersion) { 66 | outputs[curRegion].configTrafficVersion = scfOutput.ConfigTrafficVersion 67 | this.state.configTrafficVersion = scfOutput.ConfigTrafficVersion 68 | } 69 | 70 | this.state.lastVersion = outputs[curRegion].lastVersion 71 | this.state.traffic = outputs[curRegion].traffic 72 | } 73 | 74 | for (let i = 0; i < regionList.length; i++) { 75 | const curRegion = regionList[i] 76 | await funcDeployer(curRegion) 77 | } 78 | this.save() 79 | return outputs 80 | } 81 | 82 | // try to add dns record 83 | async tryToAddDnsRecord(credentials, customDomains) { 84 | try { 85 | const cns = new Cns(credentials) 86 | for (let i = 0; i < customDomains.length; i++) { 87 | const item = customDomains[i] 88 | if (item.domainPrefix) { 89 | await cns.deploy({ 90 | domain: item.subDomain.replace(`${item.domainPrefix}.`, ''), 91 | records: [ 92 | { 93 | subDomain: item.domainPrefix, 94 | recordType: 'CNAME', 95 | recordLine: '默认', 96 | value: item.cname, 97 | ttl: 600, 98 | mx: 10, 99 | status: 'enable' 100 | } 101 | ] 102 | }) 103 | } 104 | } 105 | } catch (e) { 106 | console.log('METHOD_tryToAddDnsRecord', e.message) 107 | } 108 | } 109 | 110 | async deployApigateway(credentials, inputs, regionList) { 111 | if (inputs.isDisabled) { 112 | return {} 113 | } 114 | 115 | const getServiceId = (instance, region) => { 116 | const regionState = instance.state[region] 117 | return inputs.serviceId || (regionState && regionState.serviceId) 118 | } 119 | 120 | const deployTasks = [] 121 | const outputs = {} 122 | regionList.forEach((curRegion) => { 123 | const apigwDeployer = async () => { 124 | const apigw = new Apigw(credentials, curRegion) 125 | 126 | const oldState = this.state[curRegion] || {} 127 | const apigwInputs = { 128 | ...inputs, 129 | oldState: { 130 | apiList: oldState.apiList || [], 131 | customDomains: oldState.customDomains || [] 132 | } 133 | } 134 | // different region deployment has different service id 135 | apigwInputs.serviceId = getServiceId(this, curRegion) 136 | const apigwOutput = await apigw.deploy(deepClone(apigwInputs)) 137 | outputs[curRegion] = { 138 | serviceId: apigwOutput.serviceId, 139 | subDomain: apigwOutput.subDomain, 140 | environment: apigwOutput.environment, 141 | url: `${getDefaultProtocol(inputs.protocols)}://${apigwOutput.subDomain}/${ 142 | apigwOutput.environment 143 | }${apigwInputs.endpoints[0].path}` 144 | } 145 | 146 | if (apigwOutput.customDomains) { 147 | // TODO: need confirm add cns authentication 148 | if (inputs.autoAddDnsRecord === true) { 149 | // await this.tryToAddDnsRecord(credentials, apigwOutput.customDomains) 150 | } 151 | outputs[curRegion].customDomains = apigwOutput.customDomains 152 | } 153 | this.state[curRegion] = { 154 | created: true, 155 | ...(this.state[curRegion] ? this.state[curRegion] : {}), 156 | ...outputs[curRegion], 157 | apiList: apigwOutput.apiList 158 | } 159 | } 160 | deployTasks.push(apigwDeployer()) 161 | }) 162 | 163 | await Promise.all(deployTasks) 164 | 165 | this.save() 166 | return outputs 167 | } 168 | 169 | async deploy(inputs) { 170 | console.log(`Deploying ${CONFIGS.compFullname} App...`) 171 | 172 | const credentials = this.getCredentials() 173 | 174 | // 对Inputs内容进行标准化 175 | const { regionList, functionConf, apigatewayConf } = await prepareInputs( 176 | this, 177 | credentials, 178 | inputs 179 | ) 180 | 181 | // 部署函数 + API网关 182 | const outputs = {} 183 | if (!functionConf.code.src) { 184 | outputs.templateUrl = CONFIGS.templateUrl 185 | } 186 | 187 | let apigwOutputs 188 | const functionOutputs = await this.deployFunction( 189 | credentials, 190 | functionConf, 191 | regionList, 192 | outputs 193 | ) 194 | // support apigatewayConf.isDisabled 195 | if (apigatewayConf.isDisabled !== true) { 196 | apigwOutputs = await this.deployApigateway(credentials, apigatewayConf, regionList, outputs) 197 | } else { 198 | this.state.apigwDisabled = true 199 | } 200 | 201 | // optimize outputs for one region 202 | if (regionList.length === 1) { 203 | const [oneRegion] = regionList 204 | outputs.region = oneRegion 205 | outputs['scf'] = functionOutputs[oneRegion] 206 | if (apigwOutputs) { 207 | outputs['apigw'] = apigwOutputs[oneRegion] 208 | } 209 | } else { 210 | outputs['scf'] = functionOutputs 211 | if (apigwOutputs) { 212 | outputs['apigw'] = apigwOutputs 213 | } 214 | } 215 | 216 | this.state.region = regionList[0] 217 | this.state.regionList = regionList 218 | this.state.lambdaArn = functionConf.name 219 | 220 | return outputs 221 | } 222 | 223 | async remove() { 224 | console.log(`Removing ${CONFIGS.compFullname} App...`) 225 | 226 | const { state } = this 227 | const { regionList = [] } = state 228 | 229 | const credentials = this.getCredentials() 230 | 231 | const removeHandlers = [] 232 | for (let i = 0; i < regionList.length; i++) { 233 | const curRegion = regionList[i] 234 | const curState = state[curRegion] 235 | const scf = new Scf(credentials, curRegion) 236 | const apigw = new Apigw(credentials, curRegion) 237 | const handler = async () => { 238 | // if disable apigw, no need to remove 239 | if (state.apigwDisabled !== true) { 240 | await apigw.remove({ 241 | created: curState.created, 242 | environment: curState.environment, 243 | serviceId: curState.serviceId, 244 | apiList: curState.apiList, 245 | customDomains: curState.customDomains 246 | }) 247 | } 248 | await scf.remove({ 249 | functionName: curState.functionName, 250 | namespace: curState.namespace 251 | }) 252 | } 253 | removeHandlers.push(handler()) 254 | } 255 | 256 | await Promise.all(removeHandlers) 257 | 258 | if (this.state.cns) { 259 | const cns = new Cns(credentials) 260 | for (let i = 0; i < this.state.cns.length; i++) { 261 | await cns.remove({ deleteList: this.state.cns[i].records }) 262 | } 263 | } 264 | 265 | this.state = {} 266 | } 267 | } 268 | 269 | module.exports = ServerlessComponent 270 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { Cos } = require('tencent-component-toolkit') 3 | const ensureObject = require('type/object/ensure') 4 | const ensureIterable = require('type/iterable/ensure') 5 | const ensureString = require('type/string/ensure') 6 | const download = require('download') 7 | const { TypeError } = require('tencent-component-toolkit/src/utils/error') 8 | const CONFIGS = require('./config') 9 | 10 | /* 11 | * Generates a random id 12 | */ 13 | const generateId = () => 14 | Math.random() 15 | .toString(36) 16 | .substring(6) 17 | 18 | const deepClone = (obj) => { 19 | return JSON.parse(JSON.stringify(obj)) 20 | } 21 | 22 | const getType = (obj) => { 23 | return Object.prototype.toString.call(obj).slice(8, -1) 24 | } 25 | 26 | const mergeJson = (sourceJson, targetJson) => { 27 | Object.entries(sourceJson).forEach(([key, val]) => { 28 | targetJson[key] = deepClone(val) 29 | }) 30 | return targetJson 31 | } 32 | 33 | const capitalString = (str) => { 34 | if (str.length < 2) { 35 | return str.toUpperCase() 36 | } 37 | 38 | return `${str[0].toUpperCase()}${str.slice(1)}` 39 | } 40 | 41 | const getDefaultProtocol = (protocols) => { 42 | return String(protocols).includes('https') ? 'https' : 'http' 43 | } 44 | 45 | const getDefaultFunctionName = () => { 46 | return `${CONFIGS.compName}_component_${generateId()}` 47 | } 48 | 49 | const getDefaultServiceName = () => { 50 | return 'serverless' 51 | } 52 | 53 | const getDefaultServiceDescription = () => { 54 | return 'Created by Serverless Component' 55 | } 56 | 57 | const validateTraffic = (num) => { 58 | if (getType(num) !== 'Number') { 59 | throw new TypeError( 60 | `PARAMETER_${CONFIGS.compName.toUpperCase()}_TRAFFIC`, 61 | 'traffic must be a number' 62 | ) 63 | } 64 | if (num < 0 || num > 1) { 65 | throw new TypeError( 66 | `PARAMETER_${CONFIGS.compName.toUpperCase()}_TRAFFIC`, 67 | 'traffic must be a number between 0 and 1' 68 | ) 69 | } 70 | return true 71 | } 72 | 73 | const getCodeZipPath = async (instance, inputs) => { 74 | console.log(`Packaging ${CONFIGS.compFullname} application...`) 75 | 76 | // unzip source zip file 77 | let zipPath 78 | if (!inputs.code.src) { 79 | // add default template 80 | const downloadPath = `/tmp/${generateId()}` 81 | const filename = 'template' 82 | 83 | console.log(`Installing Default ${CONFIGS.compFullname} App...`) 84 | try { 85 | await download(CONFIGS.templateUrl, downloadPath, { 86 | filename: `${filename}.zip` 87 | }) 88 | } catch (e) { 89 | throw new TypeError(`DOWNLOAD_TEMPLATE`, 'Download default template failed.') 90 | } 91 | zipPath = `${downloadPath}/${filename}.zip` 92 | } else { 93 | zipPath = inputs.code.src 94 | } 95 | 96 | return zipPath 97 | } 98 | 99 | /** 100 | * Upload code to COS 101 | * @param {Component} instance serverless component instance 102 | * @param {string} appId app id 103 | * @param {object} credentials credentials 104 | * @param {object} inputs component inputs parameters 105 | * @param {string} region region 106 | */ 107 | const uploadCodeToCos = async (instance, appId, credentials, inputs, region) => { 108 | const bucketName = inputs.code.bucket || `sls-cloudfunction-${region}-code` 109 | const objectName = inputs.code.object || `${inputs.name}-${Math.floor(Date.now() / 1000)}.zip` 110 | // if set bucket and object not pack code 111 | if (!inputs.code.bucket || !inputs.code.object) { 112 | const zipPath = await getCodeZipPath(instance, inputs) 113 | console.log(`Code zip path ${zipPath}`) 114 | 115 | // save the zip path to state for lambda to use it 116 | instance.state.zipPath = zipPath 117 | 118 | const cos = new Cos(credentials, region) 119 | 120 | if (!inputs.code.bucket) { 121 | // create default bucket 122 | await cos.deploy({ 123 | bucket: bucketName + '-' + appId, 124 | force: true, 125 | lifecycle: [ 126 | { 127 | status: 'Enabled', 128 | id: 'deleteObject', 129 | filter: '', 130 | expiration: { days: '10' }, 131 | abortIncompleteMultipartUpload: { daysAfterInitiation: '10' } 132 | } 133 | ] 134 | }) 135 | } 136 | 137 | // upload code to cos 138 | if (!inputs.code.object) { 139 | console.log(`Getting cos upload url for bucket ${bucketName}`) 140 | const uploadUrl = await cos.getObjectUrl({ 141 | bucket: bucketName + '-' + appId, 142 | object: objectName, 143 | method: 'PUT' 144 | }) 145 | 146 | // if shims and sls sdk entries had been injected to zipPath, no need to injected again 147 | console.log(`Uploading code to bucket ${bucketName}`) 148 | if (instance.codeInjected === true) { 149 | await instance.uploadSourceZipToCOS(zipPath, uploadUrl, {}, {}) 150 | } else { 151 | const slsSDKEntries = instance.getSDKEntries('_shims/handler.handler') 152 | await instance.uploadSourceZipToCOS(zipPath, uploadUrl, slsSDKEntries, { 153 | _shims: path.join(__dirname, '_shims') 154 | }) 155 | instance.codeInjected = true 156 | } 157 | console.log(`Upload ${objectName} to bucket ${bucketName} success`) 158 | } 159 | } 160 | 161 | // save bucket state 162 | instance.state.bucket = bucketName 163 | instance.state.object = objectName 164 | 165 | return { 166 | bucket: bucketName, 167 | object: objectName 168 | } 169 | } 170 | 171 | const prepareInputs = async (instance, credentials, inputs = {}) => { 172 | // 对function inputs进行标准化 173 | const tempFunctionConf = inputs.functionConf 174 | ? inputs.functionConf 175 | : inputs.functionConfig 176 | ? inputs.functionConfig 177 | : {} 178 | const fromClientRemark = `tencent-${CONFIGS.compName}` 179 | const regionList = inputs.region 180 | ? typeof inputs.region == 'string' 181 | ? [inputs.region] 182 | : inputs.region 183 | : ['ap-guangzhou'] 184 | 185 | // chenck state function name 186 | const stateFunctionName = 187 | instance.state[regionList[0]] && instance.state[regionList[0]].functionName 188 | const functionConf = Object.assign(tempFunctionConf, { 189 | code: { 190 | src: inputs.src, 191 | bucket: inputs.srcOriginal && inputs.srcOriginal.bucket, 192 | object: inputs.srcOriginal && inputs.srcOriginal.object 193 | }, 194 | name: 195 | ensureString(inputs.functionName, { isOptional: true }) || 196 | stateFunctionName || 197 | getDefaultFunctionName(), 198 | region: regionList, 199 | role: ensureString(tempFunctionConf.role ? tempFunctionConf.role : inputs.role, { 200 | default: '' 201 | }), 202 | handler: ensureString(tempFunctionConf.handler ? tempFunctionConf.handler : inputs.handler, { 203 | default: CONFIGS.handler 204 | }), 205 | runtime: ensureString(tempFunctionConf.runtime ? tempFunctionConf.runtime : inputs.runtime, { 206 | default: CONFIGS.runtime 207 | }), 208 | namespace: ensureString( 209 | tempFunctionConf.namespace ? tempFunctionConf.namespace : inputs.namespace, 210 | { default: CONFIGS.namespace } 211 | ), 212 | description: ensureString( 213 | tempFunctionConf.description ? tempFunctionConf.description : inputs.description, 214 | { 215 | default: CONFIGS.description 216 | } 217 | ), 218 | fromClientRemark, 219 | layers: ensureIterable(tempFunctionConf.layers ? tempFunctionConf.layers : inputs.layers, { 220 | default: [] 221 | }), 222 | cfs: ensureIterable(tempFunctionConf.cfs ? tempFunctionConf.cfs : inputs.cfs, { 223 | default: [] 224 | }), 225 | publish: inputs.publish, 226 | traffic: inputs.traffic, 227 | lastVersion: instance.state.lastVersion, 228 | timeout: tempFunctionConf.timeout ? tempFunctionConf.timeout : CONFIGS.timeout, 229 | memorySize: tempFunctionConf.memorySize ? tempFunctionConf.memorySize : CONFIGS.memorySize, 230 | tags: ensureObject(tempFunctionConf.tags ? tempFunctionConf.tags : inputs.tag, { 231 | default: null 232 | }) 233 | }) 234 | 235 | // validate traffic 236 | if (inputs.traffic !== undefined) { 237 | validateTraffic(inputs.traffic) 238 | } 239 | functionConf.needSetTraffic = inputs.traffic !== undefined && functionConf.lastVersion 240 | 241 | if (tempFunctionConf.environment) { 242 | functionConf.environment = tempFunctionConf.environment 243 | functionConf.environment.variables = functionConf.environment.variables || {} 244 | functionConf.environment.variables.SERVERLESS = '1' 245 | functionConf.environment.variables.SLS_ENTRY_FILE = inputs.entryFile || CONFIGS.defaultEntryFile 246 | } else { 247 | functionConf.environment = { 248 | variables: { 249 | SERVERLESS: '1', 250 | SLS_ENTRY_FILE: inputs.entryFile || CONFIGS.defaultEntryFile 251 | } 252 | } 253 | } 254 | 255 | if (tempFunctionConf.vpcConfig) { 256 | functionConf.vpcConfig = tempFunctionConf.vpcConfig 257 | } 258 | 259 | // 对apigw inputs进行标准化 260 | const tempApigwConf = inputs.apigatewayConf 261 | ? inputs.apigatewayConf 262 | : inputs.apigwConfig 263 | ? inputs.apigwConfig 264 | : {} 265 | const apigatewayConf = Object.assign(tempApigwConf, { 266 | serviceId: inputs.serviceId || tempApigwConf.serviceId, 267 | region: regionList, 268 | isDisabled: tempApigwConf.isDisabled === true, 269 | fromClientRemark: fromClientRemark, 270 | serviceName: inputs.serviceName || tempApigwConf.serviceName || getDefaultServiceName(instance), 271 | serviceDesc: tempApigwConf.serviceDesc || getDefaultServiceDescription(instance), 272 | protocols: tempApigwConf.protocols || ['http'], 273 | environment: tempApigwConf.environment ? tempApigwConf.environment : 'release', 274 | customDomains: tempApigwConf.customDomains || [] 275 | }) 276 | if (!apigatewayConf.endpoints) { 277 | apigatewayConf.endpoints = [ 278 | { 279 | path: tempApigwConf.path || '/', 280 | enableCORS: tempApigwConf.enableCORS, 281 | serviceTimeout: tempApigwConf.serviceTimeout, 282 | method: 'ANY', 283 | apiName: tempApigwConf.apiName || 'index', 284 | isBase64Encoded: tempApigwConf.isBase64Encoded, 285 | isBase64Trigger: tempApigwConf.isBase64Trigger, 286 | base64EncodedTriggerRules: tempApigwConf.base64EncodedTriggerRules, 287 | function: { 288 | isIntegratedResponse: true, 289 | functionName: functionConf.name, 290 | functionNamespace: functionConf.namespace, 291 | functionQualifier: 292 | (tempApigwConf.function && tempApigwConf.function.functionQualifier) || '$LATEST' 293 | } 294 | } 295 | ] 296 | } 297 | if (tempApigwConf.usagePlan) { 298 | apigatewayConf.endpoints[0].usagePlan = { 299 | usagePlanId: tempApigwConf.usagePlan.usagePlanId, 300 | usagePlanName: tempApigwConf.usagePlan.usagePlanName, 301 | usagePlanDesc: tempApigwConf.usagePlan.usagePlanDesc, 302 | maxRequestNum: tempApigwConf.usagePlan.maxRequestNum 303 | } 304 | } 305 | if (tempApigwConf.auth) { 306 | apigatewayConf.endpoints[0].auth = { 307 | secretName: tempApigwConf.auth.secretName, 308 | secretIds: tempApigwConf.auth.secretIds 309 | } 310 | } 311 | 312 | regionList.forEach((curRegion) => { 313 | const curRegionConf = inputs[curRegion] 314 | if (curRegionConf && curRegionConf.functionConf) { 315 | functionConf[curRegion] = curRegionConf.functionConf 316 | } 317 | if (curRegionConf && curRegionConf.apigatewayConf) { 318 | apigatewayConf[curRegion] = curRegionConf.apigatewayConf 319 | } 320 | }) 321 | 322 | return { 323 | regionList, 324 | functionConf, 325 | apigatewayConf 326 | } 327 | } 328 | 329 | module.exports = { 330 | deepClone, 331 | generateId, 332 | uploadCodeToCos, 333 | mergeJson, 334 | capitalString, 335 | getDefaultProtocol, 336 | prepareInputs 337 | } 338 | --------------------------------------------------------------------------------