├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── ci_node16.yaml │ ├── ci_with_docker_linux.yaml │ └── registry-publish.yaml ├── .gitignore ├── .markdownlint.json ├── .markdownlintignore ├── .prettierignore ├── .prettierrc.js ├── .signore ├── .stylelintignore ├── .stylelintrc.js ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __tests__ ├── complete-props.yaml ├── e2e │ ├── apt │ │ ├── code │ │ │ ├── .gitignore │ │ │ ├── apt-get.list │ │ │ ├── index.js │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ ├── run │ │ └── s.yaml │ ├── ci-mac-linux.sh │ ├── ci-windows.ps1 │ ├── ci.sh │ ├── ci_trigger.sh │ ├── command-api │ │ ├── code │ │ │ └── event.js │ │ ├── pyyaml-layer.zip │ │ ├── run │ │ ├── run-windows.ps1 │ │ ├── run_cli_mode │ │ └── s.yaml │ ├── custom-container │ │ ├── code │ │ │ ├── Dockerfile │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ └── server.js │ │ ├── run │ │ └── s.yaml │ ├── custom-domain │ │ ├── code │ │ │ └── index.py │ │ ├── s.yaml │ │ └── s2.yaml │ ├── custom.debian10 │ │ ├── go │ │ │ ├── code │ │ │ │ ├── .gitignore │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── main.go │ │ │ └── s.yaml │ │ ├── python │ │ │ ├── code │ │ │ │ ├── .gitignore │ │ │ │ ├── gunicorn_conf.py │ │ │ │ ├── requirements.txt │ │ │ │ └── server.py │ │ │ └── s.yaml │ │ └── run │ ├── custom.debian11 │ │ ├── go │ │ │ ├── code │ │ │ │ ├── .gitignore │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── main.go │ │ │ └── s.yaml │ │ └── run │ ├── custom │ │ ├── go │ │ │ ├── code │ │ │ │ ├── .gitignore │ │ │ │ ├── go.mod │ │ │ │ └── main.go │ │ │ └── s.yaml │ │ ├── python │ │ │ ├── code │ │ │ │ ├── .gitignore │ │ │ │ ├── gunicorn_conf.py │ │ │ │ ├── requirements.txt │ │ │ │ └── server.py │ │ │ └── s.yaml │ │ ├── run │ │ ├── run-windows.ps1 │ │ └── springboot │ │ │ ├── code │ │ │ ├── .fcignore │ │ │ ├── .gitignore │ │ │ ├── Dockerfile │ │ │ ├── mvnw │ │ │ ├── mvnw.cmd │ │ │ ├── pom.xml │ │ │ └── src │ │ │ │ ├── main │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── webframework │ │ │ │ │ │ └── WebFrameworkApplication.java │ │ │ │ └── resources │ │ │ │ │ └── application.properties │ │ │ │ └── test │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── webframework │ │ │ │ └── WebFrameworkApplicationTests.java │ │ │ ├── event │ │ │ └── http.json │ │ │ └── s.yaml │ ├── dotnetcore │ │ ├── HelloFcApp │ │ │ ├── HelloFcApp.csproj │ │ │ └── Program.cs │ │ ├── run │ │ └── s.yaml │ ├── go │ │ ├── code │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── main.go │ │ ├── run │ │ └── s.yaml │ ├── java │ │ ├── .gitignore │ │ ├── pom.xml │ │ ├── run │ │ ├── s.yaml │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── example │ │ │ └── App.java │ ├── local │ │ ├── layer │ │ │ ├── nodejs │ │ │ │ ├── code │ │ │ │ │ └── index.js │ │ │ │ └── s.yaml │ │ │ └── python │ │ │ │ ├── code │ │ │ │ └── index.py │ │ │ │ └── s.yaml │ │ ├── nas │ │ │ ├── code │ │ │ │ └── index.py │ │ │ └── s.yaml │ │ └── run │ ├── nodejs │ │ ├── code │ │ │ ├── event.js │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ ├── run │ │ ├── s.yaml │ │ ├── s2.yaml │ │ ├── s_auto.yaml │ │ ├── s_lock_auto.yaml │ │ ├── s_tags.yaml │ │ ├── s_tags2.yaml │ │ ├── s_tags3.yaml │ │ ├── test-auto-code │ │ │ └── index.js │ │ └── test.sh │ ├── php │ │ ├── code │ │ │ ├── .gitignore │ │ │ ├── composer.json │ │ │ ├── composer.lock │ │ │ ├── index.php │ │ │ └── test.sh │ │ ├── run │ │ ├── run-windows.ps1 │ │ └── s.yaml │ ├── python │ │ ├── code │ │ │ ├── .gitignore │ │ │ ├── apt-get.list │ │ │ ├── index.py │ │ │ └── requirements.txt │ │ ├── run │ │ ├── run-windows.ps1 │ │ └── s.yaml │ └── trigger │ │ ├── clear │ │ ├── eb │ │ ├── code │ │ │ └── index.js │ │ └── s.yaml │ │ ├── http │ │ ├── code │ │ │ └── index.js │ │ ├── evt.json │ │ └── s.yaml │ │ ├── jwt │ │ ├── code │ │ │ └── index.js │ │ └── s.yaml │ │ ├── other │ │ ├── code │ │ │ └── index.js │ │ └── s.yaml │ │ ├── run │ │ └── run-windows.ps1 ├── format.sh ├── it │ ├── code │ │ └── index.py │ └── integration_test.ts └── ut │ ├── interface_test.ts │ ├── resources_acr_test.ts │ ├── subCommands_test.ts │ └── utils_test.ts ├── commitlint.config.js ├── docs └── readme.md ├── f2elint.config.js ├── jestconfig.json ├── makefile ├── package-lock.json ├── package.json ├── patches └── ali-oss+6.18.1.patch ├── publish.yaml ├── src ├── base.ts ├── commands-help │ ├── alias.ts │ ├── build.ts │ ├── concurrency.ts │ ├── deploy.ts │ ├── index.ts │ ├── info.ts │ ├── instance.ts │ ├── invoke.ts │ ├── layer.ts │ ├── local.ts │ ├── logs.ts │ ├── plan.ts │ ├── provision.ts │ ├── remove.ts │ ├── s2tos3.ts │ ├── sync.ts │ └── version.ts ├── constant.ts ├── default │ ├── config.ts │ ├── image.ts │ └── resources.ts ├── index.ts ├── interface │ ├── async_invoke_config.ts │ ├── base.ts │ ├── cli-config │ │ ├── alias.ts │ │ ├── concurrency.ts │ │ └── provision.ts │ ├── concurrency_config.ts │ ├── function.ts │ ├── index.ts │ ├── provison_config.ts │ ├── region.ts │ └── trigger.ts ├── logger.ts ├── resources │ ├── acr │ │ ├── index.ts │ │ └── login.ts │ ├── fc │ │ ├── error-code.ts │ │ ├── impl │ │ │ ├── client.ts │ │ │ ├── replace-function-config.ts │ │ │ └── utils.ts │ │ └── index.ts │ ├── ram │ │ └── index.ts │ ├── sls │ │ └── index.ts │ └── vpc-nas │ │ └── index.ts ├── schema.json ├── subCommands │ ├── 2to3 │ │ └── index.ts │ ├── alias │ │ └── index.ts │ ├── build │ │ ├── impl │ │ │ ├── baseBuilder.ts │ │ │ ├── baseImageBuilder.ts │ │ │ ├── defaultBuilder.ts │ │ │ ├── imageBuiltKitBuilder.ts │ │ │ ├── imageDockerBuilder.ts │ │ │ └── imageKanikoBuilder.ts │ │ └── index.ts │ ├── concurrency │ │ └── index.ts │ ├── deploy │ │ ├── impl │ │ │ ├── async_invoke_config.ts │ │ │ ├── base.ts │ │ │ ├── concurrency_config.ts │ │ │ ├── custom_domain.ts │ │ │ ├── function.ts │ │ │ ├── provision_config.ts │ │ │ ├── trigger.ts │ │ │ └── vpc_binding.ts │ │ └── index.ts │ ├── info │ │ └── index.ts │ ├── instance │ │ └── index.ts │ ├── invoke │ │ └── index.ts │ ├── layer │ │ └── index.ts │ ├── local │ │ ├── impl │ │ │ ├── baseLocal.ts │ │ │ ├── invoke │ │ │ │ ├── baseLocalInvoke.ts │ │ │ │ ├── customContainerLocalInvoke.ts │ │ │ │ ├── customLocalInvoke.ts │ │ │ │ ├── dotnetLocalInvoke.ts │ │ │ │ ├── goLocalInvoke.ts │ │ │ │ ├── javaLocalInvoke.ts │ │ │ │ ├── nodejsLocalInvoke.ts │ │ │ │ ├── phpLocalInvoke.ts │ │ │ │ └── pythonLocalInvoke.ts │ │ │ ├── start │ │ │ │ ├── baseLocalStart.ts │ │ │ │ ├── customContainerLocalStart.ts │ │ │ │ ├── customLocalStart.ts │ │ │ │ ├── dotnetLocalStart.ts │ │ │ │ ├── goLocalInvoke.ts │ │ │ │ ├── javaLocalStart.ts │ │ │ │ ├── nodejsLocalStart.ts │ │ │ │ ├── phpLocalStart.ts │ │ │ │ └── pythonLocalStart.ts │ │ │ └── utils.ts │ │ └── index.ts │ ├── logs │ │ ├── constant.ts │ │ └── index.ts │ ├── plan │ │ └── index.ts │ ├── provision │ │ └── index.ts │ ├── remove │ │ └── index.ts │ ├── sync │ │ └── index.ts │ ├── trigger-template │ │ ├── event-template │ │ │ ├── cdn-CachedObjectsRefreshed.json │ │ │ ├── cdn-CdnDomainAdded.json │ │ │ ├── cdn-CdnDomainStarted.json │ │ │ ├── cdn-LogFileCreated.json │ │ │ ├── http.json │ │ │ ├── mns-stream.json │ │ │ ├── mns-with-MessageAttributes.json │ │ │ ├── mns-without-MessageAttributes.json │ │ │ ├── oss.json │ │ │ ├── sls.json │ │ │ └── tablestore.json │ │ └── index.ts │ └── version │ │ └── index.ts └── utils │ ├── index.ts │ ├── run-command.ts │ └── verify.ts ├── tsconfig.json └── version.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | quote_type = single 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | coverage/ 3 | dist/ 4 | es/ 5 | lib/ 6 | node_modules/ 7 | **/*.min.js 8 | **/*-min.js 9 | **/*.bundle.js 10 | docs/ 11 | __tests__/ 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint-config-ali/typescript', 'prettier', 'prettier/@typescript-eslint'], 3 | rules: { 4 | 'no-console': 'off', 5 | 'no-require-imports': 'off', 6 | '@typescript-eslint/explicit-member-accessibility': 0, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /.github/workflows/ci_with_docker_linux.yaml: -------------------------------------------------------------------------------- 1 | name: Linux docker (build and local) ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | REGION: cn-hongkong 10 | 11 | jobs: 12 | docker-ci: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: docker/setup-buildx-action@v2 17 | - uses: actions/setup-node@v2 18 | with: 19 | node-version: 16 20 | registry-url: https://registry.npmjs.org/ 21 | - name: Set up Go 22 | uses: actions/setup-go@v2 23 | with: 24 | go-version: 1.18 25 | - name: Set up Java 26 | uses: actions/setup-java@v1 27 | with: 28 | java-version: 8 29 | - name: Install dependencies 30 | run: | 31 | sudo apt-get update 32 | sudo apt-get install -y wget curl 33 | - name: install s 34 | run: | 35 | sudo npm i @serverless-devs/s -g 36 | - name: config s 37 | run: | 38 | sudo s config add --AccountID ${{secrets.ACCOUNTID}} --AccessKeyID ${{secrets.ACCESSKEYID}} --AccessKeySecret ${{secrets.ACCESSKEYSECRET}} -a quanxi -f 39 | - name: Configure NPM 40 | run: | 41 | npm config set registry https://registry.npmjs.org 42 | npm config set '//packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/:_authToken' ${{secrets.NPM_TOKEN}} 43 | - name: NPM install 44 | run: | 45 | npm install 46 | - name: NPM run build 47 | run: | 48 | npm run build 49 | - name: test python 50 | run: | 51 | cd __tests__/e2e/python && sudo ./run && cd - 52 | - name: test nodejs 53 | run: | 54 | cd __tests__/e2e/nodejs && sudo ./run && cd - 55 | - name: test php 56 | run: | 57 | cd __tests__/e2e/php && sudo ./run && cd - 58 | - name: test golang 59 | run: | 60 | cd __tests__/e2e/go && sudo ./run && cd - 61 | - name: test java 62 | run: | 63 | cd __tests__/e2e/java && sudo ./run && cd - 64 | - name: test dotnetcore 65 | run: | 66 | cd __tests__/e2e/dotnetcore && sudo ./run && cd - 67 | - name: test apt 68 | run: | 69 | cd __tests__/e2e/apt && sudo ./run && cd - 70 | - name: test custom.debian10 71 | run: | 72 | cd __tests__/e2e/custom.debian10 && sudo ./run && cd - 73 | - name: test custom 74 | run: | 75 | cd __tests__/e2e/custom && sudo ./run && cd - 76 | - name: test custom container 77 | run: | 78 | cd __tests__/e2e/custom-container && sudo ./run && cd - 79 | - name: test local with nas 80 | run: | 81 | cd __tests__/e2e/local && sudo ./run && cd - 82 | - name: test custom.debian11 83 | run: | 84 | cd __tests__/e2e/custom.debian11 && sudo ./run && cd - 85 | -------------------------------------------------------------------------------- /.github/workflows/registry-publish.yaml: -------------------------------------------------------------------------------- 1 | name: publish package to registry 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: 16 15 | registry-url: https://registry.npmjs.org/ 16 | - name: Install dependencies 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y git make jq curl wget 20 | - name: install s 21 | run: | 22 | npm i @serverless-devs/s@v3 -g 23 | - name: s registry login 24 | run: | 25 | s registry login --token ${{ secrets.alibaba_registry_v3_publish_token }} 26 | - name: Configure NPM 27 | run: | 28 | npm config set registry https://registry.npmjs.org 29 | npm config set '//packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/:_authToken' ${{secrets.NPM_TOKEN}} 30 | - name: release prod 31 | run: | 32 | make release-prod 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | 3 | !.*ignore 4 | !.*rc 5 | !.gitattributes 6 | !.aoneci.yml 7 | !.editorconfig 8 | 9 | # Logs 10 | *.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # Dependency directories 25 | bower_components 26 | node_modules/ 27 | jspm_packages/ 28 | __pycache__/ 29 | 30 | # Compiled binary addons (https://nodejs.org/api/addons.html) 31 | build/Release 32 | lib 33 | dist 34 | 35 | # TypeScript v1 declaration files 36 | typings/ 37 | 38 | # Output of 'npm pack' 39 | *.tgz 40 | 41 | 42 | .DS_Store 43 | ___** 44 | .s 45 | replace-committers.sh* 46 | __tests__/e2e/python/.vscode 47 | __tests__/e2e/python/code/apt-archives 48 | .vscode 49 | __tests__/e2e/php/code/vendor 50 | __tests__/e2e/go/code/target 51 | __tests__/e2e/custom.debian10/go/code/target 52 | __tests__/e2e/custom/go/code/target 53 | __tests__/e2e/dotnetcore/HelloFcApp/bin/Release/netcoreapp3.1 54 | __tests__/e2e/dotnetcore/HelloFcApp/obj 55 | __tests__/e2e/dotnetcore/HelloFcApp/target 56 | __tests__/e2e/custom.debian10/python/code/python 57 | __tests__/e2e/custom/python/code/python 58 | __tests__/e2e/python/code/python 59 | __tests__/e2e/python/code/apt-archives 60 | 61 | .env_test -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "markdownlint-config-ali" 3 | } 4 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | dist/ 4 | coverage/ 5 | es/ 6 | lib/ 7 | docs/ 8 | 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | __test__/complete-props.yaml 3 | dist/ 4 | docs/ 5 | *.json 6 | *.yaml 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | bracketSpacing: true, 8 | arrowParens: 'always', 9 | //parser: 'typescript', 10 | }; 11 | -------------------------------------------------------------------------------- /.signore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | .husky 3 | .s 4 | .github 5 | .idea 6 | .vscode 7 | .DS_Store 8 | .git 9 | .env 10 | node_modules 11 | LICENSE 12 | package-lock.json 13 | .prettierrc.js 14 | .prettierignore 15 | .eslintrc.js 16 | .eslintignore 17 | .editorconfig 18 | .gitignore 19 | makefile 20 | README.md 21 | docs 22 | replace-committers.sh 23 | f2elint.config.js 24 | commitlint.config.js 25 | .stylelintrc.js 26 | .stylelintignore 27 | .markdownlint.json 28 | .markdownlintignore 29 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | dist/ 4 | coverage/ 5 | es/ 6 | lib/ 7 | **/*.min.css 8 | **/*-min.css 9 | **/*.bundle.css 10 | __tests__/ 11 | docs/ 12 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'stylelint-config-ali', 3 | }; 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Serverless Devs 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. -------------------------------------------------------------------------------- /__tests__/e2e/apt/code/.gitignore: -------------------------------------------------------------------------------- 1 | apt-archives -------------------------------------------------------------------------------- /__tests__/e2e/apt/code/apt-get.list: -------------------------------------------------------------------------------- 1 | libjq1 2 | libonig4 3 | jq 4 | rsync 5 | -------------------------------------------------------------------------------- /__tests__/e2e/apt/code/index.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | require('chalk'); 3 | 4 | module.exports.handler = function (event, context, callback) { 5 | // throw new Error('xxx') 6 | console.log(process.env.PATH); 7 | console.log(process.env.LD_LIBRARY_PATH); 8 | console.log(event.toString()); 9 | execSync('jq --help', { 10 | shell: true, 11 | stdio: 'inherit', 12 | }); 13 | 14 | execSync('rsync --version', { 15 | shell: true, 16 | stdio: 'inherit', 17 | }); 18 | callback(null, 'hello world'); 19 | }; 20 | -------------------------------------------------------------------------------- /__tests__/e2e/apt/code/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-styles": { 8 | "version": "4.3.0", 9 | "resolved": "https://packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/ansi-styles/-/ansi-styles-4.3.0.tgz", 10 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 11 | "requires": { 12 | "color-convert": "^2.0.1" 13 | } 14 | }, 15 | "chalk": { 16 | "version": "4.1.2", 17 | "resolved": "https://packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/chalk/-/chalk-4.1.2.tgz", 18 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 19 | "requires": { 20 | "ansi-styles": "^4.1.0", 21 | "supports-color": "^7.1.0" 22 | } 23 | }, 24 | "color-convert": { 25 | "version": "2.0.1", 26 | "resolved": "https://packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/color-convert/-/color-convert-2.0.1.tgz", 27 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 28 | "requires": { 29 | "color-name": "~1.1.4" 30 | } 31 | }, 32 | "color-name": { 33 | "version": "1.1.4", 34 | "resolved": "https://packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/color-name/-/color-name-1.1.4.tgz", 35 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 36 | }, 37 | "has-flag": { 38 | "version": "4.0.0", 39 | "resolved": "https://packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/has-flag/-/has-flag-4.0.0.tgz", 40 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 41 | }, 42 | "supports-color": { 43 | "version": "7.2.0", 44 | "resolved": "https://packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/supports-color/-/supports-color-7.2.0.tgz", 45 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 46 | "requires": { 47 | "has-flag": "^4.0.0" 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /__tests__/e2e/apt/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "event.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "chalk": "^4.1.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /__tests__/e2e/apt/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "test apt-get ..." 6 | rm -rf ./code/apt-archives 7 | rm -rf ./code/node_modules 8 | export fc_component_function_name=nodejs14-$(uname)-$(uname -m) 9 | s build 10 | s local invoke -e '{"hello":"apt-get"}' 11 | s deploy -y 12 | s invoke -e '{"hello":"apt-get"}' 13 | s info 14 | s remove -y 15 | 16 | rm -rf ./code/apt-archives 17 | rm -rf ./code/node_modules 18 | -------------------------------------------------------------------------------- /__tests__/e2e/apt/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: framework 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-apt-get-${env('fc_component_function_name', 'nodejs14')} 14 | runtime: nodejs14 15 | code: ./code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 30 19 | environmentVariables: 20 | PATH: /code/apt-archives/usr/bin:/var/fc/runtime/nodejs18/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 21 | LD_LIBRARY_PATH: /code/apt-archives/usr/local/lib:/code/apt-archives/usr/lib:/code/apt-archives/usr/lib/x86_64-linux-gnu:/code/apt-archives/usr/lib64:/code/apt-archives/lib:/code/apt-archives/lib/x86_64-linux-gnu:/code 22 | -------------------------------------------------------------------------------- /__tests__/e2e/ci-windows.ps1: -------------------------------------------------------------------------------- 1 | # github action 使用 2 | 3 | $ErrorActionPreference = "Stop" 4 | 5 | # $env:region="ap-southeast-1" 6 | # $env:OS="WIN" 7 | # $env:PROCESSOR_ARCHITECTURE="NT" 8 | 9 | Write-Host "test go runtime" 10 | cd go 11 | $env:fc_component_function_name = "go1-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 12 | $env:GOOS = "linux" 13 | $env:GOARCH = "amd64" 14 | cd ./code; go mod tidy; go build -o ./target/main; cd ../ 15 | s deploy -y --skip-actions 16 | s invoke -e '{"hello":"fc go1"}' 17 | s info 18 | s remove -y 19 | Remove-Item -Recurse -Force ./code/target -ErrorAction SilentlyContinue 20 | cd .. 21 | 22 | Write-Host "test custom go runtime ..." 23 | cd custom 24 | Remove-Item -Recurse -Force ./go/code/go.sum -ErrorAction SilentlyContinue 25 | $env:fc_component_function_name = "go1-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 26 | $env:GOOS = "linux" 27 | $env:GOARCH = "amd64" 28 | cd ./go/code; go mod tidy; go build -o ./target/main; cd ../../ 29 | s deploy -y -t ./go/s.yaml --skip-actions 30 | s invoke -e '{"hello":"fc custom go"}' -t ./go/s.yaml 31 | s info -y -t ./go/s.yaml 32 | s remove -y -t ./go/s.yaml 33 | Remove-Item -Recurse -Force ./go/code/target -ErrorAction SilentlyContinue 34 | cd .. 35 | 36 | 37 | Write-Host "test java runtime" 38 | cd java 39 | $env:fc_component_function_name = "java-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 40 | s deploy -y 41 | s invoke -e '{"hello":"fc java"}' 42 | s info 43 | s remove -y 44 | Remove-Item -Recurse -Force ./target -ErrorAction SilentlyContinue 45 | cd .. 46 | 47 | # Write-Host "test nodejs runtime with auto ..." 48 | # cd nodejs 49 | # $env:fc_component_function_name = "nodejs18-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 50 | # s deploy -y -t ./s_auto.yaml 51 | # s invoke -e '{"hello":"fc nodejs with auto"}' -t ./s_auto.yaml 52 | # s info -y -t ./s_auto.yaml 53 | # s remove -y -t ./s_auto.yaml 54 | # cd ../ 55 | 56 | Write-Host " ********* command-api *********" 57 | cd command-api; ./run-windows.ps1; cd ../ 58 | 59 | s cli fc3 layer list --prefix Python --region ap-southeast-1 -a quanxi 60 | s cli fc3 layer info --layer-name Python39-Gradio --version-id 1 --region ap-southeast-1 -a quanxi 61 | -------------------------------------------------------------------------------- /__tests__/e2e/ci_trigger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 不需要使用到 build 和 local 指令的测试集合均可以加到这里 4 | # 需要 build 和 local 指令测试的集合会在 github action 中实现 5 | 6 | set -e 7 | set -v 8 | 9 | cd trigger && ./run cd - -------------------------------------------------------------------------------- /__tests__/e2e/command-api/code/event.js: -------------------------------------------------------------------------------- 1 | module.exports.handler = function (event, context, callback) { 2 | callback(null, 'hello world'); 3 | }; 4 | -------------------------------------------------------------------------------- /__tests__/e2e/command-api/pyyaml-layer.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsapp/fc3/7a2c54d5c0866b610f450e3aebb06b25aab905a6/__tests__/e2e/command-api/pyyaml-layer.zip -------------------------------------------------------------------------------- /__tests__/e2e/command-api/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -v 5 | 6 | export fc_component_function_name=node16-$(uname)-$(uname -m)-$RANDSTR 7 | 8 | echo "test command instance/version/alias/concurrency/provision ..." 9 | s deploy -y 10 | 11 | instanceId=`s invoke | grep "Invoke instanceId:" | sed "s/.*: //"` 12 | 13 | echo "instanceId: $instanceId" 14 | 15 | # s instance exec --instance-id $instanceId --cmd 'ls -lh' 16 | s instance list 17 | 18 | s version publish --description test 19 | s version list 20 | s version remove --version-id latest -y 21 | s version publish --description test 22 | 23 | s concurrency put --reserved-concurrency 80 24 | s concurrency get 25 | s concurrency remove -y 26 | 27 | s alias list 28 | s alias publish --alias-name test --version-id latest 29 | s alias get --alias-name test 30 | s alias list 31 | s alias list --table 32 | s alias remove --alias-name test -y 33 | s alias publish --alias-name test --version-id latest 34 | 35 | s provision put --qualifier test --ac --target 2 --scheduled-actions '[{"name":"scheduled-actions","startTime":"2023-08-15T02:04:00.000Z","endTime":"2033-08-15T03:04:00.000Z","target":1,"scheduleExpression":"cron(0 0 4 * * *)"}]' --target-tracking-policies '[{"name":"target-tracking-policies","startTime":"2023-08-15T02:05:00.000Z","endTime":"2033-08-15T02:55:00.000Z","metricType":"ProvisionedConcurrencyUtilization","metricTarget":0.6,"minCapacity":1,"maxCapacity":3}]' 36 | s provision get --qualifier test 37 | s provision list 38 | s provision remove --qualifier test -y 39 | s provision list 40 | s provision put --qualifier test --target 2 41 | 42 | s remove -y 43 | 44 | 45 | echo "test layer ..." 46 | export layer_name=pyyaml-layer-$(uname)-$(uname -m)-$RANDSTR 47 | s layer list 48 | s layer list --prefix python 49 | s layer info --layer-name Python39-Scrapy2x --version-id 2 50 | s layer download --layer-name Python39-Scrapy2x --version-id 2 51 | s layer publish --layer-name $layer_name --code ./pyyaml-layer.zip --compatible-runtime "python3.9,python3.10,custom,custom.debian10" 52 | s layer list --prefix $layer_name 53 | s layer list --prefix $layer_name --table 54 | s layer versions --layer-name $layer_name 55 | s layer remove -y --layer-name $layer_name 56 | -------------------------------------------------------------------------------- /__tests__/e2e/command-api/run-windows.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | $env:fc_component_function_name = "node16-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 4 | 5 | Write-Host "test command instance/version/alias/concurrency/provision ..." 6 | s deploy -y 7 | s invoke 8 | 9 | s instance list 10 | 11 | s version publish --description test 12 | s version list 13 | s version remove --version-id latest -y 14 | s version publish --description test 15 | 16 | s concurrency put --reserved-concurrency 80 17 | s concurrency get 18 | s concurrency remove -y 19 | 20 | s alias list 21 | s alias publish --alias-name test --version-id latest 22 | s alias get --alias-name test 23 | s alias list 24 | s alias list --table 25 | s alias remove --alias-name test -y 26 | s alias publish --alias-name test --version-id latest 27 | 28 | s provision put --qualifier test --ac --target 2 --scheduled-actions '[{"name":"scheduled-actions","startTime":"2023-08-15T02:04:00.000Z","endTime":"2033-08-15T02:04:00.000Z","target":1,"scheduleExpression":"cron(0 0 4 * * *)"}]' --target-tracking-policies '[{"name":"target-tracking-policies","startTime":"2023-08-15T02:05:00.000Z","endTime":"2033-08-15T02:05:00.000Z","metricType":"ProvisionedConcurrencyUtilization","metricTarget":0.6,"minCapacity":1,"maxCapacity":3}]' 29 | s provision get --qualifier test 30 | s provision list 31 | s provision remove --qualifier test -y 32 | s provision list 33 | s provision put --qualifier test --target 2 34 | 35 | s remove -y 36 | 37 | 38 | Write-Host "test layer ..." 39 | $layer_name = "pyyaml-layer-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 40 | s layer list 41 | s layer list --prefix python 42 | s layer info --layer-name Python39-Gradio --version-id 1 43 | s layer download --layer-name Python39-Gradio --version-id 1 44 | s layer publish --layer-name $layer_name --code ./pyyaml-layer.zip --compatible-runtime "python3.9,python3.10,custom,custom.debian10" 45 | s layer list --prefix $layer_name 46 | s layer list --prefix $layer_name --table 47 | s layer versions --layer-name $layer_name 48 | s layer remove -y --layer-name $layer_name -------------------------------------------------------------------------------- /__tests__/e2e/command-api/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-command-${env('fc_component_function_name', 'fc3-command')} 14 | runtime: nodejs18 15 | code: ./code 16 | handler: event.handler 17 | memorySize: 128 18 | timeout: 30 19 | logConfig: auto 20 | triggers: 21 | - triggerName: httpTrigger # 触发器名称 22 | triggerType: http # 触发器类型 23 | description: 'xxxx' 24 | qualifier: LATEST # 触发函数的版本 25 | triggerConfig: 26 | authType: anonymous # 鉴权类型,可选值:anonymous、function 27 | disableURLInternet: false # 是否禁用公网访问 URL 28 | methods: # HTTP 触发器支持的访问方法,可选值:GET、POST、PUT、DELETE、HEAD 29 | - GET 30 | asyncInvokeConfig: 31 | destinationConfig: 32 | onFailure: 33 | destination: acs:mns:${vars.region}::/topics/serverless-devs-fc3-ci-test/messages 34 | onSuccess: 35 | destination: acs:fc:${vars.region}::functions/serverless-devs-ci-async-invoke-config-succ 36 | maxAsyncEventAgeInSeconds: 360 37 | maxAsyncRetryAttempts: 3 -------------------------------------------------------------------------------- /__tests__/e2e/custom-container/code/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-slim 2 | 3 | WORKDIR /home/code 4 | COPY . . 5 | 6 | RUN npm install --registry=https://registry.npmmirror.com 7 | 8 | 9 | # ENTRYPOINT ["node", "index.js"] 10 | -------------------------------------------------------------------------------- /__tests__/e2e/custom-container/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "express": "^4.17.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/e2e/custom-container/code/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Constants 4 | const PORT = 9001; 5 | const HOST = '0.0.0.0'; 6 | const REQUEST_ID_HEADER = 'x-fc-request-id'; 7 | 8 | const express = require('express'); 9 | const app = express(); 10 | app.use(express.raw()); 11 | 12 | app.post('/initialize', (req, res) => { 13 | // console.log(JSON.stringify(req.headers)); 14 | var rid = req.headers[REQUEST_ID_HEADER]; 15 | console.log(`FC Initialize Start RequestId: ${rid}`); 16 | // do your things 17 | res.send('Hello FunctionCompute, initialize \n'); 18 | console.log(`FC Initialize End RequestId: ${rid}`); 19 | }); 20 | 21 | // invocation 22 | app.post('/invoke', (req, res) => { 23 | // console.log(JSON.stringify(req.headers)); 24 | var rid = req.headers[REQUEST_ID_HEADER]; 25 | console.log(`FC Invoke Start RequestId: ${rid}`); 26 | try { 27 | // get body to do your things 28 | var bodyStr = req.body.toString(); 29 | console.log(bodyStr); 30 | JSON.parse(bodyStr); 31 | } catch (e) { 32 | console.error(e.stack || e); 33 | return res.status(404).send(e.stack || e); 34 | } 35 | 36 | res.send('OK\n'); 37 | console.log(`FC Invoke End RequestId: ${rid}`); 38 | }); 39 | 40 | var server = app.listen(PORT, HOST); 41 | console.log(`Running on http://${HOST}:${PORT}`); 42 | 43 | server.timeout = 0; // never timeout 44 | server.keepAliveTimeout = 0; // keepalive, never timeout 45 | -------------------------------------------------------------------------------- /__tests__/e2e/custom-container/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "test custom-container runtime ..." 6 | export fc_component_function_name=custom_container-$(uname)-$(uname -m) 7 | s build --dockerfile ./code/Dockerfile 8 | s local invoke -e '{"hello":"fc custom-container"}' 9 | s deploy -y 10 | s invoke -e '{"hello":"fc custom-container"}' 11 | s info 12 | s remove -y 13 | -------------------------------------------------------------------------------- /__tests__/e2e/custom-container/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: framework 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hangzhou')} 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'custom_container')} 14 | runtime: custom-container 15 | handler: index.handler 16 | timeout: 30 17 | memorySize: 1024 18 | code: ./code 19 | instanceConcurrency: 4 20 | customContainerConfig: 21 | image: 'registry.${vars.region}.aliyuncs.com/fc-demo2/test-xiliu:ciExpressV1' 22 | entrypoint: 23 | - node 24 | command: 25 | - /home/code/server.js 26 | port: 9001 27 | -------------------------------------------------------------------------------- /__tests__/e2e/custom-domain/code/index.py: -------------------------------------------------------------------------------- 1 | def handler(event, context): 2 | return "hello wolrd!\n" 3 | -------------------------------------------------------------------------------- /__tests__/e2e/custom-domain/s.yaml: -------------------------------------------------------------------------------- 1 | # ------------------------------------ 2 | # 官方手册: https://manual.serverless-devs.com/user-guide/aliyun/#fc3 3 | # 常见小贴士: https://manual.serverless-devs.com/user-guide/tips/ 4 | # 有问题快来钉钉群问一下吧:33947367 5 | # ------------------------------------ 6 | edition: 3.0.0 7 | name: hello-world-app 8 | access: "quanxi" 9 | 10 | vars: 11 | region: 'cn-hongkong' 12 | 13 | resources: 14 | hello_world_1: 15 | #component: fc3@dev 16 | component: ${env('fc_component_version', path('../../../'))} 17 | props: 18 | region: ${vars.region} 19 | functionName: "test-cd" 20 | description: 'hello world by serverless devs' 21 | runtime: "python3.10" 22 | code: ./code 23 | handler: index.handler 24 | memorySize: 128 25 | timeout: 45 26 | triggers: 27 | - triggerName: httpTrigger 28 | triggerType: http 29 | triggerConfig: 30 | methods: 31 | - GET 32 | - POST 33 | authType: anonymous 34 | disableURLInternet: false 35 | customDomain: 36 | #domainName: auto 37 | domainName: "xiliu-test.devsapp.net" 38 | protocol: HTTP 39 | route: 40 | # methods: 41 | # - GET 42 | path: /* 43 | qualifier: LATEST 44 | 45 | hello_world_2: 46 | #component: fc3@dev 47 | component: ${env('fc_component_version', path('../../../'))} 48 | props: 49 | region: ${vars.region} 50 | functionName: "test-cd2" 51 | description: 'hello world by serverless devs' 52 | runtime: "python3.10" 53 | code: ./code 54 | handler: index.handler 55 | memorySize: 128 56 | timeout: 45 57 | triggers: 58 | - triggerName: httpTrigger 59 | triggerType: http 60 | triggerConfig: 61 | methods: 62 | - GET 63 | - POST 64 | authType: anonymous 65 | disableURLInternet: false 66 | customDomain: 67 | domainName: "xiliu-test.devsapp.net" 68 | protocol: HTTP 69 | route: 70 | methods: 71 | - GET 72 | path: /a 73 | qualifier: LATEST 74 | -------------------------------------------------------------------------------- /__tests__/e2e/custom-domain/s2.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: hello-world-app 3 | access: "quanxi" 4 | 5 | vars: 6 | region: 'cn-hongkong' 7 | 8 | resources: 9 | hello_world_1: 10 | #component: fc3@dev 11 | component: ${env('fc_component_version', path('../../../'))} 12 | props: 13 | region: ${vars.region} 14 | functionName: "test-cd3" 15 | description: 'hello world by serverless devs' 16 | runtime: "python3.10" 17 | code: ./code 18 | handler: index.handler 19 | memorySize: 128 20 | timeout: 45 21 | triggers: 22 | - triggerName: httpTrigger 23 | triggerType: http 24 | triggerConfig: 25 | methods: 26 | - GET 27 | - POST 28 | authType: anonymous 29 | disableURLInternet: false 30 | customDomain: 31 | domainName: auto 32 | protocol: HTTP 33 | route: 34 | # methods: 35 | # - GET 36 | path: /* 37 | qualifier: LATEST -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/go/code/.gitignore: -------------------------------------------------------------------------------- 1 | python 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/go/code/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/m 2 | 3 | go 1.17 4 | 5 | require github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3 6 | 7 | require ( 8 | github.com/sirupsen/logrus v1.9.0 // indirect 9 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 // indirect 10 | golang.org/x/sys v0.4.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/go/code/go.sum: -------------------------------------------------------------------------------- 1 | github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3 h1:d0/nCeQMEqCokk7qCx4kssYtdck2R5Pe6xQzptuKfO8= 2 | github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3/go.mod h1:W4bhz/v/p6E48AW5CH9j3kI1Xmp/fH/g6NNJXRvCtL4= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 10 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 11 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 12 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 16 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 17 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 18 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk= 19 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA= 20 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 24 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 27 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 28 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/go/code/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | gr "github.com/awesome-fc/golang-runtime" 7 | ) 8 | 9 | func initialize(ctx *gr.FCContext) error { 10 | ctx.GetLogger().Infoln("init golang!") 11 | return nil 12 | } 13 | 14 | func handler(ctx *gr.FCContext, event []byte) ([]byte, error) { 15 | fcLogger := ctx.GetLogger() 16 | _, err := json.Marshal(ctx) 17 | if err != nil { 18 | fcLogger.Error("error:", err) 19 | } 20 | fcLogger.Infof("hello golang!") 21 | fcLogger.Infof("hello golang2!") 22 | return event, nil 23 | } 24 | 25 | func main() { 26 | gr.Start(handler, initialize) 27 | } 28 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/go/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-custom-debian10-go 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../../'))} 11 | actions: 12 | pre-deploy: 13 | - run: go mod tidy 14 | path: ./code 15 | - run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o target/main main.go 16 | path: ./code 17 | props: 18 | region: ${vars.region} 19 | functionName: fc3-event-debian10-${env('fc_component_function_name', 'go1')} 20 | description: 'hello world by serverless devs' 21 | timeout: 30 22 | memorySize: 512 23 | cpu: 0.5 24 | diskSize: 512 25 | runtime: custom.debian10 26 | code: ./code/target 27 | customRuntimeConfig: 28 | command: 29 | - '/code/main' 30 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/python/code/.gitignore: -------------------------------------------------------------------------------- 1 | python 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/python/code/gunicorn_conf.py: -------------------------------------------------------------------------------- 1 | bind = "0.0.0.0:9000" 2 | workers = 1 3 | threads = 2 4 | keepalive = 900 5 | accesslog = "-" 6 | errorlog = "-" 7 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/python/code/requirements.txt: -------------------------------------------------------------------------------- 1 | gunicorn 2 | flask -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/python/code/server.py: -------------------------------------------------------------------------------- 1 | from flask.logging import default_handler 2 | import time 3 | from flask import Flask 4 | from flask import request 5 | import json 6 | import sys 7 | import traceback 8 | import logging 9 | 10 | 11 | log = logging.getLogger("werkzeug") 12 | log.setLevel(logging.ERROR) 13 | 14 | 15 | app = Flask(__name__) 16 | 17 | 18 | REQUEST_ID_HEADER = "x-fc-request-id" 19 | 20 | 21 | @app.route("/invoke", methods=["POST"]) 22 | def event_invoke(): 23 | rid = request.headers.get(REQUEST_ID_HEADER) 24 | print("FC Invoke Start RequestId: " + rid) 25 | 26 | data = request.stream.read() 27 | print(data) 28 | 29 | try: 30 | # do your things, for example: 31 | evt = json.loads(data) 32 | print(evt) 33 | except Exception as e: 34 | exc_info = sys.exc_info() 35 | trace = traceback.format_tb(exc_info[2]) 36 | errRet = {"message": str(e), "stack": trace} 37 | print(errRet) 38 | print("FC Invoke End RequestId: " + rid) 39 | return errRet, 404, [("x-fc-status", "404")] 40 | 41 | print("FC Invoke End RequestId: " + rid) 42 | 43 | return data 44 | 45 | 46 | @app.route("/", methods=["GET"]) 47 | def home(): 48 | return "hello world\n" 49 | 50 | 51 | if __name__ == "__main__": 52 | app.run(host="0.0.0.0", port=9000) 53 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/python/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-custom-debian10-python 3 | access: "quanxi" 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | helloworld: 10 | component: ${env('fc_component_version', path('../../../../'))} 11 | props: 12 | region: ${vars.region} 13 | functionName: fc3-event-debian10-${env('fc_component_function_name', 'python310')} 14 | handler: index.handler 15 | timeout: 60 16 | memorySize: 1536 17 | cpu: 1 18 | diskSize: 512 19 | runtime: custom.debian10 20 | code: ./code 21 | instanceConcurrency: 10 22 | environmentVariables: 23 | PYTHONPATH: /code/python 24 | PATH: /code/python/bin:/usr/local/bin/apache-maven/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/ruby/bin 25 | customRuntimeConfig: 26 | command: 27 | - gunicorn 28 | args: 29 | - '-c' 30 | - 'gunicorn_conf.py' 31 | - 'server:app' 32 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian10/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "test custom.debian10 go runtime ..." 6 | rm -rf ./go/code/go.sum 7 | export fc_component_function_name=go1-$(uname)-$(uname -m) 8 | s deploy -y -t ./go/s.yaml 9 | s invoke -e '{"hello":"fc custom.debian10 go"}' -t ./go/s.yaml 10 | s local invoke -e '{"hello":"fc custom.debian10 go"}' -t ./go/s.yaml 11 | s info -t ./go/s.yaml 12 | s remove -y -t ./go/s.yaml 13 | rm -rf ./go/code/target 14 | 15 | echo "test custom.debian10 python runtime ..." 16 | rm -rf ./python/code/python 17 | rm -rf ./python/code/__pycache__ 18 | export fc_component_function_name=python310-$(uname)-$(uname -m) 19 | s build -t ./python/s.yaml 20 | s local invoke -e '{"hello":"fc custom.debian10 python"}' -t ./python/s.yaml 21 | s deploy -y -t ./python/s.yaml 22 | s invoke -e '{"hello":"fc custom.debian10 python"}' -t ./python/s.yaml 23 | s info -t ./python/s.yaml 24 | s remove -y -t ./python/s.yaml 25 | 26 | rm -rf ./go/code/target 27 | rm -rf ./python/code/python 28 | rm -rf ./python/code/__pycache__ 29 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian11/go/code/.gitignore: -------------------------------------------------------------------------------- 1 | python 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian11/go/code/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/m 2 | 3 | go 1.17 4 | 5 | require github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3 6 | 7 | require ( 8 | github.com/sirupsen/logrus v1.9.0 // indirect 9 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 // indirect 10 | golang.org/x/sys v0.4.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian11/go/code/go.sum: -------------------------------------------------------------------------------- 1 | github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3 h1:d0/nCeQMEqCokk7qCx4kssYtdck2R5Pe6xQzptuKfO8= 2 | github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3/go.mod h1:W4bhz/v/p6E48AW5CH9j3kI1Xmp/fH/g6NNJXRvCtL4= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 10 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 11 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 12 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 16 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 17 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 18 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk= 19 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA= 20 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 24 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 27 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 28 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian11/go/code/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | gr "github.com/awesome-fc/golang-runtime" 7 | ) 8 | 9 | func initialize(ctx *gr.FCContext) error { 10 | ctx.GetLogger().Infoln("init golang!") 11 | return nil 12 | } 13 | 14 | func handler(ctx *gr.FCContext, event []byte) ([]byte, error) { 15 | fcLogger := ctx.GetLogger() 16 | _, err := json.Marshal(ctx) 17 | if err != nil { 18 | fcLogger.Error("error:", err) 19 | } 20 | fcLogger.Infof("hello golang!") 21 | fcLogger.Infof("hello golang2!") 22 | return event, nil 23 | } 24 | 25 | func main() { 26 | gr.Start(handler, initialize) 27 | } 28 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian11/go/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-custom-debian11-go 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../../'))} 11 | actions: 12 | pre-deploy: 13 | - run: go mod tidy 14 | path: ./code 15 | - run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o target/main main.go 16 | path: ./code 17 | props: 18 | region: ${vars.region} 19 | functionName: fc3-event-debian11-${env('fc_component_function_name', 'go1')} 20 | description: 'hello world by serverless devs' 21 | timeout: 30 22 | memorySize: 512 23 | cpu: 0.5 24 | diskSize: 512 25 | runtime: custom.debian11 26 | code: ./code/target 27 | customRuntimeConfig: 28 | command: 29 | - '/code/main' 30 | -------------------------------------------------------------------------------- /__tests__/e2e/custom.debian11/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "test custom.debian11 go runtime ..." 6 | rm -rf ./go/code/go.sum 7 | export fc_component_function_name=go1-$(uname)-$(uname -m) 8 | s deploy -y -t ./go/s.yaml 9 | s invoke -e '{"hello":"fc custom.debian11 go"}' -t ./go/s.yaml 10 | s local invoke -e '{"hello":"fc custom.debian11 go"}' -t ./go/s.yaml 11 | s info -t ./go/s.yaml 12 | s remove -y -t ./go/s.yaml 13 | rm -rf ./go/code/target 14 | 15 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/go/code/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | go.sum -------------------------------------------------------------------------------- /__tests__/e2e/custom/go/code/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/m 2 | 3 | go 1.17 4 | 5 | require github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3 6 | 7 | require ( 8 | github.com/sirupsen/logrus v1.9.0 // indirect 9 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 // indirect 10 | golang.org/x/sys v0.4.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/go/code/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | gr "github.com/awesome-fc/golang-runtime" 7 | ) 8 | 9 | func initialize(ctx *gr.FCContext) error { 10 | ctx.GetLogger().Infoln("init golang!") 11 | return nil 12 | } 13 | 14 | func handler(ctx *gr.FCContext, event []byte) ([]byte, error) { 15 | fcLogger := ctx.GetLogger() 16 | _, err := json.Marshal(ctx) 17 | if err != nil { 18 | fcLogger.Error("error:", err) 19 | } 20 | fcLogger.Infof("hello golang!") 21 | fcLogger.Infof("hello golang2!") 22 | return event, nil 23 | } 24 | 25 | func main() { 26 | gr.Start(handler, initialize) 27 | } 28 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/go/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-custom-go 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../../'))} 11 | actions: 12 | pre-deploy: 13 | - run: go mod tidy 14 | path: ./code 15 | - run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o target/main main.go 16 | path: ./code 17 | props: 18 | region: ${vars.region} 19 | functionName: fc3-event-custom-${env('fc_component_function_name', 'go1')} 20 | description: 'hello world by serverless devs' 21 | timeout: 30 22 | memorySize: 512 23 | cpu: 0.5 24 | diskSize: 512 25 | runtime: custom 26 | code: ./code/target 27 | customRuntimeConfig: 28 | command: 29 | - '/code/main' 30 | port: 9000 31 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/python/code/.gitignore: -------------------------------------------------------------------------------- 1 | python 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/python/code/gunicorn_conf.py: -------------------------------------------------------------------------------- 1 | bind = "0.0.0.0:8099" 2 | workers = 1 3 | threads = 2 4 | keepalive = 900 5 | accesslog = "-" 6 | errorlog = "-" 7 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/python/code/requirements.txt: -------------------------------------------------------------------------------- 1 | gunicorn 2 | flask -------------------------------------------------------------------------------- /__tests__/e2e/custom/python/code/server.py: -------------------------------------------------------------------------------- 1 | from flask.logging import default_handler 2 | import time 3 | from flask import Flask 4 | from flask import request 5 | import json 6 | import sys 7 | import traceback 8 | import logging 9 | 10 | 11 | log = logging.getLogger("werkzeug") 12 | log.setLevel(logging.ERROR) 13 | 14 | 15 | app = Flask(__name__) 16 | 17 | 18 | REQUEST_ID_HEADER = "x-fc-request-id" 19 | 20 | 21 | @app.route("/invoke", methods=["POST"]) 22 | def event_invoke(): 23 | rid = request.headers.get(REQUEST_ID_HEADER) 24 | print("FC Invoke Start RequestId: " + rid) 25 | 26 | data = request.stream.read() 27 | print(data) 28 | 29 | try: 30 | # do your things, for example: 31 | evt = json.loads(data) 32 | print(evt) 33 | except Exception as e: 34 | exc_info = sys.exc_info() 35 | trace = traceback.format_tb(exc_info[2]) 36 | errRet = {"message": str(e), "stack": trace} 37 | print(errRet) 38 | print("FC Invoke End RequestId: " + rid) 39 | return errRet, 404, [("x-fc-status", "404")] 40 | 41 | print("FC Invoke End RequestId: " + rid) 42 | 43 | return data 44 | 45 | 46 | @app.route("/", methods=["GET"]) 47 | def home(): 48 | return "hello world" 49 | 50 | 51 | if __name__ == "__main__": 52 | app.run(host="0.0.0.0", port=8099) 53 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/python/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-custom-python 3 | access: "quanxi" 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | helloworld: 10 | component: ${env('fc_component_version', path('../../../../'))} 11 | props: 12 | region: ${vars.region} 13 | functionName: fc3-event-custom-${env('fc_component_function_name', 'python310')} 14 | handler: index.handler 15 | timeout: 60 16 | memorySize: 1536 17 | cpu: 1 18 | diskSize: 512 19 | runtime: custom 20 | code: ./code 21 | instanceConcurrency: 10 22 | environmentVariables: 23 | PYTHONPATH: /code/python 24 | PATH: /code/python/bin:/usr/local/bin/apache-maven/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/ruby/bin 25 | customRuntimeConfig: 26 | command: 27 | - gunicorn 28 | args: 29 | - '-c' 30 | - 'gunicorn_conf.py' 31 | - 'server:app' 32 | port: 8099 33 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "test custom go runtime ..." 6 | rm -rf ./go/code/go.sum 7 | export fc_component_function_name=go1-$(uname)-$(uname -m) 8 | s deploy -y -t ./go/s.yaml 9 | s invoke -e '{"hello":"fc custom go"}' -t ./go/s.yaml 10 | s local invoke -e '{"hello":"fc custom go"}' -t ./go/s.yaml 11 | s info -y -t ./go/s.yaml 12 | s remove -y -t ./go/s.yaml 13 | rm -rf ./go/code/target 14 | 15 | echo "test custom python runtime ..." 16 | rm -rf ./python/code/python 17 | rm -rf ./python/code/__pycache__ 18 | export fc_component_function_name=python310-$(uname)-$(uname -m) 19 | s build -t ./python/s.yaml 20 | s local invoke -e '{"hello":"fc custom python"}' -t ./python/s.yaml 21 | s deploy -y -t ./python/s.yaml 22 | s invoke -e '{"hello":"fc custom python"}' -t ./python/s.yaml 23 | s info -y -t ./python/s.yaml 24 | s remove -y -t ./python/s.yaml 25 | 26 | echo "test custom java(springboot) runtime ..." 27 | rm -rf ./springboot/code/target 28 | export fc_component_function_name=springboot-$(uname)-$(uname -m) 29 | s deploy -y -t ./springboot/s.yaml 30 | s invoke --event-file ./springboot/event/http.json -t ./springboot/s.yaml 31 | s info -t ./springboot/s.yaml 32 | s jar_zip local invoke --event-file ./springboot/event/http.json -t ./springboot/s.yaml 33 | s jar local invoke --event-file ./springboot/event/http.json -t ./springboot/s.yaml 34 | s remove -y -t ./springboot/s.yaml 35 | 36 | rm -rf ./go/code/target 37 | rm -rf ./python/code/python 38 | rm -rf ./python/code/__pycache__ 39 | rm -rf ./springboot/code/target 40 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/run-windows.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | Write-Host "test custom go runtime ..." 4 | $env:fc_component_function_name = "go1-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 5 | $env:GOOS = "linux" 6 | $env:GOARCH = "amd64" 7 | cd ./go/code; go mod tidy; go build -o ./target/main; cd ../../ 8 | s deploy -y -t ./go/s.yaml --skip-actions 9 | s invoke -e '{"hello":"fc custom go"}' -t ./go/s.yaml 10 | s local invoke -e '{"hello":"fc custom go"}' -t ./go/s.yaml 11 | s info -y -t ./go/s.yaml 12 | s remove -y -t ./go/s.yaml 13 | Remove-Item -Recurse -Force ./go/code/target -ErrorAction SilentlyContinue 14 | 15 | 16 | Write-Host "test custom python runtime ..." 17 | $env:fc_component_function_name = "python310-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 18 | s build -t ./python/s.yaml 19 | s local invoke -e '{"hello":"fc custom python"}' -t ./python/s.yaml 20 | s deploy -y -t ./python/s.yaml 21 | s invoke -e '{"hello":"fc custom python"}' -t ./python/s.yaml 22 | s info -y -t ./python/s.yaml 23 | s remove -y -t ./python/s.yaml 24 | Remove-Item -Recurse -Force ./python/code/python -ErrorAction SilentlyContinue 25 | Remove-Item -Recurse -Force ./python/code/__pycache__ -ErrorAction SilentlyContinue 26 | 27 | Write-Host "test custom java(springboot) runtime ..." 28 | $env:fc_component_function_name = "springboot-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 29 | s deploy -y -t ./springboot/s.yaml 30 | s invoke --event-file ./springboot/event/http.json -t ./springboot/s.yaml 31 | s info -t ./springboot/s.yaml 32 | s jar_zip local invoke --event-file ./springboot/event/http.json -t ./springboot/s.yaml 33 | s jar local invoke --event-file ./springboot/event/http.json -t ./springboot/s.yaml 34 | s remove -y -t ./springboot/s.yaml 35 | Remove-Item -Recurse -Force ./springboot/code/target -ErrorAction SilentlyContinue 36 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/code/.fcignore: -------------------------------------------------------------------------------- 1 | .s 2 | Dockerfile 3 | mvnw 4 | pom.xml 5 | src 6 | mvnw.cmd 7 | .fcignore 8 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/code/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/code/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | ARG JAR_FILE=target/*.jar 3 | COPY ${JAR_FILE} webframework.jar 4 | ENTRYPOINT ["java","-jar","/webframework.jar"] -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/code/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.8.RELEASE 9 | 10 | 11 | com.example 12 | webframework 13 | 0.0.1-SNAPSHOT 14 | webframework 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-test 30 | test 31 | 32 | 33 | 34 | 35 | ${project.name} 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-maven-plugin 40 | 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/code/src/main/java/com/example/webframework/WebFrameworkApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.webframework; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestHeader; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | @SpringBootApplication 15 | @RestController 16 | public class WebFrameworkApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(WebFrameworkApplication.class, args); 20 | } 21 | 22 | @GetMapping("/") 23 | public ResponseEntity welcome() { 24 | String welcome = "" + 26 | "" + 27 | "Serverless Devs - Powered By Serverless Devs" + 28 | "" 29 | + 30 | "" + 31 | "" + 32 | "
" + 33 | "
" + 34 | "

Devsapp

" + 35 | "

这是一个 Spring Boot 项目

" + 36 | "自豪的通过Serverless Devs进行部署" + 37 | "

您也可以快速体验:
" + 38 | "• 下载Serverless Devs工具:npm install @serverless-devs/s
" + 39 | "• 初始化项目:s init start-springboot
" + 40 | "• 项目部署:s deploy

" + 41 | "Serverless Devs 钉钉交流群:33947367

" + 42 | "
"; 43 | return new ResponseEntity<>(welcome, HttpStatus.OK); 44 | } 45 | 46 | @GetMapping("/fcheaders") 47 | public ResponseEntity> listHeaders( 48 | @RequestHeader Map headers) { 49 | Map fcHeaders = new HashMap<>(); 50 | headers.forEach((key, value) -> { 51 | if (key.startsWith("x-fc")) { 52 | fcHeaders.put(key, value); 53 | } 54 | 55 | }); 56 | 57 | return new ResponseEntity<>(fcHeaders, HttpStatus.OK); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/code/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9000 2 | # server.servlet.context-path=/2016-08-15/proxy/springboot/welcome/ -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/code/src/test/java/com/example/webframework/WebFrameworkApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.webframework; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | @RunWith(SpringRunner.class) 12 | @SpringBootTest 13 | public class WebFrameworkApplicationTests { 14 | 15 | @Test 16 | public void testWelcome() { 17 | assertEquals(HttpStatus.OK, new WebFrameworkApplication().welcome().getStatusCode()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/event/http.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "/", 3 | "method": "GET", 4 | "headers": { 5 | "key": "value" 6 | }, 7 | "queries": { 8 | "key": "value" 9 | } 10 | } -------------------------------------------------------------------------------- /__tests__/e2e/custom/springboot/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-custom-springboot 3 | access: 'quanxi' 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | template: 9 | props: 10 | region: ${vars.region} 11 | triggers: 12 | - triggerName: httpTrigger 13 | triggerType: http 14 | triggerConfig: 15 | authType: anonymous 16 | methods: 17 | - GET 18 | - POST 19 | - PUT 20 | - DELETE 21 | - HEAD 22 | - OPTIONS 23 | 24 | resources: 25 | jar: 26 | component: ${env('fc_component_version', path('../../../../'))} 27 | extend: 28 | name: props 29 | actions: 30 | pre-deploy: 31 | - run: mvn package -DskipTests 32 | path: ./code 33 | props: 34 | functionName: fc3-custom-${env('fc_component_function_name', 'springboot')}-jar 35 | description: 'Serverless Devs Web Framework Function' 36 | code: ./code/target/webframework.jar 37 | runtime: custom 38 | memorySize: 1024 39 | cpu: 1 40 | diskSize: 512 41 | timeout: 30 42 | customRuntimeConfig: 43 | command: 44 | - java 45 | args: 46 | - 'org.springframework.boot.loader.JarLauncher' 47 | 48 | jar_zip: 49 | component: ${env('fc_component_version', path('../../../../'))} 50 | extend: 51 | name: props 52 | actions: 53 | pre-deploy: 54 | - run: mvn package -DskipTests 55 | path: ./code 56 | props: 57 | functionName: fc3-custom-${env('fc_component_function_name', 'springboot')} 58 | description: 'Serverless Devs Web Framework Function' 59 | code: ./code/target/webframework.jar 60 | runtime: custom 61 | memorySize: 1024 62 | cpu: 1 63 | diskSize: 512 64 | timeout: 30 65 | instanceConcurrency: 10 66 | customRuntimeConfig: 67 | command: 68 | - java 69 | - -jar 70 | args: 71 | - webframework.jar 72 | port: 9000 -------------------------------------------------------------------------------- /__tests__/e2e/dotnetcore/HelloFcApp/HelloFcApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /__tests__/e2e/dotnetcore/HelloFcApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Aliyun.Serverless.Core; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Example 7 | { 8 | public class Hello 9 | { 10 | public async Task StreamHandler(Stream input, IFcContext context) 11 | { 12 | ILogger logger = context.Logger; 13 | logger.LogInformation("Handle request: {0}", context.RequestId); 14 | MemoryStream copy = new MemoryStream(); 15 | await input.CopyToAsync(copy); 16 | copy.Seek(0, SeekOrigin.Begin); 17 | return copy; 18 | } 19 | 20 | static void Main(string[] args){} 21 | } 22 | } -------------------------------------------------------------------------------- /__tests__/e2e/dotnetcore/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "test dotnetcore3.1 runtime ..." 6 | rm -rf ./HelloFcApp/target 7 | export fc_component_function_name=dotnetcore31-$(uname)-$(uname -m) 8 | # s build 9 | # s local invoke -e '{"hello":"fc dotnetcore3.1"}' 10 | s deploy -y 11 | s info 12 | s invoke -e '{"hello":"fc dotnetcore3.1"}' 13 | s remove -y 14 | -------------------------------------------------------------------------------- /__tests__/e2e/dotnetcore/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-dotnet-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | actions: 12 | pre-deploy: 13 | - run: dotnet publish -c Release -o ./target 14 | path: ./HelloFcApp 15 | props: # 组件的属性值 16 | region: ${vars.region} 17 | functionName: fc3-event-${env('fc_component_function_name', 'dotnetcore31')} 18 | runtime: ${env('fc_component_runtime', 'dotnetcore3.1')} 19 | code: ./HelloFcApp/target/ 20 | handler: HelloFcApp::Example.Hello::StreamHandler 21 | memorySize: 512 22 | timeout: 60 23 | initializationTimeout: 60 24 | initializer: example.App::initialize -------------------------------------------------------------------------------- /__tests__/e2e/go/code/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require github.com/aliyun/fc-runtime-go-sdk v0.2.7 6 | -------------------------------------------------------------------------------- /__tests__/e2e/go/code/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aliyun/fc-runtime-go-sdk/fc" 7 | ) 8 | 9 | func main() { 10 | fc.Start(HandleRequest) 11 | } 12 | 13 | // HandleRequest ... 14 | func HandleRequest(event []byte) (string, error) { 15 | fmt.Printf("event: %s\n", string(event)) 16 | fmt.Println("hello world! 你好,世界!") 17 | return "hello world! " + string(event), nil 18 | } 19 | -------------------------------------------------------------------------------- /__tests__/e2e/go/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "test go runtime ..." 6 | rm -rf ./code/go.sum 7 | rm -rf ./code/target 8 | export fc_component_function_name=go1-$(uname)-$(uname -m) 9 | s deploy -y 10 | s invoke -e '{"hello":"fc go1"}' 11 | s local invoke -e '{"hello":"fc go1"}' 12 | s info 13 | s remove -y 14 | rm -rf ./code/target 15 | -------------------------------------------------------------------------------- /__tests__/e2e/go/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-go-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../'))} 11 | actions: 12 | pre-deploy: 13 | - run: GO111MODULE=on go mod tidy 14 | path: ./code 15 | - run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o target/main main.go 16 | path: ./code 17 | props: 18 | region: ${vars.region} 19 | functionName: fc3-event-${env('fc_component_function_name', 'go1')} 20 | description: 'hello world by serverless devs' 21 | timeout: 30 22 | runtime: go1 23 | code: ./code/target 24 | handler: main 25 | memorySize: 128 -------------------------------------------------------------------------------- /__tests__/e2e/java/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /__tests__/e2e/java/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | example 5 | ServerlessToolProject 6 | jar 7 | 1.0-SNAPSHOT 8 | ServerlessToolProject 9 | 10 | 11 | 12 | junit 13 | junit 14 | 3.8.1 15 | test 16 | 17 | 18 | com.aliyun.fc.runtime 19 | fc-java-core 20 | 1.3.0 21 | 22 | 23 | 24 | 25 | 26 | 27 | maven-assembly-plugin 28 | 29 | 30 | jar-with-dependencies 31 | 32 | 33 | 34 | 35 | make-my-jar-with-dependencies 36 | package 37 | 38 | single 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 1.8 48 | 1.8 49 | true 50 | 51 | 52 | -------------------------------------------------------------------------------- /__tests__/e2e/java/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # java8 6 | export fc_component_runtime=java8 7 | echo "test java8 runtime ..." 8 | rm -rf ./target 9 | export fc_component_function_name=java8-$(uname)-$(uname -m) 10 | s deploy -y 11 | s local invoke -e '{"hello":"fc java8"}' 12 | s invoke -e '{"hello":"fc java8"}' 13 | s info 14 | s remove -y 15 | 16 | # java11 17 | export fc_component_runtime=java11 18 | echo "test java11 runtime ..." 19 | rm -rf ./target 20 | export fc_component_function_name=java11-$(uname)-$(uname -m) 21 | s deploy -y 22 | s local invoke -e '{"hello":"fc java11"}' 23 | s invoke -e '{"hello":"fc java11"}' 24 | s remove -y 25 | 26 | rm -rf ./target 27 | -------------------------------------------------------------------------------- /__tests__/e2e/java/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-java-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | actions: 12 | pre-deploy: 13 | - run: mvn package -DskipTests 14 | path: ./ 15 | props: # 组件的属性值 16 | region: ${vars.region} 17 | functionName: fc3-event-${env('fc_component_function_name', 'java11')} 18 | runtime: ${env('fc_component_runtime', 'java11')} 19 | code: ./target/ServerlessToolProject-1.0-SNAPSHOT-jar-with-dependencies.jar 20 | handler: example.App::handleRequest 21 | memorySize: 512 22 | timeout: 30 23 | initializationTimeout: 60 24 | initializer: example.App::initialize 25 | -------------------------------------------------------------------------------- /__tests__/e2e/java/src/main/java/example/App.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | 8 | import com.aliyun.fc.runtime.Context; 9 | import com.aliyun.fc.runtime.StreamRequestHandler; 10 | import com.aliyun.fc.runtime.FunctionInitializer; 11 | 12 | /** 13 | * Hello world! 14 | * 15 | */ 16 | public class App implements StreamRequestHandler, FunctionInitializer { 17 | 18 | public void initialize(Context context) throws IOException { 19 | // TODO 20 | } 21 | 22 | @Override 23 | public void handleRequest( 24 | InputStream inputStream, OutputStream outputStream, Context context) throws IOException { 25 | outputStream.write(new String("hello world " + this.convert(inputStream)).getBytes()); 26 | } 27 | 28 | public String convert(InputStream inputStream) throws IOException { 29 | ByteArrayOutputStream result = new ByteArrayOutputStream(); 30 | byte[] buffer = new byte[1024]; 31 | int length; 32 | while ((length = inputStream.read(buffer)) != -1) { 33 | result.write(buffer, 0, length); 34 | } 35 | return result.toString("UTF-8"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /__tests__/e2e/local/layer/nodejs/code/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const koa = require('koa'); 3 | 4 | exports.handler = function (event, context, callback) { 5 | let a = 1; 6 | console.log(event.toString()); 7 | console.log('My log'); 8 | console.log('My log 2'); 9 | console.log(process.env.FC_FUNCTION_MEMORY_SIZE); 10 | callback(null, JSON.parse(event.toString())); 11 | // callback(null, 'hello world'); 12 | }; 13 | 14 | /* 15 | exports.handler = async function (event, context, callback) { 16 | callback(null, 'hello world'); 17 | }; 18 | */ 19 | -------------------------------------------------------------------------------- /__tests__/e2e/local/layer/nodejs/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: hello-world-app 3 | 4 | access: "quanxi" 5 | 6 | vars: 7 | region: ${env('REGION', 'cn-hangzhou')} 8 | 9 | resources: 10 | hello_world: 11 | component: ${env('fc_component_version', path('../../../../../'))} 12 | props: 13 | region: ${vars.region} 14 | functionName: ${env('fc_component_function_name', 'nodejs16-local-layer')} 15 | description: 'hello world by serverless devs' 16 | runtime: "nodejs16" 17 | code: ./code 18 | handler: index.handler 19 | memorySize: 128 20 | timeout: 30 21 | layers: 22 | - acs:fc:${vars.region}:official:layers/Nodejs-Puppeteer17x/versions/3 23 | - acs:fc:${vars.region}:1431999136518149:layers/devs_ci_test_koa/versions/3 24 | -------------------------------------------------------------------------------- /__tests__/e2e/local/layer/python/code/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | import os 5 | from flask import Flask 6 | import pycurl 7 | 8 | def handler(event, context): 9 | logger = logging.getLogger() 10 | logger.info(event) 11 | return event 12 | -------------------------------------------------------------------------------- /__tests__/e2e/local/layer/python/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: hello-world-app 3 | access: "quanxi" 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hangzhou')} 7 | 8 | resources: 9 | hello_world: 10 | component: ${env('fc_component_version', path('../../../../../'))} 11 | # actions: 12 | props: 13 | region: ${vars.region} 14 | functionName: ${env('fc_component_function_name', 'python39-local-layer')} 15 | description: 'hello world by serverless devs' 16 | runtime: "python3.9" 17 | code: ./code 18 | handler: index.handler 19 | memorySize: 128 20 | timeout: 30 21 | layers: 22 | - acs:fc:${vars.region}:official:layers/Python3-Flask2x/versions/2 23 | - acs:fc:${vars.region}:1431999136518149:layers/devs_ci_test_pycurl/versions/1 -------------------------------------------------------------------------------- /__tests__/e2e/local/nas/code/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | import os 5 | 6 | def handler(event, context): 7 | filename = '/mnt/auto/test.txt' 8 | if os.path.exists(filename): 9 | with open(filename, 'r') as file: 10 | content = file.read() 11 | return "read:{}".format(content) 12 | else: 13 | with open(filename, 'w') as file: 14 | file.write("hello world") 15 | with open(filename, 'r') as file: 16 | content = file.read() 17 | return "write:{}".format(content) 18 | -------------------------------------------------------------------------------- /__tests__/e2e/local/nas/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: hello-world-app 3 | access: "quanxi" 4 | 5 | resources: 6 | hello_world: 7 | component: ${env('fc_component_version', path('../../../../'))} 8 | props: 9 | region: ${env('REGION', 'cn-hongkong')} 10 | functionName: ${env('fc_component_function_name', 'python3-local-nas')} 11 | runtime: "python3.9" 12 | code: ./code 13 | handler: index.handler 14 | memorySize: 128 15 | timeout: 30 16 | nasConfig: 17 | userId: 10003 18 | groupId: 10003 19 | mountPoints: 20 | - serverAddr: xxx.cn-hangzhou.nas.aliyuncs.com:/hello-world 21 | mountDir: /mnt/auto 22 | # - serverAddr: xxx.cn-hangzhou.nas.aliyuncs.com:/hello-world-2 23 | # mountDir: /mnt/auto-2 24 | triggers: 25 | - triggerName: httpTrigger # 触发器名称 26 | triggerType: http # 触发器类型 27 | description: 'xxxx' 28 | qualifier: LATEST # 触发服务的版本 29 | triggerConfig: 30 | authType: anonymous # 鉴权类型,可选值:anonymous、function 31 | disableURLInternet: false # 是否禁用公网访问 URL 32 | methods: # HTTP 触发器支持的访问方法,可选值:GET、POST、PUT、DELETE、HEAD 33 | - GET 34 | - POST -------------------------------------------------------------------------------- /__tests__/e2e/local/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -v 5 | 6 | cd ./nas 7 | export fc_component_function_name=test-local-nas-simulate-$(uname)-$(uname -m)-$RANDSTR 8 | echo "test local nas simulate ..." 9 | s local invoke -e '{"hello": "local nas"}' 10 | s local invoke -e '{"hello": "local nas"}' 11 | 12 | cd ../layer/nodejs 13 | export fc_component_function_name=test-local-layer-simulate-nodejs-$(uname)-$(uname -m)-$RANDSTR 14 | echo "test local layer simulate nodejs ..." 15 | s local invoke -e '{"hello": "local layer"}' 16 | s local invoke -e '{"hello": "local layer"}' 17 | 18 | cd ../python 19 | export fc_component_function_name=test-local-layer-simulate-python-$(uname)-$(uname -m)-$RANDSTR 20 | echo "test local layer simulate python ..." 21 | s local invoke -e '{"hello": "local layer"}' 22 | s local invoke -e '{"hello": "local layer"}' 23 | 24 | -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/code/event.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | exports.initializer = (context, callback) => { 4 | console.log('initializing'); 5 | callback(null, ''); 6 | }; 7 | 8 | module.exports.handler = function (event, context, callback) { 9 | // throw new Error('xxx') 10 | console.log(event.toString()); 11 | console.log('typeof securityToken: ', typeof context.credentials.securityToken); 12 | // console.log('securityToken: ', JSON.stringify(context.credentials)); 13 | console.log( 14 | new Intl.DateTimeFormat('en', { timeZoneName: 'long' }) 15 | .format() 16 | .includes('China Standard Time'), 17 | ); 18 | console.log(chalk.red('hello world')); 19 | console.log(chalk.yellow('hello world')); 20 | console.log(chalk.green('hello world')); 21 | callback(null, 'hello world'); 22 | }; 23 | -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "event.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "chalk": "^4.1.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -v 5 | 6 | # nodejs14 7 | export fc_component_runtime=nodejs14 8 | export fc_component_function_name=nodejs14-$(uname)-$(uname -m) 9 | rm -rf ./code/node_modules 10 | echo "test nodejs14 runtime ..." 11 | s build 12 | s local invoke -e '{"hello":"fc nodejs14"}' 13 | s deploy -y 14 | s info 15 | s invoke -e '{"hello":"fc nodejs14"}' 16 | export BUILD_IMAGE_ENV=fc-backend 17 | rm -rf ./code/node_modules 18 | s build 19 | s deploy -y 20 | s info 21 | s invoke -e '{"hello":"fc nodejs14 fc backed"}' 22 | s remove -y 23 | unset BUILD_IMAGE_ENV 24 | 25 | # nodejs12 26 | export fc_component_runtime=nodejs12 27 | export fc_component_function_name=nodejs12-$(uname)-$(uname -m) 28 | rm -rf ./code/node_modules 29 | echo "test nodejs12 runtime ..." 30 | s build 31 | s local invoke -e '{"hello":"fc nodejs12"}' 32 | s deploy -y 33 | s invoke -e '{"hello":"fc nodejs12"}' 34 | s remove -y 35 | 36 | # nodejs16 37 | export fc_component_runtime=nodejs16 38 | export fc_component_function_name=nodejs16-$(uname)-$(uname -m) 39 | echo "test nodejs16 runtime ..." 40 | rm -rf ./code/node_modules 41 | s build 42 | s local invoke -e '{"hello":"fc nodejs16"}' 43 | s deploy -y 44 | s invoke -e '{"hello":"fc nodejs16"}' 45 | s remove -y 46 | 47 | # nodejs10 48 | export fc_component_runtime=nodejs10 49 | export fc_component_function_name=nodejs10-$(uname)-$(uname -m) 50 | rm -rf ./code/node_modules 51 | echo "test nodejs10 runtime ..." 52 | s build 53 | s local invoke -e '{"hello":"fc nodejs10"}' 54 | s deploy -y 55 | s invoke -e '{"hello":"fc nodejs10"}' 56 | s remove -y 57 | 58 | # nodejs18 59 | export fc_component_runtime=nodejs18 60 | export fc_component_function_name=nodejs18-$(uname)-$(uname -m) 61 | echo "test nodejs18 runtime ..." 62 | rm -rf ./code/node_modules 63 | s build 64 | s local invoke -e '{"hello":"fc nodejs18"}' 65 | s deploy -y 66 | s invoke -e '{"hello":"fc nodejs18"}' 67 | s remove -y 68 | 69 | # nodejs20 70 | export fc_component_runtime=nodejs20 71 | export fc_component_function_name=nodejs20-$(uname)-$(uname -m) 72 | echo "test nodejs20 runtime ..." 73 | rm -rf ./code/node_modules 74 | s build 75 | s local invoke -e '{"hello":"fc nodejs20"}' 76 | s deploy -y 77 | s invoke -e '{"hello":"fc nodejs20"}' 78 | s remove -y 79 | 80 | rm -rf ./code/node_modules 81 | 82 | # nodejs8 83 | # export fc_component_runtime=nodejs8 84 | # export fc_component_function_name=nodejs8-$(uname)-$(uname -m) 85 | # rm -rf ./code/node_modules 86 | # echo "test nodejs8 runtime ..." 87 | # s build 88 | # s local invoke -e '{"hello":"fc nodejs8"}' 89 | # s deploy -y 90 | # s invoke -e '{"hello":"fc nodejs8"}' 91 | # s remove -y 92 | -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')} 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./code 16 | handler: event.handler 17 | memorySize: 128 18 | timeout: 60 19 | instanceLifecycleConfig: 20 | initializer: 21 | handler: event.initializer 22 | timeout: 10 23 | 24 | asyncInvokeConfig: 25 | destinationConfig: 26 | onFailure: 27 | destination: acs:mns:${vars.region}::/topics/serverless-devs-fc3-ci-test/messages 28 | onSuccess: 29 | destination: acs:fc:${vars.region}::functions/serverless-devs-ci-async-invoke-config-succ 30 | maxAsyncEventAgeInSeconds: 360 31 | maxAsyncRetryAttempts: 1 32 | asyncTask: true 33 | qualifier: LATEST 34 | 35 | provisionConfig: 36 | target: 1 37 | alwaysAllocateCPU: false 38 | alwaysAllocateGPU: false 39 | scheduledActions: 40 | - name: scheduled-actions 41 | startTime: '2023-08-15T02:04:00.000Z' 42 | endTime: '2033-08-15T03:04:00.000Z' 43 | target: 1 44 | scheduleExpression: cron(0 0 4 * * *) 45 | timeZone: '' 46 | # targetTrackingPolicies: 47 | # - name: target-tracking-policies 48 | # startTime: '2023-08-15T02:05:00.000Z' 49 | # endTime: '2033-08-15T02:55:00.000Z' 50 | # metricType: ProvisionedConcurrencyUtilization 51 | # metricTarget: 0.6 52 | # minCapacity: 1 53 | # maxCapacity: 3 54 | 55 | concurrencyConfig: 56 | reservedConcurrency: 1 57 | 58 | customDomain: 59 | domainName: auto 60 | protocol: HTTP 61 | route: 62 | path: /* 63 | qualifier: LATEST -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/s2.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-part-${env('fc_component_function_name', 'nodejs18')} 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./code 16 | handler: event.handler 17 | memorySize: 128 18 | timeout: 30 19 | instanceLifecycleConfig: 20 | initializer: 21 | handler: event.initializer 22 | timeout: 10 23 | 24 | triggers: 25 | - triggerName: httpTrigger # 触发器名称 26 | triggerType: http # 触发器类型 27 | description: 'xxxx' 28 | qualifier: test # 触发服务的版本 29 | triggerConfig: 30 | authType: anonymous # 鉴权类型,可选值:anonymous、function 31 | disableURLInternet: false # 是否禁用公网访问 URL 32 | methods: # HTTP 触发器支持的访问方法,可选值:GET、POST、PUT、DELETE、HEAD 33 | - GET 34 | - POST 35 | 36 | asyncInvokeConfig: 37 | destinationConfig: 38 | onFailure: 39 | destination: acs:mns:${vars.region}::/topics/serverless-devs-fc3-ci-test/messages 40 | onSuccess: 41 | destination: acs:fc:${vars.region}::functions/serverless-devs-ci-async-invoke-config-succ 42 | maxAsyncEventAgeInSeconds: 360 43 | maxAsyncRetryAttempts: 3 44 | qualifier: test -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/s_auto.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')} 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./test-auto-code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 30 19 | instanceLifecycleConfig: 20 | initializer: 21 | handler: index.initializer 22 | timeout: 10 23 | 24 | vpcConfig: auto 25 | nasConfig: auto 26 | logConfig: auto 27 | 28 | asyncInvokeConfig: 29 | destinationConfig: 30 | onFailure: 31 | destination: acs:mns:${vars.region}::/topics/serverless-devs-fc3-ci-test/messages 32 | onSuccess: 33 | destination: acs:fc:${vars.region}::functions/serverless-devs-ci-async-invoke-config-succ 34 | maxAsyncEventAgeInSeconds: 360 35 | maxAsyncRetryAttempts: 3 36 | -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/s_lock_auto.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo1: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')}-f1 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./test-auto-code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 30 19 | vpcConfig: auto 20 | nasConfig: auto 21 | 22 | fcDemo2: # 业务名称/模块名称 23 | component: ${env('fc_component_version', path('../../../'))} 24 | props: # 组件的属性值 25 | region: ${vars.region} 26 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')}-f2 27 | runtime: ${env('fc_component_runtime', 'nodejs18')} 28 | code: ./test-auto-code 29 | handler: index.handler 30 | memorySize: 128 31 | timeout: 30 32 | vpcConfig: auto 33 | nasConfig: auto 34 | 35 | fcDemo3: # 业务名称/模块名称 36 | component: ${env('fc_component_version', path('../../../'))} 37 | props: # 组件的属性值 38 | region: ${vars.region} 39 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')}-f3 40 | runtime: ${env('fc_component_runtime', 'nodejs18')} 41 | code: ./test-auto-code 42 | handler: index.handler 43 | memorySize: 128 44 | timeout: 30 45 | vpcConfig: auto 46 | nasConfig: auto -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/s_tags.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo1: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')}-tags 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./test-auto-code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 30 19 | tags: 20 | - Key: test 21 | Value: value -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/s_tags2.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo1: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')}-tags 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./test-auto-code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 30 19 | tags: 20 | - Key: test 21 | Value: value 22 | - Key: test1 23 | Value: value1 -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/s_tags3.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo1: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'nodejs18')}-tags 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./test-auto-code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 30 -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/test-auto-code/index.js: -------------------------------------------------------------------------------- 1 | exports.initializer = (context, callback) => { 2 | console.log('initializing'); 3 | callback(null, ''); 4 | }; 5 | 6 | module.exports.handler = function (event, context, callback) { 7 | callback(null, 'hello world'); 8 | }; 9 | -------------------------------------------------------------------------------- /__tests__/e2e/nodejs/test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | s remove -y -t s2.yaml 3 | s deploy --function -t s2.yaml 4 | versionId=$(s version publish -t s2.yaml --silent -o json | jq -r '."versionId"') 5 | echo "latest version = $versionId" 6 | if [[ "$versionId" -gt 1 ]]; then 7 | mainVersion=$((versionId - 1)) 8 | echo "main version = $mainVersion" 9 | s alias publish --alias-name test --version-id $mainVersion --vw "{\"$versionId\": 0.2}" -t s2.yaml 10 | else 11 | s alias publish --alias-name test --version-id $versionId -t s2.yaml 12 | fi 13 | 14 | s deploy --trigger -t s2.yaml 15 | s deploy --async-invoke-config -t s2.yaml 16 | s info -t s2.yaml 17 | s alias list -t s2.yaml 18 | -------------------------------------------------------------------------------- /__tests__/e2e/php/code/.gitignore: -------------------------------------------------------------------------------- 1 | vendor -------------------------------------------------------------------------------- /__tests__/e2e/php/code/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "nategood/httpful": "*" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/e2e/php/code/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "c644e8e0b6b35a5ed33a5a3f1cfe5b8d", 8 | "packages": [ 9 | { 10 | "name": "nategood/httpful", 11 | "version": "0.3.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/nategood/httpful.git", 15 | "reference": "0cded3ea97ba905600de9ceb9ef13f3ab681587c" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/nategood/httpful/zipball/0cded3ea97ba905600de9ceb9ef13f3ab681587c", 20 | "reference": "0cded3ea97ba905600de9ceb9ef13f3ab681587c", 21 | "shasum": "", 22 | "mirrors": [ 23 | { 24 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 25 | "preferred": true 26 | } 27 | ] 28 | }, 29 | "require": { 30 | "ext-curl": "*", 31 | "php": ">=7.2" 32 | }, 33 | "require-dev": { 34 | "phpunit/phpunit": "*" 35 | }, 36 | "type": "library", 37 | "autoload": { 38 | "psr-0": { 39 | "Httpful": "src/" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Nate Good", 49 | "email": "me@nategood.com", 50 | "homepage": "http://nategood.com" 51 | } 52 | ], 53 | "description": "A Readable, Chainable, REST friendly, PHP HTTP Client", 54 | "homepage": "http://github.com/nategood/httpful", 55 | "keywords": [ 56 | "api", 57 | "curl", 58 | "http", 59 | "requests", 60 | "rest", 61 | "restful" 62 | ], 63 | "support": { 64 | "issues": "https://github.com/nategood/httpful/issues", 65 | "source": "https://github.com/nategood/httpful/tree/v0.3.2" 66 | }, 67 | "time": "2020-01-25T01:13:13+00:00" 68 | } 69 | ], 70 | "packages-dev": [], 71 | "aliases": [], 72 | "minimum-stability": "stable", 73 | "stability-flags": [], 74 | "prefer-stable": false, 75 | "prefer-lowest": false, 76 | "platform": [], 77 | "platform-dev": [], 78 | "plugin-api-version": "2.3.0" 79 | } 80 | -------------------------------------------------------------------------------- /__tests__/e2e/php/code/index.php: -------------------------------------------------------------------------------- 1 | info($event); 9 | // Make a request to the GitHub API with a custom 10 | // header of "X-Trvial-Header: Just as a demo". 11 | $url = "https://api.github.com/users/nategood"; 12 | $response = \Httpful\Request::get($url) 13 | ->expectsJson() 14 | ->withXTrivialHeader('Just as a demo') 15 | ->send(); 16 | return $event; 17 | } 18 | -------------------------------------------------------------------------------- /__tests__/e2e/php/code/test.sh: -------------------------------------------------------------------------------- 1 | ls -lh 2 | echo "hello" 3 | echo "world" 4 | composer install -------------------------------------------------------------------------------- /__tests__/e2e/php/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -v 5 | 6 | echo "test php runtime ..." 7 | export fc_component_function_name=php72-$(uname)-$(uname -m) 8 | 9 | s build 10 | s local invoke -e '{"hello":"fc php"}' 11 | s deploy -y 12 | s info 13 | s invoke -e '{"hello":"fc php"}' 14 | 15 | rm -rf ./code/vendor 16 | s build --script-file ./test.sh 17 | s deploy -y 18 | s info 19 | s invoke -e '{"hello":"fc php"}' 20 | 21 | rm -rf ./code/vendor 22 | s build --command='composer install -vvv' --custom-env '{"k": "v"}' --debug 23 | s deploy -y 24 | s info 25 | s invoke -e '{"hello":"fc php"}' 26 | 27 | rm -rf ./code/vendor 28 | s build --custom-args='-v' --debug 29 | s deploy -y 30 | s info 31 | s invoke -e '{"hello":"fc php"}' 32 | 33 | s remove -y 34 | rm -rf ./code/vendor 35 | -------------------------------------------------------------------------------- /__tests__/e2e/php/run-windows.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | Write-Host "test php runtime ..." 4 | $env:fc_component_function_name = "php72-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 5 | s build 6 | s local invoke -e '{"hello":"fc php"}' 7 | s deploy -y 8 | s info 9 | s invoke -e '{"hello":"fc php"}' 10 | 11 | Remove-Item -Recurse -Force ./code/vendor -ErrorAction SilentlyContinue 12 | s build --script-file ./test.sh 13 | s deploy -y 14 | s info 15 | s invoke -e '{"hello":"fc php"}' 16 | 17 | Remove-Item -Recurse -Force ./code/vendor -ErrorAction SilentlyContinue 18 | s build --command='composer install -vvv' --custom-env '{"k": "v"}' --debug 19 | s deploy -y 20 | s info 21 | s invoke -e '{"hello":"fc php"}' 22 | 23 | Remove-Item -Recurse -Force ./code/vendor -ErrorAction SilentlyContinue 24 | s build --custom-args='-v' --debug 25 | s deploy -y 26 | s info 27 | s invoke -e '{"hello":"fc php"}' 28 | 29 | s remove -y -------------------------------------------------------------------------------- /__tests__/e2e/php/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-php-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'php72')} 14 | description: 'hello world by serverless devs' 15 | runtime: php7.2 16 | code: ./code 17 | timeout: 30 18 | handler: index.handler 19 | memorySize: 128 -------------------------------------------------------------------------------- /__tests__/e2e/python/code/.gitignore: -------------------------------------------------------------------------------- 1 | python 2 | __pycache__ 3 | index.pyc 4 | -------------------------------------------------------------------------------- /__tests__/e2e/python/code/apt-get.list: -------------------------------------------------------------------------------- 1 | jq 2 | git -------------------------------------------------------------------------------- /__tests__/e2e/python/code/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | from bs4 import BeautifulSoup 4 | import logging 5 | import flask 6 | 7 | html_doc = """ 8 | The Dormouse's story 9 | 10 |

The Dormouse's story

11 | 12 |

Once upon a time there were three little sisters; and their names were 13 | Elsie, 14 | Lacie and 15 | Tillie; 16 | and they lived at the bottom of a well.

17 | 18 |

...

19 | """ 20 | 21 | 22 | def isPython3(): 23 | import sys 24 | 25 | if sys.version > "3": 26 | return True 27 | return False 28 | 29 | 30 | def handler(event, context): 31 | logger = logging.getLogger() 32 | logger.info(event) 33 | logger.info(flask.__version__) 34 | soup = BeautifulSoup(html_doc, "html.parser") 35 | print(soup.prettify()) 36 | out_bytes = subprocess.check_output(["jq", "--version"]) 37 | print(out_bytes) 38 | out_bytes = subprocess.check_output(["git", "--version"]) 39 | print(out_bytes) 40 | return event 41 | -------------------------------------------------------------------------------- /__tests__/e2e/python/code/requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4 2 | flask -------------------------------------------------------------------------------- /__tests__/e2e/python/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export fc_component_runtime=python3 6 | export fc_component_function_name=python3-$(uname)-$(uname -m) 7 | echo "test python3 runtime ..." 8 | rm -rf ./code/python 9 | s build 10 | s local invoke -e '{"hello":"fc python3"}' 11 | s deploy -y 12 | s info 13 | s invoke -e '{"hello":"fc python3"}' 14 | s remove -y 15 | 16 | export fc_component_runtime=python3.12 17 | export fc_component_function_name=python312-$(uname)-$(uname -m) 18 | rm -rf ./code/python 19 | echo "test python3.12 runtime ..." 20 | s build 21 | s local invoke -e '{"hello":"fc python3.12"}' 22 | s deploy -y 23 | s invoke -e '{"hello":"fc python3.12"}' 24 | s remove -y 25 | 26 | export fc_component_runtime=python3.10 27 | export fc_component_function_name=python310-$(uname)-$(uname -m) 28 | rm -rf ./code/python 29 | echo "test python3.10 runtime ..." 30 | s build 31 | s local invoke -e '{"hello":"fc python3.10"}' 32 | s deploy -y 33 | s invoke -e '{"hello":"fc python3.10"}' 34 | s remove -y 35 | 36 | export fc_component_runtime=python3.9 37 | export fc_component_function_name=python39-$(uname)-$(uname -m) 38 | echo "test python3.9 runtime ..." 39 | rm -rf ./code/python 40 | s build 41 | s local invoke -e '{"hello":"fc python3.9"}' 42 | s deploy -y 43 | s invoke -e '{"hello":"fc python3.9"}' 44 | s remove -y 45 | 46 | # export fc_component_runtime=python2.7 47 | # export fc_component_function_name=python27-$(uname)-$(uname -m) 48 | # echo "test python2.7 runtime ..." 49 | # rm -rf ./code/python 50 | # s build 51 | # s local invoke -e '{"hello":"fc python2.7"}' 52 | # s deploy -y 53 | # s invoke -e '{"hello":"fc python2.7"}' 54 | # s remove -y 55 | -------------------------------------------------------------------------------- /__tests__/e2e/python/run-windows.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | $env:fc_component_runtime = "python3" 4 | $env:fc_component_function_name = "python3-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 5 | Write-Host "test python3 runtime ..." 6 | s build 7 | s local invoke -e '{"hello":"fc python3"}' 8 | s deploy -y 9 | s info 10 | s invoke -e '{"hello":"fc python3"}' 11 | s remove -y 12 | 13 | $env:fc_component_runtime = "python3.12" 14 | $env:fc_component_function_name = "python312-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 15 | Remove-Item -Recurse -Force ./code/python -ErrorAction SilentlyContinue 16 | Write-Host "test python3.12 runtime ..." 17 | s build 18 | s local invoke -e '{"hello":"fc python3.12"}' 19 | s deploy -y 20 | s invoke -e '{"hello":"fc python3.12"}' 21 | s remove -y 22 | 23 | $env:fc_component_runtime = "python3.10" 24 | $env:fc_component_function_name = "python310-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 25 | Remove-Item -Recurse -Force ./code/python -ErrorAction SilentlyContinue 26 | Write-Host "test python3.10 runtime ..." 27 | s build 28 | s local invoke -e '{"hello":"fc python3.10"}' 29 | s deploy -y 30 | s invoke -e '{"hello":"fc python3.10"}' 31 | s remove -y 32 | 33 | 34 | $env:fc_component_runtime = "python3.9" 35 | $env:fc_component_function_name = "python39-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)" 36 | Write-Host "test python3.9 runtime ..." 37 | Remove-Item -Recurse -Force ./code/python -ErrorAction SilentlyContinue 38 | s build 39 | s local invoke -e '{"hello":"fc python3.9"}' 40 | s deploy -y 41 | s invoke -e '{"hello":"fc python3.9"}' 42 | s remove -y 43 | 44 | Remove-Item -Recurse -Force ./code/python -ErrorAction SilentlyContinue 45 | -------------------------------------------------------------------------------- /__tests__/e2e/python/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-py-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: 10 | component: ${env('fc_component_version', path('../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: fc3-event-${env('fc_component_function_name', 'python310')} 14 | runtime: ${env('fc_component_runtime', 'python3.10')} 15 | code: ./code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 30 19 | environmentVariables: 20 | LD_LIBRARY_PATH: /code/apt-archives/usr/local/lib:/code/apt-archives/usr/lib:/code/apt-archives/usr/lib/x86_64-linux-gnu:/code/apt-archives/usr/lib64:/code/apt-archives/lib:/code/apt-archives/lib/x86_64-linux-gnu:/code 21 | PYTHONPATH: /code/python 22 | PATH: /code/apt-archives/usr/bin:/code/python/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/code:/code/bin:/opt:/opt/bin 23 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/clear: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -v 5 | 6 | echo "********* test http trigger ************" 7 | cd ./http 8 | export fc_component_function_name=test-http-trigger-$(uname)-$(uname -m)-$RANDSTR 9 | echo "test nodejs18 runtime http trigger ..." 10 | s remove -y 11 | 12 | echo "********* test other trigger ************" 13 | cd ../other 14 | export fc_component_function_name=test-other-trigger-$(uname)-$(uname -m)-$RANDSTR 15 | echo "test nodejs18 runtime with timer/oss/sls/mns trigger ..." 16 | s remove -y --debug 17 | 18 | echo "********* test event bridge trigger ************" 19 | cd ../eb 20 | export fc_component_function_name=test-eb-trigger-$(uname)-$(uname -m)-$RANDSTR 21 | echo "test nodejs18 runtime with eb trigger ..." 22 | s remove -y 23 | cd .. 24 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/eb/code/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.handler = function (event, context, callback) { 4 | console.log(event.toString()); 5 | callback(null, event); 6 | }; 7 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/eb/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: cn-huhehaote 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: ${env('fc_component_function_name', 'nodejs18-ci')} 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 3 19 | triggers: 20 | - triggerName: kafkaTrigger 21 | triggerType: eventbridge 22 | qualifier: LATEST 23 | triggerConfig: 24 | triggerEnable: true 25 | asyncInvocationType: false 26 | eventRuleFilterPattern: '{}' 27 | eventSinkConfig: 28 | deliveryOption: 29 | mode: event-streaming # event source 为 Kafka 时,只支持 event-streaming 模式 30 | eventSchema: CloudEvents 31 | # concurrency: 1 32 | runOptions: 33 | mode: event-streaming # event source 为 Kafka 时,只支持 event-streaming 模式 34 | maximumTasks: 3 35 | errorsTolerance: 'ALL' 36 | retryStrategy: 37 | PushRetryStrategy: 'BACKOFF_RETRY' 38 | MaximumEventAgeInSeconds: 0 39 | MaximumRetryAttempts: 0 40 | # deadLetterQueue: 41 | # Arn: acs:mns:cn-qingdao:123:/queues/queueName 42 | batchWindow: 43 | CountBasedWindow: 2 44 | TimeBasedWindow: 10 45 | eventSourceConfig: 46 | eventSourceType: Kafka 47 | eventSourceParameters: 48 | sourceKafkaParameters: 49 | RegionId: ${vars.region} 50 | InstanceId: alikafka_post-cn-pe337q5qz00c 51 | Topic: testTopic 52 | ConsumerGroup: test-group 53 | OffsetReset: latest 54 | # Network: PublicNetwork 55 | # VpcId: vpc-hp3e30chfnems32zmo9xe 56 | # VSwitchIds: vsw-hp384zc64m80tyua9k4jz 57 | 58 | - triggerName: defaultTrigger 59 | triggerType: eventbridge 60 | qualifier: LATEST 61 | triggerConfig: 62 | triggerEnable: true 63 | asyncInvocationType: false 64 | eventRuleFilterPattern: '{"source":["acs.oss"],"type":["oss:BucketCreated:PutBucket"]}' 65 | eventSourceConfig: 66 | eventSourceType: Default 67 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/http/code/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.handler = function (event, context, callback) { 4 | console.log(event.toString()); 5 | callback(null, event); 6 | }; 7 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/http/evt.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v1", 3 | "rawPath": "/2023-03-30/functions/fc3-event-test-http-trigger-Darwin-x86_64/invocations", 4 | "headers": { 5 | "Accept": "application/json", 6 | "Authorization": "ACS3-HMAC-SHA256 Credential=LTA******************ibC,SignedHeaders=content-type;host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=c360d2c3319696f6e755e0ef8033c5639fc99744a06d84bde901bef1cd44f2bd", 7 | "Connection": "keep-alive", 8 | "Content-Type": "application/octet-stream", 9 | "User-Agent": "AlibabaCloud (darwin; x64) Node.js/v14.20.0 Core/1.0.1 TeaDSL/1", 10 | "X-Acs-Action": "InvokeFunction", 11 | "X-Acs-Content-Sha256": "676903d7d4ba9666e7c530ba3d8238d15b4a41d179b2f8877348e1b09a5dd13f", 12 | "X-Acs-Date": "2023-08-17T14:08:11Z", 13 | "X-Acs-Signature-Nonce": "2d13b9477e5e25d6b5c63fee372a9351", 14 | "X-Acs-Version": "2023-03-30", 15 | "X-Fc-Log-Type": "Tail" 16 | }, 17 | "queryParameters": {}, 18 | "body": "haha", 19 | "isBase64Encoded": false, 20 | "requestContext": { 21 | "accountId": "143**********149", 22 | "triggerId": "1e875ca5-8fd9-4893-b690-cc81c41e5bdc", 23 | "domainName": "143**********149.cn-huhehaote.fc.aliyuncs.com", 24 | "domainPrefix": "143**********149", 25 | "requestId": "", 26 | "time": "2023-08-17T14:08:11Z", 27 | "timeEpoch": "1692281291805", 28 | "http": { 29 | "method": "POST", 30 | "path": "/2023-03-30/functions/fc3-event-test-http-trigger-Darwin-x86_64/invocations", 31 | "protocol": "HTTP/1.1", 32 | "sourceIp": "112.10.236.5", 33 | "userAgent": "AlibabaCloud (darwin; x64) Node.js/v14.20.0 Core/1.0.1 TeaDSL/1" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /__tests__/e2e/trigger/http/s.yaml: -------------------------------------------------------------------------------- 1 | edition: 3.0.0 2 | name: test-node-app 3 | access: quanxi 4 | 5 | vars: 6 | region: ${env('REGION', 'cn-hongkong')} 7 | 8 | resources: 9 | fcDemo: # 业务名称/模块名称 10 | component: ${env('fc_component_version', path('../../../../'))} 11 | props: # 组件的属性值 12 | region: ${vars.region} 13 | functionName: ${env('fc_component_function_name', 'nodejs18-http')} 14 | runtime: ${env('fc_component_runtime', 'nodejs18')} 15 | code: ./code 16 | handler: index.handler 17 | memorySize: 128 18 | timeout: 3 19 | # vpcConfig: auto 20 | # nasConfig: auto 21 | # logConfig: auto 22 | triggers: 23 | - triggerName: http_t2 # 触发器名称 24 | triggerType: http # 触发器类型 25 | description: 'http trigger --> test' 26 | qualifier: test # 触发函数的版本 27 | triggerConfig: 28 | authType: function # 鉴权类型,可选值:anonymous、function 29 | disableURLInternet: false # 是否禁用公网访问 URL 30 | methods: # HTTP 触发器支持的访问方法,可选值:GET、POST、PUT、DELETE、HEAD 31 | - GET 32 | - POST 33 | - triggerName: http_t # 触发器名称 34 | triggerType: http # 触发器类型 35 | description: 'http trigger --> LATEST' 36 | #qualifier: LATEST # 触发服务的版本 37 | triggerConfig: 38 | authType: anonymous # 鉴权类型,可选值:anonymous、function 39 | disableURLInternet: false # 是否禁用公网访问 URL 40 | methods: # HTTP 触发器支持的访问方法,可选值:GET、POST、PUT、DELETE、HEAD 41 | - GET 42 | - POST 43 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/jwt/code/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context, callback) { 2 | let a = 1; 3 | console.log(event.toString()); 4 | console.log(JSON.stringify(context)); 5 | console.log('My logs'); 6 | console.log('My logs2 '); 7 | console.log(process.env.FC_FUNCTION_MEMORY_SIZE); 8 | callback(null, JSON.parse(event.toString())); 9 | // callback(null, 'hello world'); 10 | }; 11 | 12 | /* 13 | exports.handler = async function (event, context, callback) { 14 | callback(null, 'hello world'); 15 | }; 16 | */ 17 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/other/code/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.handler = function (event, context, callback) { 4 | console.log(event.toString()); 5 | callback(null, event); 6 | }; 7 | -------------------------------------------------------------------------------- /__tests__/e2e/trigger/run-windows.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | Write-Host "********* test http trigger ************" 4 | cd ./http 5 | 6 | $env:fc_component_function_name = "test-http-trigger-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 7 | 8 | Write-Host "test nodejs18 runtime http trigger ..." 9 | 10 | s remove -y 11 | 12 | s deploy --function -y 13 | s deploy --trigger http_t -y 14 | s version publish --description test 15 | s alias publish --alias-name test --version-id latest 16 | s alias get --alias-name test 17 | s alias list 18 | s deploy --trigger http_t2 -y 19 | s info 20 | s plan 21 | s invoke -e 'hello latest' 22 | s invoke -e 'hello latest' --qualifier 'test' 23 | s remove -y 24 | 25 | if ($env:region -ne $null -and $env:region -ne "cn-huhehaote") { 26 | Write-Host "Region is not equal to cn-huhehaote. Skip eb and other trigger test" 27 | exit 0 28 | } 29 | 30 | Write-Host "********* test other trigger ************" 31 | cd ../other 32 | $env:fc_component_function_name = "test-other-trigger-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 33 | Write-Host "test nodejs18 runtime with timer/oss/sls/mns trigger ..." 34 | s deploy -y 35 | s info 36 | s plan 37 | s remove -y 38 | 39 | Write-Host "********* test event bridge trigger ************" 40 | cd ../eb 41 | $env:fc_component_function_name = "test-eb-trigger-$($env:OS)-$($env:PROCESSOR_ARCHITECTURE)-$($env:RANDSTR)" 42 | Write-Host "test nodejs18 runtime with eb trigger ..." 43 | s deploy -y 44 | s info 45 | s plan 46 | s remove -y 47 | cd ../ -------------------------------------------------------------------------------- /__tests__/format.sh: -------------------------------------------------------------------------------- 1 | # !/bin/bash 2 | 3 | # format python 4 | black ./e2e/custom/python 5 | black ./e2e/custom.debian10/python 6 | black ./e2e/python 7 | 8 | # format golang 9 | function read_dir(){ 10 | for file in `ls $1` 11 | do 12 | if [ -d $1"/"$file ] 13 | then 14 | read_dir $1"/"$file 15 | else 16 | gofile=$1"/"$file 17 | if [[ "$gofile" == *go ]]; then 18 | echo "$gofile std formatting .." 19 | go fmt $gofile 20 | golint $gofile 21 | goimports -w $gofile 22 | fi 23 | fi 24 | done 25 | } 26 | read_dir ./e2e/custom/go 27 | read_dir ./e2e/custom.debian10/go 28 | read_dir ./e2e/go 29 | -------------------------------------------------------------------------------- /__tests__/it/code/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | 5 | 6 | def handler(event, context): 7 | logger = logging.getLogger() 8 | logger.info(event) 9 | return event -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['ali'], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | 建议您直接阅读 [Serverless Devs 官方文档](https://manual.serverless-devs.com/user-guide/aliyun/#fc3) 4 | -------------------------------------------------------------------------------- /f2elint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | enableStylelint: true, 3 | enableMarkdownlint: false, 4 | enablePrettier: true, 5 | }; 6 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transformIgnorePatterns": [ 3 | "/example/*.*" 4 | ], 5 | "transform": { 6 | "^.+\\.(ts|tsx)$": "ts-jest" 7 | }, 8 | "testRegex": "(/__tests__/(ut|it)/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 9 | "moduleFileExtensions": [ 10 | "ts", 11 | "tsx", 12 | "js", 13 | "jsx", 14 | "json", 15 | "node" 16 | ], 17 | "collectCoverage": true, 18 | "coverageReporters": ["html", "text"], 19 | "testTimeout": 30000 20 | } 21 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | statistics: 2 | @wget -q https://images.devsapp.cn/tools/git-statistics.sh && bash git-statistics.sh && rm git-statistics.sh 3 | 4 | test-nodejs: 5 | cd __tests__/e2e/nodejs && bash run && cd - 6 | 7 | test-python: 8 | cd __tests__/e2e/python && bash run && cd - 9 | 10 | test-java: 11 | cd __tests__/e2e/java && bash run && cd - 12 | 13 | test-go: 14 | cd __tests__/e2e/go && bash run && cd - 15 | 16 | test-php: 17 | cd __tests__/e2e/php && bash run && cd - 18 | 19 | test-custom: 20 | cd __tests__/e2e/custom && bash run && cd - 21 | cd __tests__/e2e/custom.debian10 && bash run && cd - 22 | 23 | test-custom-container: 24 | cd __tests__/e2e/custom-container && bash run && cd - 25 | 26 | test-apt: 27 | cd __tests__/e2e/apt && bash run && cd - 28 | 29 | test: test-nodejs test-python test-java test-go test-php test-custom test-custom-container test-apt 30 | echo "all test done!" 31 | 32 | release-dev: 33 | gsed -i "s/^Version: .*/Version: dev/" publish.yaml; \ 34 | npm run publish 35 | 36 | update-version: 37 | current_version=$$(curl -s https://api.devsapp.cn/v3/packages/fc3/release/latest | jq -r '.body.tag_name'); \ 38 | echo $$current_version;\ 39 | major_version=$$(echo $$current_version | cut -d"." -f1); \ 40 | minor_version=$$(echo $$current_version | cut -d"." -f2); \ 41 | patch_version=$$(echo $$current_version | cut -d"." -f3); \ 42 | new_patch_version=$$((patch_version + 1)); \ 43 | new_version=$$major_version.$$minor_version.$$new_patch_version; \ 44 | sed -i "s/^Version: .*/Version: $$new_version/" publish.yaml; \ 45 | git diff --exit-code || true 46 | 47 | release-prod: update-version 48 | npm run publish 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fc", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "generate-schema": "npx typescript-json-schema ./src/interface/index.ts IProps --required -o ./src/schema.json", 8 | "prewatch": "mkdir -p dist && cp src/schema.json dist/schema.json", 9 | "prebuild": "rimraf dist && mkdir -p dist && cp src/schema.json dist/schema.json", 10 | "watch": "npx tsc -w -p tsconfig.json", 11 | "start": "npm run watch", 12 | "build": "ncc build src/index.ts -m -o dist", 13 | "format": "prettier --write src", 14 | "publish": "npm i && npm run build && s registry publish", 15 | "lint": "f2elint scan", 16 | "fix": "f2elint fix", 17 | "test": "jest --config jestconfig.json", 18 | "postinstall": "patch-package" 19 | }, 20 | "repository": "git@github.com:devsapp/fc3.git", 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "dependencies": { 25 | "@alicloud/fc2": "^2.6.6", 26 | "@alicloud/fc20230330": "4.3.3", 27 | "@alicloud/pop-core": "^1.8.0", 28 | "@serverless-cd/srm-aliyun-pop-core": "^0.0.7-beta.21", 29 | "@serverless-cd/srm-aliyun-ram20150501": "^0.0.2-beta.9", 30 | "@serverless-cd/srm-aliyun-sls20201230": "0.0.5-beta.3", 31 | "@serverless-devs/diff": "^0.0.3-beta.6", 32 | "@serverless-devs/downloads": "^0.0.7", 33 | "@serverless-devs/load-component": "^0.0.9", 34 | "@serverless-devs/utils": "^0.0.17", 35 | "@serverless-devs/zip": "^0.0.3-beta.8", 36 | "ajv": "^8.17.1", 37 | "aliyun-sdk": "^1.12.10", 38 | "chalk": "^4.1.0", 39 | "crc64-ecma182.js": "^2.0.2", 40 | "decompress": "^4.2.1", 41 | "extract-zip": "^2.0.1", 42 | "fs-extra": "^11.3.0", 43 | "http-proxy": "^1.18.1", 44 | "httpx": "^2.3.2", 45 | "inquirer": "^8.2.5", 46 | "ip": "^1.1.8", 47 | "lodash": "^4.17.21", 48 | "portfinder": "^1.0.32", 49 | "rimraf": "^3.0.2", 50 | "string-random": "^0.1.3", 51 | "temp-dir": "^2.0.0", 52 | "tty-table": "^4.2.3", 53 | "uuid": "^9.0.1", 54 | "uuid-by-string": "^4.0.0", 55 | "ali-oss": "6.18.1" 56 | }, 57 | "devDependencies": { 58 | "@serverless-devs/component-interface": "^0.0.6", 59 | "@serverless-devs/logger": "^0.0.5", 60 | "@types/jest": "^29.5.14", 61 | "@types/lodash": "^4.17.16", 62 | "@types/node": "^20.12.11", 63 | "@vercel/ncc": "^0.38.3", 64 | "f2elint": "^2.2.1", 65 | "jest": "^29.7.0", 66 | "patch-package": "^8.0.0", 67 | "postinstall-prepare": "^2.0.0", 68 | "prettier": "^3.4.2", 69 | "ts-jest": "^29.2.5", 70 | "ts-node": "^10.9.2", 71 | "typescript": "^4.4.2", 72 | "typescript-json-schema": "^0.65.1" 73 | }, 74 | "resolutions": { 75 | "@alicloud/sls20191023": { 76 | "registry": "https://packages.aliyun.com/670e108663cd360abfe4be65/npm/npm-registry/" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /patches/ali-oss+6.18.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/ali-oss/lib/common/utils/checkEnv.ts b/node_modules/ali-oss/lib/common/utils/checkEnv.ts 2 | index b373658..078f65d 100644 3 | --- a/node_modules/ali-oss/lib/common/utils/checkEnv.ts 4 | +++ b/node_modules/ali-oss/lib/common/utils/checkEnv.ts 5 | @@ -1,5 +1,5 @@ 6 | export function checkEnv(msg: string) { 7 | - if (process.browser) { 8 | + if ((process as any).browser) { 9 | console.warn(msg); 10 | } 11 | } 12 | diff --git a/node_modules/ali-oss/lib/common/utils/isDingTalk.ts b/node_modules/ali-oss/lib/common/utils/isDingTalk.ts 13 | index aa7ff33..5e2b7c1 100644 14 | --- a/node_modules/ali-oss/lib/common/utils/isDingTalk.ts 15 | +++ b/node_modules/ali-oss/lib/common/utils/isDingTalk.ts 16 | @@ -1,5 +1,5 @@ 17 | export function isDingTalk() { 18 | - if (process.browser && window.navigator.userAgent.toLowerCase().includes('aliapp(dingtalk')) { 19 | + if ((process as any).browser && window.navigator.userAgent.toLowerCase().includes('aliapp(dingtalk')) { 20 | return true; 21 | } 22 | return false; 23 | -------------------------------------------------------------------------------- /publish.yaml: -------------------------------------------------------------------------------- 1 | Edition: 3.0.0 2 | Type: Component 3 | Name: fc3 4 | Provider: 5 | - 阿里云 6 | Version: dev 7 | Description: 阿里云函数计算全生命周期管理 8 | HomePage: https://github.com/devsapp/fc3 9 | Organization: 阿里云函数计算(FC) 10 | Effective: Public 11 | Tags: #标签详情 12 | - 部署函数 13 | - fc3.0组件 14 | Category: 基础云服务 15 | 16 | Service: 17 | 函数计算: 18 | Authorities: 19 | - AliyunFCFullAccess 20 | 21 | Commands: 22 | deploy: 部署函数 23 | build: 构建函数 24 | remove: 删除函数 25 | plan: 计划变更 26 | invoke: 调用函数 27 | local: 本地调试 28 | instance: 实例登录 29 | version: 函数版本操作 30 | alias: 函数别名操作 31 | provision: 函数预留操作 32 | concurrency: 函数资源配额操作 33 | layer: 层操作 34 | logs: 函数日志查询 35 | info: 查看函数详情 36 | sync: 线上资源同步到本地 37 | s2tos3: fc2.0 yaml 转 fc3.0 yaml 38 | 39 | Parameters: 40 | type: object 41 | additionalProperties: false 42 | required: # 必填项 43 | - region 44 | - service 45 | properties: 46 | region: 47 | type: string 48 | required: true 49 | description: 地域 50 | default: cn-hangzhou 51 | enum: 52 | - cn-beijing 53 | - cn-hangzhou 54 | - cn-shanghai 55 | - cn-qingdao 56 | - cn-zhangjiakou 57 | - cn-huhehaote 58 | - cn-shenzhen 59 | - cn-chengdu 60 | - cn-hongkong 61 | - ap-southeast-1 62 | - ap-southeast-2 63 | - ap-southeast-3 64 | - ap-southeast-5 65 | - ap-southeast-7 66 | - ap-northeast-1 67 | - ap-northeast-2 68 | - eu-central-1 69 | - eu-west-1 70 | - us-west-1 71 | - us-east-1 72 | - ap-south-1 73 | 74 | functionName: 75 | type: string 76 | required: true 77 | description: 函数名字 78 | 79 | runtime: 80 | type: string 81 | required: true 82 | description: 地域 83 | default: nodejs18 84 | enum: 85 | - nodejs10 86 | - nodejs12 87 | - nodejs14 88 | - nodejs16 89 | - nodejs18 90 | - nodejs20 91 | - python3 92 | - python3.9 93 | - python3.10 94 | - python3.12 95 | - php7.2 96 | - java8 97 | - java11 98 | - dotnetcore3.1 99 | - custom 100 | - custom.debian10 101 | - custom.debian11 102 | - custom-container 103 | 104 | code: 105 | type: string | object 106 | required: true 107 | description: 函数代码包地址,本地文件夹/本地zip文件/oss bucket+object 108 | 109 | triggers: 110 | type: array 111 | required: false 112 | description: 触发器配置,和 FC3.0 CreateTrigger API 对齐 113 | 114 | asyncInvokeConfig: 115 | type: object 116 | required: false 117 | description: 异步调用配置,和 FC3.0 CreateAsyncInvokeConfig API 对齐 118 | -------------------------------------------------------------------------------- /src/commands-help/build.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Build the dependencies. 4 | 5 | Examples: 6 | $ s build 7 | $ s build --publish-layer 8 | $ s build --dockerfile ./code/Dockerfile --context ./code 9 | $ s build --custom-env "{\\"myenv\\": \\"test\\"}" --custom-args="-i https://pypi.tuna.tsinghua.edu.cn/simple" 10 | $ s build --command="pip install -t . flask -i https://pypi.tuna.tsinghua.edu.cn/simple" 11 | $ s build --script-file my_script.sh`, 12 | summary: 'Build the dependencies', 13 | option: [ 14 | ['--publish-layer', '[Optional] Publishing the built artifact as a layer'], 15 | ['--use-sandbox', '[Optional] Enter the sandbox container of the corresponding runtime'], 16 | ['--custom-env ', '[Optional] Custom environment variables injected during build'], 17 | [ 18 | '--custom-args ', 19 | '[Optional] Additional parameters when using the default build behavior, such as specifying a pypi or NPM source', 20 | ], 21 | ['--command ', '[Optional] Using custom commands'], 22 | ['--script-file ', '[Optional] Using custom shell scripts'], 23 | [ 24 | '-f, --dockerfile ', 25 | '[Optional] Specify the dockerfile path, Use docker to build the image of the custom container runtime', 26 | ], 27 | [ 28 | 'context ', 29 | '[Optional] custom-container context directory for constructing the image', 30 | ], 31 | ], 32 | }, 33 | verify: false, 34 | }; 35 | -------------------------------------------------------------------------------- /src/commands-help/concurrency.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: 'Function concurrency operation ', 4 | summary: 'Function concurrency operation ', 5 | }, 6 | subCommands: { 7 | get: { 8 | help: { 9 | description: `Get function concurrency detail. 10 | 11 | Examples with Yaml: 12 | $ s concurrency get 13 | 14 | Examples with CLI: 15 | $ s cli fc3 concurrency get --region cn-hangzhou --function-name test -a default`, 16 | summary: 'Get function concurrency detail', 17 | option: [ 18 | [ 19 | '--region ', 20 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 21 | ], 22 | ['--function-name ', '[C-Required] Specify function name'], 23 | ], 24 | }, 25 | }, 26 | put: { 27 | help: { 28 | description: `Put function concurrency. 29 | 30 | Examples with Yaml: 31 | $ s concurrency put --reserved-concurrency 5 32 | 33 | Examples with CLI: 34 | $ s cli fc3 concurrency put --reserved-concurrency 3 --region cn-hangzhou --function-name test -a default`, 35 | summary: 'Put function concurrency', 36 | option: [ 37 | [ 38 | '--region ', 39 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 40 | ], 41 | ['--function-name ', '[C-Required] Specify function name'], 42 | ['--reserved-concurrency ', '[Required] Specify reserved concurrency'], 43 | ], 44 | }, 45 | }, 46 | remove: { 47 | help: { 48 | description: `Remove function concurrency. 49 | 50 | Examples with Yaml: 51 | $ s concurrency remove 52 | $ s concurrency remove -y 53 | 54 | Examples with CLI: 55 | $ s cli fc3 concurrency remove --region cn-hangzhou --function-name test -a default`, 56 | summary: 'Remove function concurrency', 57 | option: [ 58 | [ 59 | '--region ', 60 | '[C-Required] Specify the fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 61 | ], 62 | ['--function-name ', '[C-Required] Specify function name'], 63 | ['-y, --assume-yes', "Don't ask, delete directly"], 64 | ], 65 | }, 66 | }, 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /src/commands-help/deploy.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Deploy local resources online. 4 | 5 | Examples: 6 | $ s deploy 7 | $ s deploy --skip-push 8 | $ s deploy --function code 9 | $ s deploy --function config 10 | $ s deploy --trigger triggerName 11 | $ s deploy --trigger triggerName1,trigggerName2`, 12 | summary: 'Deploy local resources online', 13 | option: [ 14 | ['-y, --assume-yes', "[Optional] Don't ask, delete directly"], 15 | ['--skip-push', '[Optional] Specify if skip automatically pushing docker container images'], 16 | [ 17 | "--function ['code'/'config']", 18 | "[Optional] Only deploy function configuration or code. Use 'code' to deploy function code only, use 'config' to deploy function configuration only", 19 | ], 20 | [ 21 | '--trigger [triggerName]', 22 | "[Optional] Only deploy trigger only. Specify a trigger name to deploy only the specified trigger; Multiple names can be split by ','", 23 | ], 24 | ], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/commands-help/index.ts: -------------------------------------------------------------------------------- 1 | import deploy from './deploy'; 2 | import remove from './remove'; 3 | import info from './info'; 4 | import sync from './sync'; 5 | import invoke from './invoke'; 6 | import version from './version'; 7 | import concurrency from './concurrency'; 8 | import plan from './plan'; 9 | import alias from './alias'; 10 | import provision from './provision'; 11 | import layer from './layer'; 12 | import instance from './instance'; 13 | import build from './build'; 14 | import local from './local'; 15 | import s2tos3 from './s2tos3'; 16 | import logs from './logs'; 17 | 18 | export default { 19 | deploy, 20 | remove, 21 | info, 22 | sync, 23 | invoke, 24 | version, 25 | alias, 26 | concurrency, 27 | plan, 28 | provision, 29 | layer, 30 | instance, 31 | build, 32 | local, 33 | s2tos3, 34 | logs, 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands-help/info.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Query online resource details. 4 | 5 | Examples with Yaml: 6 | $ s info 7 | 8 | Examples with CLI: 9 | $ s cli fc3 info --region cn-hangzhou --function-name test -a default`, 10 | summary: 'Query online resource details', 11 | option: [ 12 | [ 13 | '--region ', 14 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 15 | ], 16 | ['--function-name ', '[C-Required] Specify function name'], 17 | ], 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/commands-help/instance.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: 'Function instance operation', 4 | summary: 'Function instance operation', 5 | }, 6 | subCommands: { 7 | exec: { 8 | help: { 9 | description: `Execute a command in a instance. 10 | Examples with Yaml: 11 | $ s instance exec --instance-id c-6******-27c4833c325445879a28 --cmd "ls -lh" 12 | $ s instance exec --instance-id c-6******-27c4833c325445879a28 13 | $ s instance exec --instance-id \`s invoke | grep "Invoke instanceId:" | sed "s/.*: //"\` 14 | 15 | Examples with CLI: 16 | $ s cli fc3 instance --instance-id c-64fec1fc-27c4833c325445879a28 --region cn-hangzhou --function-name test -a default`, 17 | summary: 'Execute a command in a instance', 18 | option: [ 19 | [ 20 | '--region ', 21 | '[C-Required] Specify the fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 22 | ], 23 | ['--function-name ', '[C-Required] Specify function name'], 24 | ['--instance-id ', '[Required] Specify function instance id'], 25 | ['--cmd ', '[Optional] Command string to be executed'], 26 | ['--qualifier ', '[Optional] Specify version or alias, default is LATEST'], 27 | ], 28 | }, 29 | }, 30 | list: { 31 | help: { 32 | description: `View the list of active function instance. 33 | Example: 34 | $ s instance list 35 | $ s cli fc3 instance list --region cn-hangzhou --function-name test -a default`, 36 | summary: 'View the list of active function instance', 37 | option: [ 38 | [ 39 | '--region ', 40 | '[C-Required] Specify the fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 41 | ], 42 | ['--function-name ', '[C-Required] Specify function name'], 43 | ['--qualifier ', '[Optional] Specify version or alias, default is LATEST'], 44 | ], 45 | }, 46 | }, 47 | }, 48 | verify: false, 49 | }; 50 | -------------------------------------------------------------------------------- /src/commands-help/invoke.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Invoke online functions. 4 | 5 | Examples with Yaml: 6 | $ s invoke -e "{\\"key\\": \\"val\\"}" 7 | $ s invoke -f evt.json 8 | $ s invoke --invocation-type Async 9 | $ s invoke --invocation-type Async --async-task-id task-uuid 10 | 11 | Examples with CLI: 12 | $ s cli fc3 invoke -e "payload" --region cn-huhehaote --function-name test -a default`, 13 | summary: 'Invoke online functions', 14 | option: [ 15 | [ 16 | '--region ', 17 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 18 | ], 19 | ['--function-name ', '[C-Required] Specify function name'], 20 | ['--qualifier ', '[Optional] Specify version or alias, default is LATEST'], 21 | ['--timeout ', '[Optional] Specify client timeout'], 22 | [ 23 | '-e, --event ', 24 | '[Optional] Specify event data passed to the function during invocation (default: "") ', 25 | ], 26 | [ 27 | '-f, --event-file ', 28 | '[Optional] Specify event file: A file containing event data passed to the function during invoke', 29 | ], 30 | [ 31 | '--invocation-type ', 32 | '[Optional] Specify Invocation type, value: Async/Async, default: Sync', 33 | ], 34 | [ 35 | '--async-task-id ', 36 | '[Optional] Specify The ID of the asynchronous task, only takes effect when --invocation-type=Async', 37 | ], 38 | ], 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/commands-help/local.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Local invoke fc function. 4 | 5 | Examples: 6 | $ s local invoke -e '{"key":"val"}' 7 | $ s local invoke -f evt.json 8 | $ s local invoke -e '{"key":"val"}' -c vscode -d 3000 9 | $ s local start`, 10 | summary: 'Local invoke fc function', 11 | option: [ 12 | [ 13 | '-e, --event string', 14 | '[Optional] Event data passed to the function during invocation (default: "")', 15 | ], 16 | [ 17 | '-f, --event-file string', 18 | '[Optional] A file containing event data passed to the function during invoke', 19 | ], 20 | [ 21 | '-c, --config [vscode/intellij]', 22 | '[Optional] Select which IDE to use when debugging and output related debug config tips for the IDE.value: vscode/intellij', 23 | ], 24 | [ 25 | '-d, --debug-port number', 26 | '[Optional] Specify the local function container starting in debug mode, and exposing this port on localhost', 27 | ], 28 | ], 29 | }, 30 | verify: false, 31 | }; 32 | -------------------------------------------------------------------------------- /src/commands-help/logs.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Query the function log. You need to open SLS log service 4 | 5 | Examples with Yaml: 6 | $ s logs --tail 7 | $ s logs -s 2023-11-02T02:54:00+08:00 -e 2023-11-02T02:54:59+08:00 8 | 9 | 10 | Examples with CLI: 11 | $ s cli fc3 logs --region cn-hangzhou --function-name functionName -s 2023-11-02T14:00:00+08:00 -e 2023-11-02T14:04:59+08:00 -a default`, 12 | summary: 'Query the function log', 13 | option: [ 14 | [ 15 | '--region ', 16 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 17 | ], 18 | ['--function-name ', '[C-Required] Specify function name'], 19 | ['--request-id ', '[Optional] Query according to requestId'], 20 | ['--instance-id ', '[Optional] Query according to instanceId'], 21 | [ 22 | '-s, --start-time', 23 | '[Optional] Query log start time (timestamp or time format,like 1611827290000 or 2021-11-11T11:11:12+00:00)', 24 | ], 25 | [ 26 | '-e, --end-time', 27 | '[Optional] Query log end time (timestamp or time format,like 1611827290000 or 2021-11-11T11:11:12+00:00)', 28 | ], 29 | ['--tail', '[Optional] Continuous log output mode'], 30 | ['--type ', '[Optional] Query according to Log type, value: success/fail'], 31 | ['--qualifier ', '[Optional] Query according to the specified version or alias'], 32 | ['--match ', '[Optional] The matched character is highlighted'], 33 | [ 34 | '--search ', 35 | '[Optional] Query according to keyword, Document: https://help.aliyun.com/document_detail/29060.html', 36 | ], 37 | ], 38 | }, 39 | verify: false, 40 | }; 41 | -------------------------------------------------------------------------------- /src/commands-help/plan.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Show the differences between the local and remote. 4 | 5 | Examples: 6 | $ s plan`, 7 | summary: 'Perceive resource change', 8 | option: [], 9 | }, 10 | verify: false, 11 | }; 12 | -------------------------------------------------------------------------------- /src/commands-help/remove.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Remove resources online. 4 | 5 | Examples with Yaml: 6 | $ s remove 7 | $ s remove -y 8 | $ s remove --trigger 9 | $ s remove --trigger triggerName 10 | $ s remove --trigger triggerName1,trigggerName2 11 | 12 | Examples with CLI: 13 | $ s cli fc3 remove --region cn-hangzhou --function-name test -a default`, 14 | summary: 'Remove resources online', 15 | option: [ 16 | [ 17 | '--region ', 18 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 19 | ], 20 | ['--function-name ', '[C-Required] Specify function name'], 21 | [ 22 | '--trigger [triggerName]', 23 | "[Optional] Only remove trigger only. Specify a trigger name to deploy only the specified trigger; Multiple names can be split by ','; A null value means to delete all triggers", 24 | ], 25 | ['-y, --assume-yes', "[Optional] Don't ask, delete directly"], 26 | ], 27 | }, 28 | verify: false, 29 | }; 30 | -------------------------------------------------------------------------------- /src/commands-help/s2tos3.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Translate the s.yaml file with Serverless Devs 2.0 specification into s.yaml file with 3.0 specification. 4 | 5 | Examples with CLI: 6 | $ s cli fc3 s2tos3 --source s.yaml --target s3.yaml`, 7 | summary: 8 | 'Translate the s.yaml file with Serverless Devs 2.0 specification into s.yaml file with 3.0 specification.', 9 | option: [ 10 | [ 11 | '--source ', 12 | '[C-Required] Specify s.yaml with Serverless Devs 2.0 specification file path, Both relative paths and absolute paths can be used, default is s.yaml or s.yml.', 13 | ], 14 | [ 15 | '--target ', 16 | '[C-Required] Specify s.yaml with Serverless Devs 3.0 specification file path, Both relative paths and absolute paths can be used.', 17 | ], 18 | ], 19 | }, 20 | verify: false, 21 | }; 22 | -------------------------------------------------------------------------------- /src/commands-help/sync.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: `Synchronize online resources to offline resources. 4 | 5 | Examples with Yaml: 6 | $ s sync 7 | $ s sync --target-dir ./test --qualifier testAlias 8 | 9 | Examples with CLI: 10 | $ s cli fc3 sync --region cn-hangzhou --function-name test -a default 11 | $ s cli fc3 sync --region cn-hangzhou --function-name s1\\$f1 -a default`, 12 | summary: 'Synchronize online resources to offline resources', 13 | option: [ 14 | [ 15 | '--region ', 16 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 17 | ], 18 | ['--function-name ', '[C-Required] Specify function name'], 19 | [ 20 | '--target-dir ', 21 | '[Optional] [Optional] Specify storage directory, default directory is ./sync-clone', 22 | ], 23 | ['--qualifier ', '[Optional] Specify version or alias, default is LATEST'], 24 | ], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/commands-help/version.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | help: { 3 | description: 'Function version operation ', 4 | summary: 'Function version operation ', 5 | }, 6 | subCommands: { 7 | list: { 8 | help: { 9 | description: `View the list of function versions. 10 | 11 | Examples with Yaml: 12 | $ s version list 13 | 14 | Examples with CLI: 15 | $ s cli fc3 version list --region cn-hangzhou --function-name test -a default`, 16 | summary: 'View the list of function versions', 17 | option: [ 18 | [ 19 | '--region ', 20 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 21 | ], 22 | ['--function-name ', '[C-Required] Specify function name'], 23 | ], 24 | }, 25 | }, 26 | publish: { 27 | help: { 28 | description: `Publish function version. 29 | 30 | Examples with Yaml: 31 | $ s version publish 32 | $ s version publish --description "test desc" 33 | 34 | Examples with CLI: 35 | $ s cli fc3 version publish --description "test desc" --region cn-hangzhou --function-name test -a default`, 36 | summary: 'Publish function version', 37 | option: [ 38 | [ 39 | '--region ', 40 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 41 | ], 42 | ['--function-name ', '[C-Required] Specify function name'], 43 | ['--description ', '[Optional] Specify version description'], 44 | ], 45 | }, 46 | }, 47 | remove: { 48 | help: { 49 | description: `Remove function version. 50 | 51 | Examples with Yaml: 52 | $ s version remove --version-id 123 53 | $ s version remove --version-id 123 -y 54 | 55 | Examples with CLI: 56 | $ s cli fc3 version remove --version-id 123 --region cn-hangzhou --function-name test -a default`, 57 | summary: 'Remove function version', 58 | option: [ 59 | [ 60 | '--region ', 61 | '[C-Required] Specify fc region, you can see all supported regions in https://help.aliyun.com/document_detail/2512917.html', 62 | ], 63 | ['--function-name ', '[C-Required] Specify function name'], 64 | ['--version-id', '[Required] Specify the version-id or LATEST'], 65 | ['-y, --assume-yes', "[Optional] Don't ask, delete directly"], 66 | ], 67 | }, 68 | }, 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /src/constant.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export const IDE_VSCODE = 'vscode'; 4 | export const IDE_INTELLIJ = 'intellij'; 5 | export const FC3_DOMAIN_COMPONENT_NAME = 'fc3-domain'; 6 | 7 | export const SCHEMA_FILE_PATH = path.join(__dirname, 'schema.json'); 8 | -------------------------------------------------------------------------------- /src/default/config.ts: -------------------------------------------------------------------------------- 1 | // 非 custom 的默认配置 2 | export const FUNCTION_DEFAULT_CONFIG = { 3 | internetAccess: true, 4 | description: '', 5 | memorySize: 512, 6 | timeout: 3, 7 | }; 8 | 9 | // custom* 的默认配置 10 | export const FUNCTION_CUSTOM_DEFAULT_CONFIG = { 11 | internetAccess: true, 12 | description: '', 13 | instanceConcurrency: 1, 14 | memorySize: 512, 15 | timeout: 3, 16 | }; 17 | 18 | // 如果函数资源为空时使用默认配置,为了覆盖线上的配置 19 | export const FC_RESOURCES_EMPTY_CONFIG = { 20 | vpcConfig: { 21 | securityGroupId: '', 22 | vSwitchIds: [], 23 | vpcId: '', 24 | }, 25 | tracingConfig: {}, 26 | ossMountConfig: { 27 | mountPoints: [], 28 | }, 29 | nasConfig: { 30 | groupId: 0, 31 | userId: 0, 32 | mountPoints: [], 33 | }, 34 | logConfig: { 35 | enableInstanceMetrics: false, 36 | enableRequestMetrics: false, 37 | logBeginRule: 'None', 38 | logstore: '', 39 | project: '', 40 | }, 41 | environmentVariables: {}, 42 | layers: [], 43 | }; 44 | 45 | export const FC_TRIGGER_DEFAULT_CONFIG = { 46 | qualifier: 'LATEST', 47 | description: '', 48 | }; 49 | 50 | export const IMAGE_ACCELERATION_REGION = [ 51 | 'cn-hangzhou', 52 | 'cn-shanghai', 53 | 'cn-beijing', 54 | 'cn-zhangjiakou', 55 | 'cn-shenzhen', 56 | 'cn-hongkong', 57 | 'ap-southeast-1', 58 | 'ap-northeast-1', 59 | 'us-east-1', 60 | 'us-west-1', 61 | 'cn-huhehaote', 62 | 'eu-central-1', 63 | ]; 64 | 65 | export const FC_CLIENT_CONNECT_TIMEOUT: number = 66 | parseInt(process.env.FC_CLIENT_CONNECT_TIMEOUT || '60', 10) * 1000; 67 | 68 | export const FC_CLIENT_READ_TIMEOUT: number = 69 | parseInt(process.env.FC_CLIENT_READ_TIMEOUT || '60', 10) * 1000; 70 | 71 | // seconds 72 | export const FC_INSTANCE_EXEC_TIMEOUT: number = parseInt( 73 | process.env.FC_INSTANCE_EXEC_TIMEOUT || '600', 74 | 10, 75 | ); 76 | 77 | export const FC_DEPLOY_RETRY_COUNT = 3; 78 | -------------------------------------------------------------------------------- /src/default/image.ts: -------------------------------------------------------------------------------- 1 | import { isChinaUser } from '@serverless-devs/utils'; 2 | 3 | const getDockerRegistry = () => { 4 | if (process.env.FC_DOCKER_REGISTRY) { 5 | return process.env.FC_DOCKER_REGISTRY; 6 | } 7 | 8 | if (isChinaUser()) { 9 | return 'registry.cn-beijing.aliyuncs.com'; 10 | } 11 | 12 | return 'registry.hub.docker.com'; 13 | }; 14 | 15 | export const fcDockerVersion = process.env.FC_DOCKER_VERSION || '3.1.0'; 16 | 17 | export const fcDockerVersionRegistry = getDockerRegistry(); 18 | 19 | export const fcDockerNameSpace = 'aliyunfc'; 20 | 21 | export const fcDockerUseImage = process.env.FC_DOCKER_IMAGE_URL; 22 | 23 | export const buildPythonLocalPath = process.env.FC_BUILD_PYTHON_DIR || 'python'; 24 | -------------------------------------------------------------------------------- /src/default/resources.ts: -------------------------------------------------------------------------------- 1 | const defaultName = 'Alibaba-Fc-V3-Component-Generated'; 2 | export const VPC_AND_NAS_NAME = process.env.FC_GENERATE_VPC_AND_NAS_NAME || defaultName; 3 | 4 | const defaultLogStoreName = 'default-logs'; 5 | export const PROJECT = process.env.FC_GENERATE_PROJECT_NAME; 6 | export const LOG_STORE = process.env.FC_GENERATE_LOGSTORE_NAME || defaultLogStoreName; 7 | -------------------------------------------------------------------------------- /src/interface/async_invoke_config.ts: -------------------------------------------------------------------------------- 1 | export interface IDestination { 2 | destination?: string; 3 | } 4 | export interface IDestinationConfig { 5 | onFailure?: IDestination; 6 | onSuccess?: IDestination; 7 | } 8 | 9 | export interface IAsyncInvokeConfig { 10 | destinationConfig?: IDestinationConfig; 11 | maxAsyncEventAgeInSeconds?: number; 12 | maxAsyncRetryAttempts?: number; 13 | asyncTask?: boolean; 14 | qualifier?: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/interface/base.ts: -------------------------------------------------------------------------------- 1 | export interface IKV { 2 | [key: string]: string; 3 | } 4 | 5 | export type ICodeUri = 6 | | string 7 | | { 8 | src: string; 9 | ossBucketName: string; 10 | ossObjectName: string; 11 | }; 12 | 13 | export enum Protocol { 14 | HTTP = 'HTTP', 15 | HTTPS = 'HTTPS', 16 | 'HTTP,HTTPS' = 'HTTP,HTTPS', 17 | } 18 | 19 | export enum Runtime { 20 | 'nodejs8' = 'nodejs8', 21 | 'nodejs10' = 'nodejs10', 22 | 'nodejs12' = 'nodejs12', 23 | 'nodejs14' = 'nodejs14', 24 | 'nodejs16' = 'nodejs16', 25 | 'nodejs18' = 'nodejs18', 26 | 'nodejs20' = 'nodejs20', 27 | 'python3.12' = 'python3.12', 28 | 'python3.10' = 'python3.10', 29 | 'python3.9' = 'python3.9', 30 | 'python3' = 'python3', 31 | 'python2.7' = 'python2.7', 32 | 'java11' = 'java11', 33 | 'java8' = 'java8', 34 | 'go1' = 'go1', 35 | 'php7.2' = 'php7.2', 36 | 'dotnetcore3.1' = 'dotnetcore3.1', 37 | 'dotnetcore2.1' = 'dotnetcore2.1', 38 | 'custom.debian10' = 'custom.debian10', 39 | 'custom.debian11' = 'custom.debian11', 40 | 'custom.debian12' = 'custom.debian12', 41 | 'custom' = 'custom', 42 | 'custom-container' = 'custom-container', 43 | } 44 | 45 | export const RuntimeList = Object.values(Runtime); 46 | 47 | export enum TriggerType { 48 | oss = 'oss', 49 | log = 'log', 50 | timer = 'timer', 51 | eventbridge = 'eventbridge', 52 | http = 'http', 53 | mns_topic = 'mns_topic', 54 | cdn_events = 'cdn_events', 55 | tablestore = 'tablestore', 56 | } 57 | 58 | export enum Methods { 59 | HEAD = 'HEAD', 60 | DELETE = 'DELETE', 61 | POST = 'POST', 62 | GET = 'GET', 63 | OPTIONS = 'OPTIONS', 64 | PUT = 'PUT', 65 | PATCH = 'PATCH', 66 | } 67 | 68 | export enum OSSEvents { 69 | CREATED_ALL = 'oss:ObjectCreated:*', 70 | CREATED_PutObject = 'oss:ObjectCreated:PutObject', 71 | CREATED_PostObject = 'oss:ObjectCreated:PostObject', 72 | CREATED_CompleteMultipartUpload = 'oss:ObjectCreated:CompleteMultipartUpload', 73 | CREATED_PutSymlink = 'oss:ObjectCreated:PutSymlink', 74 | CREATED_CopyObject = 'oss:ObjectCreated:CopyObject', 75 | CREATED_InitiateMultipartUpload = 'oss:ObjectCreated:InitiateMultipartUpload', 76 | CREATED_UploadPart = 'oss:ObjectCreated:UploadPart', 77 | CREATED_UploadPartCopy = 'oss:ObjectCreated:UploadPartCopy', 78 | CREATED_AppendObject = 'oss:ObjectCreated:AppendObject', 79 | REMOVED_DeleteObject = 'oss:ObjectRemoved:DeleteObject', 80 | REMOVED_DeleteObjects = 'oss:ObjectRemoved:DeleteObjects', 81 | REMOVED_AbortMultipartUpload = 'oss:ObjectRemoved:AbortMultipartUpload', 82 | MODIFIED_UpdateObjectMeta = 'oss:ObjectModified:UpdateObjectMeta', 83 | } 84 | -------------------------------------------------------------------------------- /src/interface/cli-config/alias.ts: -------------------------------------------------------------------------------- 1 | export interface IAlias { 2 | aliasName: string; 3 | versionId: string; 4 | description?: string; 5 | additionalVersionWeight?: Record; // e.g.: {"2":0.05} 版本二占比 5% 灰度 6 | // routePolicy?: IAliasRoutePolicy; 7 | } 8 | 9 | /* 10 | e.g.: 11 | { 12 | "condition": "OR", 13 | "policyItems": [ 14 | { 15 | "type": "Param", 16 | "key": "xxx", 17 | "value": "xxxxx", 18 | "operator": "=" 19 | }, 20 | { 21 | "type": "Cookie", 22 | "key": "????", 23 | "value": "xxxx", 24 | "operator": ">=" 25 | }, 26 | { 27 | "type": "Header", 28 | "key": "xxxx", 29 | "value": "asasd", 30 | "operator": "=" 31 | } 32 | ] 33 | } 34 | */ 35 | // export interface IAliasRoutePolicy { 36 | // condition: 'AND' | 'OR'; 37 | // policyItems: IAliasRoutePolicyItem[]; 38 | // } 39 | 40 | // export interface IAliasRoutePolicyItem { 41 | // type: 'Param' | 'Cookie' | 'Header'; 42 | // key: string; 43 | // value: string; 44 | // operator: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'in' | 'percent'; 45 | // } 46 | -------------------------------------------------------------------------------- /src/interface/cli-config/concurrency.ts: -------------------------------------------------------------------------------- 1 | export interface IConcurrency { 2 | reservedConcurrency: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/interface/cli-config/provision.ts: -------------------------------------------------------------------------------- 1 | export interface IProvision { 2 | alwaysAllocateCPU?: boolean; 3 | alwaysAllocateGPU?: boolean; 4 | scheduledActions?: IProvisionScheduledAction[]; 5 | targetTrackingPolicies?: IProvisionTargetTrackingPolicy[]; 6 | target?: number; 7 | defaultTarget?: number; 8 | } 9 | 10 | export interface IProvisionScheduledAction { 11 | endTime?: string; 12 | name?: string; 13 | scheduleExpression?: string; 14 | startTime?: string; 15 | target?: number; 16 | } 17 | 18 | export interface IProvisionTargetTrackingPolicy { 19 | endTime?: string; 20 | maxCapacity?: number; 21 | metricTarget?: number; 22 | metricType?: string; 23 | minCapacity?: number; 24 | name?: string; 25 | startTime?: string; 26 | } 27 | -------------------------------------------------------------------------------- /src/interface/concurrency_config.ts: -------------------------------------------------------------------------------- 1 | export interface IConcurrencyConfig { 2 | reservedConcurrency: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/interface/index.ts: -------------------------------------------------------------------------------- 1 | import { IInputs as _IInputs, ICredentials } from '@serverless-devs/component-interface'; 2 | import { IFunction } from './function'; 3 | import { ITrigger } from './trigger'; 4 | import { IRegion } from './region'; 5 | import { IAsyncInvokeConfig } from './async_invoke_config'; 6 | import { IConcurrencyConfig } from './concurrency_config'; 7 | import { IProvisionConfig } from './provison_config'; 8 | 9 | export * from './region'; 10 | export * from './function'; 11 | export * from './trigger'; 12 | export * from './base'; 13 | export * from './async_invoke_config'; 14 | 15 | export interface IProps extends IFunction { 16 | region: IRegion; 17 | triggers?: ITrigger[]; 18 | asyncInvokeConfig?: IAsyncInvokeConfig; 19 | concurrencyConfig?: IConcurrencyConfig; 20 | provisionConfig?: IProvisionConfig; 21 | endpoint?: string; 22 | } 23 | 24 | export interface IInputs extends _IInputs { 25 | props: IProps; 26 | baseDir: string; 27 | credential?: ICredentials; 28 | userAgent?: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/interface/provison_config.ts: -------------------------------------------------------------------------------- 1 | export interface ScheduledAction { 2 | name: string; 3 | startTime: string; 4 | endTime: string; 5 | target: number; 6 | scheduleExpression: string; 7 | timeZone: string; 8 | } 9 | 10 | export interface TargetTrackingPolicy { 11 | name: string; 12 | startTime: string; 13 | endTime: string; 14 | metricType: string; 15 | metricTarget: number; 16 | minCapacity: number; 17 | maxCapacity: number; 18 | timeZone: string; 19 | } 20 | 21 | export interface IProvisionConfig { 22 | defaultTarget: number; 23 | alwaysAllocateCPU: boolean; 24 | alwaysAllocateGPU: boolean; 25 | scheduledActions: ScheduledAction[]; 26 | targetTrackingPolicies: TargetTrackingPolicy[]; 27 | } 28 | -------------------------------------------------------------------------------- /src/interface/region.ts: -------------------------------------------------------------------------------- 1 | enum Region { 2 | 'cn-hangzhou' = 'cn-hangzhou', 3 | 'cn-shanghai' = 'cn-shanghai', 4 | 'cn-qingdao' = 'cn-qingdao', 5 | 'cn-beijing' = 'cn-beijing', 6 | 'cn-zhangjiakou' = 'cn-zhangjiakou', 7 | 'cn-huhehaote' = 'cn-huhehaote', 8 | 'cn-shenzhen' = 'cn-shenzhen', 9 | 'ap-northeast-2' = 'ap-northeast-2', 10 | 'cn-chengdu' = 'cn-chengdu', 11 | 'cn-hongkong' = 'cn-hongkong', 12 | 'ap-southeast-1' = 'ap-southeast-1', 13 | 'ap-southeast-2' = 'ap-southeast-2', 14 | 'ap-southeast-3' = 'ap-southeast-3', 15 | 'ap-southeast-5' = 'ap-southeast-5', 16 | 'ap-southeast-7' = 'ap-southeast-7', 17 | 'ap-northeast-1' = 'ap-northeast-1', 18 | 'eu-central-1' = 'eu-central-1', 19 | 'eu-west-1' = 'eu-west-1', 20 | 'us-east-1' = 'us-east-1', 21 | 'us-west-1' = 'us-west-1', 22 | 'ap-south-1' = 'ap-south-1', 23 | 'cn-heyuan-acdr-1' = 'cn-heyuan-acdr-1', 24 | 'cn-wulanchabu' = 'cn-wulanchabu', 25 | 'cn-shanghai-finance-1' = 'cn-shanghai-finance-1', 26 | 'cn-shanghai-cloudspe' = 'cn-shanghai-cloudspe', 27 | } 28 | 29 | export type IRegion = `${Region}`; 30 | 31 | export const RegionList = Object.values(Region) as string[]; 32 | 33 | export function checkRegion(r: IRegion): boolean { 34 | if (!r) { 35 | throw new Error('Region not specified, please specify --region'); 36 | } 37 | if (!RegionList.includes(r)) { 38 | throw new Error(`Invalid region, The allowed regions are ${RegionList}`); 39 | } 40 | return true; 41 | } 42 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-spread */ 2 | class Logger { 3 | instance: any; 4 | 5 | log: (...args: unknown[]) => void; 6 | info: (...args: unknown[]) => void; 7 | debug: (...args: unknown[]) => void; 8 | warn: (...args: unknown[]) => void; 9 | write: (...args: unknown[]) => void; 10 | error: (...args: unknown[]) => void; 11 | output: (...args: unknown[]) => void; 12 | spin: (...args: unknown[]) => void; 13 | tips: (...args: unknown[]) => void; 14 | append: (...args: unknown[]) => void; 15 | tipsOnce: (...args: unknown[]) => void; 16 | warnOnce: (...args: unknown[]) => void; 17 | writeOnce: (...args: unknown[]) => void; 18 | 19 | _set = (logger) => { 20 | this.log = (...args) => logger.log.apply(logger, args); 21 | this.info = (...args) => logger.info.apply(logger, args); 22 | this.debug = (...args) => logger.debug.apply(logger, args); 23 | this.warn = (...args) => logger.warn.apply(logger, args); 24 | this.write = (...args) => logger.write.apply(logger, args); 25 | this.error = (...args) => logger.error.apply(logger, args); 26 | this.output = (...args) => logger.output.apply(logger, args); 27 | this.spin = (...args) => logger.spin.apply(logger, args); 28 | this.append = (...args) => logger.append.apply(logger, args); 29 | this.tips = (...args) => logger.tips.apply(logger, args); 30 | // 兼容性加入 31 | if (logger?.tipsOnce) this.tipsOnce = (...args) => logger.tipsOnce.apply(logger, args); 32 | if (logger?.warnOnce) this.warnOnce = (...args) => logger.warnOnce.apply(logger, args); 33 | if (logger?.writeOnce) this.writeOnce = (...args) => logger.writeOnce.apply(logger, args); 34 | this.instance = logger; 35 | }; 36 | } 37 | 38 | export default new Logger(); 39 | -------------------------------------------------------------------------------- /src/resources/fc/error-code.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export enum FC_API_ERROR_CODE { 4 | FunctionNotFound = 'FunctionNotFound', // 函数不存在 5 | FunctionAlreadyExists = 'FunctionAlreadyExists', // 函数已存在 6 | AccessDenied = 'AccessDenied', // 没有权限 7 | TriggerNotFound = 'TriggerNotFound', // 触发器不存在 8 | TriggerAlreadyExists = 'TriggerAlreadyExists', // 函数已存在 9 | AliasNotFound = 'AliasNotFound', // 别名不存在 10 | AliasAlreadyExists = 'AliasAlreadyExists', // 别名已存在 11 | } 12 | 13 | export const isSlsNotExistException = (project: string, logstore: string, ex) => { 14 | if ( 15 | _.startsWith( 16 | ex?.message, 17 | `InvalidArgument: code: 400, project '${project}' does not exist request id:`, 18 | ) 19 | ) { 20 | return true; 21 | } 22 | 23 | if ( 24 | _.startsWith( 25 | ex?.message, 26 | `InvalidArgument: code: 400, logstore '${logstore}' does not exist request id:`, 27 | ) 28 | ) { 29 | return true; 30 | } 31 | 32 | return false; 33 | }; 34 | 35 | export const isAccessDenied = (ex) => ex.statusCode === 403; 36 | 37 | export const isInvalidArgument = (ex) => ex.statusCode === 400; 38 | 39 | export const isFailedState = (ex) => { 40 | if (_.startsWith(ex.message, 'retry to wait function state ok failed reach 3 times')) { 41 | return true; 42 | } 43 | return false; 44 | }; 45 | -------------------------------------------------------------------------------- /src/resources/fc/impl/utils.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { INasConfig, IVpcConfig, ILogConfig, Runtime } from '../../../interface'; 3 | import { isAuto, isAutoVpcConfig } from '../../../utils'; 4 | import logger from '../../../logger'; 5 | import { isDebugMode } from '@serverless-devs/utils'; 6 | 7 | export function isCustomContainerRuntime(runtime: string): boolean { 8 | return runtime === Runtime['custom-container']; 9 | } 10 | 11 | export function isCustomRuntime(runtime: string): boolean { 12 | return ( 13 | runtime === Runtime.custom || 14 | runtime === Runtime['custom.debian10'] || 15 | runtime === Runtime['custom.debian11'] || 16 | runtime === Runtime['custom.debian12'] 17 | ); 18 | } 19 | 20 | /** 21 | * 获取线上资源配置 22 | */ 23 | export function getRemoteResourceConfig(remote) { 24 | const remoteNasConfig = _.get(remote, 'nasConfig') as INasConfig; 25 | const remoteVpcConfig = _.get(remote, 'vpcConfig') as IVpcConfig; 26 | const remoteLogConfig = _.get(remote, 'logConfig') as ILogConfig; 27 | const remoteRole = _.get(remote, 'role'); 28 | return { remoteNasConfig, remoteVpcConfig, remoteLogConfig, remoteRole }; 29 | } 30 | 31 | /** 32 | * 计算当前local那些资源是 auto 33 | * @returns 34 | */ 35 | export function computeLocalAuto(local) { 36 | const nasAuto = isAuto(local.nasConfig); 37 | const vpcAuto = isAutoVpcConfig(local.vpcConfig) || (!local.vpcConfig && nasAuto); 38 | const slsAuto = isAuto(local.logConfig); 39 | // auto 是在 preDeploy 和 plan 之间的阶段设置的,用于提示 40 | // 如果用户设置了 auto 会在 handlePreRun 方法变成 arn 41 | const roleAuto = 42 | isAuto(local.role) || (_.isNil(local.role) && !_.isEmpty(local?.ossMountConfig?.mountPoints)); 43 | return { nasAuto, vpcAuto, slsAuto, roleAuto }; 44 | } 45 | 46 | /** 47 | * 处理自定义 endpoint 48 | * @returns 49 | */ 50 | export const getCustomEndpoint = ( 51 | endpoint?: string, 52 | ): { host?: string; endpoint?: string; protocol?: string } => { 53 | const CUSTOM_ENDPOINT = endpoint || process.env.FC_CLIENT_CUSTOM_ENDPOINT; 54 | logger.debug(`get custom endpoint: ${CUSTOM_ENDPOINT}`); 55 | 56 | let protocol = 'https'; 57 | if (isDebugMode()) { 58 | protocol = 'http'; 59 | } 60 | 61 | if (!CUSTOM_ENDPOINT) { 62 | return { protocol }; 63 | } 64 | 65 | // logger.info(`get custom endpoint: ${CUSTOM_ENDPOINT}`); 66 | 67 | if (CUSTOM_ENDPOINT.startsWith('http://')) { 68 | return { 69 | protocol: 'http', 70 | host: CUSTOM_ENDPOINT.replace('http://', ''), 71 | endpoint: CUSTOM_ENDPOINT, 72 | }; 73 | } 74 | 75 | if (CUSTOM_ENDPOINT.startsWith('https://')) { 76 | return { 77 | protocol: 'https', 78 | host: CUSTOM_ENDPOINT.replace('https://', ''), 79 | endpoint: CUSTOM_ENDPOINT, 80 | }; 81 | } 82 | 83 | return { 84 | protocol: 'https', 85 | host: endpoint, 86 | endpoint: `https://${endpoint}`, 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /src/resources/ram/index.ts: -------------------------------------------------------------------------------- 1 | import { ICredentials } from '@serverless-devs/component-interface'; 2 | import Ram from '@serverless-cd/srm-aliyun-ram20150501'; 3 | import { Config } from '@alicloud/openapi-client'; 4 | import _ from 'lodash'; 5 | import logger from '../../logger'; 6 | 7 | export default class Role { 8 | static isRoleArnFormat = (role: string): boolean => /^acs:ram::\d+:role\/.+/.test(role); 9 | 10 | static completionArn(role: string, accountID: string): string { 11 | if (!_.isString(role)) { 12 | logger.debug(`Role ${role} is not a string, skipping handle`); 13 | return role; 14 | } 15 | 16 | if (this.isRoleArnFormat(role)) { 17 | logger.debug(`Use role: ${role}`); 18 | return role; 19 | } 20 | 21 | const arn = `acs:ram::${accountID}:role/${role}`; 22 | logger.debug(`Assemble role: ${arn}`); 23 | return arn; 24 | } 25 | } 26 | 27 | export class RamClient extends Ram { 28 | constructor(credentials: ICredentials) { 29 | const config = new Config({ 30 | accessKeyId: credentials.AccessKeyID, 31 | accessKeySecret: credentials.AccessKeySecret, 32 | securityToken: credentials.SecurityToken, 33 | endpoint: 'ram.aliyuncs.com', 34 | userAgent: 'serverless-devs', 35 | }); 36 | super(credentials.AccountID, config); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/resources/sls/index.ts: -------------------------------------------------------------------------------- 1 | import { ICredentials } from '@serverless-devs/component-interface'; 2 | import Sls20201230, { InitSlsRequest } from '@serverless-cd/srm-aliyun-sls20201230'; 3 | import { Config } from '@alicloud/openapi-client'; 4 | import { IRegion } from '../../interface'; 5 | import { PROJECT, LOG_STORE } from '../../default/resources'; 6 | import logger from '../../logger'; 7 | import getUuid from 'uuid-by-string'; 8 | 9 | export default class Sls { 10 | static generateProjectName = (region: IRegion, accountID: string): string => { 11 | let project = PROJECT || `serverless-${region}-${getUuid(accountID)}`; 12 | 13 | if (project.length > 63) { 14 | project = project.substring(0, 63); 15 | } 16 | 17 | return project; 18 | }; 19 | static generateLogstoreName = (): string => { 20 | return LOG_STORE; 21 | }; 22 | 23 | readonly client: Sls20201230; 24 | private accountID: string; 25 | 26 | constructor(private region: IRegion, credentials: ICredentials) { 27 | const config = new Config({ 28 | accessKeyId: credentials.AccessKeyID, 29 | accessKeySecret: credentials.AccessKeySecret, 30 | securityToken: credentials.SecurityToken, 31 | endpoint: `${region}.log.aliyuncs.com`, 32 | userAgent: 'serverless-devs', 33 | regionId: region, 34 | readTimeout: 60000, 35 | connectTimeout: 5000, 36 | }); 37 | this.client = new Sls20201230(config); 38 | this.accountID = credentials.AccountID; 39 | } 40 | 41 | async deploy(): Promise<{ project: string; logstore: string }> { 42 | const project = Sls.generateProjectName(this.region, this.accountID); 43 | const logstore = Sls.generateLogstoreName(); 44 | 45 | const request = new InitSlsRequest({ 46 | project, 47 | logstore, 48 | description: 'Generated by Aliyun fc3.x component', 49 | accountID: this.accountID, 50 | region: this.region, 51 | }); 52 | 53 | logger.debug(`init sls: ${JSON.stringify(request)}`); 54 | 55 | logger.spin( 56 | 'creating', 57 | 'sls', 58 | `region: ${this.region}; project: ${project}; logstore: ${logstore}`, 59 | ); 60 | await this.client.initSls(request); 61 | logger.spin( 62 | 'created', 63 | 'sls', 64 | `region: ${this.region}; project: ${project}; logstore: ${logstore}`, 65 | ); 66 | return { project, logstore }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/subCommands/build/impl/baseImageBuilder.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Builder } from './baseBuilder'; 3 | import logger from '../../../logger'; 4 | import { runCommand } from '../../../utils'; 5 | import { parseArgv } from '@serverless-devs/utils'; 6 | import { IInputs } from '../../../interface'; 7 | 8 | export class ImageBuilder extends Builder { 9 | protected opts: any; 10 | constructor(readonly inputs: IInputs) { 11 | super(inputs); 12 | const opts = parseArgv(inputs.args, { 13 | alias: { help: 'h', dockerfile: 'f' }, 14 | boolean: ['help'], 15 | string: ['dockerfile', 'context'], 16 | }); 17 | this.opts = opts; 18 | logger.debug(`ImageBuilder opts: ${JSON.stringify(opts)}`); 19 | } 20 | 21 | protected getBuildContext(): any { 22 | let dockerFile = path.join(this.getCodeUri(), 'Dockerfile'); 23 | let context = this.getCodeUri(); 24 | 25 | if (this.opts['dockerfile']) { 26 | dockerFile = this.opts['dockerfile']; 27 | dockerFile = path.isAbsolute(dockerFile) ? dockerFile : path.join(this.baseDir, dockerFile); 28 | context = path.dirname(dockerFile); 29 | } 30 | if (this.opts['context']) { 31 | context = this.opts['context']; 32 | context = path.isAbsolute(context) ? context : path.join(this.baseDir, context); 33 | } 34 | return { dockerFile, context }; 35 | } 36 | async runBuild() { 37 | logger.debug(`ImageDockerBuilder building ... ${JSON.stringify(this.inputs)}`); 38 | const { dockerFile, context } = this.getBuildContext(); 39 | const image = await this.getRuntimeBuildImage(); 40 | let dockerCmdStr = `docker build --platform linux/amd64 -t ${image} -f ${dockerFile} ${context}`; 41 | await runCommand(dockerCmdStr, runCommand.showStdout.inherit); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/subCommands/build/impl/imageBuiltKitBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ImageBuilder } from './baseImageBuilder'; 2 | import logger from '../../../logger'; 3 | import { runCommand } from '../../../utils'; 4 | import path from 'path'; 5 | 6 | export class ImageBuiltKitBuilder extends ImageBuilder { 7 | buildKitServerAddr = process.env.buildkitServerAddr || 'localhost'; 8 | buildKitServerPort = process.env.buildkitServerPort || 65360; 9 | async runBuild() { 10 | logger.debug(`ImageBuiltKitBuilder building ... ${JSON.stringify(this.inputs)}`); 11 | await this.mockDockerLogin(); 12 | 13 | const { dockerFileName, context } = this.getBuildContext(); 14 | const image = await this.getRuntimeBuildImage(); 15 | 16 | let buildCmdStr = `buildctl --addr tcp://${this.buildKitServerAddr}:${this.buildKitServerPort} build --no-cache --frontend dockerfile.v0 --local context=${context} --local dockerfile=${context} --opt filename=${dockerFileName} --output type=image,name=${image},push=true`; 17 | await runCommand(buildCmdStr, runCommand.showStdout.inherit); 18 | } 19 | 20 | protected getBuildContext(): any { 21 | let dockerFileName = 'Dockerfile'; 22 | let dockerFile = path.join(this.getCodeUri(), 'Dockerfile'); 23 | let context = this.getCodeUri(); 24 | 25 | if (this.opts['dockerfile']) { 26 | dockerFile = this.opts['dockerfile']; 27 | dockerFile = path.isAbsolute(dockerFile) ? dockerFile : path.join(this.baseDir, dockerFile); 28 | context = path.dirname(dockerFile); 29 | dockerFileName = path.basename(dockerFile); 30 | } 31 | if (this.opts['context']) { 32 | context = this.opts['context']; 33 | context = path.isAbsolute(context) ? context : path.join(this.baseDir, context); 34 | } 35 | return { dockerFileName, context }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/subCommands/build/impl/imageDockerBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ImageBuilder } from './baseImageBuilder'; 2 | import logger from '../../../logger'; 3 | import { runCommand } from '../../../utils'; 4 | 5 | export class ImageDockerBuilder extends ImageBuilder { 6 | async runBuild() { 7 | logger.debug(`ImageDockerBuilder building ... ${JSON.stringify(this.inputs)}`); 8 | const { dockerFile, context } = this.getBuildContext(); 9 | const image = await this.getRuntimeBuildImage(); 10 | let buildCmdStr = `docker build --platform linux/amd64 -t ${image} -f ${dockerFile} ${context}`; 11 | await runCommand(buildCmdStr, runCommand.showStdout.inherit); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/subCommands/build/impl/imageKanikoBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ImageBuilder } from './baseImageBuilder'; 2 | import logger from '../../../logger'; 3 | import { runCommand } from '../../../utils'; 4 | 5 | export class ImageKanikoBuilder extends ImageBuilder { 6 | async runBuild() { 7 | logger.debug(`ImageKanikoBuilder building ... ${JSON.stringify(this.inputs)}`); 8 | await this.mockDockerLogin(); 9 | 10 | const { dockerFile, context } = this.getBuildContext(); 11 | const image = await this.getRuntimeBuildImage(); 12 | let cmdStr = `executor --force=true --cache=false --use-new-run=true --dockerfile ${dockerFile} --context ${context} --destination ${image}`; 13 | 14 | await runCommand(cmdStr, runCommand.showStdout.inherit); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/subCommands/build/index.ts: -------------------------------------------------------------------------------- 1 | import { IInputs } from '../../interface'; 2 | 3 | import { ImageBuiltKitBuilder } from './impl/imageBuiltKitBuilder'; 4 | import { ImageDockerBuilder } from './impl/imageDockerBuilder'; 5 | import { ImageKanikoBuilder } from './impl/imageKanikoBuilder'; 6 | import { DefaultBuilder } from './impl/defaultBuilder'; 7 | import { Builder } from './impl/baseBuilder'; 8 | 9 | import logger from '../../logger'; 10 | 11 | export enum BuildType { 12 | ImageDocker = 'IAMGE_BULD_DOCKER', 13 | ImageBuildKit = 'IAMGE_BULD_KIT', 14 | ImageKaniko = 'IMAGE_BUILD_KANIKO', 15 | Default = 'DEFAULT', 16 | } 17 | 18 | export default class BuilderFactory { 19 | public static getBuilder(buildType: BuildType, inputs: IInputs): Builder { 20 | switch (buildType) { 21 | case BuildType.ImageDocker: 22 | return new ImageDockerBuilder(inputs); 23 | case BuildType.ImageBuildKit: 24 | return new ImageBuiltKitBuilder(inputs); 25 | case BuildType.ImageKaniko: 26 | return new ImageKanikoBuilder(inputs); 27 | case BuildType.Default: 28 | return new DefaultBuilder(inputs); 29 | default: 30 | logger.error(`Invalid buildType ${buildType}`); 31 | throw new Error(`Invalid buildType ${buildType}`); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/subCommands/concurrency/index.ts: -------------------------------------------------------------------------------- 1 | import { parseArgv } from '@serverless-devs/utils'; 2 | import commandsHelp from '../../commands-help/concurrency'; 3 | import { IInputs, IRegion, checkRegion } from '../../interface'; 4 | import logger from '../../logger'; 5 | import _ from 'lodash'; 6 | import FC from '../../resources/fc'; 7 | import { promptForConfirmOrDetails } from '../../utils'; 8 | 9 | const commandsList = Object.keys(commandsHelp.subCommands); 10 | 11 | export default class Concurrency { 12 | readonly subCommand: string; 13 | private region: IRegion; 14 | private functionName: string; 15 | private reservedConcurrency?: number; 16 | private fcSdk: FC; 17 | private yes: boolean; 18 | 19 | constructor(inputs: IInputs) { 20 | const { 21 | 'function-name': functionName, 22 | 'reserved-concurrency': reservedConcurrency, 23 | region, 24 | 'assume-yes': yes, 25 | _: subCommands, 26 | } = parseArgv(inputs.args, { 27 | alias: { 28 | 'assume-yes': 'y', 29 | }, 30 | boolean: ['y'], 31 | string: ['reserved-concurrency', 'function-name', 'region'], 32 | }); 33 | 34 | logger.debug('subCommands: ', subCommands); 35 | const subCommand = _.get(subCommands, '[0]'); 36 | if (!subCommand || !commandsList.includes(subCommand)) { 37 | throw new Error( 38 | `Command "${subCommand}" not found, Please use "s cli fc3 concurrency -h" to query how to use the command`, 39 | ); 40 | } 41 | 42 | this.region = region || _.get(inputs, 'props.region'); 43 | logger.debug(`region: ${this.region}`); 44 | checkRegion(this.region); 45 | this.functionName = functionName || _.get(inputs, 'props.functionName'); 46 | if (!this.functionName) { 47 | throw new Error('Function name not specified, please specify --function-name'); 48 | } 49 | 50 | this.reservedConcurrency = reservedConcurrency ? Number(reservedConcurrency) : undefined; 51 | logger.debug(`reservedConcurrency: ${reservedConcurrency}`); 52 | 53 | this.yes = !!yes; 54 | this.subCommand = subCommand; 55 | 56 | this.fcSdk = new FC(this.region, inputs.credential, { 57 | endpoint: inputs.props.endpoint, 58 | userAgent: `${ 59 | inputs.userAgent || 60 | `serverless-devs;Nodejs:${process.version};OS:${process.platform}-${process.arch}` 61 | }command:concurrency`, 62 | }); 63 | } 64 | 65 | async get() { 66 | return await this.fcSdk.getFunctionConcurrency(this.functionName); 67 | } 68 | 69 | async put() { 70 | if (!_.isNumber(this.reservedConcurrency)) { 71 | throw new Error( 72 | `ReservedConcurrency must be a number, got ${this.reservedConcurrency}. Please specify a number through --reserved-concurrency `, 73 | ); 74 | } 75 | return await this.fcSdk.putFunctionConcurrency(this.functionName, this.reservedConcurrency); 76 | } 77 | 78 | async remove() { 79 | if (!this.yes) { 80 | const y = await promptForConfirmOrDetails( 81 | `Are you sure you want to delete the ${this.functionName} function concurrency?`, 82 | ); 83 | if (!y) { 84 | logger.debug(`Skip remove ${this.functionName} function concurrency`); 85 | return; 86 | } 87 | } 88 | return await this.fcSdk.removeFunctionConcurrency(this.functionName); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/subCommands/deploy/impl/base.ts: -------------------------------------------------------------------------------- 1 | import { ICredentials } from '@serverless-devs/component-interface'; 2 | import { IInputs } from '../../../interface'; 3 | import FC from '../../../resources/fc'; 4 | 5 | export default abstract class Base { 6 | readonly fcSdk: FC; 7 | needDeploy: boolean | undefined; 8 | 9 | constructor(readonly inputs: IInputs, needDeploy: boolean | undefined) { 10 | this.needDeploy = needDeploy; 11 | this.fcSdk = new FC(inputs.props.region, inputs.credential as ICredentials, { 12 | endpoint: inputs.props.endpoint, 13 | userAgent: `${ 14 | inputs.userAgent || 15 | `Component:fc3;Nodejs:${process.version};OS:${process.platform}-${process.arch}` 16 | }command:deploy`, 17 | }); 18 | } 19 | 20 | abstract before(): Promise; 21 | abstract run(): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /src/subCommands/instance/index.ts: -------------------------------------------------------------------------------- 1 | import { parseArgv } from '@serverless-devs/utils'; 2 | import commandsHelp from '../../commands-help/instance'; 3 | import { IInputs, IRegion, checkRegion } from '../../interface'; 4 | import logger from '../../logger'; 5 | import _ from 'lodash'; 6 | import FC from '../../resources/fc'; 7 | 8 | const commandsList = Object.keys(commandsHelp.subCommands); 9 | 10 | export default class Instance { 11 | readonly subCommand: string; 12 | private region: IRegion; 13 | private fcSdk: FC; 14 | private opts: any; 15 | 16 | constructor(readonly inputs: IInputs) { 17 | const opts = parseArgv(inputs.args, { 18 | alias: { help: 'h', 'assume-yes': 'y' }, 19 | boolean: ['help', 'y'], 20 | string: ['region', 'function-name', 'qualifier'], 21 | }); 22 | logger.debug(`Instance opts: ${JSON.stringify(opts)}`); 23 | const { region, _: subCommands } = opts; 24 | 25 | logger.debug('subCommands: ', subCommands); 26 | const subCommand = _.get(subCommands, '[0]'); 27 | if (!subCommand || !commandsList.includes(subCommand)) { 28 | throw new Error( 29 | `Command "${subCommand}" not found, Please use "s cli fc3 instance -h" to query how to use the command`, 30 | ); 31 | } 32 | 33 | this.region = region || _.get(inputs, 'props.region'); 34 | checkRegion(this.region); 35 | logger.debug(`region: ${this.region}`); 36 | 37 | this.subCommand = subCommand; 38 | this.fcSdk = new FC(this.region, inputs.credential, { 39 | endpoint: inputs.props.endpoint, 40 | userAgent: `${ 41 | inputs.userAgent || 42 | `Component:fc3;Nodejs:${process.version};OS:${process.platform}-${process.arch}` 43 | }command:instance`, 44 | }); 45 | 46 | this.opts = opts; 47 | } 48 | 49 | async list() { 50 | const functionName = this.opts['function-name'] || _.get(this.inputs, 'props.functionName'); 51 | if (_.isEmpty(functionName)) { 52 | throw new Error('functionName not specified, please specify --function-name'); 53 | } 54 | const qualifier = this.opts.qualifier || 'LATEST'; 55 | const list = await this.fcSdk.listInstances(functionName, qualifier); 56 | return list; 57 | } 58 | 59 | /** 60 | * s instance exec --instance-id c-64fec1fc-27c4833c325445879a28 --cmd "ls -lh" 61 | * s instance exec --instance-id c-64fec1fc-27c4833c325445879a28 62 | * s instance exec --instance-id `s invoke | grep 'Invoke instanceId:' | sed 's/.*: //'` 63 | */ 64 | async exec() { 65 | const functionName = this.opts['function-name'] || _.get(this.inputs, 'props.functionName'); 66 | if (_.isEmpty(functionName)) { 67 | throw new Error('functionName not specified, please specify --function-name'); 68 | } 69 | const instanceId = this.opts['instance-id']; 70 | if (_.isEmpty(instanceId)) { 71 | throw new Error('instanceId not specified, please specify --instance-id'); 72 | } 73 | const qualifier = this.opts.qualifier || 'LATEST'; 74 | const cmd = this.opts.cmd as string; 75 | let rawData = []; 76 | if (cmd) { 77 | rawData = ['bash', '-c', cmd]; 78 | } else { 79 | rawData = ['bash', '-c', '(cd /code || cd / ) && bash']; 80 | } 81 | await this.fcSdk.instanceExec(functionName, instanceId, rawData, qualifier, true); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/invoke/customLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalInvoke } from './baseLocalInvoke'; 2 | import _ from 'lodash'; 3 | import { runCommand } from '../../../../utils'; 4 | import logger from '../../../../logger'; 5 | import * as portFinder from 'portfinder'; 6 | import chalk from 'chalk'; 7 | 8 | export class CustomLocalInvoke extends BaseLocalInvoke { 9 | getDebugArgs(): string { 10 | if (_.isFinite(this.getDebugPort())) { 11 | // TODO 参数支持自定义调试参数实现断点调试 12 | // 比如调试的是 node 编写的 custom runtime 函数, DebugArgs 可以和 nodejs runtime 的看齐 13 | } 14 | return ''; 15 | } 16 | 17 | // custom runtime 暂时不能 docker run -d 模式, 否则无法打印函数返回值 18 | // TODO 等新的 custom runtime 镜像 19 | async getLocalInvokeCmdStr(): Promise { 20 | const port = await portFinder.getPortPromise({ port: this.getCaPort() }); 21 | this.port = port; 22 | 23 | const mntStr = await this.getMountString(); 24 | const image = await this.getRuntimeRunImage(); 25 | const envStr = await this.getEnvString(); 26 | const nasStr = this.getNasMountString(); 27 | const dockerCmdStr = `docker run --name ${this.getContainerName()} --platform linux/amd64 --rm -p ${port}:${this.getCaPort()} --memory=${this.getMemorySize()}m ${mntStr} ${envStr} ${nasStr} ${image} --event '${this.getEventString()}'`; 28 | if (!_.isEmpty(this.getDebugArgs())) { 29 | if (this.debugIDEIsVsCode()) { 30 | await this.writeVscodeDebugConfig(); 31 | } 32 | } 33 | logger.debug(`${chalk.blue(dockerCmdStr)}\n`); 34 | return dockerCmdStr; 35 | } 36 | 37 | async runInvoke() { 38 | const cmdStr = await this.getLocalInvokeCmdStr(); 39 | await runCommand(cmdStr, runCommand.showStdout.inherit); 40 | } 41 | 42 | async getEnvString(): Promise { 43 | let envStr = await super.getEnvString(); 44 | // AGENT_SCRIPT 45 | let agent_script = ''; 46 | const { customRuntimeConfig } = this.inputs.props; 47 | if (!_.isEmpty(customRuntimeConfig)) { 48 | const { command } = customRuntimeConfig; 49 | const { args } = customRuntimeConfig; 50 | if (!_.isEmpty(command)) { 51 | agent_script += command.join(' '); 52 | } 53 | 54 | if (!_.isEmpty(args)) { 55 | agent_script += ` ${args.join(' ')}`; 56 | } 57 | 58 | if (!_.isEmpty(agent_script)) { 59 | envStr += ` -e "AGENT_SCRIPT=${agent_script}"`; 60 | } 61 | } 62 | // FC_SERVER_PORT 63 | envStr += ` -e "FC_SERVER_PORT=${this.getCaPort()}"`; 64 | return envStr; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/invoke/dotnetLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | import { BaseLocalInvoke } from './baseLocalInvoke'; 3 | import _ from 'lodash'; 4 | 5 | export class DotnetLocalInvoke extends BaseLocalInvoke { 6 | getDebugArgs(): string { 7 | if (_.isFinite(this.getDebugPort())) { 8 | if (this.getRuntime() === 'dotnetcore3.1') { 9 | return `DEBUG_OPTIONS=true`; 10 | } 11 | // TODO dotnetcore3.1, fc-docker also not support dotnetcore3.1 12 | } 13 | return ''; 14 | } 15 | 16 | async generateVscodeDebugConfig(): Promise { 17 | const codePath = await this.getCodeUri(); 18 | const debugPort = this.getDebugPort(); 19 | const functionName = this.getFunctionName(); 20 | return JSON.stringify( 21 | { 22 | version: '0.2.0', 23 | configurations: [ 24 | { 25 | name: `fc/${functionName}`, 26 | type: 'coreclr', 27 | request: 'attach', 28 | processName: 'dotnet', 29 | pipeTransport: { 30 | pipeProgram: 'sh', 31 | pipeArgs: [ 32 | '-c', 33 | `docker exec -i $(docker ps -q -f publish=${debugPort}) \${debuggerCommand}`, 34 | ], 35 | debuggerPath: '/vsdbg/vsdbg', 36 | pipeCwd: '${workspaceFolder}', 37 | }, 38 | windows: { 39 | pipeTransport: { 40 | pipeProgram: 'powershell', 41 | pipeArgs: [ 42 | '-c', 43 | `docker exec -i $(docker ps -q -f publish=${debugPort}) \${debuggerCommand}`, 44 | ], 45 | debuggerPath: '/vsdbg/vsdbg', 46 | pipeCwd: '${workspaceFolder}', 47 | }, 48 | }, 49 | sourceFileMap: { 50 | '/code': codePath, 51 | }, 52 | }, 53 | ], 54 | }, 55 | null, 56 | 4, 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/invoke/goLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalInvoke } from './baseLocalInvoke'; 2 | 3 | export class GoLocalInvoke extends BaseLocalInvoke {} 4 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/invoke/javaLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | import { IDE_INTELLIJ, IDE_VSCODE } from '../../../../constant'; 2 | import logger from '../../../../logger'; 3 | import { BaseLocalInvoke } from './baseLocalInvoke'; 4 | import _ from 'lodash'; 5 | 6 | export class JavaLocalInvoke extends BaseLocalInvoke { 7 | beforeInvoke(): boolean { 8 | const ret = super.beforeInvoke(); 9 | if (!ret) { 10 | return ret; 11 | } 12 | const debugIDEArray: string[] = [IDE_VSCODE, IDE_INTELLIJ]; 13 | if (_.isString(this.getDebugIDE()) && !debugIDEArray.includes(this.getDebugIDE())) { 14 | logger.error('java runtime debug only support vscode and intellij'); 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | getDebugArgs(): string { 21 | if (_.isFinite(this.getDebugPort())) { 22 | if (this.getRuntime() === 'java8') { 23 | return `FC_DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=${this.getDebugPort()}`; 24 | } 25 | if (this.getRuntime() === 'java11') { 26 | return `FC_DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:${this.getDebugPort()}`; 27 | } 28 | } 29 | return ''; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/invoke/nodejsLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalInvoke } from './baseLocalInvoke'; 2 | import _ from 'lodash'; 3 | import { IDE_VSCODE } from '../../../../constant'; 4 | import logger from '../../../../logger'; 5 | 6 | export class NodejsLocalInvoke extends BaseLocalInvoke { 7 | beforeInvoke(): boolean { 8 | const ret = super.beforeInvoke(); 9 | if (!ret) { 10 | return ret; 11 | } 12 | if (_.isString(this.getDebugIDE()) && this.getDebugIDE() !== IDE_VSCODE) { 13 | logger.error('nodejs runtime debug only support vscode'); 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | getDebugArgs(): string { 20 | if (_.isFinite(this.getDebugPort())) { 21 | if (this.getRuntime() === 'nodejs6') { 22 | return `FC_DEBUG_ARGS=--debug-brk=${this.getDebugPort()}`; 23 | } else { 24 | return `FC_DEBUG_ARGS=--inspect-brk=0.0.0.0:${this.getDebugPort()}`; 25 | } 26 | } 27 | return ''; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/invoke/phpLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalInvoke } from './baseLocalInvoke'; 2 | import _ from 'lodash'; 3 | import * as ip from 'ip'; 4 | import { IDE_VSCODE } from '../../../../constant'; 5 | import logger from '../../../../logger'; 6 | 7 | export class PhpLocalInvoke extends BaseLocalInvoke { 8 | beforeInvoke(): boolean { 9 | const ret = super.beforeInvoke(); 10 | if (!ret) { 11 | return ret; 12 | } 13 | if (_.isString(this.getDebugIDE()) && this.getDebugIDE() !== IDE_VSCODE) { 14 | logger.error('php runtime debug only support vscode'); 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | getDebugArgs(): string { 21 | const remoteIp = ip.address(); 22 | logger.debug(`using remote_ip ${remoteIp}`); 23 | if (_.isFinite(this.getDebugPort())) { 24 | return `FC_DEBUG_ARGS=remote_enable=1 remote_autostart=1 remote_port=${this.getDebugPort()} remote_host=${remoteIp}`; 25 | } 26 | return ''; 27 | } 28 | 29 | async generateVscodeDebugConfig(): Promise { 30 | const codePath = await this.getCodeUri(); 31 | const debugPort = this.getDebugPort(); 32 | const functionName = this.getFunctionName(); 33 | 34 | return JSON.stringify( 35 | { 36 | version: '0.2.0', 37 | configurations: [ 38 | { 39 | name: `fc/${functionName}`, 40 | type: 'php', 41 | request: 'launch', 42 | port: debugPort, 43 | stopOnEntry: false, 44 | pathMappings: { 45 | '/code': `${codePath}`, 46 | }, 47 | ignore: ['/var/fc/runtime/**'], 48 | }, 49 | ], 50 | }, 51 | null, 52 | 4, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/invoke/pythonLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalInvoke } from './baseLocalInvoke'; 2 | import _ from 'lodash'; 3 | import { IDE_VSCODE } from '../../../../constant'; 4 | import logger from '../../../../logger'; 5 | 6 | export class PythonLocalInvoke extends BaseLocalInvoke { 7 | beforeInvoke(): boolean { 8 | const ret = super.beforeInvoke(); 9 | if (!ret) { 10 | return ret; 11 | } 12 | const debugIDEArray: string[] = [IDE_VSCODE]; 13 | if (_.isString(this.getDebugIDE()) && !debugIDEArray.includes(this.getDebugIDE())) { 14 | logger.error('python runtime debug only support vscode'); 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | getDebugArgs(): string { 21 | if (_.isFinite(this.getDebugPort())) { 22 | // return `FC_DEBUG_ARGS=-m ptvsd --host 0.0.0.0 --port ${this.getDebugPort()} --wait`; 23 | return `FC_DEBUG_ARGS=-m debugpy --listen 0.0.0.0:${this.getDebugPort()} --wait-for-client`; 24 | } 25 | return ''; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/customContainerLocalStart.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalStart } from './baseLocalStart'; 2 | import _ from 'lodash'; 3 | import logger from '../../../../logger'; 4 | import * as portFinder from 'portfinder'; 5 | import { execSync } from 'child_process'; 6 | import { runCommand } from '../../../../utils'; 7 | import chalk from 'chalk'; 8 | 9 | export class CustomContainerLocalStart extends BaseLocalStart { 10 | getDebugArgs(): string { 11 | if (_.isFinite(this.getDebugPort())) { 12 | // TODO 参数支持自定义调试参数实现断点调试 13 | // 比如调试的是 node 编写的 custom container runtime 函数, DebugArgs 可以和 nodejs runtime 的看齐 14 | } 15 | return ''; 16 | } 17 | 18 | getBootStrap(): string { 19 | if (!this.isCustomContainerRuntime()) { 20 | throw new Error('only custom container get entrypoint and args'); 21 | } 22 | let bootStrap = ''; 23 | const { customContainerConfig } = this.inputs.props; 24 | if (_.has(customContainerConfig, 'entrypoint')) { 25 | bootStrap += customContainerConfig.entrypoint.join(' '); 26 | } 27 | if (_.has(customContainerConfig, 'command')) { 28 | bootStrap += ` ${customContainerConfig.command.join(' ')}`; 29 | } 30 | return bootStrap; 31 | } 32 | 33 | async getLocalStartCmdStr(): Promise { 34 | const port = await portFinder.getPortPromise({ port: this.getCaPort() }); 35 | const msg = `You can use curl or Postman to make an HTTP request to localhost:${port} to test the function.`; 36 | console.log(chalk.green(msg)); 37 | const mntStr = await this.getMountString(); 38 | const envStr = await this.getEnvString(); 39 | const nasStr = this.getNasMountString(); 40 | const image = await this.getRuntimeRunImage(); 41 | let dockerCmdStr = `docker run --platform linux/amd64 --rm -p ${port}:${this.getCaPort()} --memory=${this.getMemorySize()}m ${mntStr} ${envStr} ${nasStr} ${image}`; 42 | if (!_.isEmpty(this.getBootStrap())) { 43 | dockerCmdStr += ` ${this.getBootStrap()}`; 44 | } 45 | if (!_.isEmpty(this.getDebugArgs())) { 46 | if (this.debugIDEIsVsCode()) { 47 | await this.writeVscodeDebugConfig(); 48 | } 49 | } 50 | return dockerCmdStr; 51 | } 52 | 53 | async runStart() { 54 | const image = await this.getRuntimeRunImage(); 55 | process.on('DEVS:SIGINT', () => { 56 | console.log('\nDEVS:SIGINT, stop container'); 57 | const out = execSync(`docker ps -a | grep ${image} | awk '{print $1}' | xargs docker kill`); 58 | logger.debug(`stdout: ${out}`); 59 | process.exit(); 60 | }); 61 | const cmdStr = await this.getLocalStartCmdStr(); 62 | await runCommand(cmdStr, runCommand.showStdout.inherit); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/customLocalStart.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalStart } from './baseLocalStart'; 2 | import { runCommand } from '../../../../utils'; 3 | import _ from 'lodash'; 4 | import chalk from 'chalk'; 5 | // import logger from '../logger'; 6 | 7 | export class CustomLocalStart extends BaseLocalStart { 8 | getDebugArgs(): string { 9 | if (_.isFinite(this.getDebugPort())) { 10 | // TODO 参数支持自定义调试参数实现断点调试 11 | // 比如调试的是 node 编写的 custom runtime 函数, DebugArgs 可以和 nodejs runtime 的看齐 12 | } 13 | return ''; 14 | } 15 | 16 | async runStart() { 17 | const cmdStr = await this.getLocalStartCmdStr(); 18 | const msg = `You can use curl or Postman to make an HTTP request to localhost:${this.getCaPort()} to test the function`; 19 | console.log(chalk.green(msg)); 20 | await runCommand(cmdStr, runCommand.showStdout.ignore); 21 | } 22 | 23 | async getEnvString(): Promise { 24 | let envStr = await super.getEnvString(); 25 | // AGENT_SCRIPT 26 | let agent_script = ''; 27 | const { customRuntimeConfig } = this.inputs.props; 28 | if (!_.isEmpty(customRuntimeConfig)) { 29 | const { command } = customRuntimeConfig; 30 | const { args } = customRuntimeConfig; 31 | if (!_.isEmpty(command)) { 32 | agent_script += command.join(' '); 33 | } 34 | 35 | if (!_.isEmpty(args)) { 36 | agent_script += ` ${args.join(' ')}`; 37 | } 38 | 39 | if (!_.isEmpty(agent_script)) { 40 | envStr += ` -e "AGENT_SCRIPT=${agent_script}"`; 41 | } 42 | } 43 | // FC_SERVER_PORT 44 | envStr += ` -e "FC_SERVER_PORT=${this.getCaPort()}"`; 45 | return envStr; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/dotnetLocalStart.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | import { BaseLocalStart } from './baseLocalStart'; 3 | import _ from 'lodash'; 4 | 5 | export class DotnetLocalStart extends BaseLocalStart { 6 | getDebugArgs(): string { 7 | if (_.isFinite(this.getDebugPort())) { 8 | if (this.getRuntime() === 'dotnetcore3.1') { 9 | return `DEBUG_OPTIONS=true`; 10 | } 11 | // TODO dotnetcore3.1, fc-docker also not support dotnetcore3.1 12 | } 13 | return ''; 14 | } 15 | 16 | async generateVscodeDebugConfig(): Promise { 17 | const codePath = await this.getCodeUri(); 18 | const debugPort = this.getDebugPort(); 19 | const functionName = this.getFunctionName(); 20 | return JSON.stringify( 21 | { 22 | version: '0.2.0', 23 | configurations: [ 24 | { 25 | name: `fc/${functionName}`, 26 | type: 'coreclr', 27 | request: 'attach', 28 | processName: 'dotnet', 29 | pipeTransport: { 30 | pipeProgram: 'sh', 31 | pipeArgs: [ 32 | '-c', 33 | `docker exec -i $(docker ps -q -f publish=${debugPort}) \${debuggerCommand}`, 34 | ], 35 | debuggerPath: '/vsdbg/vsdbg', 36 | pipeCwd: '${workspaceFolder}', 37 | }, 38 | windows: { 39 | pipeTransport: { 40 | pipeProgram: 'powershell', 41 | pipeArgs: [ 42 | '-c', 43 | `docker exec -i $(docker ps -q -f publish=${debugPort}) \${debuggerCommand}`, 44 | ], 45 | debuggerPath: '/vsdbg/vsdbg', 46 | pipeCwd: '${workspaceFolder}', 47 | }, 48 | }, 49 | sourceFileMap: { 50 | '/code': codePath, 51 | }, 52 | }, 53 | ], 54 | }, 55 | null, 56 | 4, 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/goLocalInvoke.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalStart } from './baseLocalStart'; 2 | 3 | export class GoLocalStart extends BaseLocalStart {} 4 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/javaLocalStart.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalStart } from './baseLocalStart'; 2 | import _ from 'lodash'; 3 | import { IDE_INTELLIJ, IDE_VSCODE } from '../../../../constant'; 4 | import logger from '../../../../logger'; 5 | 6 | export class JavaLocalStart extends BaseLocalStart { 7 | beforeStart(): boolean { 8 | const ret = super.beforeStart(); 9 | if (!ret) { 10 | return ret; 11 | } 12 | const debugIDEArray: string[] = [IDE_VSCODE, IDE_INTELLIJ]; 13 | if (_.isString(this.getDebugIDE()) && !debugIDEArray.includes(this.getDebugIDE())) { 14 | logger.error('java runtime debug only support vscode and intellij'); 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | getDebugArgs(): string { 21 | if (_.isFinite(this.getDebugPort())) { 22 | if (this.getRuntime() === 'java8') { 23 | return `FC_DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=${this.getDebugPort()}`; 24 | } 25 | if (this.getRuntime() === 'java11') { 26 | return `FC_DEBUG_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:${this.getDebugPort()}`; 27 | } 28 | } 29 | return ''; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/nodejsLocalStart.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalStart } from './baseLocalStart'; 2 | import _ from 'lodash'; 3 | import { IDE_VSCODE } from '../../../../constant'; 4 | import logger from '../../../../logger'; 5 | 6 | export class NodejsLocalStart extends BaseLocalStart { 7 | beforeStart(): boolean { 8 | const ret = super.beforeStart(); 9 | if (!ret) { 10 | return ret; 11 | } 12 | if (_.isString(this.getDebugIDE()) && this.getDebugIDE() !== IDE_VSCODE) { 13 | logger.error('nodejs runtime debug only support vscode'); 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | getDebugArgs(): string { 20 | if (_.isFinite(this.getDebugPort())) { 21 | if (this.getRuntime() === 'nodejs6') { 22 | return `FC_DEBUG_ARGS=--debug-brk=${this.getDebugPort()}`; 23 | } else { 24 | return `FC_DEBUG_ARGS=--inspect-brk=0.0.0.0:${this.getDebugPort()}`; 25 | } 26 | } 27 | return ''; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/phpLocalStart.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import * as ip from 'ip'; 3 | import { IDE_VSCODE } from '../../../../constant'; 4 | import logger from '../../../../logger'; 5 | import { BaseLocalStart } from './baseLocalStart'; 6 | 7 | export class PhpLocalStart extends BaseLocalStart { 8 | beforeStart(): boolean { 9 | const ret = super.beforeStart(); 10 | if (!ret) { 11 | return ret; 12 | } 13 | if (_.isString(this.getDebugIDE()) && this.getDebugIDE() !== IDE_VSCODE) { 14 | logger.error('php runtime debug only support vscode'); 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | getDebugArgs(): string { 21 | const remoteIp = ip.address(); 22 | logger.debug(`using remote_ip ${remoteIp}`); 23 | if (_.isFinite(this.getDebugPort())) { 24 | return `FC_DEBUG_ARGS=remote_enable=1 remote_autostart=1 remote_port=${this.getDebugPort()} remote_host=${remoteIp}`; 25 | } 26 | return ''; 27 | } 28 | 29 | async generateVscodeDebugConfig(): Promise { 30 | const codePath = await this.getCodeUri(); 31 | const debugPort = this.getDebugPort(); 32 | const functionName = this.getFunctionName(); 33 | 34 | return JSON.stringify( 35 | { 36 | version: '0.2.0', 37 | configurations: [ 38 | { 39 | name: `fc/${functionName}`, 40 | type: 'php', 41 | request: 'launch', 42 | port: debugPort, 43 | stopOnEntry: false, 44 | pathMappings: { 45 | '/code': `${codePath}`, 46 | }, 47 | ignore: ['/var/fc/runtime/**'], 48 | }, 49 | ], 50 | }, 51 | null, 52 | 4, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/start/pythonLocalStart.ts: -------------------------------------------------------------------------------- 1 | import { BaseLocalStart } from './baseLocalStart'; 2 | import _ from 'lodash'; 3 | import { IDE_VSCODE } from '../../../../constant'; 4 | import logger from '../../../../logger'; 5 | 6 | export class PythonLocalStart extends BaseLocalStart { 7 | beforeStart(): boolean { 8 | const ret = super.beforeStart(); 9 | if (!ret) { 10 | return ret; 11 | } 12 | const debugIDEArray: string[] = [IDE_VSCODE]; 13 | if (_.isString(this.getDebugIDE()) && !debugIDEArray.includes(this.getDebugIDE())) { 14 | logger.error('python runtime debug only support vscode'); 15 | return false; 16 | } 17 | return true; 18 | } 19 | 20 | getDebugArgs(): string { 21 | if (_.isFinite(this.getDebugPort())) { 22 | return `FC_DEBUG_ARGS=-m debugpy --listen 0.0.0.0:${this.getDebugPort()} --wait-for-client`; 23 | } 24 | return ''; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/subCommands/local/impl/utils.ts: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import fs from 'fs'; 3 | import { promisify } from 'util'; 4 | import logger from '../../../logger'; 5 | 6 | export function getTimeZone(): string { 7 | const timeZone = `UTC+${0 - new Date().getTimezoneOffset() / 60}`; 8 | return timeZone; 9 | } 10 | 11 | export function formatJsonString(str: string): string { 12 | try { 13 | const jsonObj = JSON.parse(str); 14 | const formattedStr = JSON.stringify(jsonObj, null, 0); 15 | return formattedStr; 16 | } catch (e) { 17 | return str; 18 | } 19 | } 20 | 21 | export async function downloadFile(url: string, filename: string) { 22 | try { 23 | const file = fs.createWriteStream(filename); 24 | // eslint-disable-next-line @typescript-eslint/no-require-imports 25 | const pipeline = promisify(require('stream').pipeline); 26 | const response = await http.get(url); 27 | await pipeline(response, file); 28 | logger.info(`${url} ==> ${filename} has been downloaded.`); 29 | } catch (err) { 30 | logger.error(`Error downloading file: ${err}`); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/subCommands/logs/constant.ts: -------------------------------------------------------------------------------- 1 | export const TIME_ERROR_TIP = 2 | "The obtained time format is wrong. The time parameter can be a timestamp, or the format: 'yyyy-MM-ddTHH:mm:ssZ', such as '1623005699000', '2021-06-07T02:54:59+08:00', '2021-06-06T18:54:59Z'"; 3 | 4 | export const DATE_TIME_REG = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?Z?/; 5 | -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/cdn-CachedObjectsRefreshed.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventName": "CachedObjectsRefreshed", 5 | "eventVersion": "1.0.0", 6 | "eventSource": "cdn", 7 | "region": "cn-hangzhou", 8 | "eventTime": "2018-03-16T14:19:55+08:00", 9 | "traceId": "cf89e5a8-7d59-4bb5-a33e-4c3d08e25acf", 10 | "resource": { 11 | "domain": "example.com" 12 | }, 13 | "eventParameter": { 14 | "objectPath": [ 15 | "/2018/03/16/13/33b430c57e7.mp4", 16 | "/2018/03/16/14/4ff6b9bd54d.mp4" 17 | ], 18 | "createTime": 1521180769, 19 | "domain": "example.com", 20 | "completeTime": 1521180777, 21 | "objectType": "File", 22 | "taskId": 2089687230 23 | }, 24 | "userIdentity": { 25 | "aliUid": "1xxxxxxxxxx" 26 | } 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/cdn-CdnDomainAdded.json: -------------------------------------------------------------------------------- 1 | { "events": [ 2 | { 3 | "eventName": "CdnDomainStarted", 4 | "eventVersion": "1.0.0", 5 | "eventSource": "cdn", 6 | "region": "cn-hangzhou", 7 | "eventTime": "2018-03-16T14:19:55+08:00", 8 | "traceId": "cf89e5a8-7d59-4bb5-a33e-4c3d08e25acf", 9 | "resource": { 10 | "domain": "chongshi.alicdn.com" 11 | }, 12 | "eventParameter": { 13 | "domain": "chongshi.alicdn.com", 14 | }, 15 | "userIdentity": { 16 | "aliUid": "12345678" 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/cdn-CdnDomainStarted.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventName": "CdnDomainStarted", 5 | "eventVersion": "1.0.0", 6 | "eventSource": "cdn", 7 | "region": "cn-hangzhou", 8 | "eventTime": "2018-03-16T14:19:55+08:00", 9 | "traceId": "cf89e5a8-7d59-4bb5-a33e-4c3d08e25acf", 10 | "resource": { 11 | "domain": "chongshi.alicdn.com" 12 | }, 13 | "eventParameter": { 14 | "domain": "chongshi.alicdn.com", 15 | "status": "online" 16 | }, 17 | "userIdentity": { 18 | "aliUid": "12345678" 19 | } 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/cdn-LogFileCreated.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventName": "LogFileCreated", 5 | "eventSource": "cdn", 6 | "region": "cn-hangzhou", 7 | "eventVersion": "1.0.0", 8 | "eventTime": "2018-06-14T15:31:49+08:00", 9 | "traceId": "c6459282-6a4d-4413-894c-e4ea39686738", 10 | "userIdentity": { 11 | "aliUid": "1xxxxxxxxxxxx" 12 | }, 13 | "resource": { 14 | "domain": "example.com" 15 | }, 16 | "eventParameter": { 17 | "domain": "example.com", 18 | "endTime": 1528959900, 19 | "fileSize": 1788115, 20 | "filePath": "http://cdnlog.cn-hangzhou.oss.aliyun-inc.com/www.aliyun.com/2017_12_27/www.aliyun.com_2017_12_27_0800_0900.gz?OSSAccessKeyId=xxxx&Expires=xxxx&Signature=xxxx", 21 | "startTime": 1528959600 22 | }, 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/http.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v1", 3 | "rawPath": "/2023-03-30/functions/fc3-event-test-http-trigger-Darwin-x86_64/invocations", 4 | "headers": { 5 | "Accept": "application/json", 6 | "Authorization": "ACS3-HMAC-SHA256 Credential=LTA******************ibC,SignedHeaders=content-type;host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=c360d2c33196********901bef1cd44f2bd", 7 | "Connection": "keep-alive", 8 | "Content-Type": "application/octet-stream", 9 | "User-Agent": "AlibabaCloud (darwin; x64) Node.js/v14.20.0 Core/1.0.1 TeaDSL/1", 10 | "X-Acs-Action": "InvokeFunction", 11 | "X-Acs-Content-Sha256": "676903d7d4ba9666*********8877348e1b09a5dd13f", 12 | "X-Acs-Date": "2023-08-17T14:08:11Z", 13 | "X-Acs-Signature-Nonce": "2d13b9477e5e25d6b5c63fee372a9351", 14 | "X-Acs-Version": "2023-03-30", 15 | "X-Fc-Log-Type": "Tail" 16 | }, 17 | "queryParameters": {}, 18 | "body": "haha", 19 | "isBase64Encoded": false, 20 | "requestContext": { 21 | "accountId": "143**********149", 22 | "triggerId": "1e875ca5-8fd9-4893-b690-cc81c41e5bdc", 23 | "domainName": "143**********149.cn-huhehaote.fc.aliyuncs.com", 24 | "domainPrefix": "143**********149", 25 | "requestId": "", 26 | "time": "2023-08-17T14:08:11Z", 27 | "timeEpoch": "1692281291805", 28 | "http": { 29 | "method": "POST", 30 | "path": "/2023-03-30/functions/fc3-event-test-http-trigger-Darwin-x86_64/invocations", 31 | "protocol": "HTTP/1.1", 32 | "sourceIp": "112.10.236.5", 33 | "userAgent": "AlibabaCloud (darwin; x64) Node.js/v14.20.0 Core/1.0.1 TeaDSL/1" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/mns-stream.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "hello topic", 3 | "attrs": { 4 | "Extend": "{\"key\":\"value\"}" 5 | } 6 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/mns-with-MessageAttributes.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "value", 3 | "TopicOwner": "118620210433****", 4 | "Message": "hello topic", 5 | "Subscriber": "118620210433****", 6 | "PublishTime": 1550216302888, 7 | "SubscriptionName": "test-fc-subscribe", 8 | "MessageMD5": "BA4BA9B48AC81F0F9C66F6C909C3****", 9 | "TopicName": "test-topic", 10 | "MessageId": "2F5B3C281B283D4EAC694B742528****" 11 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/mns-without-MessageAttributes.json: -------------------------------------------------------------------------------- 1 | { 2 | "TopicOwner": "118620210433****", 3 | "Message": "hello topic", 4 | "Subscriber": "118620210433****", 5 | "PublishTime": 1550216480040, 6 | "SubscriptionName": "test-fc-subscribe", 7 | "MessageMD5": "BA4BA9B48AC81F0F9C66F6C909C3****", 8 | "TopicName": "test-topic", 9 | "MessageId": "2F5B3C082B923D4EAC694B76D928****" 10 | } 11 | -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/oss.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventName": "ObjectCreated:PutObject", 5 | "eventSource": "acs:oss", 6 | "eventTime": "2017-04-21T12:46:37.000Z", 7 | "eventVersion": "1.0", 8 | "oss": { 9 | "bucket": { 10 | "arn": "acs:oss:cn-shanghai:123456789:bucketname", 11 | "name": "testbucket", 12 | "ownerIdentity": "123456789", 13 | "virtualBucket": "" 14 | }, 15 | "object": { 16 | "deltaSize": 122539, 17 | "eTag": "688A7BF4F233DC9C88A80BF985AB7329", 18 | "key": "image/a.jpg", 19 | "size": 122539 20 | }, 21 | "ossSchemaVersion": "1.0", 22 | "ruleId": "9adac8e253828f4f7c0466d941fa3db81161****" 23 | }, 24 | "region": "cn-shanghai", 25 | "requestParameters": { 26 | "sourceIPAddress": "140.205.***.***" 27 | }, 28 | "responseElements": { 29 | "requestId": "58F9FF2D3DF792092E12044C" 30 | }, 31 | "userIdentity": { 32 | "principalId": "123456789" 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/sls.json: -------------------------------------------------------------------------------- 1 | { 2 | "parameter": {}, 3 | "source": { 4 | "endpoint": "http://cn-shanghai-intranet.log.aliyuncs.com", 5 | "projectName": "log-com", 6 | "logstoreName": "log-en", 7 | "shardId": 0, 8 | "beginCursor": "MTUyOTQ4MDIwOTY1NTk3ODQ2Mw==", 9 | "endCursor": "MTUyOTQ4MDIwOTY1NTk3ODQ2NA==" 10 | }, 11 | "jobName": "1f7043ced683de1a4e3d8d70b5a412843d817a39", 12 | "taskId": "c2691505-38da-4d1b-998a-f1d4bb8c9994", 13 | "cursorTime": 1529486425 14 | } -------------------------------------------------------------------------------- /src/subCommands/trigger-template/event-template/tablestore.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "string", 3 | "Records": [ 4 | { 5 | "Type": "string", 6 | "Info": { 7 | "Timestamp": "int64" 8 | }, 9 | "PrimaryKey": [ 10 | { 11 | "ColumnName": "string", 12 | "Value": "formated_value" 13 | } 14 | ], 15 | "Columns": [ 16 | { 17 | "Type": "string", 18 | "ColumnName": "string", 19 | "Value": "formated_value", 20 | "Timestamp": "int64" 21 | } 22 | ] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /src/utils/run-command.ts: -------------------------------------------------------------------------------- 1 | import { spawn, StdioOptions } from 'child_process'; 2 | import logger from '../logger'; 3 | 4 | enum COMMAND_STDIO { 5 | inherit = 'inherit', 6 | pipe = 'pipe', 7 | ignore = 'ignore', 8 | } 9 | 10 | async function runCommand( 11 | command: string, 12 | showStdout: COMMAND_STDIO, 13 | shellScript?: string, 14 | cwd?: string, 15 | ) { 16 | logger.info(`runCommand ===> \n${command} ${shellScript || ''}`); 17 | const [cmdStr, ...args] = command.split(' '); 18 | let cmd = cmdStr; 19 | if (cmd.includes('=') && args.length > 0) { 20 | const c = args.shift(); 21 | cmd = `${cmd} ${c}`; 22 | } 23 | logger.debug(`runCommand cmd = ${cmd}`); 24 | 25 | if (shellScript) { 26 | args.push(shellScript); 27 | // args.push(...shellScript.split(' ')); 28 | } 29 | logger.debug(`runCommand args = ${JSON.stringify(args)}`); 30 | 31 | console.log(''); // 独立出来一个空行,是日志看起来有一点结构行 32 | 33 | return new Promise((resolve, reject) => { 34 | const options = { 35 | shell: true, 36 | stdio: showStdout as StdioOptions, 37 | cwd, 38 | }; 39 | const dProcess = spawn(cmd, args, options); 40 | 41 | if (showStdout === COMMAND_STDIO.pipe) { 42 | dProcess.stdout.on('data', (data) => { 43 | if (showStdout === COMMAND_STDIO.pipe) { 44 | logger.append(data.toString()); 45 | } 46 | }); 47 | 48 | dProcess.stderr.on('data', (data) => { 49 | const warnErrorMsg = data.toString(); 50 | if (showStdout === COMMAND_STDIO.pipe) { 51 | logger.append(warnErrorMsg); 52 | } 53 | }); 54 | } 55 | 56 | dProcess.on('close', (code) => { 57 | console.log(''); // 独立出来一个空行,是日志看起来有一点结构行 58 | if (code === 0) { 59 | resolve(); 60 | } else { 61 | reject(new Error(`command failed with code ${code}`)); 62 | } 63 | }); 64 | }); 65 | } 66 | 67 | runCommand.showStdout = COMMAND_STDIO; 68 | 69 | export default runCommand; 70 | -------------------------------------------------------------------------------- /src/utils/verify.ts: -------------------------------------------------------------------------------- 1 | import Ajv, { ErrorObject } from 'ajv'; 2 | 3 | import { IProps } from '../interface'; 4 | import logger from '../logger'; 5 | import { SCHEMA_FILE_PATH } from '../constant'; 6 | import { yellow } from 'chalk'; 7 | 8 | export default (props: IProps) => { 9 | // 注意:ncc 或者 esbuild 之后 __dirname 会变为 dist/ 10 | logger.debug(`Validating file path: ${SCHEMA_FILE_PATH}`); 11 | 12 | try { 13 | // eslint-disable-next-line @typescript-eslint/no-require-imports 14 | const schema = require(SCHEMA_FILE_PATH); 15 | const ajv = new Ajv({ 16 | allErrors: true, 17 | strictSchema: true, 18 | validateSchema: true, 19 | }); 20 | const valid = ajv.validate(schema, props); 21 | 22 | logger.debug(`validate status: ${valid}`); 23 | if (!valid) { 24 | logger.debug(`validate error: ${JSON.stringify(ajv.errors, null, 2)}`); 25 | logger.debug(yellow(`Valid function props error:`)); 26 | for (const error of ajv.errors as Array, unknown>>) { 27 | logger.debug(yellow(` ${error.instancePath}: ${error.message}`)); 28 | } 29 | logger.debug(' \n '); 30 | } 31 | } catch (ex) { 32 | logger.debug(`Validate Error: ${ex}`); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "resolveJsonModule": true, 6 | "lib": [ 7 | "es2015", 8 | "dom", 9 | "es6" 10 | ], 11 | "target": "es2015", 12 | "module": "commonjs", 13 | "moduleResolution": "node", 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true, 16 | "inlineSourceMap": true, 17 | "noImplicitThis": true, 18 | "noUnusedLocals": true, 19 | "stripInternal": true, 20 | "pretty": false, 21 | "declaration": true, 22 | "typeRoots": [ 23 | "./typings", 24 | "./node_modules/@types" 25 | ], 26 | "rootDir": "src", 27 | "outDir": "dist" 28 | }, 29 | "exclude": [ 30 | "node_modules", 31 | "dist", 32 | "__tests__" 33 | ], 34 | "include": [ 35 | "./src/**/*.ts", 36 | "src/**/*.json" 37 | ] 38 | } -------------------------------------------------------------------------------- /version.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsapp/fc3/7a2c54d5c0866b610f450e3aebb06b25aab905a6/version.md --------------------------------------------------------------------------------