├── test ├── invokeData.json ├── credentials ├── project │ ├── index.js │ ├── src │ │ ├── postHandler.js │ │ ├── getHandler.js │ │ └── ossTriggerHandler.js │ ├── package.json │ └── serverless.yml ├── aliyunCommand.js ├── response.json ├── serverless.js ├── .serverless │ ├── configuration-template-create.json │ └── configuration-template-update.json └── data.js ├── .travis.yml ├── bin └── npm-postinstall ├── .eslintignore ├── .gitignore ├── .npmignore ├── shared ├── utils.js ├── utils.test.js ├── visitor.js ├── validate.js └── validate.test.js ├── package ├── lib │ ├── cleanupServerlessDir.js │ ├── mergeServiceResources.js │ ├── writeFilesToDisk.js │ ├── prepareDeployment.test.js │ ├── prepareDeployment.js │ ├── writeFilesToDisk.test.js │ ├── mergeServiceResources.test.js │ ├── cleanupServerlessDir.test.js │ └── compileFunctions.js ├── aliyunPackage.js └── aliyunPackage.test.js ├── provider └── templates │ └── core-configuration-template.json ├── deploy ├── lib │ ├── loadTemplates.js │ ├── uploadArtifacts.js │ ├── loadTemplates.test.js │ ├── setupFunctions.js │ ├── setupRole.js │ ├── uploadArtifacts.test.js │ ├── setupService.js │ ├── setupFunctions.test.js │ └── setupEvents.js ├── aliyunDeploy.js └── aliyunDeployFunction.js ├── remove ├── lib │ ├── loadTemplates.js │ ├── getFunctionsAndService.js │ ├── removeLogProject.js │ ├── removeArtifacts.js │ ├── loadTemplates.test.js │ ├── removeRoleAndPolicies.js │ ├── removeFunctionsAndService.js │ ├── getFunctionsAndService.test.js │ ├── removeEvents.js │ ├── removeRoleAndPolicies.test.js │ ├── removeArtifacts.test.js │ ├── removeFunctionsAndService.test.js │ └── removeLogProject.test.js └── aliyunRemove.js ├── logs ├── aliyunLogs.js ├── lib │ ├── retrieveLogs.js │ └── retrieveLogs.test.js └── aliyunLogs.test.js ├── invoke ├── aliyunInvoke.js ├── lib │ ├── invokeFunction.js │ └── invokeFunction.test.js └── aliyunInvoke.test.js ├── info ├── aliyunInfo.js ├── aliyunInfo.test.js └── lib │ ├── displayServiceInfo.js │ └── displayServiceInfo.test.js ├── index.js ├── package.json ├── .eslintrc └── README.md /test/invokeData.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar" 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | -------------------------------------------------------------------------------- /bin/npm-postinstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../shared/visitor').sendDownloaded(); -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.debug.js 2 | *.min.js 3 | node_modules/* 4 | assets/scripts/lib/* 5 | test/project/node_modules/* 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules 3 | coverage 4 | package-lock.json 5 | test/project/.serverless 6 | .idea 7 | -------------------------------------------------------------------------------- /test/credentials: -------------------------------------------------------------------------------- 1 | [default] 2 | aliyun_access_key_secret = acccesskeysecret 3 | aliyun_access_key_id = accesskeyid 4 | aliyun_account_id = accountid 5 | -------------------------------------------------------------------------------- /test/project/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.postHandler = require('./src/postHandler'); 4 | module.exports.getHandler = require('./src/getHandler'); 5 | module.exports.ossTriggerHandler = require('./src/ossTriggerHandler'); -------------------------------------------------------------------------------- /test/project/src/postHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (event, context, callback) => { 4 | const response = { 5 | statusCode: 200, 6 | body: JSON.stringify({ 7 | event: JSON.parse(event.toString()), 8 | context: context 9 | }) 10 | }; 11 | 12 | callback(null, response); 13 | }; 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | 5 | # Coverage directory used by tools like istanbul 6 | coverage 7 | 8 | # Dependency directories 9 | node_modules 10 | 11 | # Optional npm cache directory 12 | .npm 13 | 14 | # Optional REPL history 15 | .node_repl_history 16 | 17 | package-lock.json 18 | 19 | # tests 20 | test 21 | 22 | **/*.test.js 23 | -------------------------------------------------------------------------------- /shared/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | setDefaults() { 7 | this.options.stage = _.get(this, 'options.stage') 8 | || 'dev'; 9 | this.options.region = _.get(this, 'options.region') 10 | || _.get(this, 'serverless.service.provider.region') 11 | || 'cn-shanghai'; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /test/project/src/getHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const moment = require('moment'); 4 | 5 | module.exports = (event, context, callback) => { 6 | const time = moment().format('YYYY-MM-DD:HH:mm:ss'); 7 | const response = { 8 | statusCode: 200, 9 | body: JSON.stringify({ 10 | message: `Hello, the current time is ${time}.` 11 | }) 12 | }; 13 | 14 | callback(null, response); 15 | }; 16 | -------------------------------------------------------------------------------- /test/project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-service", 3 | "version": "0.1.0", 4 | "description": "An example of making Aliyun http endpoints using Serverless Framework.", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "license": "MIT", 9 | "dependencies": { 10 | "moment": "^2.18.1" 11 | }, 12 | "devDependencies": { 13 | "serverless-aliyun-function-compute": "*" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package/lib/cleanupServerlessDir.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fse = require('fs-extra'); 5 | 6 | module.exports = { 7 | cleanupServerlessDir() { 8 | if (this.serverless.config.servicePath) { 9 | const serverlessDirPath = path.join(this.serverless.config.servicePath, '.serverless'); 10 | 11 | if (fse.pathExistsSync(serverlessDirPath)) { 12 | fse.removeSync(serverlessDirPath); 13 | } 14 | } 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /test/aliyunCommand.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // mock to test functionality in a command unrelated matter 4 | // this mean that not e.g. aliyunDeploy but the more abstract aliyunCommand can be used 5 | class AliyunCommand { 6 | constructor(serverless, options, testSubject) { 7 | this.options = options; 8 | this.serverless = serverless; 9 | this.provider = this.serverless.getProvider('aliyun'); 10 | 11 | Object.assign( 12 | this, 13 | testSubject); 14 | } 15 | } 16 | 17 | module.exports = AliyunCommand; 18 | -------------------------------------------------------------------------------- /provider/templates/core-configuration-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "Resources": { 3 | "sls-storage-bucket": { 4 | "Type": "ALIYUN::OSS:Bucket", 5 | "Properties": { 6 | "BucketName": "to-be-replaced-by-serverless", 7 | "Region": "to-be-replaced-by-serverless" 8 | } 9 | }, 10 | "sls-function-service": { 11 | "Type": "ALIYUN::FC::Service", 12 | "Properties": { 13 | "name": "to-be-replaced-by-serverless", 14 | "region": "to-be-replaced-by-serverless" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /deploy/lib/loadTemplates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | async loadTemplates() { 7 | const createFilePath = path.join(this.serverless.config.servicePath, 8 | '.serverless', 'configuration-template-create.json'); 9 | const updateFilePath = path.join(this.serverless.config.servicePath, 10 | '.serverless', 'configuration-template-update.json'); 11 | 12 | this.templates = { 13 | create: this.serverless.utils.readFileSync(createFilePath), 14 | update: this.serverless.utils.readFileSync(updateFilePath) 15 | }; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /deploy/lib/uploadArtifacts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async uploadArtifacts() { 5 | const objectId = this.provider.getStorageObjectId(); 6 | const object = this.templates.update.Resources[objectId].Properties; 7 | const bucket = this.templates.create.Resources[this.provider.getStorageBucketId()].Properties; 8 | 9 | this.serverless.cli.log(`Uploading ${object.ObjectName} to OSS bucket ${bucket.BucketName}...`); 10 | await this.provider.uploadObject(object.ObjectName, object.LocalPath); 11 | this.serverless.cli.log(`Uploaded ${object.ObjectName} to OSS bucket ${bucket.BucketName}`); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /remove/lib/loadTemplates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | async loadTemplates(provider) { 7 | const createFilePath = path.join(provider.serverless.config.servicePath, 8 | '.serverless', 'configuration-template-create.json'); 9 | const updateFilePath = path.join(provider.serverless.config.servicePath, 10 | '.serverless', 'configuration-template-update.json'); 11 | 12 | provider.templates = { 13 | create: provider.serverless.utils.readFileSync(createFilePath), 14 | update: provider.serverless.utils.readFileSync(updateFilePath) 15 | }; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /remove/lib/getFunctionsAndService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async getFunctionsAndService() { 5 | this.fcService = undefined; 6 | this.fcFunctions = []; 7 | 8 | await this.getService(); 9 | await this.getFunctions(); 10 | }, 11 | 12 | async getService() { 13 | const serviceName = this.provider.getServiceName(); 14 | this.fcService = await this.provider.getService(serviceName); 15 | }, 16 | 17 | async getFunctions() { 18 | if (!this.fcService) { 19 | return; 20 | } 21 | const serviceName = this.fcService.serviceName; 22 | this.fcFunctions = await this.provider.getFunctions(serviceName); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /package/lib/mergeServiceResources.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-use-before-define: 0 */ 4 | 5 | const _ = require('lodash'); 6 | 7 | module.exports = { 8 | mergeServiceResources() { 9 | const resources = this.serverless.service.resources; 10 | 11 | if ((typeof resources === 'undefined') || _.isEmpty(resources)) {return;} 12 | 13 | _.mergeWith( 14 | this.serverless.service.provider.compiledConfigurationTemplate, 15 | { Resources: resources && resources.resources }, 16 | mergeCustomizer); 17 | 18 | return; 19 | }, 20 | }; 21 | 22 | const mergeCustomizer = (objValue, srcValue) => { 23 | if (Array.isArray(objValue)) { 24 | return [...objValue, ...srcValue]; 25 | } 26 | return objValue; 27 | }; 28 | -------------------------------------------------------------------------------- /test/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "body": "{\"foo\":\"test\"}", 4 | "headers": { 5 | "X-Ca-Api-Gateway": "some-id", 6 | "X-Forwarded-For": "some-ip" 7 | }, 8 | "httpMethod": "POST", 9 | "isBase64Encoded": false, 10 | "path": "/baz", 11 | "pathParameters": { 12 | 13 | }, 14 | "queryParameters": { 15 | 16 | } 17 | }, 18 | "context": { 19 | "requestId": "some-id", 20 | "credentials": { 21 | "accessKeyId": "", 22 | "accessKeySecret": "", 23 | "securityToken": "" 24 | }, 25 | "function": { 26 | "name": "my-service-dev-postTest", 27 | "handler": "index.postHandler", 28 | "memory": 128, 29 | "timeout": 30 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /logs/aliyunLogs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validate = require('../shared/validate'); 4 | const utils = require('../shared/utils'); 5 | const retrieveLogs = require('./lib/retrieveLogs'); 6 | const { hooksWrap } = require('../shared/visitor'); 7 | 8 | class AliyunLogs { 9 | constructor(serverless, options) { 10 | this.serverless = serverless; 11 | this.options = options; 12 | this.provider = this.serverless.getProvider('aliyun'); 13 | 14 | Object.assign( 15 | this, 16 | validate, 17 | utils, 18 | retrieveLogs 19 | ); 20 | 21 | this.hooks = hooksWrap({ 22 | 'before:logs:logs': async () => { 23 | this.validate(); 24 | this.setDefaults(); 25 | }, 26 | 27 | 'logs:logs': async () => { 28 | await this.retrieveLogs(); 29 | }, 30 | }, 'logs'); 31 | } 32 | } 33 | 34 | module.exports = AliyunLogs; 35 | -------------------------------------------------------------------------------- /package/lib/writeFilesToDisk.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-use-before-define: 0 */ 4 | 5 | const path = require('path'); 6 | 7 | module.exports = { 8 | saveCreateTemplateFile() { 9 | const packagePath = 10 | path.join(this.serverless.config.servicePath || '.', '.serverless'); 11 | const filePath = path.join(packagePath,'configuration-template-create.json'); 12 | 13 | this.serverless.utils.writeFileSync(filePath, 14 | this.serverless.service.provider.compiledConfigurationTemplate); 15 | }, 16 | 17 | saveUpdateTemplateFile() { 18 | const packagePath = 19 | path.join(this.serverless.config.servicePath || '.', '.serverless'); 20 | const filePath = path.join(packagePath, 'configuration-template-update.json'); 21 | 22 | this.serverless.utils.writeFileSync(filePath, 23 | this.serverless.service.provider.compiledConfigurationTemplate); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /invoke/aliyunInvoke.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validate = require('../shared/validate'); 4 | const utils = require('../shared/utils'); 5 | const invokeFunction = require('./lib/invokeFunction'); 6 | const { hooksWrap } = require('../shared/visitor'); 7 | 8 | class AliyunInvoke { 9 | constructor(serverless, options) { 10 | this.serverless = serverless; 11 | this.options = options; 12 | this.provider = this.serverless.getProvider('aliyun'); 13 | 14 | Object.assign( 15 | this, 16 | validate, 17 | utils, 18 | invokeFunction 19 | ); 20 | 21 | this.hooks = hooksWrap({ 22 | 'before:invoke:invoke': async () => { 23 | await this.validate(); 24 | this.setDefaults(); 25 | }, 26 | 27 | 'invoke:invoke': async () => { 28 | await this.invokeFunction(); 29 | }, 30 | }, 'invoke'); 31 | } 32 | } 33 | 34 | module.exports = AliyunInvoke; 35 | -------------------------------------------------------------------------------- /info/aliyunInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validate = require('../shared/validate'); 4 | const utils = require('../shared/utils'); 5 | const displayServiceInfo = require('./lib/displayServiceInfo'); 6 | const { hooksWrap } = require('../shared/visitor'); 7 | 8 | class AliyunInfo { 9 | constructor(serverless, options) { 10 | this.serverless = serverless; 11 | this.options = options; 12 | this.provider = this.serverless.getProvider('aliyun'); 13 | 14 | Object.assign( 15 | this, 16 | validate, 17 | utils, 18 | displayServiceInfo 19 | ); 20 | 21 | this.hooks = hooksWrap({ 22 | 'before:info:info': async () => { 23 | await this.validate(); 24 | this.setDefaults(); 25 | }, 26 | 27 | // 'deploy:deploy': () => BbPromise.bind(this) 28 | // .then(this.displayServiceInfo), 29 | 30 | 'info:info': async () => { 31 | await this.displayServiceInfo(); 32 | }, 33 | }, 'info'); 34 | } 35 | } 36 | 37 | module.exports = AliyunInfo; 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AliyunProvider = require('./provider/aliyunProvider'); 4 | const AliyunPackage = require('./package/aliyunPackage'); 5 | const AliyunDeploy = require('./deploy/aliyunDeploy'); 6 | const AliyunDeployFunction = require('./deploy/aliyunDeployFunction'); 7 | const AliyunRemove = require('./remove/aliyunRemove'); 8 | const AliyunInvoke = require('./invoke/aliyunInvoke'); 9 | const AliyunLogs = require('./logs/aliyunLogs'); 10 | const AliyunInfo = require('./info/aliyunInfo'); 11 | 12 | class AliyunIndex { 13 | constructor(serverless, options) { 14 | this.serverless = serverless; 15 | this.options = options; 16 | 17 | this.serverless.pluginManager.addPlugin(AliyunProvider); 18 | this.serverless.pluginManager.addPlugin(AliyunPackage); 19 | this.serverless.pluginManager.addPlugin(AliyunDeploy); 20 | this.serverless.pluginManager.addPlugin(AliyunDeployFunction); 21 | this.serverless.pluginManager.addPlugin(AliyunRemove); 22 | this.serverless.pluginManager.addPlugin(AliyunInvoke); 23 | this.serverless.pluginManager.addPlugin(AliyunLogs); 24 | this.serverless.pluginManager.addPlugin(AliyunInfo); 25 | } 26 | } 27 | 28 | module.exports = AliyunIndex; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-aliyun-function-compute", 3 | "version": "1.2.6", 4 | "description": "Provider plugin for the Serverless Framework v1.x which adds support for Aliyun Function Compute", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "lint": "eslint --fix deploy info invoke logs package provider remove shared test index.js", 9 | "postinstall": "node bin/npm-postinstall" 10 | }, 11 | "keywords": [ 12 | "serverless", 13 | "framework", 14 | "function", 15 | "compute" 16 | ], 17 | "author": "Joyee Cheung ", 18 | "license": "MIT", 19 | "dependencies": { 20 | "@alicloud/cloudapi": "^1.1.0", 21 | "@alicloud/fc": "^1.1.0", 22 | "@alicloud/log": "^1.0.0", 23 | "@alicloud/ram": "^1.0.0", 24 | "ali-oss": "^4.8.0", 25 | "chalk": "^2.0.1", 26 | "ci-info": "^2.0.0", 27 | "co": "^4.6.0", 28 | "conf": "^6.0.1", 29 | "detect-mocha": "^0.1.0", 30 | "fs-extra": "^3.0.1", 31 | "ini": "^1.3.4", 32 | "lodash": "^4.17.15", 33 | "universal-analytics": "^0.4.20" 34 | }, 35 | "devDependencies": { 36 | "eslint": "^4.6.1", 37 | "jest": "^20.0.4", 38 | "sinon": "^2.4.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /shared/utils.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const setDefaults = require('./utils'); 6 | const AliyunProvider = require('../provider/aliyunProvider'); 7 | const Serverless = require('../test/serverless'); 8 | const AliyunCommand = require('../test/aliyunCommand'); 9 | 10 | describe('Utils', () => { 11 | let serverless; 12 | let aliyunCommand; 13 | 14 | beforeEach(() => { 15 | serverless = new Serverless(); 16 | serverless.setProvider('aliyun', new AliyunProvider(serverless, {})); 17 | aliyunCommand = new AliyunCommand(serverless, {}, setDefaults); 18 | }); 19 | 20 | describe('#setDefaults()', () => { 21 | it('should set default values for options if not provided', () => { 22 | aliyunCommand.setDefaults(); 23 | expect(aliyunCommand.options.stage).toEqual('dev'); 24 | expect(aliyunCommand.options.region).toEqual('cn-shanghai'); 25 | }); 26 | 27 | it('should set the options when they are provided', () => { 28 | aliyunCommand.options.stage = 'my-stage'; 29 | aliyunCommand.options.region = 'my-region'; 30 | 31 | aliyunCommand.setDefaults(); 32 | expect(aliyunCommand.options.stage).toEqual('my-stage'); 33 | expect(aliyunCommand.options.region).toEqual('my-region'); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/project/src/ossTriggerHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const oss = require('ali-oss').Wrapper; 4 | 5 | module.exports = (event, context, callback) => { 6 | const parsedEvent = JSON.parse(event); 7 | const ossEvent = parsedEvent.events[0]; 8 | // Required by OSS sdk: OSS region is prefixed with "oss-", e.g. "oss-cn-shanghai" 9 | const ossRegion = `oss-${ossEvent.region}`; 10 | // Create oss client. 11 | const client = new oss({ 12 | region: ossRegion, 13 | bucket: ossEvent.oss.bucket.name, 14 | // Credentials can be retrieved from context 15 | accessKeyId: context.credentials.accessKeyId, 16 | accessKeySecret: context.credentials.accessKeySecret, 17 | stsToken: context.credentials.securityToken 18 | }); 19 | 20 | const objKey = ossEvent.oss.object.key; 21 | console.log('Getting object: ', objKey); 22 | client.get(objKey).then(function(val) { 23 | const newKey = objKey.replace('source/', 'processed/'); 24 | return client.put(newKey, val.content).then(function (val) { 25 | console.log('Put object:', val); 26 | callback(null, val); 27 | }).catch(function (err) { 28 | console.error('Failed to put object: %j', err); 29 | callback(err); 30 | }); 31 | }).catch(function (err) { 32 | console.error('Failed to get object: %j', err); 33 | callback(err); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /remove/lib/removeLogProject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async removeLogProject(projectName, logStoreName){ 5 | if (!this.options['remove-logstore']) { 6 | this.serverless.cli.log(`Skip removing log project`); 7 | return; 8 | } 9 | if (! await this.provider.getLogProject(projectName)){ 10 | this.serverless.cli.log(`No log project to remove`); 11 | return; 12 | } 13 | if (await this.provider.getLogStore(projectName, logStoreName)){ 14 | this.serverless.cli.log(`Removing index from log project ${projectName} log store ${logStoreName}...`); 15 | await this.provider.deleteLogIndex(projectName, logStoreName); 16 | this.serverless.cli.log(`Removed index from log project ${projectName} log store ${logStoreName}`); 17 | this.serverless.cli.log(`Removing log store from log project ${projectName}...`); 18 | await this.provider.deleteLogStore(projectName, logStoreName); 19 | this.serverless.cli.log(`Removed log store from log project ${projectName}`); 20 | } 21 | else { 22 | this.serverless.cli.log(`No log store to remove`); 23 | } 24 | this.serverless.cli.log(`Removing log project ${projectName}...`); 25 | await this.provider.deleteLogProject(projectName); 26 | this.serverless.cli.log(`Removed log project ${projectName}`); 27 | }, 28 | } -------------------------------------------------------------------------------- /deploy/aliyunDeploy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validate = require('../shared/validate'); 4 | const utils = require('../shared/utils'); 5 | const loadTemplates = require('./lib/loadTemplates'); 6 | const setupService = require('./lib/setupService'); 7 | const uploadArtifacts = require('./lib/uploadArtifacts'); 8 | const setupFunctions = require('./lib/setupFunctions'); 9 | const setupEvents = require('./lib/setupEvents'); 10 | const setupRole = require('./lib/setupRole'); 11 | const { hooksWrap } = require('../shared/visitor'); 12 | 13 | class AliyunDeploy { 14 | constructor(serverless, options) { 15 | this.serverless = serverless; 16 | this.options = options; 17 | this.provider = this.serverless.getProvider('aliyun'); 18 | 19 | Object.assign( 20 | this, 21 | validate, 22 | utils, 23 | loadTemplates, 24 | setupService, 25 | uploadArtifacts, 26 | setupFunctions, 27 | setupEvents, 28 | setupRole); 29 | 30 | this.hooks = hooksWrap({ 31 | 'before:deploy:deploy': async () => { 32 | await this.validate(); 33 | this.setDefaults(); 34 | await this.loadTemplates(); 35 | }, 36 | 37 | 'deploy:deploy': async () => { 38 | await this.setupService(); 39 | await this.uploadArtifacts(); 40 | await this.setupFunctions(); 41 | await this.setupEvents(); 42 | } 43 | }, 'deploy'); 44 | } 45 | } 46 | 47 | module.exports = AliyunDeploy; 48 | -------------------------------------------------------------------------------- /package/lib/prepareDeployment.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | const AliyunProvider = require('../../provider/aliyunProvider'); 8 | const AliyunPackage = require('../aliyunPackage'); 9 | const Serverless = require('../../test/serverless'); 10 | const { ramRoleStatements } = require('../../test/data'); 11 | 12 | describe('PrepareDeployment', () => { 13 | let serverless; 14 | let aliyunPackage; 15 | 16 | beforeEach(() => { 17 | serverless = new Serverless(); 18 | serverless.service.service = 'my-service'; 19 | serverless.service.provider = { 20 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials'), 21 | ramRoleStatements: ramRoleStatements 22 | }; 23 | const options = { 24 | stage: 'dev', 25 | region: 'cn-shanghai', 26 | }; 27 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 28 | aliyunPackage = new AliyunPackage(serverless, options); 29 | }); 30 | 31 | describe('#prepareDeployment()', () => { 32 | it('should load the core configuration template into the serverless instance', () => { 33 | const expectedCompiledConfiguration = require( 34 | path.join(__dirname, '..', '..', 'test', '.serverless','configuration-template-create.json')); 35 | 36 | aliyunPackage.prepareDeployment(); 37 | expect(serverless.service.provider.compiledConfigurationTemplate) 38 | .toEqual(expectedCompiledConfiguration); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [2, 2], 4 | "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], 5 | "linebreak-style": [2, "unix"], 6 | "semi": [2, "always"], 7 | "strict": [2, "global"], 8 | "curly": 2, 9 | "eqeqeq": 2, 10 | "no-eval": 2, 11 | "guard-for-in": 2, 12 | "no-caller": 2, 13 | "no-else-return": 2, 14 | "no-eq-null": 2, 15 | "no-extend-native": 2, 16 | "no-extra-bind": 2, 17 | "no-floating-decimal": 2, 18 | "no-implied-eval": 2, 19 | "no-labels": 2, 20 | "no-with": 2, 21 | "no-loop-func": 1, 22 | "no-native-reassign": 2, 23 | "no-redeclare": [2, {"builtinGlobals": true}], 24 | "no-delete-var": 2, 25 | "no-shadow-restricted-names": 2, 26 | "no-undef-init": 2, 27 | "no-use-before-define": 2, 28 | "no-unused-vars": [2, {"args": "none"}], 29 | "no-undef": 2, 30 | "callback-return": [2, ["callback", "cb", "next"]], 31 | "global-require": 0, 32 | "no-console": 0, 33 | "generator-star-spacing": ["error", "after"], 34 | "require-yield": 0 35 | }, 36 | "env": { 37 | "es6": true, 38 | "node": true, 39 | "browser": true 40 | }, 41 | "globals": { 42 | "describe": true, 43 | "it": true, 44 | "before": true, 45 | "after": true, 46 | "xdescribe": true 47 | }, 48 | "parserOptions": { 49 | "ecmaVersion": 8, 50 | "sourceType": "script", 51 | "ecmaFeatures": { 52 | "jsx": true 53 | } 54 | }, 55 | "extends": "eslint:recommended" 56 | } 57 | -------------------------------------------------------------------------------- /invoke/lib/invokeFunction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const fs = require('fs-extra'); 5 | 6 | module.exports = { 7 | async invokeFunction() { 8 | try { 9 | const result = await this.invoke(); 10 | this.serverless.cli.log(result); 11 | } catch (ex) { 12 | this.serverless.cli.log(ex); 13 | } 14 | }, 15 | 16 | invoke() { 17 | const serviceName = this.provider.getServiceName(); 18 | const func = this.options.function; 19 | const funcObject = this.serverless.service.getFunction(func); 20 | const functionName = funcObject.name; 21 | 22 | let data, raw; 23 | if (this.options.path) { 24 | [data, raw] = this.getDataFromPath(this.options.path); 25 | } else if (this.options.data) { 26 | [data, raw] = this.getDataFromInput(this.options.data); 27 | } 28 | let displayedData = ''; 29 | if (data !== undefined) { 30 | displayedData = ' with ' + util.inspect(data); 31 | if (displayedData.indexOf('\n') !== -1) { 32 | displayedData = '\n' + displayedData; 33 | } 34 | } 35 | this.serverless.cli.log( 36 | `Invoking ${functionName} of ${serviceName}${displayedData}` 37 | ); 38 | return this.provider.invokeFunction(serviceName, functionName, raw); 39 | }, 40 | 41 | getDataFromInput(input) { 42 | let data; 43 | try { 44 | data = JSON.parse(input); 45 | } catch(e) { 46 | data = input; 47 | } 48 | return [data, input]; 49 | }, 50 | 51 | getDataFromPath(path) { 52 | return [ 53 | this.serverless.utils.readFileSync(path), 54 | fs.readFileSync(path) 55 | ]; 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /remove/aliyunRemove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validate = require('../shared/validate'); 4 | const utils = require('../shared/utils'); 5 | const getFunctionsAndService = require('./lib/getFunctionsAndService'); 6 | const removeEvents = require('./lib/removeEvents'); 7 | const removeFunctionsAndService = require('./lib/removeFunctionsAndService'); 8 | const removeArtifacts = require('./lib/removeArtifacts'); 9 | const removeRoleAndPolicies = require('./lib/removeRoleAndPolicies'); 10 | const serviceTemplate = require('./lib/loadTemplates'); 11 | const removeLogProject = require('./lib/removeLogProject'); 12 | const { hooksWrap } = require('../shared/visitor'); 13 | 14 | class AliyunRemove { 15 | constructor(serverless, options) { 16 | this.serverless = serverless; 17 | this.options = options; 18 | this.provider = this.serverless.getProvider('aliyun'); 19 | 20 | Object.assign( 21 | this, 22 | validate, 23 | utils, 24 | getFunctionsAndService, 25 | removeEvents, 26 | removeLogProject, 27 | removeFunctionsAndService, 28 | removeArtifacts, 29 | removeRoleAndPolicies 30 | ); 31 | 32 | this.hooks = hooksWrap({ 33 | 'before:remove:remove': async () => { 34 | this.validate(); 35 | this.setDefaults(); 36 | await serviceTemplate.loadTemplates(this); 37 | }, 38 | 39 | 'remove:remove': async () => { 40 | await this.getFunctionsAndService(); 41 | await this.removeEvents(); 42 | await this.removeFunctionsAndService(); 43 | await this.removeArtifacts(); 44 | } 45 | }, 'remove'); 46 | } 47 | } 48 | 49 | module.exports = AliyunRemove; 50 | -------------------------------------------------------------------------------- /deploy/lib/loadTemplates.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunDeploy = require('../aliyunDeploy'); 10 | const Serverless = require('../../test/serverless'); 11 | 12 | describe('UploadArtifacts', () => { 13 | let serverless; 14 | let aliyunDeploy; 15 | const servicePath = path.join(__dirname, '..', '..', 'test'); 16 | 17 | beforeEach(() => { 18 | serverless = new Serverless(); 19 | serverless.service.service = 'my-service'; 20 | serverless.service.provider = { 21 | name: 'aliyun', 22 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 23 | }; 24 | serverless.config = { servicePath }; 25 | const options = { 26 | stage: 'dev', 27 | region: 'cn-shanghai', 28 | }; 29 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 30 | aliyunDeploy = new AliyunDeploy(serverless, options); 31 | }); 32 | 33 | describe('#loadTemplates()', () => { 34 | it('should make the templates accessible', () => { 35 | const create = fs.readFileSync( 36 | path.join(servicePath, '.serverless', 'configuration-template-create.json'), 'utf8'); 37 | const update = fs.readFileSync( 38 | path.join(servicePath, '.serverless', 'configuration-template-update.json'), 'utf8'); 39 | const templates = { 40 | create: JSON.parse(create), 41 | update: JSON.parse(update) 42 | }; 43 | return aliyunDeploy.loadTemplates().then(() => { 44 | expect(aliyunDeploy.templates).toEqual(templates); 45 | }); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /remove/lib/removeArtifacts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async removeArtifacts() { 5 | this.bucket = undefined; 6 | this.objects = []; 7 | await this.getBucket(); 8 | await this.getObjectsToRemove(); 9 | await this.removeObjects(); 10 | await this.removeBucket(); 11 | }, 12 | 13 | async getBucket() { 14 | const bucketName = this.provider.getDeploymentBucketName(); 15 | this.bucket = await this.provider.getBucket(bucketName); 16 | }, 17 | 18 | async getObjectsToRemove() { 19 | if (!this.bucket) {return;} 20 | const bucketName = this.bucket.name; 21 | this.provider.resetOssClient(bucketName); 22 | const prefix = this.provider.getArtifactDirectoryPrefix(); 23 | this.objects = await this.provider.getObjects({ prefix }); 24 | }, 25 | 26 | async removeObjects() { 27 | if (!this.objects.length) { 28 | this.serverless.cli.log(`No artifacts to remove.`); 29 | return; 30 | } 31 | 32 | const bucketName = this.bucket.name; 33 | const names = this.objects.map((obj) => obj.name); 34 | this.serverless.cli.log(`Removing ${names.length} artifacts in OSS bucket ${bucketName}...`); 35 | await this.provider.deleteObjects(names); 36 | this.serverless.cli.log(`Removed ${names.length} artifacts in OSS bucket ${bucketName}`); 37 | }, 38 | 39 | async removeBucket() { 40 | if (!this.bucket) { 41 | this.serverless.cli.log(`No buckets to remove.`); 42 | return; 43 | } 44 | 45 | const bucketName = this.bucket.name; 46 | this.serverless.cli.log(`Removing OSS bucket ${bucketName}...`); 47 | await this.provider.deleteBucket(bucketName); 48 | this.serverless.cli.log(`Removed OSS bucket ${bucketName}`); 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /remove/lib/loadTemplates.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunRemove = require('../aliyunRemove'); 10 | const Serverless = require('../../test/serverless'); 11 | const serviceTemplate = require('./loadTemplates') 12 | 13 | describe('UploadArtifacts', () => { 14 | let serverless; 15 | let aliyunRemove; 16 | const servicePath = path.join(__dirname, '..', '..', 'test'); 17 | 18 | beforeEach(() => { 19 | serverless = new Serverless(); 20 | serverless.service.service = 'my-service'; 21 | serverless.service.provider = { 22 | name: 'aliyun', 23 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 24 | }; 25 | serverless.config = { servicePath }; 26 | const options = { 27 | stage: 'dev', 28 | region: 'cn-shanghai', 29 | }; 30 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 31 | aliyunRemove = new AliyunRemove(serverless, options); 32 | }); 33 | 34 | describe('#loadTemplates()', () => { 35 | it('should make the templates accessible', () => { 36 | const create = fs.readFileSync( 37 | path.join(servicePath, '.serverless', 'configuration-template-create.json'), 'utf8'); 38 | const update = fs.readFileSync( 39 | path.join(servicePath, '.serverless', 'configuration-template-update.json'), 'utf8'); 40 | const templates = { 41 | create: JSON.parse(create), 42 | update: JSON.parse(update) 43 | }; 44 | return serviceTemplate.loadTemplates(aliyunRemove).then(() => { 45 | expect(aliyunRemove.templates).toEqual(templates); 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /deploy/lib/setupFunctions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | async setupFunctions() { 7 | this.functions = _.filter(this.templates.update.Resources, 8 | (item) => this.provider.isFunctionType(item.Type)) 9 | .map((item) => item.Properties); 10 | this.functionMap = new Map(); 11 | 12 | await this.checkExistingFunctions(); 13 | await this.createOrUpdateFunctions(); 14 | }, 15 | 16 | async checkExistingFunctions() { 17 | return Promise.all(this.functions.map(async (func) => { 18 | const foundFunction = await this.provider.getFunction(func.service, func.name); 19 | this.functionMap.set(func.name, !!foundFunction); 20 | })); 21 | }, 22 | 23 | async createOrUpdateFunctions() { 24 | for (var i = 0; i < this.functions.length; i++) { 25 | const func = this.functions[i]; 26 | await this.createOrUpdateFunction(func); 27 | } 28 | }, 29 | 30 | async createOrUpdateFunction(func) { 31 | if (this.functionMap.get(func.name)) { 32 | this.serverless.cli.log(`Updating function ${func.name}...`); 33 | try { 34 | await this.provider.updateFunction(func.service, func.name, func); 35 | this.serverless.cli.log(`Updated function ${func.name}`); 36 | } catch (err) { 37 | this.serverless.cli.log(`Failed to update function ${func.name}!`); 38 | throw err; 39 | } 40 | return; 41 | } 42 | this.serverless.cli.log(`Creating function ${func.name}...`); 43 | try { 44 | await this.provider.createFunction(func.service, func.name, func); 45 | this.serverless.cli.log(`Created function ${func.name}`); 46 | } catch (err) { 47 | this.serverless.cli.log(`Failed to create function ${func.name}!`); 48 | throw err; 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /remove/lib/removeRoleAndPolicies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async removeRoleAndPolicies(roleName) { 5 | if (!this.options['remove-roles']) { 6 | return; 7 | } 8 | const roleSym = Symbol(roleName); 9 | const policySym = Symbol(roleName + '-policies'); 10 | this[roleSym] = undefined; 11 | this[policySym] = []; 12 | 13 | await this.getRamInfo(roleName, roleSym, policySym); 14 | await this.removePolicyIfExists(roleName, roleSym, policySym); 15 | await this.removeRoleIfExists(roleName, roleSym, policySym); 16 | }, 17 | 18 | async getRamInfo(roleName, roleSym, policySym) { 19 | const foundRole = await this.provider.getRole(roleName); 20 | if (!foundRole) { 21 | return; 22 | } 23 | this[roleSym] = foundRole; 24 | this[policySym] = await this.provider.getPoliciesForRole(roleName); 25 | }, 26 | 27 | async removePolicyIfExists(roleName, roleSym, policySym) { 28 | if (!this[roleSym] || !this[policySym].length) { 29 | return; 30 | } 31 | const role = this[roleSym]; 32 | const policies = this[policySym]; 33 | for (var i = 0; i < policies.length; i++) { 34 | const policyProps = policies[i]; 35 | const policyName = policyProps.PolicyName; 36 | this.serverless.cli.log(`Detaching RAM policy ${policyName} from ${roleName}...`); 37 | await this.provider.detachPolicyFromRole(role, policyProps); 38 | this.serverless.cli.log(`Detached RAM policy ${policyName} from ${roleName}`); 39 | } 40 | }, 41 | 42 | async removeRoleIfExists(roleName, roleSym, policySym) { 43 | if (!this[roleSym]) { 44 | return; 45 | } 46 | this.serverless.cli.log(`Removing RAM role ${roleName}...`); 47 | await this.provider.deleteRole(roleName); 48 | this.serverless.cli.log(`Removed RAM role ${roleName}`); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /remove/lib/removeFunctionsAndService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async removeFunctionsAndService() { 5 | this.serverless.cli.log('Removing functions...'); 6 | const execRoleName = this.provider.getExecRoleName(); 7 | this.logProjectSpec = this.templates.create.Resources[this.provider.getLogProjectId()].Properties; 8 | this.logStoreSpec = this.templates.create.Resources[this.provider.getLogStoreId()].Properties; 9 | this.logIndexSpec = this.templates.create.Resources[this.provider.getLogIndexId()].Properties; 10 | const slsProjectName = this.logProjectSpec.projectName; 11 | const slsLogStoreName = this.logStoreSpec.storeName; 12 | await this.removeFunctions(); 13 | await this.removeServiceIfExists(); 14 | await this.removeRoleAndPolicies(execRoleName); 15 | await this.removeLogProject(slsProjectName, slsLogStoreName); 16 | }, 17 | 18 | async removeFunctions() { 19 | if (!this.fcFunctions.length) { 20 | this.serverless.cli.log(`No functions to remove.`); 21 | return; 22 | } 23 | 24 | const serviceName = this.fcService.serviceName; 25 | for (var i = 0; i < this.fcFunctions.length; i++) { 26 | const func = this.fcFunctions[i]; 27 | const funcName = func.functionName; 28 | this.serverless.cli.log(`Removing function ${funcName} of service ${serviceName}...`); 29 | await this.provider.deleteFunction(serviceName, funcName); 30 | this.serverless.cli.log(`Removed function ${funcName} of service ${serviceName}`); 31 | } 32 | }, 33 | 34 | async removeServiceIfExists() { 35 | if (!this.fcService) { 36 | this.serverless.cli.log(`No services to remove.`); 37 | return; 38 | } 39 | const serviceName = this.fcService.serviceName; 40 | this.serverless.cli.log(`Removing service ${serviceName}...`); 41 | await this.provider.deleteService(serviceName); 42 | this.serverless.cli.log(`Removed service ${serviceName}`); 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /test/project/serverless.yml: -------------------------------------------------------------------------------- 1 | service: my-service 2 | 3 | provider: 4 | name: aliyun 5 | runtime: nodejs6 6 | credentials: ~/.aliyuncli/credentials # path must be absolute 7 | ramRoleStatements: 8 | - Effect: Allow 9 | Action: 10 | - oss:GetObject 11 | - oss:PutObject 12 | Resource: 13 | - '*' 14 | 15 | plugins: 16 | - serverless-aliyun-function-compute 17 | 18 | package: 19 | exclude: 20 | - package-lock.json 21 | - .gitignore 22 | - .git/** 23 | - node_modules/** # exclude all node_modules.... 24 | include: 25 | - node_modules/moment/** # except necessary ones 26 | excludeDevDependencies: false 27 | 28 | functions: 29 | postTest: 30 | handler: index.postHandler 31 | events: 32 | - http: 33 | path: /baz 34 | method: post 35 | requestMode: mapping 36 | bodyFormat: form 37 | parameters: 38 | - name: foo 39 | type: string 40 | location: body 41 | optional: true 42 | default: bar 43 | demo: bar 44 | description: foo 45 | 46 | getTest: 47 | handler: index.getHandler 48 | events: 49 | - http: 50 | path: /quo 51 | method: get 52 | requestMode: mapping 53 | postTest2: 54 | handler: index.postHandler 55 | events: 56 | - https: 57 | path: /baz2 58 | method: post 59 | 60 | getTest2: 61 | handler: index.getHandler 62 | events: 63 | - https: 64 | path: /quo2 65 | method: get 66 | ossTriggerTest: 67 | handler: index.ossTriggerHandler 68 | events: 69 | - oss: 70 | sourceArn: acs:oss:cn-shanghai:${env:ALIYUN_ACCOUNT}:my-service-resource 71 | triggerConfig: 72 | events: 73 | - oss:ObjectCreated:PostObject 74 | - oss:ObjectCreated:PutObject 75 | filter: 76 | key: 77 | prefix: source/ 78 | -------------------------------------------------------------------------------- /logs/lib/retrieveLogs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chalk = require('chalk'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = { 7 | async retrieveLogs() { 8 | this.logs = []; 9 | 10 | const data = await this.getLogs(); 11 | this.displayLogs(data); 12 | }, 13 | 14 | async getLogs() { 15 | const projectName = this.provider.getLogProjectName(); 16 | const storeName = this.provider.getLogStoreName(); 17 | const functionName = this.serverless.service.getFunction(this.options.function).name; 18 | const count = this.options.count; 19 | 20 | this.logs = await this.provider.getLogsIfAvailable(projectName, storeName, 1, { functionName }, count); 21 | return this.logs; 22 | }, 23 | 24 | displayLogs(data) { 25 | let message = ''; 26 | const service = this.serverless.service.service; 27 | const stage = this.options.stage; 28 | const region = this.options.region; 29 | 30 | // get all the service related information 31 | message += `${chalk.yellow.underline('Service Information')}\n`; 32 | message += `${chalk.yellow('service:')} ${service}\n`; 33 | message += `${chalk.yellow('stage:')} ${stage}\n`; 34 | message += `${chalk.yellow('region:')} ${region}\n`; 35 | 36 | message += '\n'; 37 | 38 | // get all the functions 39 | message += `${chalk.yellow.underline('Logs')}\n`; 40 | if (this.logs.length) { 41 | const grouped = _.groupBy(this.logs, 'functionName'); 42 | 43 | _.forEach(grouped, (logs, functionName) => { 44 | const serviceName = logs[0].serviceName; 45 | const func = `${serviceName}/${functionName}`; 46 | message += ` ${chalk.yellow(func)}\n`; 47 | logs.forEach((log) => { 48 | const time = new Date(parseInt(log.__time__) * 1000); 49 | message += ` - ${time.toISOString()}: ${log.message}\n`; 50 | }); 51 | }); 52 | } else { 53 | message += 'There are no logs to show\n'; 54 | } 55 | 56 | this.serverless.cli.consoleLog(message); 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /package/aliyunPackage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cleanupServerlessDir = require('./lib/cleanupServerlessDir'); 4 | const validate = require('../shared/validate'); 5 | const utils = require('../shared/utils'); 6 | const prepareDeployment = require('./lib/prepareDeployment'); 7 | const saveCreateTemplateFile = require('./lib/writeFilesToDisk'); 8 | const mergeServiceResources = require('./lib/mergeServiceResources'); 9 | const compileFunctions = require('./lib/compileFunctions'); 10 | const saveUpdateTemplateFile = require('./lib/writeFilesToDisk'); 11 | const { hooksWrap } = require('../shared/visitor'); 12 | 13 | class AliyunPackage { 14 | constructor(serverless, options) { 15 | this.serverless = serverless; 16 | this.options = options; 17 | this.provider = this.serverless.getProvider('aliyun'); 18 | 19 | Object.assign( 20 | this, 21 | cleanupServerlessDir, 22 | validate, 23 | utils, 24 | prepareDeployment, 25 | saveCreateTemplateFile, 26 | compileFunctions, 27 | mergeServiceResources, 28 | saveUpdateTemplateFile); 29 | 30 | this.hooks = hooksWrap({ 31 | 'package:cleanup': async () => { 32 | // TODO: change to async method 33 | this.cleanupServerlessDir(); 34 | }, 35 | 36 | 'before:package:initialize': async () => { 37 | this.validate(); 38 | this.setDefaults(); 39 | }, 40 | 41 | 'package:initialize': async () => { 42 | await this.prepareDeployment(); 43 | this.saveCreateTemplateFile(); 44 | }, 45 | 46 | 'package:compileFunctions': async () => { 47 | this.compileFunctions(); 48 | }, 49 | 50 | 'package:finalize': async () => { 51 | this.mergeServiceResources(); 52 | this.saveUpdateTemplateFile(); 53 | // TODO(joyeecheung): move the artifact to the path 54 | // specified by --package 55 | this.serverless.cli.log('Finished Packaging.'); 56 | }, 57 | }, 'package'); 58 | } 59 | } 60 | 61 | module.exports = AliyunPackage; 62 | -------------------------------------------------------------------------------- /package/lib/prepareDeployment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-use-before-define: 0 */ 4 | 5 | const _ = require('lodash'); 6 | 7 | module.exports = { 8 | prepareDeployment() { 9 | this.provider.initializeTemplate(); 10 | const deploymentTemplate = this.serverless.service.provider.compiledConfigurationTemplate; 11 | const resources = deploymentTemplate.Resources; 12 | 13 | // resource: oss bucket 14 | const bucketId = this.provider.getStorageBucketId(); 15 | resources[bucketId] = this.provider.getStorageBucketResource(); 16 | 17 | // resource: log project 18 | const logProjectId = this.provider.getLogProjectId(); 19 | const logProjectResource = this.provider.getLogProjectResource(); 20 | _.merge(resources, { [logProjectId]: logProjectResource}); 21 | 22 | // resource: log store 23 | const logStoreId = this.provider.getLogStoreId(); 24 | const logStoreResource = this.provider.getLogStoreResource(); 25 | _.merge(resources, { [logStoreId]: logStoreResource}); 26 | 27 | // resource: log index 28 | const logIndexId = this.provider.getLogIndexId(); 29 | const logIndexResource = this.provider.getLogIndexResource(); 30 | _.merge(resources, { [logIndexId]: logIndexResource}); 31 | 32 | // resource: exec role 33 | const execRoleId = this.provider.getExecRoleLogicalId(); 34 | const execResource = this.provider.getExecRoleResource(); 35 | this.provider.letExecRoleAccessLog(execResource); 36 | const ramRoleStatements = this.serverless.service.provider.ramRoleStatements; 37 | if (Array.isArray(ramRoleStatements)) { 38 | ramRoleStatements.forEach((stmt) => { 39 | // TODO: validation 40 | this.provider.addRamRoleStatementsToExecRole(execResource, stmt); 41 | }); 42 | } 43 | resources[execRoleId] = execResource; 44 | 45 | const serviceId = this.provider.getServiceId(); 46 | resources[serviceId] = this.provider.getServiceResource(); 47 | 48 | this.serverless.service.provider.compiledConfigurationTemplate = deploymentTemplate; 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /test/serverless.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | const getFuncName = Symbol('getfuncname'); 6 | 7 | process.env.ALIYUN_ACCOUNT = 'accountid'; 8 | 9 | // mock of the serverless instance 10 | class Serverless { 11 | constructor() { 12 | const sls = this; 13 | this.providers = {}; 14 | this.service = {}; 15 | this.service.getAllFunctions = function () { //eslint-disable-line 16 | return Object.keys(this.functions); 17 | }; 18 | this.service.getFunction = function (functionName) { //eslint-disable-line 19 | // NOTE the stage is always 'dev'! 20 | if (!this.functions[functionName]) { 21 | throw new Error(`Function "${functionName}" doesn't exist in this Service`); 22 | } 23 | this.functions[functionName] 24 | .name = sls.pluginManager[getFuncName](functionName); 25 | return this.functions[functionName]; 26 | }; 27 | this.utils = { 28 | writeFileSync() {}, 29 | readFileSync(path) { 30 | return JSON.parse(fs.readFileSync(path, 'utf8')); 31 | }, 32 | }; 33 | 34 | this.cli = { 35 | log() {}, 36 | consoleLog() {}, 37 | printDot() {}, 38 | }; 39 | this.plugins = []; 40 | this.pluginManager = { 41 | addPlugin: plugin => this.plugins.push(plugin), 42 | spawn(key) { 43 | if (key === 'package:function') { 44 | const func = this.cliOptions.function; 45 | sls.cli.log(`Packaging function: ${func}...`); 46 | sls.service.functions[func].package = { 47 | artifact: `${func}.zip` 48 | }; 49 | } 50 | }, 51 | setCliOptions(options) { 52 | this.cliOptions = options; 53 | }, 54 | [getFuncName](func) { 55 | const service = sls.service.service; 56 | const stage = this.cliOptions.stage; 57 | // const stage = this.cliOptions ? this.cliOptions.stage : 'dev'; 58 | return `${service}-${stage}-${func}`; 59 | } 60 | }; 61 | } 62 | 63 | setProvider(name, provider) { 64 | this.providers[name] = provider; 65 | } 66 | 67 | getProvider(name) { 68 | return this.providers[name]; 69 | } 70 | } 71 | 72 | module.exports = Serverless; 73 | -------------------------------------------------------------------------------- /logs/aliyunLogs.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | 7 | const AliyunProvider = require('../provider/aliyunProvider'); 8 | const AliyunLogs = require('./aliyunLogs'); 9 | const Serverless = require('../test/serverless'); 10 | 11 | describe('AliyunLogs', () => { 12 | let serverless; 13 | let options; 14 | let aliyunLogs; 15 | 16 | beforeEach(() => { 17 | serverless = new Serverless(); 18 | options = { 19 | stage: 'my-stage', 20 | region: 'my-region', 21 | function: 'postTest' 22 | }; 23 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 24 | serverless.pluginManager.setCliOptions(options); 25 | aliyunLogs = new AliyunLogs(serverless, options); 26 | }); 27 | 28 | describe('#constructor()', () => { 29 | it('should set the serverless instance', () => { 30 | expect(aliyunLogs.serverless).toEqual(serverless); 31 | }); 32 | 33 | it('should set options if provided', () => { 34 | expect(aliyunLogs.options).toEqual(options); 35 | }); 36 | 37 | it('should make the provider accessible', () => { 38 | expect(aliyunLogs.provider).toBeInstanceOf(AliyunProvider); 39 | }); 40 | 41 | describe('hooks', () => { 42 | let validateStub; 43 | let setDefaultsStub; 44 | let retrieveLogsStub; 45 | 46 | beforeEach(() => { 47 | validateStub = sinon.stub(aliyunLogs, 'validate') 48 | .returns(Promise.resolve()); 49 | setDefaultsStub = sinon.stub(aliyunLogs, 'setDefaults') 50 | .returns(); 51 | retrieveLogsStub = sinon.stub(aliyunLogs, 'retrieveLogs') 52 | .returns(Promise.resolve()); 53 | }); 54 | 55 | afterEach(() => { 56 | aliyunLogs.validate.restore(); 57 | aliyunLogs.setDefaults.restore(); 58 | aliyunLogs.retrieveLogs.restore(); 59 | }); 60 | 61 | it('should run "before:logs:logs" promise chain', () => aliyunLogs 62 | .hooks['before:logs:logs']().then(() => { 63 | expect(validateStub.calledOnce).toEqual(true); 64 | expect(setDefaultsStub.calledAfter(validateStub)).toEqual(true); 65 | })); 66 | 67 | it('should run "logs:logs" promise chain', () => aliyunLogs 68 | .hooks['logs:logs']().then(() => { 69 | expect(retrieveLogsStub.calledOnce).toEqual(true); 70 | })); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /invoke/aliyunInvoke.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | 7 | const AliyunProvider = require('../provider/aliyunProvider'); 8 | const AliyunInvoke = require('./aliyunInvoke'); 9 | const Serverless = require('../test/serverless'); 10 | 11 | describe('AliyunInvoke', () => { 12 | let serverless; 13 | let options; 14 | let aliyunInvoke; 15 | 16 | beforeEach(() => { 17 | serverless = new Serverless(); 18 | options = { 19 | stage: 'my-stage', 20 | region: 'my-region', 21 | }; 22 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 23 | serverless.pluginManager.setCliOptions(options); 24 | aliyunInvoke = new AliyunInvoke(serverless, options); 25 | }); 26 | 27 | describe('#constructor()', () => { 28 | it('should set the serverless instance', () => { 29 | expect(aliyunInvoke.serverless).toEqual(serverless); 30 | }); 31 | 32 | it('should set options if provided', () => { 33 | expect(aliyunInvoke.options).toEqual(options); 34 | }); 35 | 36 | it('should make the provider accessible', () => { 37 | expect(aliyunInvoke.provider).toBeInstanceOf(AliyunProvider); 38 | }); 39 | 40 | describe('hooks', () => { 41 | let validateStub; 42 | let setDefaultsStub; 43 | let invokeFunctionStub; 44 | 45 | beforeEach(() => { 46 | validateStub = sinon.stub(aliyunInvoke, 'validate') 47 | .returns(Promise.resolve()); 48 | setDefaultsStub = sinon.stub(aliyunInvoke, 'setDefaults') 49 | .returns(); 50 | invokeFunctionStub = sinon.stub(aliyunInvoke, 'invokeFunction') 51 | .returns(Promise.resolve()); 52 | }); 53 | 54 | afterEach(() => { 55 | aliyunInvoke.validate.restore(); 56 | aliyunInvoke.setDefaults.restore(); 57 | aliyunInvoke.invokeFunction.restore(); 58 | }); 59 | 60 | it('should run "before:invoke:invoke" promise chain', () => aliyunInvoke 61 | .hooks['before:invoke:invoke']().then(() => { 62 | expect(validateStub.calledOnce).toEqual(true); 63 | expect(setDefaultsStub.calledAfter(validateStub)).toEqual(true); 64 | })); 65 | 66 | it('should run "invoke:invoke" promise chain', () => aliyunInvoke 67 | .hooks['invoke:invoke']().then(() => { 68 | expect(invokeFunctionStub.calledOnce).toEqual(true); 69 | })); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /package/lib/writeFilesToDisk.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | const sinon = require('sinon'); 8 | 9 | const AliyunProvider = require('../../provider/aliyunProvider'); 10 | const AliyunPackage = require('../aliyunPackage'); 11 | const Serverless = require('../../test/serverless'); 12 | 13 | describe('WriteFilesToDisk', () => { 14 | let serverless; 15 | let aliyunPackage; 16 | let writeFileSyncStub; 17 | 18 | beforeEach(() => { 19 | serverless = new Serverless(); 20 | serverless.service.package = {}; 21 | serverless.service.service = 'my-service'; 22 | serverless.service.provider = { 23 | compiledConfigurationTemplate: { 24 | foo: 'bar', 25 | }, 26 | }; 27 | serverless.config = { 28 | servicePath: 'foo/my-service', 29 | }; 30 | const options = { 31 | stage: 'dev', 32 | region: 'cn-shanghai', 33 | }; 34 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 35 | aliyunPackage = new AliyunPackage(serverless, options); 36 | writeFileSyncStub = sinon.stub(aliyunPackage.serverless.utils, 'writeFileSync'); 37 | }); 38 | 39 | afterEach(() => { 40 | aliyunPackage.serverless.utils.writeFileSync.restore(); 41 | }); 42 | 43 | describe('#saveCreateTemplateFile()', () => { 44 | it('should write the template file into the services .serverless directory', () => { 45 | const createFilePath = path.join( 46 | aliyunPackage.serverless.config.servicePath, 47 | '.serverless', 48 | 'configuration-template-create.json' 49 | ); 50 | 51 | aliyunPackage.saveCreateTemplateFile(); 52 | expect(writeFileSyncStub.calledWithExactly( 53 | createFilePath, 54 | aliyunPackage.serverless.service.provider.compiledConfigurationTemplate 55 | )).toEqual(true); 56 | }); 57 | }); 58 | 59 | describe('#saveUpdateTemplateFile()', () => { 60 | it('should write the template file into the services .serverless directory', () => { 61 | const updateFilePath = path.join( 62 | aliyunPackage.serverless.config.servicePath, 63 | '.serverless', 64 | 'configuration-template-update.json' 65 | ); 66 | 67 | aliyunPackage.saveUpdateTemplateFile(); 68 | expect(writeFileSyncStub.calledWithExactly( 69 | updateFilePath, 70 | aliyunPackage.serverless.service.provider.compiledConfigurationTemplate 71 | )).toEqual(true); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /package/lib/mergeServiceResources.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const AliyunProvider = require('../../provider/aliyunProvider'); 6 | const AliyunPackage = require('../aliyunPackage'); 7 | const Serverless = require('../../test/serverless'); 8 | 9 | describe('MergeServiceResources', () => { 10 | let serverless; 11 | let aliyunPackage; 12 | 13 | beforeEach(() => { 14 | serverless = new Serverless(); 15 | serverless.service.service = 'my-service'; 16 | serverless.service.provider = { 17 | compiledConfigurationTemplate: {}, 18 | }; 19 | const options = { 20 | stage: 'dev', 21 | region: 'cn-shanghai', 22 | }; 23 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 24 | aliyunPackage = new AliyunPackage(serverless, options); 25 | }); 26 | 27 | it('should resolve if service resources are not defined', () => { 28 | aliyunPackage.mergeServiceResources(); 29 | expect(serverless.service.provider.compiledConfigurationTemplate) 30 | .toEqual({}); 31 | }); 32 | 33 | it('should resolve if service resources is empty', () => { 34 | serverless.service.resources = {}; 35 | 36 | aliyunPackage.mergeServiceResources(); 37 | expect(serverless.service.provider 38 | .compiledConfigurationTemplate).toEqual({}); 39 | }); 40 | 41 | it('should merge all the resources if provided', () => { 42 | serverless.service.provider.compiledConfigurationTemplate = { 43 | 'Resources': [ 44 | { 45 | name: 'resource1', 46 | type: 'type1', 47 | properties: { 48 | property1: 'value1', 49 | }, 50 | }, 51 | ], 52 | }; 53 | 54 | serverless.service.resources = { 55 | resources: [ 56 | { 57 | name: 'resource2', 58 | type: 'type2', 59 | properties: { 60 | property1: 'value1', 61 | }, 62 | }, 63 | ] 64 | }; 65 | 66 | const expectedResult = { 67 | 'Resources': [ 68 | { 69 | name: 'resource1', 70 | type: 'type1', 71 | properties: { 72 | property1: 'value1', 73 | }, 74 | }, 75 | { 76 | name: 'resource2', 77 | type: 'type2', 78 | properties: { 79 | property1: 'value1', 80 | }, 81 | }, 82 | ] 83 | }; 84 | 85 | aliyunPackage.mergeServiceResources(); 86 | expect(serverless.service.provider.compiledConfigurationTemplate) 87 | .toEqual(expectedResult); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /info/aliyunInfo.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | 7 | const AliyunProvider = require('../provider/aliyunProvider'); 8 | const AliyunInfo = require('./aliyunInfo'); 9 | const Serverless = require('../test/serverless'); 10 | 11 | describe('AliyunInfo', () => { 12 | let serverless; 13 | let options; 14 | let aliyunInfo; 15 | 16 | beforeEach(() => { 17 | serverless = new Serverless(); 18 | options = { 19 | stage: 'my-stage', 20 | region: 'my-region', 21 | }; 22 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 23 | serverless.pluginManager.setCliOptions(options); 24 | aliyunInfo = new AliyunInfo(serverless, options); 25 | }); 26 | 27 | describe('#constructor()', () => { 28 | it('should set the serverless instance', () => { 29 | expect(aliyunInfo.serverless).toEqual(serverless); 30 | }); 31 | 32 | it('should set options if provided', () => { 33 | expect(aliyunInfo.options).toEqual(options); 34 | }); 35 | 36 | it('should make the provider accessible', () => { 37 | expect(aliyunInfo.provider).toBeInstanceOf(AliyunProvider); 38 | }); 39 | 40 | describe('hooks', () => { 41 | let validateStub; 42 | let setDefaultsStub; 43 | let displayServiceInfoStub; 44 | 45 | beforeEach(() => { 46 | validateStub = sinon.stub(aliyunInfo, 'validate') 47 | .returns(Promise.resolve()); 48 | setDefaultsStub = sinon.stub(aliyunInfo, 'setDefaults') 49 | .returns(); 50 | displayServiceInfoStub = sinon.stub(aliyunInfo, 'displayServiceInfo') 51 | .returns(Promise.resolve()); 52 | }); 53 | 54 | afterEach(() => { 55 | aliyunInfo.validate.restore(); 56 | aliyunInfo.setDefaults.restore(); 57 | aliyunInfo.displayServiceInfo.restore(); 58 | }); 59 | 60 | it('should run "before:info:info" promise chain', () => aliyunInfo 61 | .hooks['before:info:info']().then(() => { 62 | expect(validateStub.calledOnce).toEqual(true); 63 | expect(setDefaultsStub.calledAfter(validateStub)).toEqual(true); 64 | })); 65 | 66 | // it('should run "deploy:deploy" promise chain', () => aliyunInfo 67 | // .hooks['deploy:deploy']().then(() => { 68 | // expect(displayServiceInfoStub.calledOnce).toEqual(true); 69 | // })); 70 | 71 | it('should run "info:info" promise chain', () => aliyunInfo 72 | .hooks['info:info']().then(() => { 73 | expect(displayServiceInfoStub.calledOnce).toEqual(true); 74 | })); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /deploy/aliyunDeployFunction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validate = require('../shared/validate'); 4 | const utils = require('../shared/utils'); 5 | const prepareDeployment = require('../package/lib/prepareDeployment'); 6 | const mergeServiceResources = require('../package/lib/mergeServiceResources'); 7 | const compileFunction = require('../package/lib/compileFunctions'); 8 | 9 | const setupService = require('./lib/setupService'); 10 | const uploadArtifacts = require('./lib/uploadArtifacts'); 11 | const setupFunctions = require('./lib/setupFunctions'); 12 | const setupEvents = require('./lib/setupEvents'); 13 | const setupRole = require('./lib/setupRole'); 14 | const { hooksWrap } = require('../shared/visitor'); 15 | 16 | class AliyunDeployFunction { 17 | constructor(serverless, options) { 18 | this.serverless = serverless; 19 | this.options = options; 20 | this.provider = this.serverless.getProvider('aliyun'); 21 | 22 | Object.assign( 23 | this, 24 | validate, 25 | utils, 26 | prepareDeployment, 27 | compileFunction, 28 | mergeServiceResources, 29 | setupService, 30 | uploadArtifacts, 31 | setupFunctions, 32 | setupEvents, 33 | setupRole 34 | ); 35 | 36 | this.hooks = hooksWrap({ 37 | 'deploy:function:initialize': async () => { 38 | await this.validate(); 39 | this.setDefaults(); 40 | // TODO: verify this.options.function 41 | }, 42 | 43 | 'deploy:function:packageFunction': async () => { 44 | await this.packageFunction(); 45 | await this.compileTemplates(); 46 | }, 47 | 48 | 'deploy:function:deploy': async () => { 49 | await this.setupService(); 50 | await this.uploadArtifacts(); 51 | await this.setupFunctions(); 52 | await this.setupEvents(); 53 | }, 54 | }, 'deploy:function'); 55 | } 56 | 57 | packageFunction() { 58 | return this.serverless.pluginManager.spawn('package:function'); 59 | } 60 | 61 | async compileTemplates() { 62 | this.prepareDeployment(); 63 | await this.initializeTemplates(); 64 | await this.compileFunctionToTemplate(); 65 | this.mergeServiceResources(); 66 | await this.updateTemplates(); 67 | } 68 | 69 | initializeTemplates() { 70 | this.templates = { 71 | create: this.serverless.service.provider.compiledConfigurationTemplate 72 | }; 73 | } 74 | 75 | compileFunctionToTemplate() { 76 | const funcName = this.options.function; 77 | const funcObject = this.serverless.service.getFunction(funcName); 78 | return this.compileFunction(funcName, funcObject); 79 | } 80 | 81 | updateTemplates() { 82 | this.templates.update = this.serverless.service.provider.compiledConfigurationTemplate; 83 | } 84 | } 85 | 86 | module.exports = AliyunDeployFunction; 87 | -------------------------------------------------------------------------------- /package/lib/cleanupServerlessDir.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | const sinon = require('sinon'); 8 | const fse = require('fs-extra'); 9 | 10 | const AliyunProvider = require('../../provider/aliyunProvider'); 11 | const AliyunPackage = require('../aliyunPackage'); 12 | const Serverless = require('../../test/serverless'); 13 | 14 | describe('CleanupServerlessDir', () => { 15 | let serverless; 16 | let aliyunPackage; 17 | let pathExistsSyncStub; 18 | let removeSyncStub; 19 | 20 | beforeEach(() => { 21 | serverless = new Serverless(); 22 | serverless.service.service = 'my-service'; 23 | serverless.config = { 24 | servicePath: false, 25 | }; 26 | const options = { 27 | stage: 'dev', 28 | region: 'cn-shanghai', 29 | }; 30 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 31 | aliyunPackage = new AliyunPackage(serverless, options); 32 | pathExistsSyncStub = sinon.stub(fse, 'pathExistsSync'); 33 | removeSyncStub = sinon.stub(fse, 'removeSync').returns(); 34 | }); 35 | 36 | afterEach(() => { 37 | fse.pathExistsSync.restore(); 38 | fse.removeSync.restore(); 39 | }); 40 | 41 | describe('#cleanupServerlessDir()', () => { 42 | it('should resolve if no servicePath is given', () => { 43 | aliyunPackage.serverless.config.servicePath = false; 44 | 45 | pathExistsSyncStub.returns(); 46 | 47 | aliyunPackage.cleanupServerlessDir(); 48 | expect(pathExistsSyncStub.calledOnce).toEqual(false); 49 | expect(removeSyncStub.calledOnce).toEqual(false); 50 | }); 51 | 52 | it('should remove the .serverless directory if it exists', () => { 53 | const serviceName = aliyunPackage.serverless.service.service; 54 | aliyunPackage.serverless.config.servicePath = serviceName; 55 | const serverlessDirPath = path.join(serviceName, '.serverless'); 56 | 57 | pathExistsSyncStub.returns(true); 58 | 59 | aliyunPackage.cleanupServerlessDir(); 60 | expect(pathExistsSyncStub.calledWithExactly(serverlessDirPath)).toEqual(true); 61 | expect(removeSyncStub.calledWithExactly(serverlessDirPath)).toEqual(true); 62 | }); 63 | 64 | it('should not remove the .serverless directory if does not exist', () => { 65 | const serviceName = aliyunPackage.serverless.service.service; 66 | aliyunPackage.serverless.config.servicePath = serviceName; 67 | const serverlessDirPath = path.join(serviceName, '.serverless'); 68 | 69 | pathExistsSyncStub.returns(false); 70 | 71 | aliyunPackage.cleanupServerlessDir(); 72 | expect(pathExistsSyncStub.calledWithExactly(serverlessDirPath)).toEqual(true); 73 | expect(removeSyncStub.calledWithExactly(serverlessDirPath)).toEqual(false); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /shared/visitor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const pkg = require('../package.json'); 4 | const uuid = require('uuid'); 5 | const ua = require('universal-analytics'); 6 | const ci = require('ci-info'); 7 | const osName = require('os-name'); 8 | const Conf = require('conf'); 9 | const detectMocha = require('detect-mocha'); 10 | 11 | const os = osName(); 12 | const packageName = pkg.name; 13 | const nodeVersion = process.version; 14 | const appVersion = pkg.version; 15 | 16 | const conf = new Conf({ 17 | configName: `ga-${packageName}`, 18 | projectName: packageName, 19 | defaults: { 20 | cid: uuid.v4() 21 | } 22 | }); 23 | 24 | var fake = { 25 | pageview: () => { 26 | return { 27 | send: () => { return 'fake'; } 28 | }; 29 | }, 30 | event: () => { 31 | return { 32 | send: () => { return 'fake'; } 33 | }; 34 | } 35 | }; 36 | 37 | var fakeMocha = { 38 | pageview: () => { 39 | return { 40 | send: () => { return 'fakeMocha'; } 41 | }; 42 | }, 43 | event: () => { 44 | return { 45 | send: () => { return 'fakeMocha'; } 46 | }; 47 | } 48 | }; 49 | 50 | var real = ua('UA-148336517-1', conf.get('cid')); 51 | 52 | real.set('cd1', os); 53 | real.set('cd2', nodeVersion); 54 | real.set('cd3', appVersion); 55 | 56 | let visitor; 57 | 58 | function getVisitor() { 59 | 60 | if (!visitor) { 61 | 62 | if (detectMocha()) { 63 | return fakeMocha; 64 | } 65 | 66 | if (ci.isCI) { 67 | return fake; 68 | } 69 | 70 | visitor = real; 71 | } 72 | 73 | return visitor; 74 | } 75 | 76 | function sendDownloaded() { 77 | real.pageview('/downloaded').send(); 78 | if (ci.isCI) { 79 | real.pageview(`/downloaded/ci/${ci.name}`).send(); 80 | } 81 | } 82 | 83 | function visitorWrap(category) { 84 | const visitor = getVisitor(); 85 | return ((category) => { 86 | return (action, func) => { 87 | return async () => { 88 | try { 89 | visitor.pageview(`/${category}`).send(); 90 | const result = await func(); 91 | visitor.event({ 92 | ec: category, 93 | ea: action, 94 | el: 'success', 95 | dp: `/${category}` 96 | }).send(); 97 | return result; 98 | } catch (e) { 99 | visitor.event({ 100 | ec: category, 101 | ea: action, 102 | el: 'error', 103 | dp: `/${category}` 104 | }).send(); 105 | throw e; 106 | } 107 | }; 108 | }; 109 | })(category); 110 | } 111 | 112 | function hooksWrap(hooks, category) { 113 | const wrap = visitorWrap(category); 114 | const reducer = (acc, hookEvt) => { 115 | acc[hookEvt] = wrap(hookEvt, hooks[hookEvt]); 116 | return acc; 117 | }; 118 | return Object.keys(hooks).reduce(reducer, {}); 119 | } 120 | 121 | module.exports = { getVisitor, visitorWrap, hooksWrap, sendDownloaded }; 122 | -------------------------------------------------------------------------------- /deploy/lib/setupRole.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async setupRole(role) { 5 | const roleSym = Symbol('role'); 6 | 7 | await this.createRoleIfNotExists(role, roleSym); 8 | await this.createPoliciesIfNeeded(role, roleSym); 9 | // HACK: must wait for a while for the ram role to take effect 10 | await this.provider.sleep(this.provider.roleDelay); 11 | await this.attachPoliciesIfNeeded(role, roleSym); 12 | return this[roleSym]; 13 | }, 14 | 15 | async createRoleIfNotExists(role, roleSym) { 16 | const foundRole = await this.provider.getRole(role.RoleName); 17 | if (foundRole) { 18 | // TODO: update if AssumeRolePolicyDocument is different/smaller 19 | this[roleSym] = foundRole; 20 | this.serverless.cli.log(`RAM role ${role.RoleName} exists.`); 21 | return; 22 | } 23 | this.serverless.cli.log(`Creating RAM role ${role.RoleName}...`); 24 | const createdRole = this.provider.createRole(role); 25 | this.serverless.cli.log(`Created RAM role ${role.RoleName}`); 26 | this[roleSym] = createdRole; 27 | }, 28 | 29 | async createPoliciesIfNeeded(role, roleSym) { 30 | if (!this[roleSym]) { 31 | return; 32 | } 33 | 34 | for (var i = 0; i < role.Policies.length; i++) { 35 | const policy = role.Policies[i]; 36 | await this.createPolicyIfNeeded(policy); 37 | } 38 | }, 39 | 40 | async createPolicyIfNeeded(policy) { 41 | if (policy.PolicyType === 'System') { 42 | return; 43 | } 44 | const policyName = policy.PolicyName; 45 | const foundPolicy = await this.provider.getPolicy(policyName, 'Custom'); 46 | if (foundPolicy) { 47 | // TODO: Update if PolicyDocument is different 48 | this.serverless.cli.log(`RAM policy ${policyName} exists.`); 49 | return; 50 | } 51 | 52 | this.serverless.cli.log(`Creating RAM policy ${policyName}...`); 53 | await this.provider.createPolicy(policy); 54 | this.serverless.cli.log(`Created RAM policy ${policyName}`); 55 | }, 56 | 57 | async attachPoliciesIfNeeded(role, roleSym) { 58 | if (!this[roleSym]) { 59 | return; 60 | } 61 | const roleName = role.RoleName; 62 | 63 | const attached = await this.provider.getPoliciesForRole(roleName); 64 | 65 | await Promise.all(role.Policies.map(async (policyProps) => { 66 | const policyName = policyProps.PolicyName; 67 | const policy = attached.find( 68 | (item) => item.PolicyName === policyName 69 | ); 70 | 71 | if (policy) { 72 | this.serverless.cli.log(`RAM policy ${policyName} has been attached to ${roleName}.`); 73 | return; 74 | } 75 | 76 | this.serverless.cli.log(`Attaching RAM policy ${policyName} to ${roleName}...`); 77 | await this.provider.attachPolicyToRole(role, { 78 | PolicyName: policyName, 79 | PolicyType: policyProps.PolicyType || 'Custom' 80 | }); 81 | this.serverless.cli.log(`Attached RAM policy ${policyName} to ${roleName}`); 82 | })); 83 | }, 84 | 85 | }; 86 | -------------------------------------------------------------------------------- /deploy/lib/uploadArtifacts.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | const sinon = require('sinon'); 8 | 9 | const AliyunProvider = require('../../provider/aliyunProvider'); 10 | const AliyunDeploy = require('../aliyunDeploy'); 11 | const Serverless = require('../../test/serverless'); 12 | 13 | describe('UploadArtifacts', () => { 14 | let serverless; 15 | let aliyunDeploy; 16 | 17 | beforeEach(() => { 18 | serverless = new Serverless(); 19 | serverless.service.service = 'my-service'; 20 | serverless.service.package = { 21 | artifactFilePath: '/some-remote-file-path', 22 | artifact: 'artifact.zip' 23 | }; 24 | serverless.service.provider = { 25 | name: 'aliyun', 26 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 27 | }; 28 | serverless.config = { 29 | servicePath: path.join(__dirname, '..', '..', 'test') 30 | }; 31 | const options = { 32 | stage: 'dev', 33 | region: 'cn-shanghai', 34 | }; 35 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 36 | aliyunDeploy = new AliyunDeploy(serverless, options); 37 | aliyunDeploy.provider.resetOssClient('test-bucket'); 38 | aliyunDeploy.templates = { 39 | create: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-create.json')), 40 | update: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-update.json')), 41 | }; 42 | }); 43 | 44 | describe('#uploadArtifacts()', () => { 45 | let consoleLogStub; 46 | let uploadObjectStub; 47 | 48 | beforeEach(() => { 49 | consoleLogStub = sinon.stub(aliyunDeploy.serverless.cli, 'log').returns(); 50 | uploadObjectStub = sinon.stub(aliyunDeploy.provider, 'uploadObject'); 51 | }); 52 | 53 | afterEach(() => { 54 | aliyunDeploy.serverless.cli.log.restore(); 55 | aliyunDeploy.provider.uploadObject.restore(); 56 | }); 57 | 58 | it('should upload corresponding objects to deployment bucket', () => { 59 | uploadObjectStub.returns(Promise.resolve()); 60 | return aliyunDeploy 61 | .uploadArtifacts().then(() => { 62 | expect(uploadObjectStub.calledOnce).toEqual(true); 63 | expect(uploadObjectStub.calledWithExactly( 64 | 'serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip', 65 | '/projects/.serverless/my-service.zip' 66 | )).toEqual(true); 67 | const logs = [ 68 | 'Uploading serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip to OSS bucket sls-accountid-cn-shanghai...', 69 | 'Uploaded serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip to OSS bucket sls-accountid-cn-shanghai' 70 | ]; 71 | expect(consoleLogStub.callCount).toEqual(logs.length); 72 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 73 | expect(consoleLogStub.calledWithExactly(logs[i])).toEqual(true); 74 | } 75 | }); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /remove/lib/getFunctionsAndService.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunRemove = require('../aliyunRemove'); 10 | const Serverless = require('../../test/serverless'); 11 | const { functionDefs, fullFunctions, fullService } = require('../../test/data'); 12 | 13 | describe('removeFunctionsAndService', () => { 14 | let serverless; 15 | let aliyunRemove; 16 | 17 | beforeEach(() => { 18 | serverless = new Serverless(); 19 | serverless.service.service = 'my-service'; 20 | serverless.service.provider = { 21 | name: 'aliyun', 22 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 23 | }; 24 | serverless.config = { 25 | servicePath: path.join(__dirname, '..', '..', 'test') 26 | }; 27 | const options = { 28 | stage: 'dev', 29 | region: 'cn-shanghai', 30 | }; 31 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 32 | serverless.pluginManager.setCliOptions(options); 33 | 34 | aliyunRemove = new AliyunRemove(serverless, options); 35 | }); 36 | 37 | describe('#removeFunctionsAndService()', () => { 38 | let getServiceStub; 39 | let getFunctionsStub; 40 | 41 | beforeEach(() => { 42 | aliyunRemove.serverless.service.functions = {}; 43 | sinon.stub(aliyunRemove.serverless.cli, 'log').returns(); 44 | getServiceStub = sinon.stub(aliyunRemove.provider, 'getService'); 45 | getFunctionsStub = sinon.stub(aliyunRemove.provider, 'getFunctions'); 46 | }); 47 | 48 | afterEach(() => { 49 | aliyunRemove.serverless.cli.log.restore(); 50 | aliyunRemove.provider.getService.restore(); 51 | aliyunRemove.provider.getFunctions.restore(); 52 | }); 53 | 54 | it('should get existing functions and service', () => { 55 | aliyunRemove.serverless.service.functions = functionDefs; 56 | getServiceStub.returns(Promise.resolve(fullService)); 57 | getFunctionsStub.returns(Promise.resolve(fullFunctions)); 58 | 59 | return aliyunRemove.getFunctionsAndService().then(() => { 60 | expect(getServiceStub.calledOnce).toEqual(true); 61 | expect(getServiceStub.calledWithExactly('my-service-dev')).toEqual(true); 62 | 63 | expect(getFunctionsStub.calledAfter(getServiceStub)).toEqual(true); 64 | expect(getFunctionsStub.calledOnce).toEqual(true); 65 | expect(getFunctionsStub.calledWithExactly('my-service-dev')).toEqual(true); 66 | 67 | expect(aliyunRemove.fcService).toEqual(fullService); 68 | expect(aliyunRemove.fcFunctions).toEqual(fullFunctions); 69 | }); 70 | }); 71 | 72 | it('should handle non-existing service', () => { 73 | aliyunRemove.serverless.service.functions = functionDefs; 74 | getServiceStub.returns(Promise.resolve(undefined)); 75 | getFunctionsStub.returns(Promise.resolve([])); 76 | 77 | return aliyunRemove.getFunctionsAndService().then(() => { 78 | expect(getServiceStub.calledOnce).toEqual(true); 79 | expect(getServiceStub.calledWithExactly('my-service-dev')).toEqual(true); 80 | 81 | expect(getFunctionsStub.called).toEqual(false); 82 | expect(aliyunRemove.fcService).toEqual(undefined); 83 | expect(aliyunRemove.fcFunctions).toEqual([]); 84 | }); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/.serverless/configuration-template-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "Resources": { 3 | "sls-storage-bucket": { 4 | "Type": "ALIYUN::OSS:Bucket", 5 | "Properties": { 6 | "BucketName": "sls-accountid-cn-shanghai", 7 | "Region": "cn-shanghai" 8 | } 9 | }, 10 | "sls-function-service": { 11 | "Type": "ALIYUN::FC::Service", 12 | "Properties": { 13 | "name": "my-service-dev", 14 | "region": "cn-shanghai", 15 | "logConfig": { 16 | "logstore": "my-service-dev", 17 | "project": "sls-accountid-cn-shanghai-logs" 18 | } 19 | } 20 | }, 21 | "sls-log-project": { 22 | "Type": "ALIYUN::SLS::Project", 23 | "Properties": { 24 | "projectName": "sls-accountid-cn-shanghai-logs", 25 | "description": "Log project for serverless service my-service, generated by the Serverless framework" 26 | } 27 | }, 28 | "sls-log-store": { 29 | "Type": "ALIYUN::SLS::Store", 30 | "Properties": { 31 | "projectName": "sls-accountid-cn-shanghai-logs", 32 | "storeName": "my-service-dev", 33 | "description": "Log store for Function Compute service my-service-dev, generated by the Serverless framework", 34 | "ttl": 30, 35 | "shardCount": 2 36 | } 37 | }, 38 | "sls-log-index": { 39 | "Type": "ALIYUN::SLS::Index", 40 | "Properties": { 41 | "projectName": "sls-accountid-cn-shanghai-logs", 42 | "storeName": "my-service-dev", 43 | "ttl": 30, 44 | "keys": { 45 | "functionName": { 46 | "caseSensitive": false, 47 | "token": [ 48 | "\n", 49 | "\t", 50 | ";", 51 | ",", 52 | "=", 53 | ":" 54 | ], 55 | "type": "text" 56 | } 57 | } 58 | } 59 | }, 60 | "sls-fc-exec-role": { 61 | "Type": "ALIYUN::RAM::Role", 62 | "Properties": { 63 | "RoleName": "sls-my-service-dev-cn-shanghai-exec-role", 64 | "Description": "Allow Function Compute service my-service-dev to access other services, generated by the Serverless framework", 65 | "AssumeRolePolicyDocument": { 66 | "Version": "1", 67 | "Statement": [ 68 | { 69 | "Action": "sts:AssumeRole", 70 | "Effect": "Allow", 71 | "Principal": { 72 | "Service": [ 73 | "fc.aliyuncs.com" 74 | ] 75 | } 76 | } 77 | ] 78 | }, 79 | "Policies": [ 80 | { 81 | "PolicyName": "fc-my-service-dev-cn-shanghai-access", 82 | "Description": "Allow Function Compute service my-service-dev to access other services, generated by the Serverless framework", 83 | "PolicyDocument": { 84 | "Version": "1", 85 | "Statement": [ 86 | { 87 | "Action": [ 88 | "log:PostLogStoreLogs" 89 | ], 90 | "Resource": [ 91 | "acs:log:*:accountid:project/sls-accountid-cn-shanghai-logs/logstore/my-service-dev" 92 | ], 93 | "Effect": "Allow" 94 | }, 95 | { 96 | "Effect": "Allow", 97 | "Action": [ 98 | "oss:GetObject", 99 | "oss:PutObject" 100 | ], 101 | "Resource": [ 102 | "acs:oss:cn-shanghai:accountid:my-service-resource" 103 | ] 104 | } 105 | ] 106 | } 107 | } 108 | ] 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /info/lib/displayServiceInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chalk = require('chalk'); 4 | 5 | module.exports = { 6 | async displayServiceInfo() { 7 | this.fcService = undefined; 8 | this.fcFunctions = []; 9 | this.apiGroup = undefined; 10 | this.apis = []; 11 | 12 | await this.getService(); 13 | await this.getFunctions(); 14 | await this.getApiInfo(); 15 | const data = this.gatherData(); 16 | this.printInfo(data); 17 | }, 18 | 19 | async getService() { 20 | const serviceName = this.provider.getServiceName(); 21 | this.fcService = await this.provider.getService(serviceName); 22 | }, 23 | 24 | async getFunctions() { 25 | if (!this.fcService) { 26 | return; 27 | } 28 | const serviceName = this.fcService.serviceName; 29 | this.fcFunctions = await this.provider.getFunctions(serviceName); 30 | }, 31 | 32 | async getApiInfo() { 33 | await this.getApiGroup(); 34 | await this.getApis(); 35 | await this.getVerboseApiInfo(); 36 | }, 37 | 38 | async getApiGroup() { 39 | const groupName = this.provider.getApiGroupName(); 40 | this.apiGroup = await this.provider.getApiGroup(groupName); 41 | }, 42 | 43 | async getApis() { 44 | if (!this.apiGroup) { 45 | return; 46 | } 47 | const groupId = this.apiGroup.GroupId; 48 | 49 | this.apis = await this.provider.getApis({GroupId: groupId}); 50 | }, 51 | 52 | async getVerboseApiInfo() { 53 | if (!this.apiGroup || !this.apis.length) { 54 | return; 55 | } 56 | 57 | return Promise.all(this.apis.map(async (api, index) => { 58 | const verboseApi = await this.provider.getApi(this.apiGroup, api); 59 | Object.assign(this.apis[index], verboseApi); 60 | })); 61 | }, 62 | 63 | gatherData() { 64 | const data = {}; 65 | data.service = this.serverless.service.service; 66 | data.stage = this.options.stage; 67 | data.region = this.options.region; 68 | return data; 69 | }, 70 | 71 | printInfo(data) { 72 | let message = ''; 73 | 74 | // get all the service related information 75 | message += `${chalk.yellow.underline('Service Information')}\n`; 76 | message += `${chalk.yellow('service:')} ${data.service}\n`; 77 | message += `${chalk.yellow('stage:')} ${data.stage}\n`; 78 | message += `${chalk.yellow('region:')} ${data.region}\n`; 79 | 80 | message += '\n'; 81 | 82 | // get all the functions 83 | message += `${chalk.yellow.underline('Functions')}\n`; 84 | if (this.fcFunctions.length) { 85 | this.fcFunctions.forEach((func) => { 86 | message += `- ${chalk.yellow(func.functionName)}\n`; 87 | message += ` last modified: ${func.lastModifiedTime}\n`; 88 | }); 89 | } else { 90 | message += 'There are no functions deployed yet\n'; 91 | } 92 | 93 | message += `\n${chalk.yellow.underline('Endpoints')}\n`; 94 | if (this.apis.length) { 95 | message += `API Group: ${chalk.yellow(this.apiGroup.GroupName)}\n`; 96 | const domain = this.apiGroup.SubDomain; 97 | this.apis.forEach((api) => { 98 | const deployStatus = api.DeployedInfos.DeployedInfo[0]; 99 | const deployed = deployStatus.DeployedStatus === 'DEPLOYED' ? 'deployed' : 'not deployed'; 100 | const req = api.RequestConfig; 101 | const method = req.RequestHttpMethod; 102 | const protocol = req.RequestProtocol.toLowerCase(); 103 | const path = req.RequestPath; 104 | 105 | message += `- ${chalk.yellow(api.ApiName)} (${deployed})\n`; 106 | message += ` ${method} ${protocol}://${domain}${path}\n`; 107 | }); 108 | } else { 109 | message += 'There are no endpoints yet\n'; 110 | } 111 | 112 | // TODO(joyeecheung): display triggers 113 | 114 | this.serverless.cli.consoleLog(message); 115 | return; 116 | }, 117 | }; 118 | -------------------------------------------------------------------------------- /remove/lib/removeEvents.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async removeEvents() { 5 | this.apiGroup = undefined; 6 | this.apis = []; 7 | this.deployedApis = []; 8 | this.triggers = []; 9 | 10 | this.serverless.cli.log('Removing events...'); 11 | await this.removeApisIfNeeded(); 12 | await this.removeTriggersIfNeeded(); 13 | await this.removeInvokeRole(); 14 | }, 15 | 16 | removeInvokeRole() { 17 | const invokeRoleName = this.provider.getInvokeRoleName(); 18 | return this.removeRoleAndPolicies(invokeRoleName); 19 | }, 20 | 21 | async removeApisIfNeeded() { 22 | await this.getApiInfo(); 23 | await this.abolishApisIfDeployed(); 24 | await this.removeApisIfExists(); 25 | await this.removeApiGroupIfExists(); 26 | }, 27 | 28 | async getApiInfo() { 29 | await this.getApiGroup(); 30 | await this.getApis(); 31 | await this.getDeployedApis(); 32 | }, 33 | 34 | async getApiGroup() { 35 | const groupName = this.provider.getApiGroupName(); 36 | this.apiGroup = await this.provider.getApiGroup(groupName); 37 | }, 38 | 39 | async getApis() { 40 | if (!this.apiGroup) { 41 | return; 42 | } 43 | const groupId = this.apiGroup.GroupId; 44 | 45 | this.apis = await this.provider.getApis({ GroupId: groupId}); 46 | }, 47 | 48 | async getDeployedApis() { 49 | if (!this.apiGroup || !this.apis.length) { 50 | return; 51 | } 52 | const groupId = this.apiGroup.GroupId; 53 | this.deployedApis = await this.provider.getDeployedApis({ GroupId: groupId }); 54 | }, 55 | 56 | async abolishApisIfDeployed() { 57 | if (!this.deployedApis.length) { 58 | this.serverless.cli.log(`No deployed APIs to abolish.`); 59 | return; // no API deployed 60 | } 61 | 62 | for (var i = 0; i < this.deployedApis.length; i++) { 63 | const api = this.deployedApis[i]; 64 | this.serverless.cli.log(`Abolishing API ${api.ApiName}...`); 65 | await this.provider.abolishApi(this.apiGroup, api); 66 | this.serverless.cli.log(`Abolished API ${api.ApiName}`); 67 | } 68 | }, 69 | 70 | async removeApisIfExists() { 71 | if (!this.apis.length) { 72 | this.serverless.cli.log(`No APIs to remove.`); 73 | return; // no API 74 | } 75 | 76 | for (var i = 0; i < this.apis.length; i++) { 77 | const api = this.apis[i]; 78 | this.serverless.cli.log(`Removing API ${api.ApiName}...`); 79 | await this.provider.deleteApi(this.apiGroup, api); 80 | this.serverless.cli.log(`Removed API ${api.ApiName}`); 81 | } 82 | }, 83 | 84 | async removeApiGroupIfExists() { 85 | if (!this.apiGroup) { 86 | this.serverless.cli.log(`No API groups to remove.`); 87 | return; 88 | } 89 | 90 | const groupName = this.apiGroup.GroupName; 91 | this.serverless.cli.log(`Removing API group ${groupName}...`); 92 | await this.provider.deleteApiGroup(this.apiGroup); 93 | this.serverless.cli.log(`Removed API group ${groupName}`); 94 | }, 95 | 96 | async removeTriggersIfNeeded() { 97 | if (!this.fcService || !this.fcFunctions.length) { 98 | this.serverless.cli.log(`No triggers to remove.`); 99 | return; 100 | } 101 | 102 | const serviceName = this.fcService.serviceName; 103 | for (var i = 0; i < this.fcFunctions.length; i++) { 104 | const func = this.fcFunctions[i]; 105 | const functionName = func.functionName; 106 | const triggers = await this.provider.listTriggers(serviceName, functionName); 107 | await this.removeTriggers(serviceName, functionName, triggers); 108 | } 109 | }, 110 | 111 | async removeTriggers(serviceName, functionName, triggers) { 112 | for (var i = 0; i < triggers.length; i++) { 113 | const trigger = triggers[i]; 114 | const triggerName = trigger.triggerName; 115 | this.serverless.cli.log(`Removing trigger ${triggerName}...`); 116 | await this.provider.deleteTrigger(serviceName, functionName, triggerName); 117 | this.serverless.cli.log(`Removed trigger ${triggerName}`); 118 | } 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /shared/validate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | validate() { 7 | this.validateServicePath(); 8 | this.validateServiceName(); 9 | this.validateHandlers(); 10 | }, 11 | 12 | validateApiGroupName() { 13 | // characters, digits, underscores 14 | }, 15 | 16 | validateApiName() { 17 | // characters, digits, underscores 18 | }, 19 | 20 | validateBucketName() { 21 | // characters, digits, dashes 22 | }, 23 | 24 | validateServicePath() { 25 | if (!this.serverless.config.servicePath) { 26 | throw new Error('This command can only be run inside a service directory'); 27 | } 28 | return; 29 | }, 30 | 31 | validateServiceName() { 32 | const serviceName = this.serverless.service.service; 33 | if (!/^[a-zA-Z_][a-zA-Z0-9\-_]*$/.test(serviceName)) { 34 | throw new Error( 35 | `The name of your service ${serviceName} is invalid. A service` + 36 | ' name should consist only of letters, digits, underscores and' + 37 | ' dashes, and it can not start with digits or underscores'); 38 | } 39 | if (serviceName.length > 128) { 40 | throw new Error( 41 | `The name of your service ${serviceName} is invalid. A service` + 42 | ' name should not be longer than 128 characters'); 43 | } 44 | return; 45 | }, 46 | 47 | validateHandlers() { 48 | const functions = this.serverless.service.functions; 49 | _.forEach(functions, (funcObject, funcKey) => { 50 | if (!/^[a-zA-Z_][a-zA-Z0-9\-_]*$/.test(funcKey)) { 51 | throw new Error( 52 | `The name of your function ${funcKey} is invalid. A function` + 53 | ' name should consist only of letters, digits, underscores and' + 54 | ' dashes, and it can not start with digits or underscores'); 55 | } 56 | if (funcKey.length > 128) { 57 | throw new Error( 58 | `The name of your function ${funcKey} is invalid. A function` + 59 | ' name should not be longer than 128 characters'); 60 | } 61 | 62 | if (!funcObject.handler) { 63 | const errorMessage = [ 64 | `Missing "handler" property for function "${funcKey}".`, 65 | ' Your function needs a "handler".', 66 | ' Please check the docs for more info.', 67 | ].join(''); 68 | throw new Error(errorMessage); 69 | } 70 | 71 | if (!/^[^.]+\.[^.]+$/.test(funcObject.handler)) { 72 | const errorMessage = [ 73 | `The "handler" property for the function "${funcKey}" is invalid.`, 74 | ' Handlers should be specified like ${fileName}.${funcName}', 75 | ' Please check the docs for more info.', 76 | ].join(''); 77 | throw new Error(errorMessage); 78 | } 79 | }); 80 | return; 81 | }, 82 | 83 | validateEventsProperty () { 84 | const functions = this.serverless.service.functions; 85 | _.forEach(functions, (funcObject, funcKey) => { 86 | 87 | if (!funcObject.events || funcObject.events.length === 0) { 88 | const errorMessage = [ 89 | `Missing "events" property for function "${funcKey}".`, 90 | ' Your function needs at least one "event".', 91 | ' Please check the docs for more info.', 92 | ].join(''); 93 | throw new Error(errorMessage); 94 | } 95 | 96 | if (funcObject.events.length > 1) { 97 | const errorMessage = [ 98 | `The function "${funcKey}" has more than one event.`, 99 | ' Only one event per function is supported.', 100 | ' Please check the docs for more info.', 101 | ].join(''); 102 | throw new Error(errorMessage); 103 | } 104 | 105 | const supportedEvents = [ 106 | 'http', 107 | 'oss' 108 | ]; 109 | const eventType = Object.keys(funcObject.events[0])[0]; 110 | if (supportedEvents.indexOf(eventType) === -1) { 111 | const errorMessage = [ 112 | `Event type "${eventType}" of function "${funcKey}" not supported.`, 113 | ` supported event types are: ${supportedEvents.join(', ')}`, 114 | ].join(''); 115 | throw new Error(errorMessage); 116 | } 117 | // TODO: verify that http events has paths and oss events have triggerConfig, sourceArn, .etc 118 | }); 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /package/lib/compileFunctions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-use-before-define: 0 */ 4 | 5 | const path = require('path'); 6 | 7 | const _ = require('lodash'); 8 | 9 | module.exports = { 10 | compileFunctions() { 11 | this.resources = this.serverless.service.provider.compiledConfigurationTemplate.Resources; 12 | this.compileStorage(this.serverless.service.package.artifact); 13 | this.serverless.service.getAllFunctions().forEach((functionName) => { 14 | const funcObject = this.serverless.service.getFunction(functionName); 15 | this.compileFunctionAndEvent(functionName, funcObject); 16 | }); 17 | }, 18 | 19 | compileFunction(funcName, funcObject) { 20 | this.resources = this.serverless.service.provider.compiledConfigurationTemplate.Resources; 21 | // Notice artifact is different 22 | this.compileStorage(funcObject.package.artifact); 23 | this.compileFunctionAndEvent(funcName, funcObject); 24 | }, 25 | 26 | compileStorage(artifact) { 27 | const objectId = this.provider.getStorageObjectId(); 28 | const resources = this.resources; 29 | 30 | const fileName = artifact.split(path.sep).pop(); 31 | const directory = this.provider.getArtifactDirectoryName(); 32 | const artifactFilePath = `${directory}/${fileName}`; 33 | this.serverless.service.package.artifactFilePath = artifactFilePath; 34 | this.serverless.service.package.artifactDirectoryName = directory; 35 | 36 | const packagePath = 37 | path.join(this.serverless.config.servicePath || '.', '.serverless'); 38 | const filePath = path.join(packagePath, fileName); 39 | 40 | const objectResource = this.provider.getObjectResource(artifactFilePath, filePath); 41 | 42 | _.merge(resources, { [objectId]: objectResource }); 43 | }, 44 | 45 | compileFunctionAndEvent(functionName, funcObject) { 46 | const resources = this.resources; 47 | this.serverless.cli 48 | .log(`Compiling function "${functionName}"...`); 49 | 50 | const funcId = this.provider.getFunctionLogicalId(funcObject.name); 51 | const funcResource = this.provider.getFunctionResource(funcObject); 52 | // recursive merge 53 | _.merge(resources, { [funcId]: funcResource }); 54 | 55 | this.compileApiGateway.call(this, funcObject); 56 | this.compileOSSTrigger.call(this, funcObject); 57 | this.compileEvents.call(this, funcObject); 58 | }, 59 | 60 | compileApiGateway(funcObject) { 61 | const resources = this.resources; 62 | const agLogicalId = this.provider.getApiGroupLogicalId(); 63 | const invokeRoleId = this.provider.getInvokeRoleLogicalId(); 64 | 65 | if (funcObject.events.some(needsApiGateway)) { 66 | if (!resources[agLogicalId]) { 67 | resources[agLogicalId] = this.provider.getApiGroupResource(); 68 | } 69 | let invokeResource = resources[invokeRoleId]; 70 | if (!invokeResource) { 71 | invokeResource = this.provider.getInvokeRoleResource(); 72 | } 73 | this.provider.makeRoleAccessibleFromAG(invokeResource); 74 | resources[invokeRoleId] = invokeResource; 75 | } 76 | }, 77 | 78 | compileOSSTrigger(funcObject) { 79 | const resources = this.resources; 80 | const invokeRoleId = this.provider.getInvokeRoleLogicalId(); 81 | 82 | if (funcObject.events.some(needsOSSTrigger)) { 83 | let invokeResource = resources[invokeRoleId]; 84 | if (!invokeResource) { 85 | invokeResource = this.provider.getInvokeRoleResource(); 86 | } 87 | this.provider.makeRoleAccessibleFromOSS(invokeResource); 88 | resources[invokeRoleId] = invokeResource; 89 | } 90 | }, 91 | 92 | compileEvents(funcObject) { 93 | const resources = this.resources; 94 | 95 | funcObject.events.forEach((event) => { 96 | const eventType = Object.keys(event)[0]; 97 | // TODO: support more event types 98 | if (eventType === 'http' || eventType === 'https' ) { 99 | const apiResource = this.provider.getHttpApiResource(event.http || event.https, funcObject, eventType); 100 | const apiName = apiResource.Properties.ApiName; 101 | _.merge(resources, { [apiName]: apiResource }); 102 | } else if (eventType === 'oss') { 103 | const triggerResource = this.provider.getOSSTriggerResource(event.oss, funcObject); 104 | const triggerName = triggerResource.Properties.triggerName; 105 | _.merge(resources, { [triggerName]: triggerResource }); 106 | } 107 | }); 108 | } 109 | }; 110 | 111 | function needsApiGateway(event) { 112 | return Object.keys(event)[0] === 'http' || Object.keys(event)[0] === 'https'; 113 | } 114 | 115 | function needsOSSTrigger(event) { 116 | return Object.keys(event)[0] === 'oss'; 117 | } 118 | -------------------------------------------------------------------------------- /deploy/lib/setupService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async setupService() { 5 | this.logProjectSpec = this.templates.create.Resources[this.provider.getLogProjectId()].Properties; 6 | this.logStoreSpec = this.templates.create.Resources[this.provider.getLogStoreId()].Properties; 7 | this.logIndexSpec = this.templates.create.Resources[this.provider.getLogIndexId()].Properties; 8 | 9 | this.logProject = undefined; 10 | this.logStore = undefined; 11 | this.logIndex = undefined; 12 | 13 | await this.createLogConfigIfNotExists(); 14 | await this.setupExecRole(); 15 | // HACK: must wait for a while for the ram policy to take effect 16 | await this.provider.sleep(this.provider.projectDelay); 17 | const foundService = await this.checkForExistingService(); 18 | await this.createServiceIfNotExists(foundService); 19 | await this.createBucketIfNotExists(); 20 | }, 21 | 22 | async setupExecRole() { 23 | const role = this.templates.create.Resources[this.provider.getExecRoleLogicalId()].Properties; 24 | this.execRole = await this.setupRole(role); 25 | }, 26 | 27 | async createLogConfigIfNotExists() { 28 | await this.createLogProjectIfNotExists(); 29 | await this.createLogStoreIfNotExists(); 30 | await this.createLogIndexIfNotExists(); 31 | }, 32 | 33 | async createLogProjectIfNotExists() { 34 | const projectName = this.logProjectSpec.projectName; 35 | const logProject = await this.provider.getLogProject(projectName); 36 | if (logProject) { 37 | this.serverless.cli.log(`Log project ${projectName} already exists.`); 38 | this.logProject = logProject; 39 | return; 40 | } 41 | 42 | this.serverless.cli.log(`Creating log project ${projectName}...`); 43 | const createdProject = await this.provider.createLogProject(projectName, this.logProjectSpec); 44 | this.serverless.cli.log(`Created log project ${projectName}`); 45 | this.logProject = createdProject; 46 | }, 47 | 48 | async createLogStoreIfNotExists() { 49 | if (!this.logProject) { 50 | return; 51 | } 52 | const projectName = this.logProjectSpec.projectName; 53 | const storeName = this.logStoreSpec.storeName; 54 | const logStore = await this.provider.getLogStore(projectName, storeName); 55 | 56 | if (logStore) { 57 | this.serverless.cli.log(`Log store ${projectName}/${storeName} already exists.`); 58 | this.logStore = logStore; 59 | return; 60 | } 61 | 62 | this.serverless.cli.log(`Creating log store ${projectName}/${storeName}...`); 63 | const createdStore = await this.provider.createLogStore(projectName, storeName, this.logStoreSpec); 64 | 65 | this.serverless.cli.log(`Created log store ${projectName}/${storeName}`); 66 | this.logStore = createdStore; 67 | }, 68 | 69 | async createLogIndexIfNotExists() { 70 | if (!this.logProject || !this.logStore) { 71 | return; 72 | } 73 | const projectName = this.logProjectSpec.projectName; 74 | const storeName = this.logStoreSpec.storeName; 75 | const logIndex = await this.provider.getLogIndex(projectName, storeName); 76 | if (logIndex) { 77 | this.serverless.cli.log(`Log store ${projectName}/${storeName} already has an index.`); 78 | this.logIndex = logIndex; 79 | return; 80 | } 81 | 82 | this.serverless.cli.log(`Creating log index for ${projectName}/${storeName}...`); 83 | const createdIndex = await this.provider.createLogIndex(projectName, storeName, this.logIndexSpec); 84 | this.serverless.cli.log(`Created log index for ${projectName}/${storeName}`); 85 | this.logIndex = createdIndex; 86 | }, 87 | 88 | checkForExistingService() { 89 | const service = this.templates.create.Resources[this.provider.getServiceId()].Properties; 90 | 91 | return this.provider.getService(service.name); 92 | }, 93 | 94 | async createServiceIfNotExists(foundService) { 95 | const service = this.templates.create.Resources[this.provider.getServiceId()].Properties; 96 | 97 | if (foundService) { 98 | this.serverless.cli.log(`Service ${service.name} already exists.`); 99 | return; 100 | } 101 | 102 | this.serverless.cli.log(`Creating service ${service.name}...`); 103 | // TODO(joyeecheung): generate description 104 | // TODO(joyeecheung): update service 105 | const spec = Object.assign({ 106 | role: this.execRole.Arn 107 | }, service); 108 | await this.provider.createService(service.name, spec); 109 | this.serverless.cli.log(`Created service ${service.name}`); 110 | }, 111 | 112 | async createBucketIfNotExists() { 113 | const bucket = this.templates.create.Resources[this.provider.getStorageBucketId()].Properties; 114 | 115 | const foundBucket = await this.provider.getBucket(bucket.BucketName); 116 | if (foundBucket) { 117 | this.serverless.cli.log(`Bucket ${bucket.BucketName} already exists.`); 118 | } else { 119 | this.serverless.cli.log(`Creating bucket ${bucket.BucketName}...`); 120 | await this.provider.createBucket(bucket.BucketName); 121 | this.serverless.cli.log(`Created bucket ${bucket.BucketName}`); 122 | } 123 | 124 | this.provider.resetOssClient(bucket.BucketName); 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aliyun Function Compute Serverless Plugin 2 | 3 | This plugin enables Aliyun Function Compute support within the Serverless Framework. 4 | 5 | - [![Build Status](https://travis-ci.org/aliyun/serverless-aliyun-function-compute.svg?branch=master)](https://travis-ci.org/aliyun/serverless-aliyun-function-compute) 6 | 7 | ## Getting started 8 | 9 | ### Pre-requisites 10 | 11 | * Node.js v8.x for using the plugin. 12 | * Serverless CLI v1.26.1+. You can get it by running `npm i -g serverless`. 13 | * An Aliyun account. 14 | 15 | ### Example 16 | 17 | You can install the following example from GitHub: 18 | 19 | ```sh 20 | $ serverless install --url https://github.com/aliyun/serverless-function-compute-examples/tree/master/aliyun-nodejs 21 | ``` 22 | 23 | The structure of the project should look something like this: 24 | 25 | ``` 26 | ├── index.js 27 | ├── node_modules 28 | ├── package.json 29 | └── serverless.yml 30 | ``` 31 | 32 | Install `serverless-aliyun-function-compute` plugin to your service. 33 | 34 | ```yaml 35 | $ serverless plugin install --name serverless-aliyun-function-compute 36 | ``` 37 | 38 | `serverless.yml`: 39 | 40 | ```yaml 41 | service: serverless-aliyun-hello-world 42 | 43 | provider: 44 | name: aliyun 45 | runtime: nodejs8 46 | credentials: ~/.aliyun_credentials # path must be absolute 47 | 48 | plugins: 49 | - serverless-aliyun-function-compute 50 | 51 | package: 52 | exclude: 53 | - package-lock.json 54 | - .gitignore 55 | - .git/** 56 | 57 | functions: 58 | hello: 59 | handler: index.hello 60 | events: 61 | - http: 62 | path: /foo 63 | method: get 64 | ``` 65 | 66 | `package.json`: 67 | 68 | ```json 69 | { 70 | "name": "serverless-aliyun-hello-world", 71 | "version": "0.1.0", 72 | "description": "Hello World example for aliyun provider with Serverless Framework.", 73 | "main": "index.js", 74 | "license": "MIT" 75 | } 76 | ``` 77 | 78 | `index.js`: 79 | 80 | ```js 81 | 'use strict'; 82 | 83 | exports.hello = (event, context, callback) => { 84 | const response = { 85 | statusCode: 200, 86 | body: JSON.stringify({ message: 'Hello!' }), 87 | }; 88 | 89 | callback(null, response); 90 | }; 91 | ``` 92 | 93 | In order to deploy this function, we need the credentials with permissions to access Aliyun Function Compute. 94 | Please create a `credentials` file and configure the credentials in it. 95 | Here is an example `credentials` file: 96 | 97 | ```ini 98 | [default] 99 | aliyun_access_key_id = xxxxxxxx 100 | aliyun_access_key_secret = xxxxxxxxxxxxxxxxxxxx 101 | aliyun_account_id = 1234567890 102 | ``` 103 | 104 | You can find the `aliyun_access_key_secret` and `aliyun_access_key_id` from https://ak-console.aliyun.com/?#/accesskey. You can also chose to create an Access Key or use sub-account Access Key. 105 | You can find the `aliyun_account_id` from https://account-intl.console.aliyun.com/?#/secure . 106 | After creating the `credentials` file, please make sure to change the `credentials` field value in `serverless.yml` to the absolute file path. 107 | 108 | See [test/project](./test/project) for a more detailed example (including how to access other Aliyun services, how to set up a HTTP POST endpoint, how to set up OSS triggers, etc.). 109 | 110 | ### Workflow 111 | 112 | Make sure that you have activated Function Compute and any other dependent services such as RAM, Log Service, API Gateway and OSS before attempting to deploy your function. 113 | 114 | * Deploy your service to Aliyun: 115 | 116 | ```sh 117 | $ serverless deploy 118 | ``` 119 | 120 | If your service contains HTTP endpoints, you will see the URLs for invoking your functions after a successful deployment. 121 | 122 | Note: you can use `serverless deploy function --function ` to deploy a single function instead of the entire service. 123 | * Invoke a function directly (without going through the API gateway): 124 | 125 | ```sh 126 | $ serverless invoke --function hello 127 | ``` 128 | * Retrieve the LogHub logs generated by your function: 129 | 130 | ```sh 131 | $ serverless logs --function hello 132 | ``` 133 | * Get information on your deployed functions 134 | 135 | ```sh 136 | $ serverless info 137 | ``` 138 | * When you no longer needs your service, you can remove the service, functions, along with deployed endpoints and triggers using: 139 | 140 | ```sh 141 | $ serverless remove 142 | ``` 143 | 144 | Note: by default RAM roles and policies created during the deployment are not removed. You can use `serverless remove --remove-roles` if you do want to remove them. 145 | 146 | #### Change Region 147 | 148 | * Changing the region in provider of serverless.yml: 149 | ```yaml 150 | provider: 151 | name: aliyun 152 | region: cn-hongkong 153 | ``` 154 | 155 | * Changing the region in CLI parameters: 156 | ```sh 157 | $ serverless deploy --region cn-hongkong 158 | ``` 159 | 160 | Note: CLI parameter `--region` has higher priority than provider, But you have to add this parameter to all the invocations, not only `deploy`. 161 | 162 | ## Develop 163 | 164 | ```sh 165 | # clone this repo 166 | git clone git@github.com:aliyun/serverless-aliyun-function-compute.git 167 | 168 | # link this module to global node_modules 169 | cd serverless-aliyun-function-compute 170 | npm install 171 | npm link 172 | 173 | # try it out by packaging the test project 174 | cd test/project 175 | npm install 176 | npm link serverless-aliyun-function-compute 177 | serverless package 178 | ``` 179 | 180 | ## License 181 | 182 | MIT 183 | -------------------------------------------------------------------------------- /deploy/lib/setupFunctions.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | const sinon = require('sinon'); 8 | 9 | const AliyunProvider = require('../../provider/aliyunProvider'); 10 | const AliyunDeploy = require('../aliyunDeploy'); 11 | const Serverless = require('../../test/serverless'); 12 | 13 | const { functions } = require('../../test/data'); 14 | 15 | describe('setupFunctions', () => { 16 | let serverless; 17 | let aliyunDeploy; 18 | 19 | beforeEach(() => { 20 | serverless = new Serverless(); 21 | serverless.service.service = 'my-service'; 22 | serverless.service.provider = { 23 | name: 'aliyun', 24 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials'), 25 | }; 26 | const options = { 27 | stage: 'dev', 28 | region: 'cn-shanghai', 29 | }; 30 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 31 | aliyunDeploy = new AliyunDeploy(serverless, options); 32 | aliyunDeploy.templates = { 33 | create: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-create.json')), 34 | update: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-update.json')), 35 | }; 36 | }); 37 | 38 | describe('#setupFunctions()', () => { 39 | let checkExistingFunctionsStub; 40 | let createOrUpdateFunctionsStub; 41 | 42 | beforeEach(() => { 43 | checkExistingFunctionsStub = sinon.stub(aliyunDeploy, 'checkExistingFunctions') 44 | .returns(Promise.resolve()); 45 | createOrUpdateFunctionsStub = sinon.stub(aliyunDeploy, 'createOrUpdateFunctions') 46 | .returns(Promise.resolve()); 47 | }); 48 | 49 | afterEach(() => { 50 | aliyunDeploy.checkExistingFunctions.restore(); 51 | aliyunDeploy.createOrUpdateFunctions.restore(); 52 | }); 53 | 54 | it('should run promise chain and set up functions to create', async () => { 55 | await aliyunDeploy.setupFunctions(); 56 | expect(checkExistingFunctionsStub.calledOnce).toEqual(true); 57 | expect(createOrUpdateFunctionsStub.calledAfter(checkExistingFunctionsStub)); 58 | expect(aliyunDeploy.functions).toEqual(functions); 59 | expect(aliyunDeploy.functionMap).toBeInstanceOf(Map); 60 | }); 61 | }); 62 | 63 | describe('#setupFunctions()', () => { 64 | let getFunctionStub; 65 | let updateFunctionStub; 66 | let createFunctionStub; 67 | let consoleLogStub; 68 | 69 | beforeEach(() => { 70 | getFunctionStub = sinon.stub(aliyunDeploy.provider, 'getFunction'); 71 | updateFunctionStub = sinon.stub(aliyunDeploy.provider, 'updateFunction'); 72 | createFunctionStub = sinon.stub(aliyunDeploy.provider, 'createFunction'); 73 | consoleLogStub = sinon.stub(aliyunDeploy.serverless.cli, 'log').returns(); 74 | }); 75 | 76 | afterEach(() => { 77 | aliyunDeploy.provider.getFunction.restore(); 78 | aliyunDeploy.provider.updateFunction.restore(); 79 | aliyunDeploy.provider.createFunction.restore(); 80 | aliyunDeploy.serverless.cli.log.restore(); 81 | }); 82 | 83 | it('should create and update functions according to the templates', async () => { 84 | getFunctionStub 85 | .withArgs('my-service-dev', 'my-service-dev-postTest') 86 | .returns(Promise.resolve(functions[0])); 87 | getFunctionStub 88 | .withArgs('my-service-dev', 'my-service-dev-getTest') 89 | .returns(Promise.resolve(undefined)); 90 | getFunctionStub 91 | .withArgs('my-service-dev', 'my-service-dev-ossTriggerTest') 92 | .returns(Promise.resolve(undefined)); 93 | 94 | updateFunctionStub.returns(Promise.resolve()); 95 | createFunctionStub.returns(Promise.resolve()); 96 | await aliyunDeploy.setupFunctions(); 97 | expect(getFunctionStub.callCount).toEqual(3); 98 | expect(getFunctionStub.calledWithExactly('my-service-dev', 'my-service-dev-postTest')).toEqual(true); 99 | expect(getFunctionStub.calledWithExactly('my-service-dev', 'my-service-dev-getTest')).toEqual(true); 100 | expect(getFunctionStub.calledWithExactly('my-service-dev', 'my-service-dev-ossTriggerTest')).toEqual(true); 101 | 102 | expect(updateFunctionStub.calledAfter(getFunctionStub)).toEqual(true); 103 | expect(updateFunctionStub.calledOnce).toEqual(true); 104 | expect(updateFunctionStub.getCall(0).args).toEqual([ 105 | 'my-service-dev', 106 | 'my-service-dev-postTest', 107 | functions[0] 108 | ]); 109 | 110 | expect(createFunctionStub.calledAfter(updateFunctionStub)).toEqual(true); 111 | expect(createFunctionStub.calledTwice).toEqual(true); 112 | expect(createFunctionStub.getCall(0).args).toEqual([ 113 | 'my-service-dev', 114 | 'my-service-dev-getTest', 115 | functions[1] 116 | ]); 117 | expect(createFunctionStub.getCall(1).args).toEqual([ 118 | 'my-service-dev', 119 | 'my-service-dev-ossTriggerTest', 120 | functions[2] 121 | ]); 122 | 123 | const logs = [ 124 | 'Updating function my-service-dev-postTest...', 125 | 'Updated function my-service-dev-postTest', 126 | 'Creating function my-service-dev-getTest...', 127 | 'Created function my-service-dev-getTest', 128 | 'Creating function my-service-dev-ossTriggerTest...', 129 | 'Created function my-service-dev-ossTriggerTest' 130 | ]; 131 | expect(consoleLogStub.callCount).toEqual(logs.length); 132 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 133 | expect(consoleLogStub.calledWithExactly(logs[i])).toEqual(true); 134 | } 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /info/lib/displayServiceInfo.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const chalk = require('chalk'); 7 | 8 | const path = require('path'); 9 | const AliyunProvider = require('../../provider/aliyunProvider'); 10 | const AliyunInfo = require('../aliyunInfo'); 11 | const Serverless = require('../../test/serverless'); 12 | const { fullGroup, fullApis, apis, fullFunctions, functionDefs } = require('../../test/data'); 13 | 14 | describe('DisplayServiceInfo', () => { 15 | let serverless; 16 | let aliyunInfo; 17 | 18 | beforeEach(() => { 19 | serverless = new Serverless(); 20 | serverless.service.service = 'my-service'; 21 | serverless.service.functions = functionDefs; 22 | serverless.service.provider = { 23 | name: 'aliyun', 24 | credentials: path.join(__dirname, '..', 'test', 'credentials'), 25 | }; 26 | serverless.config = { 27 | servicePath: path.join(__dirname, '..', '..', 'test') 28 | }; 29 | const options = { 30 | stage: 'dev', 31 | region: 'cn-shanghai', 32 | }; 33 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 34 | serverless.pluginManager.setCliOptions(options); 35 | aliyunInfo = new AliyunInfo(serverless, options); 36 | }); 37 | 38 | describe('#displayServiceInfo()', () => { 39 | let consoleLogStub; 40 | let getServiceStub; 41 | let getFunctionsStub; 42 | let getApiGroupStub; 43 | let getApisStub; 44 | let getApiStub; 45 | 46 | beforeEach(() => { 47 | consoleLogStub = sinon.stub(aliyunInfo.serverless.cli, 'consoleLog').returns(); 48 | getServiceStub = sinon.stub(aliyunInfo.provider, 'getService'); 49 | getFunctionsStub = sinon.stub(aliyunInfo.provider, 'getFunctions'); 50 | getApiGroupStub = sinon.stub(aliyunInfo.provider, 'getApiGroup'); 51 | getApisStub = sinon.stub(aliyunInfo.provider, 'getApis'); 52 | getApiStub = sinon.stub(aliyunInfo.provider, 'getApi'); 53 | }); 54 | 55 | afterEach(() => { 56 | aliyunInfo.serverless.cli.consoleLog.restore(); 57 | aliyunInfo.provider.getService.restore(); 58 | aliyunInfo.provider.getFunctions.restore(); 59 | aliyunInfo.provider.getApiGroup.restore(); 60 | aliyunInfo.provider.getApis.restore(); 61 | aliyunInfo.provider.getApi.restore(); 62 | }); 63 | 64 | it('should print relevant data on the console', () => { 65 | const serviceId = new Date().getTime().toString(16); 66 | getServiceStub.returns(Promise.resolve({ 67 | serviceId: serviceId, 68 | serviceName: 'my-service-dev' 69 | })); 70 | getFunctionsStub.returns(Promise.resolve(fullFunctions)); 71 | getApiGroupStub.returns(Promise.resolve(fullGroup)); 72 | getApisStub.returns(Promise.resolve(fullApis)); 73 | getApiStub.onCall(0).returns(Promise.resolve( 74 | Object.assign({}, apis[0], { 75 | DeployedInfos: { 76 | DeployedInfo: [{ 77 | DeployedStatus: 'DEPLOYED', 78 | StageName: 'RELEASE', 79 | EffectiveVersion: '20170727201804434' 80 | }] 81 | } 82 | }))); 83 | getApiStub.onCall(1).returns(Promise.resolve( 84 | Object.assign({}, apis[1], { 85 | DeployedInfos: { 86 | DeployedInfo: [{ DeployedStatus: 'NONDEPLOYED', StageName: 'PRE' }] 87 | } 88 | }))); 89 | 90 | let expectedOutput = [ 91 | `${chalk.yellow.underline('Service Information')}`, 92 | `${chalk.yellow('service:')} my-service`, 93 | `${chalk.yellow('stage:')} dev`, 94 | `${chalk.yellow('region:')} cn-shanghai`, 95 | '', 96 | `${chalk.yellow.underline('Functions')}`, 97 | `- ${chalk.yellow('my-service-dev-postTest')}`, 98 | ' last modified: 2017-07-21T07:38:41.413Z', 99 | `- ${chalk.yellow('my-service-dev-getTest')}`, 100 | ' last modified: 2017-07-21T07:38:41.413Z', 101 | `- ${chalk.yellow('my-service-dev-ossTriggerTest')}`, 102 | ' last modified: 2017-07-21T07:38:41.413Z', 103 | '', 104 | `${chalk.yellow.underline('Endpoints')}`, 105 | `API Group: ${chalk.yellow('my_service_dev_api')}`, 106 | `- ${chalk.yellow('sls_http_my_service_dev_postTest')} (deployed)`, 107 | ' POST http://523e8dc7bbe04613b5b1d726c2a7889d-cn-shanghai.alicloudapi.com/baz', 108 | `- ${chalk.yellow('sls_http_my_service_dev_getTest')} (not deployed)`, 109 | ' GET http://523e8dc7bbe04613b5b1d726c2a7889d-cn-shanghai.alicloudapi.com/quo', 110 | '' 111 | ]; 112 | return aliyunInfo.displayServiceInfo().then(() => { 113 | expect(consoleLogStub.getCall(0).args[0].split('\n')).toEqual(expectedOutput); 114 | }); 115 | }); 116 | 117 | it('should print an info if functions are not yet deployed', () => { 118 | getServiceStub.returns(Promise.resolve()); 119 | getFunctionsStub.returns(Promise.resolve([])); 120 | getApiGroupStub.returns(Promise.resolve()); 121 | getApisStub.returns(Promise.resolve([])); 122 | getApiStub.returns(Promise.resolve()); 123 | 124 | let expectedOutput = [ 125 | `${chalk.yellow.underline('Service Information')}`, 126 | `${chalk.yellow('service:')} my-service`, 127 | `${chalk.yellow('stage:')} dev`, 128 | `${chalk.yellow('region:')} cn-shanghai`, 129 | '', 130 | `${chalk.yellow.underline('Functions')}`, 131 | 'There are no functions deployed yet', 132 | '', 133 | `${chalk.yellow.underline('Endpoints')}`, 134 | 'There are no endpoints yet', 135 | '' 136 | ]; 137 | return aliyunInfo.displayServiceInfo().then(() => { 138 | expect(consoleLogStub.getCall(0).args[0].split('\n')).toEqual(expectedOutput); 139 | }); 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /logs/lib/retrieveLogs.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const chalk = require('chalk'); 7 | 8 | const path = require('path'); 9 | const AliyunProvider = require('../../provider/aliyunProvider'); 10 | const AliyunLogs = require('../aliyunLogs'); 11 | const Serverless = require('../../test/serverless'); 12 | const { logs, functionDefs } = require('../../test/data'); 13 | 14 | describe('DisplayServiceLogs', () => { 15 | let serverless; 16 | let aliyunLogs; 17 | 18 | beforeEach(() => { 19 | serverless = new Serverless(); 20 | serverless.service.service = 'my-service'; 21 | serverless.service.functions = functionDefs; 22 | serverless.service.provider = { 23 | name: 'aliyun', 24 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials'), 25 | }; 26 | serverless.config = { 27 | servicePath: path.join(__dirname, '..', '..', 'test') 28 | }; 29 | }); 30 | 31 | describe('#retrieveLogs()', () => { 32 | let consoleLogStub; 33 | let getLogsIfAvailableStub; 34 | 35 | beforeEach(() => { 36 | const options = { 37 | stage: 'dev', 38 | region: 'cn-shanghai', 39 | function: 'postTest' 40 | }; 41 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 42 | serverless.pluginManager.setCliOptions(options); 43 | aliyunLogs = new AliyunLogs(serverless, options); 44 | consoleLogStub = sinon.stub(aliyunLogs.serverless.cli, 'consoleLog').returns(); 45 | getLogsIfAvailableStub = sinon.stub(aliyunLogs.provider, 'getLogsIfAvailable'); 46 | }); 47 | 48 | afterEach(() => { 49 | aliyunLogs.serverless.cli.consoleLog.restore(); 50 | aliyunLogs.provider.getLogsIfAvailable.restore(); 51 | }); 52 | 53 | it('should print relevant data on the console', async () => { 54 | getLogsIfAvailableStub.returns(Promise.resolve(logs)); 55 | 56 | let expectedOutput = [ 57 | `${chalk.yellow.underline('Service Information')}`, 58 | `${chalk.yellow('service:')} my-service`, 59 | `${chalk.yellow('stage:')} dev`, 60 | `${chalk.yellow('region:')} cn-shanghai`, 61 | '', 62 | `${chalk.yellow.underline('Logs')}`, 63 | ` ${chalk.yellow('my-service-dev/my-service-dev-postTest')}`, 64 | ' - 2017-08-18T10:18:26.000Z: 2017-08-18T10:18:26.131Z [info] FunctionCompute nodejs runtime inited.\r', 65 | ' - 2017-08-18T10:18:26.000Z: FC Invoke Start RequestId: 332425-41-143112-415219434\r', 66 | ' - 2017-08-18T10:34:21.000Z: FC Invoke End RequestId: 25222ee9-41-143112-415219434', 67 | '' 68 | ]; 69 | await aliyunLogs.retrieveLogs(); 70 | expect(consoleLogStub.getCall(0).args[0].split('\n')).toEqual(expectedOutput); 71 | expect(getLogsIfAvailableStub.calledOnce).toEqual(true); 72 | expect(getLogsIfAvailableStub.getCall(0).args).toEqual([ 73 | 'sls-accountid-cn-shanghai-logs', 74 | 'my-service-dev', 75 | 1, 76 | { functionName: 'my-service-dev-postTest' }, 77 | undefined 78 | ]); 79 | }); 80 | 81 | it('should print logs if functions are not yet deployed', async () => { 82 | getLogsIfAvailableStub.returns(Promise.resolve([])); 83 | 84 | let expectedOutput = [ 85 | `${chalk.yellow.underline('Service Information')}`, 86 | `${chalk.yellow('service:')} my-service`, 87 | `${chalk.yellow('stage:')} dev`, 88 | `${chalk.yellow('region:')} cn-shanghai`, 89 | '', 90 | `${chalk.yellow.underline('Logs')}`, 91 | 'There are no logs to show', 92 | '' 93 | ]; 94 | await aliyunLogs.retrieveLogs(); 95 | expect(consoleLogStub.getCall(0).args[0].split('\n')).toEqual(expectedOutput); 96 | }); 97 | }); 98 | 99 | describe('#retrieveLogs() with --count', () => { 100 | let consoleLogStub; 101 | let getLogsIfAvailableStub; 102 | 103 | beforeEach(() => { 104 | const options = { 105 | stage: 'dev', 106 | region: 'cn-shanghai', 107 | function: 'postTest', 108 | count: 2 109 | }; 110 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 111 | serverless.pluginManager.setCliOptions(options); 112 | aliyunLogs = new AliyunLogs(serverless, options); 113 | consoleLogStub = sinon.stub(aliyunLogs.serverless.cli, 'consoleLog').returns(); 114 | getLogsIfAvailableStub = sinon.stub(aliyunLogs.provider, 'getLogsIfAvailable'); 115 | }); 116 | 117 | afterEach(() => { 118 | aliyunLogs.serverless.cli.consoleLog.restore(); 119 | aliyunLogs.provider.getLogsIfAvailable.restore(); 120 | }); 121 | 122 | it('should print relevant data on the console', async () => { 123 | getLogsIfAvailableStub.returns(Promise.resolve(logs.slice(0, 2))); 124 | 125 | let expectedOutput = [ 126 | `${chalk.yellow.underline('Service Information')}`, 127 | `${chalk.yellow('service:')} my-service`, 128 | `${chalk.yellow('stage:')} dev`, 129 | `${chalk.yellow('region:')} cn-shanghai`, 130 | '', 131 | `${chalk.yellow.underline('Logs')}`, 132 | ` ${chalk.yellow('my-service-dev/my-service-dev-postTest')}`, 133 | ' - 2017-08-18T10:18:26.000Z: 2017-08-18T10:18:26.131Z [info] FunctionCompute nodejs runtime inited.\r', 134 | ' - 2017-08-18T10:18:26.000Z: FC Invoke Start RequestId: 332425-41-143112-415219434\r', 135 | '' 136 | ]; 137 | await aliyunLogs.retrieveLogs(); 138 | expect(consoleLogStub.getCall(0).args[0].split('\n')).toEqual(expectedOutput); 139 | expect(getLogsIfAvailableStub.getCall(0).args).toEqual([ 140 | 'sls-accountid-cn-shanghai-logs', 141 | 'my-service-dev', 142 | 1, 143 | { functionName: 'my-service-dev-postTest' }, 144 | 2 145 | ]); 146 | }); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /shared/validate.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | 7 | const validate = require('./validate'); 8 | const AliyunProvider = require('../provider/aliyunProvider'); 9 | const Serverless = require('../test/serverless'); 10 | const AliyunCommand = require('../test/aliyunCommand'); 11 | 12 | describe('Validate', () => { 13 | let serverless; 14 | let aliyunCommand; 15 | 16 | beforeEach(() => { 17 | serverless = new Serverless(); 18 | serverless.config = { 19 | servicePath: true, 20 | }; 21 | serverless.service = { 22 | service: 'some-default-service', 23 | }; 24 | serverless.setProvider('aliyun', new AliyunProvider(serverless, {})); 25 | aliyunCommand = new AliyunCommand(serverless, {}, validate); 26 | }); 27 | 28 | describe('#validate()', () => { 29 | let validateServicePathStub; 30 | let validateServiceNameStub; 31 | let validateHandlersStub; 32 | 33 | beforeEach(() => { 34 | validateServicePathStub = sinon.stub(aliyunCommand, 'validateServicePath') 35 | .returns(); 36 | validateServiceNameStub = sinon.stub(aliyunCommand, 'validateServiceName') 37 | .returns(); 38 | validateHandlersStub = sinon.stub(aliyunCommand, 'validateHandlers') 39 | .returns(); 40 | }); 41 | 42 | afterEach(() => { 43 | aliyunCommand.validateServicePath.restore(); 44 | aliyunCommand.validateServiceName.restore(); 45 | aliyunCommand.validateHandlers.restore(); 46 | }); 47 | 48 | it('should run ok', () => { 49 | aliyunCommand.validate(); 50 | expect(validateServicePathStub.calledOnce).toEqual(true); 51 | expect(validateServiceNameStub.calledAfter(validateServicePathStub)); 52 | expect(validateHandlersStub.calledAfter(validateServiceNameStub)); 53 | }); 54 | }); 55 | 56 | describe('#validateServicePath()', () => { 57 | it('should throw an error if not inside service', () => { 58 | serverless.config.servicePath = false; 59 | 60 | expect(() => aliyunCommand.validateServicePath()).toThrow(Error); 61 | }); 62 | 63 | it('should not throw an error if inside service', () => { 64 | serverless.config.servicePath = true; 65 | 66 | expect(() => aliyunCommand.validateServicePath()).not.toThrow(Error); 67 | }); 68 | }); 69 | 70 | describe('#validateServiceName()', () => { 71 | it('should throw an error if the service name contains an invalid cahracter', () => { 72 | serverless.service.service = 'service name'; 73 | 74 | expect(() => aliyunCommand.validateServiceName()).toThrow(Error); 75 | }); 76 | 77 | it('should throw an error if the service name is too long', () => { 78 | serverless.service.service = 's'.repeat(130); 79 | 80 | expect(() => aliyunCommand.validateServiceName()).toThrow(Error); 81 | }); 82 | 83 | it('should not throw an error if the service name is valid', () => { 84 | serverless.service.service = 'service-name'; 85 | 86 | expect(() => aliyunCommand.validateServiceName()).not.toThrow(Error); 87 | }); 88 | }); 89 | 90 | describe('#validateHandlers()', () => { 91 | it('should throw an error if the function has no handler property', () => { 92 | aliyunCommand.serverless.service.functions = { 93 | func1: { 94 | handler: null, 95 | }, 96 | }; 97 | 98 | expect(() => aliyunCommand.validateHandlers()).toThrow(Error); 99 | }); 100 | 101 | it('should throw an error if the handler name is invalid', () => { 102 | aliyunCommand.serverless.service.functions = { 103 | foo: { 104 | handler: '.invalid.handler', 105 | }, 106 | bar: { 107 | handler: 'invalid.handler.', 108 | }, 109 | }; 110 | 111 | expect(() => aliyunCommand.validateHandlers()).toThrow(Error); 112 | }); 113 | 114 | it('should not throw an error if the function handler is valid', () => { 115 | aliyunCommand.serverless.service.functions = { 116 | foo: { 117 | handler: 'valid.handler', 118 | }, 119 | bar: { 120 | handler: 'src/valid_file.handler-test', 121 | }, 122 | }; 123 | 124 | expect(() => aliyunCommand.validateHandlers()).not.toThrow(Error); 125 | }); 126 | }); 127 | 128 | describe('#validateEventsProperty()', () => { 129 | it('should throw an error if the function has no events property', () => { 130 | aliyunCommand.serverless.service.functions = { 131 | func1: { 132 | handler: 'func1', 133 | events: null, 134 | }, 135 | }; 136 | 137 | expect(() => aliyunCommand.validateEventsProperty()).toThrow(Error); 138 | }); 139 | 140 | it('should throw an error if the function has 0 events', () => { 141 | aliyunCommand.serverless.service.functions = { 142 | func1: { 143 | handler: 'func1', 144 | events: [], 145 | }, 146 | }; 147 | 148 | expect(() => aliyunCommand.validateEventsProperty()).toThrow(Error); 149 | }); 150 | 151 | it('should throw an error if the function has more than 1 event', () => { 152 | aliyunCommand.serverless.service.functions = { 153 | func1: { 154 | handler: 'func1', 155 | events: [ 156 | { http: 'event1' }, 157 | { http: 'event2' }, 158 | ], 159 | }, 160 | }; 161 | 162 | expect(() => aliyunCommand.validateEventsProperty()).toThrow(Error); 163 | }); 164 | 165 | it('should throw an error if the functions event is not supported', () => { 166 | aliyunCommand.serverless.service.functions = { 167 | func1: { 168 | handler: 'func1', 169 | events: [ 170 | { invalidEvent: 'event1' }, 171 | ], 172 | }, 173 | }; 174 | 175 | expect(() => aliyunCommand.validateEventsProperty()).toThrow(Error); 176 | }); 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /remove/lib/removeRoleAndPolicies.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunRemove = require('../aliyunRemove'); 10 | const Serverless = require('../../test/serverless'); 11 | const { role, fullRole } = require('../../test/data'); 12 | 13 | describe('removeRoleAndPolicies', () => { 14 | let serverless; 15 | let aliyunRemove; 16 | const roleName = 'sls-my-service-dev-cn-shanghai-invoke-role'; 17 | 18 | beforeEach(() => { 19 | serverless = new Serverless(); 20 | serverless.service.service = 'my-service'; 21 | serverless.service.provider = { 22 | name: 'aliyun', 23 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 24 | }; 25 | serverless.config = { 26 | servicePath: path.join(__dirname, '..', '..', 'test') 27 | }; 28 | }); 29 | 30 | describe('#removeRoleAndPolicies()', () => { 31 | let consoleLogStub; 32 | let getRoleStub; 33 | let getPoliciesForRoleStub; 34 | let detachPolicyFromRoleStub; 35 | let deleteRoleStub; 36 | 37 | beforeEach(() => { 38 | const options = { 39 | stage: 'dev', 40 | region: 'cn-shanghai', 41 | 'remove-roles': true 42 | }; 43 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 44 | serverless.pluginManager.setCliOptions(options); 45 | aliyunRemove = new AliyunRemove(serverless, options); 46 | consoleLogStub = sinon.stub(aliyunRemove.serverless.cli, 'log').returns(); 47 | getRoleStub = sinon.stub(aliyunRemove.provider, 'getRole'); 48 | getPoliciesForRoleStub = sinon.stub(aliyunRemove.provider, 'getPoliciesForRole'); 49 | detachPolicyFromRoleStub = sinon.stub(aliyunRemove.provider, 'detachPolicyFromRole'); 50 | deleteRoleStub = sinon.stub(aliyunRemove.provider, 'deleteRole'); 51 | }); 52 | 53 | afterEach(() => { 54 | aliyunRemove.serverless.cli.log.restore(); 55 | aliyunRemove.provider.getRole.restore(); 56 | aliyunRemove.provider.getPoliciesForRole.restore(); 57 | aliyunRemove.provider.detachPolicyFromRole.restore(); 58 | aliyunRemove.provider.deleteRole.restore(); 59 | }); 60 | 61 | it('should remove existing role and policies', () => { 62 | getRoleStub.returns(Promise.resolve(fullRole)); 63 | getPoliciesForRoleStub.returns(Promise.resolve(role.Policies)); 64 | detachPolicyFromRoleStub.returns(Promise.resolve()); 65 | deleteRoleStub.returns(Promise.resolve()); 66 | 67 | return aliyunRemove.removeRoleAndPolicies(roleName).then(() => { 68 | expect(getRoleStub.calledOnce).toEqual(true); 69 | expect(getRoleStub.calledWithExactly(roleName)).toEqual(true); 70 | expect(getPoliciesForRoleStub.calledAfter(getRoleStub)).toEqual(true); 71 | expect(getPoliciesForRoleStub.calledWithExactly(roleName)).toEqual(true); 72 | 73 | expect(detachPolicyFromRoleStub.calledAfter(getPoliciesForRoleStub)).toEqual(true); 74 | expect(detachPolicyFromRoleStub.calledOnce).toEqual(true); 75 | expect(detachPolicyFromRoleStub.calledWithExactly(fullRole, role.Policies[0])).toEqual(true); 76 | expect(deleteRoleStub.calledAfter(detachPolicyFromRoleStub)).toEqual(true); 77 | expect(deleteRoleStub.calledWithExactly(roleName)).toEqual(true); 78 | 79 | const logs = [ 80 | 'Detaching RAM policy AliyunFCInvocationAccess from sls-my-service-dev-cn-shanghai-invoke-role...', 81 | 'Detached RAM policy AliyunFCInvocationAccess from sls-my-service-dev-cn-shanghai-invoke-role', 82 | 'Removing RAM role sls-my-service-dev-cn-shanghai-invoke-role...', 83 | 'Removed RAM role sls-my-service-dev-cn-shanghai-invoke-role' 84 | ]; 85 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 86 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 87 | } 88 | }); 89 | }); 90 | 91 | it('should skip non-existing role and policies', () => { 92 | getRoleStub.returns(Promise.resolve()); 93 | getPoliciesForRoleStub.returns(Promise.resolve([])); 94 | detachPolicyFromRoleStub.returns(Promise.resolve()); 95 | deleteRoleStub.returns(Promise.resolve()); 96 | 97 | return aliyunRemove.removeRoleAndPolicies(roleName).then(() => { 98 | expect(getRoleStub.calledWithExactly(roleName)).toEqual(true); 99 | expect(getPoliciesForRoleStub.called).toEqual(false); 100 | expect(detachPolicyFromRoleStub.called).toEqual(false); 101 | expect(deleteRoleStub.called).toEqual(false); 102 | 103 | const logs = []; 104 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 105 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 106 | } 107 | }); 108 | }); 109 | }); 110 | 111 | describe('#removeRoleAndPolicies()', () => { 112 | let getRoleStub; 113 | let getPoliciesForRoleStub; 114 | let detachPolicyFromRoleStub; 115 | let deleteRoleStub; 116 | 117 | beforeEach(() => { 118 | const options = { 119 | stage: 'dev', 120 | region: 'cn-shanghai' 121 | }; 122 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 123 | serverless.pluginManager.setCliOptions(options); 124 | aliyunRemove = new AliyunRemove(serverless, options); 125 | sinon.stub(aliyunRemove.serverless.cli, 'log').returns(); 126 | getRoleStub = sinon.stub(aliyunRemove.provider, 'getRole'); 127 | getPoliciesForRoleStub = sinon.stub(aliyunRemove.provider, 'getPoliciesForRole'); 128 | detachPolicyFromRoleStub = sinon.stub(aliyunRemove.provider, 'detachPolicyFromRole'); 129 | deleteRoleStub = sinon.stub(aliyunRemove.provider, 'deleteRole'); 130 | }); 131 | 132 | afterEach(() => { 133 | aliyunRemove.serverless.cli.log.restore(); 134 | aliyunRemove.provider.getRole.restore(); 135 | aliyunRemove.provider.getPoliciesForRole.restore(); 136 | aliyunRemove.provider.detachPolicyFromRole.restore(); 137 | aliyunRemove.provider.deleteRole.restore(); 138 | }); 139 | 140 | it('should not do anything if there is no --remove-roles', () => { 141 | getRoleStub.returns(Promise.resolve()); 142 | getPoliciesForRoleStub.returns(Promise.resolve([])); 143 | detachPolicyFromRoleStub.returns(Promise.resolve()); 144 | deleteRoleStub.returns(Promise.resolve()); 145 | 146 | return aliyunRemove.removeRoleAndPolicies(roleName).then(() => { 147 | expect(getRoleStub.called).toEqual(false); 148 | expect(getPoliciesForRoleStub.called).toEqual(false); 149 | expect(detachPolicyFromRoleStub.called).toEqual(false); 150 | expect(deleteRoleStub.called).toEqual(false); 151 | }); 152 | }); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /remove/lib/removeArtifacts.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunRemove = require('../aliyunRemove'); 10 | const Serverless = require('../../test/serverless'); 11 | const { bucket, objects } = require('../../test/data'); 12 | 13 | describe('removeArtifacts', () => { 14 | let serverless; 15 | let aliyunRemove; 16 | 17 | beforeEach(() => { 18 | serverless = new Serverless(); 19 | serverless.service.service = 'my-service'; 20 | serverless.service.provider = { 21 | name: 'aliyun', 22 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 23 | }; 24 | serverless.config = { 25 | servicePath: path.join(__dirname, '..', '..', 'test') 26 | }; 27 | const options = { 28 | stage: 'dev', 29 | region: 'cn-shanghai', 30 | }; 31 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 32 | serverless.pluginManager.setCliOptions(options); 33 | aliyunRemove = new AliyunRemove(serverless, options); 34 | }); 35 | 36 | describe('#removeArtifacts()', () => { 37 | let consoleLogStub; 38 | let getBucketStub; 39 | let getObjectsStub; 40 | let deleteObjectsStub; 41 | let deleteBucketStub; 42 | 43 | beforeEach(() => { 44 | aliyunRemove.serverless.service.functions = {}; 45 | consoleLogStub = sinon.stub(aliyunRemove.serverless.cli, 'log').returns(); 46 | getBucketStub = sinon.stub(aliyunRemove.provider, 'getBucket'); 47 | getObjectsStub = sinon.stub(aliyunRemove.provider, 'getObjects'); 48 | deleteObjectsStub = sinon.stub(aliyunRemove.provider, 'deleteObjects'); 49 | deleteBucketStub = sinon.stub(aliyunRemove.provider, 'deleteBucket'); 50 | }); 51 | 52 | afterEach(() => { 53 | aliyunRemove.serverless.cli.log.restore(); 54 | aliyunRemove.provider.getBucket.restore(); 55 | aliyunRemove.provider.getObjects.restore(); 56 | aliyunRemove.provider.deleteObjects.restore(); 57 | aliyunRemove.provider.deleteBucket.restore(); 58 | }); 59 | 60 | it('should remove existing bucket and objects', () => { 61 | getBucketStub.returns(Promise.resolve(bucket)); 62 | getObjectsStub.returns(Promise.resolve(objects)); 63 | deleteObjectsStub.returns(Promise.resolve()); 64 | deleteBucketStub.returns(Promise.resolve()); 65 | 66 | return aliyunRemove.removeArtifacts().then(() => { 67 | expect(getBucketStub.calledOnce).toEqual(true); 68 | expect(getBucketStub.calledWithExactly('sls-accountid-cn-shanghai')).toEqual(true); 69 | 70 | expect(getObjectsStub.calledAfter(getBucketStub)).toEqual(true); 71 | expect(getObjectsStub.calledOnce).toEqual(true); 72 | expect(getObjectsStub.calledWithExactly({ 73 | prefix: 'serverless/my-service/dev' 74 | })).toEqual(true); 75 | 76 | expect(deleteObjectsStub.calledAfter(getObjectsStub)).toEqual(true); 77 | expect(deleteObjectsStub.calledOnce).toEqual(true); 78 | expect(deleteObjectsStub.calledWithExactly(objects.map((i) => i.name))).toEqual(true); 79 | 80 | expect(deleteBucketStub.calledAfter(deleteObjectsStub)).toEqual(true); 81 | expect(deleteBucketStub.calledOnce).toEqual(true); 82 | expect(deleteBucketStub.calledWithExactly('sls-accountid-cn-shanghai')).toEqual(true); 83 | 84 | const logs = [ 85 | 'Removing 3 artifacts in OSS bucket sls-accountid-cn-shanghai...', 86 | 'Removed 3 artifacts in OSS bucket sls-accountid-cn-shanghai', 87 | 'Removing OSS bucket sls-accountid-cn-shanghai...', 88 | 'Removed OSS bucket sls-accountid-cn-shanghai' 89 | ]; 90 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 91 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 92 | } 93 | }); 94 | }); 95 | 96 | it('should only remove existing objects', () => { 97 | getBucketStub.returns(Promise.resolve(bucket)); 98 | getObjectsStub.returns(Promise.resolve([objects[0]])); 99 | deleteObjectsStub.returns(Promise.resolve()); 100 | deleteBucketStub.returns(Promise.resolve()); 101 | 102 | return aliyunRemove.removeArtifacts().then(() => { 103 | expect(deleteObjectsStub.calledOnce).toEqual(true); 104 | expect(deleteObjectsStub.calledWithExactly([objects[0].name])).toEqual(true); 105 | 106 | expect(deleteBucketStub.calledAfter(deleteObjectsStub)).toEqual(true); 107 | expect(deleteBucketStub.calledOnce).toEqual(true); 108 | expect(deleteBucketStub.calledWithExactly('sls-accountid-cn-shanghai')).toEqual(true); 109 | 110 | const logs = [ 111 | 'Removing 1 artifacts in OSS bucket sls-accountid-cn-shanghai...', 112 | 'Removed 1 artifacts in OSS bucket sls-accountid-cn-shanghai', 113 | 'Removing OSS bucket sls-accountid-cn-shanghai...', 114 | 'Removed OSS bucket sls-accountid-cn-shanghai' 115 | ]; 116 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 117 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 118 | } 119 | }); 120 | }); 121 | 122 | it('should not remove any objects if there is none', async () => { 123 | getBucketStub.returns(Promise.resolve(bucket)); 124 | getObjectsStub.returns(Promise.resolve([])); 125 | deleteObjectsStub.returns(Promise.resolve()); 126 | deleteBucketStub.returns(Promise.resolve()); 127 | 128 | await aliyunRemove.removeArtifacts(); 129 | expect(deleteObjectsStub.called).toEqual(false); 130 | 131 | expect(deleteBucketStub.calledOnce).toEqual(true); 132 | expect(deleteBucketStub.calledWithExactly('sls-accountid-cn-shanghai')).toEqual(true); 133 | 134 | const logs = [ 135 | 'No artifacts to remove.', 136 | 'Removing OSS bucket sls-accountid-cn-shanghai...', 137 | 'Removed OSS bucket sls-accountid-cn-shanghai' 138 | ]; 139 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 140 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 141 | } 142 | }); 143 | 144 | it('should not remove the bucket if there is not any', () => { 145 | getBucketStub.returns(Promise.resolve(undefined)); 146 | getObjectsStub.returns(Promise.resolve([])); 147 | deleteObjectsStub.returns(Promise.resolve()); 148 | deleteBucketStub.returns(Promise.resolve()); 149 | 150 | return aliyunRemove.removeArtifacts().then(() => { 151 | expect(deleteObjectsStub.called).toEqual(false); 152 | expect(deleteBucketStub.called).toEqual(false); 153 | const logs = [ 154 | 'No artifacts to remove.', 155 | 'No buckets to remove.' 156 | ]; 157 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 158 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 159 | } 160 | }); 161 | }); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /deploy/lib/setupEvents.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | module.exports = { 6 | async setupEvents() { 7 | this.apis = _.filter( 8 | this.templates.update.Resources, 9 | (item) => this.provider.isApiType(item.Type)) 10 | .map((item) => item.Properties); 11 | this.triggers = _.filter( 12 | this.templates.update.Resources, 13 | (item) => this.provider.isTriggerType(item.Type)) 14 | .map((item) => item.Properties); 15 | 16 | await this.setupInvokeRole(); 17 | await this.createApisIfNeeded(); 18 | await this.createTriggersIfNeeded(); 19 | }, 20 | 21 | async setupInvokeRole() { 22 | const invokeRoleResource = this.templates.update.Resources[this.provider.getInvokeRoleLogicalId()]; 23 | if(invokeRoleResource){ 24 | const role = invokeRoleResource.Properties; 25 | // TODO: update if needed 26 | this.invokeRole = await this.setupRole(role); 27 | } 28 | }, 29 | 30 | async createApisIfNeeded() { 31 | if (!this.apis.length) { 32 | return; 33 | } 34 | 35 | await this.createApiGroupIfNotExists(); 36 | await this.checkExistingApis(); 37 | await this.createOrUpdateApis(); 38 | await this.deployApis(); 39 | }, 40 | 41 | async createTriggersIfNeeded() { 42 | if (!this.triggers.length) { 43 | return; 44 | } 45 | 46 | await this.checkExistingTriggers(); 47 | await this.createOrUpdateTriggers(); 48 | }, 49 | 50 | async createApiGroupIfNotExists() { 51 | const groupResource = this.templates.update.Resources[this.provider.getApiGroupLogicalId()]; 52 | if (!groupResource) { 53 | return; // No API needed 54 | } 55 | 56 | const group = groupResource.Properties; 57 | const groupName = group.GroupName; 58 | 59 | const foundGroup = await this.provider.getApiGroup(groupName); 60 | if (foundGroup) { 61 | this.apiGroup = foundGroup; 62 | this.serverless.cli.log(`API group ${group.GroupName} exists.`); 63 | return foundGroup; 64 | } 65 | 66 | await this.createApiGroup(group); 67 | }, 68 | 69 | async createApiGroup(group) { 70 | this.serverless.cli.log(`Creating API group ${group.GroupName}...`); 71 | const createdGroup = await this.provider.createApiGroup(group); 72 | this.apiGroup = createdGroup; 73 | this.serverless.cli.log(`Created API group ${group.GroupName}`); 74 | return createdGroup; 75 | }, 76 | 77 | async checkExistingApis() { 78 | if (!this.apis.length) { 79 | return; 80 | } 81 | 82 | const apis = await this.provider.getApis({ 83 | GroupId: this.apiGroup.GroupId 84 | }); 85 | this.apiMap = new Map(apis.map((api) => [api.ApiName, api])); 86 | this.apis.forEach((api) => { 87 | if (!this.apiMap.get(api.ApiName)) { 88 | this.apiMap.set(api.ApiName, false); 89 | } 90 | }); 91 | }, 92 | 93 | async createOrUpdateApis() { 94 | if (!this.apis.length) { 95 | return; 96 | } 97 | 98 | for (var i = 0; i < this.apis.length; i++) { 99 | const api = this.apis[i]; 100 | await this.createOrUpdateApi(api); 101 | } 102 | }, 103 | 104 | async createOrUpdateApi(api) { 105 | const group = this.apiGroup; 106 | const role = this.invokeRole; 107 | const apiInMap = this.apiMap.get(api.ApiName); 108 | if (apiInMap) { 109 | const apiProps = Object.assign({ApiId: apiInMap.ApiId}, api); 110 | this.serverless.cli.log(`Updating API ${api.ApiName}...`); 111 | try { 112 | await this.provider.updateApi(group, role, apiProps); 113 | this.serverless.cli.log(`Updated API ${api.ApiName}`); 114 | } catch (err) { 115 | this.serverless.cli.log(`Failed to update API ${api.ApiName}!`); 116 | throw err; 117 | } 118 | return; 119 | } 120 | 121 | this.serverless.cli.log(`Creating API ${api.ApiName}...`); 122 | let newApi; 123 | try { 124 | newApi = await this.provider.createApi(group, role, api); 125 | } catch (err) { 126 | this.serverless.cli.log(`Failed to create API ${api.ApiName}!`); 127 | throw err; 128 | } 129 | this.serverless.cli.log(`Created API ${api.ApiName}`); 130 | this.apiMap.set(api.ApiName, newApi); 131 | }, 132 | 133 | async deployApis() { 134 | const group = this.apiGroup; 135 | for (var i = 0; i < this.apis.length; i++) { 136 | const api = this.apis[i]; 137 | const apiProps = this.apiMap.get(api.ApiName); 138 | this.serverless.cli.log(`Deploying API ${api.ApiName}...`); 139 | try { 140 | await this.provider.deployApi(group, apiProps); 141 | this.serverless.cli.log(`Deployed API ${api.ApiName}`); 142 | const config = api.RequestConfig; 143 | const func = api.ServiceConfig.FunctionComputeConfig; 144 | this.serverless.cli.log(`${config.RequestHttpMethod} ` + 145 | `http://${this.apiGroup.SubDomain}${config.RequestPath} -> ` + 146 | `${func.ServiceName}.${func.FunctionName}`); 147 | } catch (err) { 148 | this.serverless.cli.log(`Failed to deploy API ${api.ApiName}!`); 149 | throw err; 150 | } 151 | } 152 | }, 153 | 154 | async checkExistingTriggers() { 155 | this.triggerMap = new Map(); 156 | for (var i = 0; i < this.triggers.length; i++) { 157 | const trigger = this.triggers[i]; 158 | const foundTrigger = await this.provider.getTrigger( 159 | trigger.serviceName, trigger.functionName, trigger.triggerName 160 | ); 161 | if (foundTrigger) { 162 | this.triggerMap.set(trigger.triggerName, foundTrigger); 163 | } 164 | } 165 | }, 166 | 167 | async createOrUpdateTriggers() { 168 | if (!this.triggers.length) { 169 | return; 170 | } 171 | 172 | for (var i = 0; i < this.triggers.length; i++) { 173 | const trigger = this.triggers[i]; 174 | await this.createOrUpdateTrigger(trigger); 175 | } 176 | }, 177 | 178 | async createOrUpdateTrigger(trigger) { 179 | const role = this.invokeRole; 180 | const triggerName = trigger.triggerName; 181 | const serviceName = trigger.serviceName; 182 | const functionName = trigger.functionName; 183 | const triggerInMap = this.triggerMap.get(triggerName); 184 | if (triggerInMap) { 185 | this.serverless.cli.log(`Updating trigger ${triggerName}...`); 186 | try { 187 | await this.provider.updateTrigger(serviceName, functionName, triggerName, trigger, role); 188 | this.serverless.cli.log(`Updated trigger ${triggerName}`); 189 | } catch (err) { 190 | this.serverless.cli.log(`Failed to update trigger ${triggerName}!`); 191 | throw err; 192 | } 193 | return; 194 | } 195 | 196 | this.serverless.cli.log(`Creating trigger ${triggerName}...`); 197 | try { 198 | const newtrigger = await this.provider.createTrigger(serviceName, functionName, trigger, role); 199 | this.serverless.cli.log(`Created trigger ${triggerName}`); 200 | this.triggerMap.set(triggerName, newtrigger); 201 | } catch (err) { 202 | this.serverless.cli.log(`Failed to create trigger ${triggerName}!`); 203 | throw err; 204 | } 205 | } 206 | }; 207 | -------------------------------------------------------------------------------- /invoke/lib/invokeFunction.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunInvoke = require('../aliyunInvoke'); 10 | const Serverless = require('../../test/serverless'); 11 | 12 | describe('InvokeFunction', () => { 13 | let serverless; 14 | let aliyunInvoke; 15 | 16 | beforeEach(() => { 17 | serverless = new Serverless(); 18 | serverless.service.service = 'my-service'; 19 | serverless.service.provider = { 20 | name: 'aliyun', 21 | credentials: path.join(__dirname, '..', 'test', 'credentials'), 22 | }; 23 | serverless.config = { 24 | servicePath: path.join(__dirname, '..', 'test') 25 | }; 26 | const options = { 27 | stage: 'dev', 28 | region: 'my-region', 29 | }; 30 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 31 | serverless.pluginManager.setCliOptions(options); 32 | aliyunInvoke = new AliyunInvoke(serverless, options); 33 | }); 34 | 35 | describe('#invokeFunction()', () => { 36 | let invokeStub; 37 | 38 | beforeEach(() => { 39 | invokeStub = sinon.stub(aliyunInvoke, 'invoke'); 40 | }); 41 | 42 | afterEach(() => { 43 | aliyunInvoke.invoke.restore(); 44 | }); 45 | 46 | it('should run promise chain when invocation succeeds', () => { 47 | const data = { test: 'ok' }; 48 | invokeStub.returns(Promise.resolve(data)); 49 | return aliyunInvoke.invokeFunction().then(() => { 50 | expect(invokeStub.calledOnce).toEqual(true); 51 | }); 52 | }); 53 | 54 | it('should run promise chain when invocation fails', () => { 55 | const err = new Error(); 56 | invokeStub.returns(Promise.reject(err)); 57 | return aliyunInvoke.invokeFunction().then(() => { 58 | expect(invokeStub.calledOnce).toEqual(true); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('#invokeFunction()', () => { 64 | let invokeFunctionStub; 65 | let consoleLogStub; 66 | 67 | const response = '{"statusCode":200,"body":"{"message":"' + 68 | 'Hello, the current time is 2017-07-22:15:20:42!"}"}'; 69 | 70 | beforeEach(() => { 71 | invokeFunctionStub = sinon.stub(aliyunInvoke.provider, 'invokeFunction'); 72 | consoleLogStub = sinon.stub(aliyunInvoke.serverless.cli, 'log').returns(); 73 | aliyunInvoke.serverless.service.functions = { 74 | getTest: { 75 | handler: 'index.getHandler', 76 | events: [ 77 | { http: { 78 | path: '/baz', 79 | method: 'get' 80 | } } 81 | ] 82 | }, 83 | }; 84 | }); 85 | 86 | afterEach(() => { 87 | aliyunInvoke.provider.invokeFunction.restore(); 88 | aliyunInvoke.serverless.cli.log.restore(); 89 | }); 90 | 91 | it('should invoke the provided function without data option', () => { 92 | aliyunInvoke.options.function = 'getTest'; 93 | invokeFunctionStub.returns(Promise.resolve(response)); 94 | return aliyunInvoke.invokeFunction().then(() => { 95 | expect(invokeFunctionStub.calledOnce).toEqual(true); 96 | expect( 97 | invokeFunctionStub.calledWithExactly( 98 | 'my-service-dev', 99 | 'my-service-dev-getTest', 100 | undefined) 101 | ).toEqual(true); 102 | const logs = [ 103 | 'Invoking my-service-dev-getTest of my-service-dev', 104 | response 105 | ]; 106 | expect(consoleLogStub.callCount).toEqual(logs.length); 107 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 108 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 109 | } 110 | }); 111 | }); 112 | 113 | it('should invoke the provided function with JSON data', () => { 114 | aliyunInvoke.options.function = 'getTest'; 115 | aliyunInvoke.options.data = '{"a": "b"}'; 116 | invokeFunctionStub.returns(Promise.resolve(response)); 117 | 118 | return aliyunInvoke.invokeFunction().then(() => { 119 | expect(invokeFunctionStub.calledOnce).toEqual(true); 120 | expect( 121 | invokeFunctionStub.calledWithExactly( 122 | 'my-service-dev', 123 | 'my-service-dev-getTest', 124 | {a: 'b'}) 125 | ).toEqual(true); 126 | const logs = [ 127 | 'Invoking my-service-dev-getTest of my-service-dev with { a: \'b\' }', 128 | response 129 | ]; 130 | expect(consoleLogStub.callCount).toEqual(logs.length); 131 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 132 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 133 | } 134 | }); 135 | }); 136 | 137 | it('should invoke the provided function with string data', () => { 138 | aliyunInvoke.options.function = 'getTest'; 139 | aliyunInvoke.options.data = 'test'; 140 | invokeFunctionStub.returns(Promise.resolve(response)); 141 | 142 | return aliyunInvoke.invokeFunction().then(() => { 143 | expect(invokeFunctionStub.calledOnce).toEqual(true); 144 | expect( 145 | invokeFunctionStub.calledWithExactly( 146 | 'my-service-dev', 147 | 'my-service-dev-getTest', 148 | 'test') 149 | ).toEqual(true); 150 | const logs = [ 151 | 'Invoking my-service-dev-getTest of my-service-dev with \'test\'', 152 | response 153 | ]; 154 | expect(consoleLogStub.callCount).toEqual(logs.length); 155 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 156 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 157 | } 158 | }); 159 | }); 160 | 161 | it.only('should invoke the provided function with data in path', () => { 162 | aliyunInvoke.options.function = 'getTest'; 163 | aliyunInvoke.options.path = path.join(__dirname, '..', '..', 'test', 'invokeData.json'); 164 | invokeFunctionStub.returns(Promise.resolve(response)); 165 | 166 | return aliyunInvoke.invokeFunction().then(() => { 167 | expect(invokeFunctionStub.calledOnce).toEqual(true); 168 | expect( 169 | invokeFunctionStub.calledWithExactly( 170 | 'my-service-dev', 171 | 'my-service-dev-getTest', 172 | Buffer.from('{\n "foo": "bar"\n}', 'utf8')) 173 | ).toEqual(true); 174 | const logs = [ 175 | 'Invoking my-service-dev-getTest of my-service-dev with { foo: \'bar\' }', 176 | response 177 | ]; 178 | expect(consoleLogStub.callCount).toEqual(logs.length); 179 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 180 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 181 | } 182 | }); 183 | }); 184 | 185 | it('should log an error if the function could not be found in the service', () => { 186 | aliyunInvoke.options.function = 'missingFunc'; 187 | return aliyunInvoke.invokeFunction().then(() => { 188 | expect(consoleLogStub.calledOnce).toEqual(true); 189 | expect(consoleLogStub.getCall(0).args[0]).toBeInstanceOf(Error); 190 | }); 191 | }); 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /package/aliyunPackage.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const path = require('path'); 7 | const _ = require('lodash'); 8 | const fs = require('fs'); 9 | 10 | const AliyunProvider = require('../provider/aliyunProvider'); 11 | const AliyunPackage = require('./aliyunPackage'); 12 | const Serverless = require('../test/serverless'); 13 | const { ramRoleStatements, functionDefs, directory } = require('../test/data'); 14 | 15 | describe('AliyunPackage', () => { 16 | let serverless; 17 | let options; 18 | let aliyunPackage; 19 | let consoleLogStub; 20 | const servicePath = path.join(__dirname, '..', 'test', 'project'); 21 | beforeEach(() => { 22 | serverless = new Serverless(); 23 | serverless.service.service = 'my-service'; 24 | serverless.service.functions = _.cloneDeep(functionDefs); 25 | serverless.service.package = { 26 | artifact: '/tmp/my-service.zip' 27 | }; 28 | serverless.config = { 29 | servicePath: servicePath 30 | }; 31 | serverless.service.provider = { 32 | name: 'aliyun', 33 | credentials: path.join(__dirname, '..', 'test', 'credentials'), 34 | runtime: 'nodejs6', 35 | ramRoleStatements 36 | }; 37 | options = { 38 | stage: 'dev', 39 | region: 'cn-shanghai', 40 | }; 41 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 42 | serverless.pluginManager.setCliOptions(options); 43 | aliyunPackage = new AliyunPackage(serverless, options); 44 | consoleLogStub = sinon.stub(aliyunPackage.serverless.cli, 'log').returns(); 45 | sinon.stub(aliyunPackage.provider, 'getArtifactDirectoryName').returns(directory); 46 | }); 47 | 48 | afterEach(() => { 49 | aliyunPackage.serverless.cli.log.restore(); 50 | aliyunPackage.provider.getArtifactDirectoryName.restore(); 51 | }); 52 | 53 | xdescribe('#constructor()', () => { 54 | it('should set the serverless instance', () => { 55 | expect(aliyunPackage.serverless).toEqual(serverless); 56 | }); 57 | 58 | it('should set options if provided', () => { 59 | expect(aliyunPackage.options).toEqual(options); 60 | }); 61 | 62 | it('should make the provider accessible', () => { 63 | expect(aliyunPackage.provider).toBeInstanceOf(AliyunProvider); 64 | }); 65 | 66 | describe('hooks', () => { 67 | let cleanupServerlessDirStub; 68 | let validateStub; 69 | let setDefaultsStub; 70 | let prepareDeploymentStub; 71 | let saveCreateTemplateFileStub; 72 | let compileFunctionsStub; 73 | let mergeServiceResourcesStub; 74 | let saveUpdateTemplateFileStub; 75 | 76 | beforeEach(() => { 77 | cleanupServerlessDirStub = sinon.stub(aliyunPackage, 'cleanupServerlessDir') 78 | .returns(); 79 | validateStub = sinon.stub(aliyunPackage, 'validate') 80 | .returns(Promise.resolve()); 81 | setDefaultsStub = sinon.stub(aliyunPackage, 'setDefaults') 82 | .returns(); 83 | prepareDeploymentStub = sinon.stub(aliyunPackage, 'prepareDeployment') 84 | .returns(); 85 | saveCreateTemplateFileStub = sinon.stub(aliyunPackage, 'saveCreateTemplateFile') 86 | .returns(); 87 | compileFunctionsStub = sinon.stub(aliyunPackage, 'compileFunctions') 88 | .returns(Promise.resolve()); 89 | mergeServiceResourcesStub = sinon.stub(aliyunPackage, 'mergeServiceResources') 90 | .returns(); 91 | saveUpdateTemplateFileStub = sinon.stub(aliyunPackage, 'saveUpdateTemplateFile') 92 | .returns(); 93 | }); 94 | 95 | afterEach(() => { 96 | aliyunPackage.cleanupServerlessDir.restore(); 97 | aliyunPackage.validate.restore(); 98 | aliyunPackage.setDefaults.restore(); 99 | aliyunPackage.prepareDeployment.restore(); 100 | aliyunPackage.saveCreateTemplateFile.restore(); 101 | aliyunPackage.compileFunctions.restore(); 102 | aliyunPackage.mergeServiceResources.restore(); 103 | aliyunPackage.saveUpdateTemplateFile.restore(); 104 | }); 105 | 106 | it('should run "package:cleanup" promise chain', () => aliyunPackage 107 | .hooks['package:cleanup']().then(() => { 108 | expect(cleanupServerlessDirStub.calledOnce).toEqual(true); 109 | })); 110 | 111 | it('should run "before:package:initialize" promise chain', () => aliyunPackage 112 | .hooks['before:package:initialize']().then(() => { 113 | expect(validateStub.calledOnce).toEqual(true); 114 | expect(setDefaultsStub.calledAfter(validateStub)).toEqual(true); 115 | })); 116 | 117 | it('should run "package:initialize" promise chain', () => aliyunPackage 118 | .hooks['package:initialize']().then(() => { 119 | expect(prepareDeploymentStub.calledOnce).toEqual(true); 120 | expect(saveCreateTemplateFileStub.calledAfter(prepareDeploymentStub)).toEqual(true); 121 | })); 122 | 123 | it('should run "package:compileFunctions" promise chain', () => aliyunPackage 124 | .hooks['package:compileFunctions']().then(() => { 125 | expect(compileFunctionsStub.calledOnce).toEqual(true); 126 | })); 127 | 128 | it('should run "package:finalize" promise chain', () => aliyunPackage 129 | .hooks['package:finalize']().then(() => { 130 | expect(mergeServiceResourcesStub.calledOnce).toEqual(true); 131 | expect(saveUpdateTemplateFileStub.calledAfter(mergeServiceResourcesStub)).toEqual(true); 132 | })); 133 | }); 134 | }); 135 | 136 | describe('#package()', () => { 137 | beforeEach(() => { 138 | aliyunPackage.serverless.utils.writeFileSync = (filename, data) => { 139 | const dir = path.dirname(filename); 140 | if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } 141 | fs.writeFileSync(filename, JSON.stringify(data, null, 2), 'utf8'); 142 | }; 143 | }); 144 | 145 | afterEach(() => { 146 | aliyunPackage.serverless.utils.writeFileSync = () => {}; 147 | }); 148 | 149 | it('should output proper templates', () => { 150 | const serverlessPath = path.join(servicePath, '.serverless'); 151 | return aliyunPackage.hooks['package:cleanup']() 152 | .then(() => { 153 | expect(fs.existsSync(serverlessPath)).toBe(false); 154 | }) 155 | .then(() => aliyunPackage.hooks['before:package:initialize']()) 156 | .then(() => aliyunPackage.hooks['package:initialize']()) 157 | .then(() => { 158 | expect(fs.existsSync(serverlessPath)).toBe(true); 159 | }) 160 | .then(() => aliyunPackage.hooks['before:package:initialize']()) 161 | .then(() => aliyunPackage.hooks['package:compileFunctions']()) 162 | .then(() => aliyunPackage.hooks['package:finalize']()) 163 | .then(() => { 164 | const files = fs.readdirSync(serverlessPath); 165 | const createName = 'configuration-template-create.json'; 166 | const updateName = 'configuration-template-update.json'; 167 | expect(files).toContain(createName); 168 | expect(files).toContain(updateName); 169 | 170 | const createTmpl = JSON.parse( 171 | fs.readFileSync(path.join(serverlessPath, createName), 'utf8') 172 | ); 173 | const updateTmpl = JSON.parse( 174 | fs.readFileSync(path.join(serverlessPath, updateName), 'utf8') 175 | ); 176 | 177 | const testSlsPath = path.join(__dirname, '..', 'test', '.serverless'); 178 | const createExpected = JSON.parse( 179 | fs.readFileSync(path.join(testSlsPath, createName), 'utf8') 180 | ); 181 | const updateExpected = JSON.parse( 182 | fs.readFileSync(path.join(testSlsPath, updateName), 'utf8') 183 | ); 184 | 185 | updateExpected.Resources['sls-storage-object'].Properties.LocalPath = path.join(serverlessPath, 'my-service.zip'); 186 | 187 | expect(createTmpl).toEqual(createExpected); 188 | expect(updateTmpl).toEqual(updateExpected); 189 | 190 | const logs = [ 191 | 'Compiling function "postTest"...', 192 | 'Compiling function "getTest"...', 193 | 'Compiling function "ossTriggerTest"...', 194 | 'Finished Packaging.' 195 | ]; 196 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 197 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 198 | } 199 | }); 200 | }); 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /remove/lib/removeFunctionsAndService.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunRemove = require('../aliyunRemove'); 10 | const Serverless = require('../../test/serverless'); 11 | const { functionDefs, fullFunctions, fullService } = require('../../test/data'); 12 | 13 | describe('removeFunctionsAndService', () => { 14 | let serverless; 15 | let aliyunRemove; 16 | 17 | beforeEach(() => { 18 | serverless = new Serverless(); 19 | serverless.service.service = 'my-service'; 20 | serverless.service.provider = { 21 | name: 'aliyun', 22 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 23 | }; 24 | serverless.config = { 25 | servicePath: path.join(__dirname, '..', '..', 'test') 26 | }; 27 | const options = { 28 | stage: 'dev', 29 | region: 'cn-shanghai', 30 | }; 31 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 32 | serverless.pluginManager.setCliOptions(options); 33 | 34 | aliyunRemove = new AliyunRemove(serverless, options); 35 | }); 36 | 37 | describe('#removeFunctionsAndService()', () => { 38 | 39 | let consoleLogStub; 40 | let deleteFunctionStub; 41 | let deleteServiceStub; 42 | let removeRoleAndPoliciesStub; 43 | let removeLogProjectStub; 44 | 45 | beforeEach(() => { 46 | 47 | const options = { 48 | stage: 'dev', 49 | region: 'cn-shanghai', 50 | 'remove-logstore': true 51 | }; 52 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 53 | serverless.pluginManager.setCliOptions(options); 54 | 55 | aliyunRemove = new AliyunRemove(serverless, options); 56 | aliyunRemove.templates = { 57 | create: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-create.json')), 58 | update: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-update.json')), 59 | }; 60 | aliyunRemove.serverless.service.functions = {}; 61 | consoleLogStub = sinon.stub(aliyunRemove.serverless.cli, 'log').returns(); 62 | deleteFunctionStub = sinon.stub(aliyunRemove.provider, 'deleteFunction'); 63 | deleteServiceStub = sinon.stub(aliyunRemove.provider, 'deleteService'); 64 | removeRoleAndPoliciesStub = sinon.stub(aliyunRemove, 'removeRoleAndPolicies').returns(Promise.resolve()); 65 | removeLogProjectStub = sinon.stub(aliyunRemove, 'removeLogProject').returns(Promise.resolve()); 66 | aliyunRemove.fcService = undefined; 67 | aliyunRemove.fcFunctions = []; 68 | }); 69 | 70 | afterEach(() => { 71 | aliyunRemove.serverless.cli.log.restore(); 72 | aliyunRemove.provider.deleteFunction.restore(); 73 | aliyunRemove.provider.deleteService.restore(); 74 | aliyunRemove.removeRoleAndPolicies.restore(); 75 | aliyunRemove.removeLogProject.restore(); 76 | }); 77 | 78 | it('should remove existing functions and service', () => { 79 | aliyunRemove.serverless.service.functions = functionDefs; 80 | aliyunRemove.fcService = fullService; 81 | aliyunRemove.fcFunctions = fullFunctions; 82 | deleteFunctionStub.returns(Promise.resolve()); 83 | deleteServiceStub.returns(Promise.resolve()); 84 | removeLogProjectStub.returns(Promise.resolve()); 85 | 86 | return aliyunRemove.removeFunctionsAndService().then(() => { 87 | expect(deleteFunctionStub.callCount).toEqual(3); 88 | expect(deleteFunctionStub.getCall(0).args) 89 | .toEqual([ 90 | 'my-service-dev', 91 | 'my-service-dev-postTest' 92 | ]); 93 | expect(deleteFunctionStub.getCall(1).args) 94 | .toEqual([ 95 | 'my-service-dev', 96 | 'my-service-dev-getTest' 97 | ]); 98 | expect(deleteFunctionStub.getCall(2).args) 99 | .toEqual([ 100 | 'my-service-dev', 101 | 'my-service-dev-ossTriggerTest' 102 | ]); 103 | 104 | expect(deleteServiceStub.calledAfter(deleteFunctionStub)).toEqual(true); 105 | expect(deleteServiceStub.calledOnce).toEqual(true); 106 | expect(deleteServiceStub.calledWithExactly('my-service-dev')).toEqual(true); 107 | 108 | expect(removeRoleAndPoliciesStub.calledAfter(deleteServiceStub)).toEqual(true); 109 | expect(removeRoleAndPoliciesStub.calledWithExactly('sls-my-service-dev-cn-shanghai-exec-role')).toEqual(true); 110 | 111 | expect(removeLogProjectStub.calledAfter(removeRoleAndPoliciesStub)).toEqual(true); 112 | expect(removeLogProjectStub.calledWithExactly('sls-accountid-cn-shanghai-logs', 'my-service-dev')).toEqual(true); 113 | 114 | const logs = [ 115 | 'Removing functions...', 116 | 'Removing function my-service-dev-postTest of service my-service-dev...', 117 | 'Removed function my-service-dev-postTest of service my-service-dev', 118 | 'Removing function my-service-dev-getTest of service my-service-dev...', 119 | 'Removed function my-service-dev-getTest of service my-service-dev', 120 | 'Removing function my-service-dev-ossTriggerTest of service my-service-dev...', 121 | 'Removed function my-service-dev-ossTriggerTest of service my-service-dev', 122 | 'Removing service my-service-dev...', 123 | 'Removed service my-service-dev', 124 | 'Removing index from log project sls-accountid-cn-shanghai-logs log store my-service-dev...', 125 | 'Removed index from log project sls-accountid-cn-shanghai-logs log store my-service-dev', 126 | 'Removing log store from log project sls-accountid-cn-shanghai-logs...', 127 | 'Removed log store from log project sls-accountid-cn-shanghai-logs', 128 | 'Removing log project sls-accountid-cn-shanghai-logs...', 129 | 'Removed log project sls-accountid-cn-shanghai-logs' 130 | ]; 131 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 132 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 133 | } 134 | }); 135 | }); 136 | 137 | it('should only remove existing functions', () => { 138 | aliyunRemove.serverless.service.functions = functionDefs; 139 | aliyunRemove.fcService = fullService; 140 | aliyunRemove.fcFunctions = [fullFunctions[0]]; 141 | deleteFunctionStub.returns(Promise.resolve()); 142 | deleteServiceStub.returns(Promise.resolve()); 143 | 144 | return aliyunRemove.removeFunctionsAndService().then(() => { 145 | expect(deleteFunctionStub.calledOnce).toEqual(true); 146 | expect(deleteFunctionStub.getCall(0).args) 147 | .toEqual([ 148 | 'my-service-dev', 149 | 'my-service-dev-postTest' 150 | ]); 151 | 152 | expect(deleteServiceStub.calledAfter(deleteFunctionStub)).toEqual(true); 153 | expect(deleteServiceStub.calledOnce).toEqual(true); 154 | expect(deleteServiceStub.calledWithExactly('my-service-dev')).toEqual(true); 155 | 156 | expect(removeRoleAndPoliciesStub.calledAfter(deleteServiceStub)).toEqual(true); 157 | expect(removeRoleAndPoliciesStub.calledWithExactly('sls-my-service-dev-cn-shanghai-exec-role')).toEqual(true); 158 | 159 | const logs = [ 160 | 'Removing functions...', 161 | 'Removing function my-service-dev-postTest of service my-service-dev...', 162 | 'Removed function my-service-dev-postTest of service my-service-dev', 163 | 'Removing service my-service-dev...', 164 | 'Removed service my-service-dev' 165 | ]; 166 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 167 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 168 | } 169 | }); 170 | }); 171 | 172 | it('should not remove service if it does not exist', () => { 173 | aliyunRemove.serverless.service.functions = functionDefs; 174 | aliyunRemove.fcService = undefined; 175 | aliyunRemove.fcFunctions = []; 176 | deleteFunctionStub.returns(Promise.resolve()); 177 | deleteServiceStub.returns(Promise.resolve()); 178 | 179 | return aliyunRemove.removeFunctionsAndService().then(() => { 180 | expect(deleteFunctionStub.called).toEqual(false); 181 | expect(deleteServiceStub.called).toEqual(false); 182 | expect(removeRoleAndPoliciesStub.calledOnce).toEqual(true); 183 | expect(removeRoleAndPoliciesStub.calledWithExactly('sls-my-service-dev-cn-shanghai-exec-role')).toEqual(true); 184 | 185 | const logs = [ 186 | 'Removing functions...', 187 | 'No functions to remove.', 188 | 'No services to remove.' 189 | ]; 190 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 191 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 192 | } 193 | }); 194 | }); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /remove/lib/removeLogProject.test.js: -------------------------------------------------------------------------------- 1 | /*global beforeEach, afterEach, expect*/ 2 | 3 | 'use strict'; 4 | 5 | const sinon = require('sinon'); 6 | const path = require('path'); 7 | 8 | const AliyunProvider = require('../../provider/aliyunProvider'); 9 | const AliyunRemove = require('../aliyunRemove'); 10 | const Serverless = require('../../test/serverless'); 11 | const { role, fullRole } = require('../../test/data'); 12 | 13 | describe('removeLogProject', () => { 14 | let serverless; 15 | let aliyunRemove; 16 | const projectName = 'sls-accountid-cn-shanghai-logs'; 17 | const logStoreName = 'my-service-dev'; 18 | beforeEach(() => { 19 | serverless = new Serverless(); 20 | serverless.service.service = 'my-service'; 21 | serverless.service.provider = { 22 | name: 'aliyun', 23 | credentials: path.join(__dirname, '..', '..', 'test', 'credentials') 24 | }; 25 | serverless.config = { 26 | servicePath: path.join(__dirname, '..', '..', 'test') 27 | }; 28 | }); 29 | 30 | describe('#removeRoleAndPolicies()', () => { 31 | let consoleLogStub; 32 | 33 | let getLogStoreStub; 34 | let getLogProjectStub; 35 | let deleteLogIndexStub; 36 | let deleteLogStoreStub; 37 | let deleteLogProjectStub; 38 | 39 | beforeEach(() => { 40 | const options = { 41 | stage: 'dev', 42 | region: 'cn-shanghai', 43 | 'remove-logstore': true 44 | }; 45 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 46 | serverless.pluginManager.setCliOptions(options); 47 | aliyunRemove = new AliyunRemove(serverless, options); 48 | aliyunRemove.templates = { 49 | create: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-create.json')), 50 | update: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-update.json')), 51 | }; 52 | consoleLogStub = sinon.stub(aliyunRemove.serverless.cli, 'log').returns(); 53 | 54 | getLogStoreStub = sinon.stub(aliyunRemove.provider, 'getLogStore'); 55 | getLogProjectStub = sinon.stub(aliyunRemove.provider, 'getLogProject'); 56 | deleteLogIndexStub = sinon.stub(aliyunRemove.provider, 'deleteLogIndex'); 57 | deleteLogStoreStub = sinon.stub(aliyunRemove.provider, 'deleteLogStore'); 58 | deleteLogProjectStub = sinon.stub(aliyunRemove.provider, 'deleteLogProject'); 59 | }); 60 | 61 | afterEach(() => { 62 | aliyunRemove.serverless.cli.log.restore(); 63 | aliyunRemove.provider.getLogStore.restore(); 64 | aliyunRemove.provider.getLogProject.restore(); 65 | aliyunRemove.provider.deleteLogIndex.restore(); 66 | aliyunRemove.provider.deleteLogStore.restore(); 67 | aliyunRemove.provider.deleteLogProject.restore(); 68 | }); 69 | 70 | it('should remove existing log project', () => { 71 | getLogProjectStub.returns(Promise.resolve({})); 72 | getLogStoreStub.returns(Promise.resolve({})); 73 | deleteLogProjectStub.returns(Promise.resolve()); 74 | deleteLogStoreStub.returns(Promise.resolve()); 75 | deleteLogIndexStub.returns(Promise.resolve()); 76 | 77 | return aliyunRemove.removeLogProject(projectName, logStoreName).then(() => { 78 | expect(getLogProjectStub.calledWithExactly(projectName)).toEqual(true); 79 | expect(deleteLogProjectStub.calledAfter(deleteLogStoreStub)).toEqual(true); 80 | expect(deleteLogStoreStub.calledAfter(deleteLogIndexStub)).toEqual(true); 81 | 82 | const logs = [ 83 | 'Removing index from log project sls-accountid-cn-shanghai-logs log store my-service-dev...', 84 | 'Removed index from log project sls-accountid-cn-shanghai-logs log store my-service-dev', 85 | 'Removing log store from log project sls-accountid-cn-shanghai-logs...', 86 | 'Removed log store from log project sls-accountid-cn-shanghai-logs', 87 | 'Removing log project sls-accountid-cn-shanghai-logs...', 88 | 'Removed log project sls-accountid-cn-shanghai-logs', 89 | ]; 90 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 91 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 92 | } 93 | }); 94 | }); 95 | 96 | it('should skip non-existing log project', () => { 97 | getLogProjectStub.returns(Promise.resolve(undefined)); 98 | getLogStoreStub.returns(Promise.resolve({})); 99 | deleteLogProjectStub.returns(Promise.resolve()); 100 | deleteLogStoreStub.returns(Promise.resolve()); 101 | deleteLogIndexStub.returns(Promise.resolve()); 102 | 103 | return aliyunRemove.removeLogProject(projectName, logStoreName).then(() => { 104 | expect(getLogProjectStub.calledWithExactly(projectName)).toEqual(true); 105 | expect(getLogStoreStub.called).toEqual(false); 106 | expect(deleteLogProjectStub.called).toEqual(false); 107 | expect(deleteLogStoreStub.called).toEqual(false); 108 | expect(deleteLogIndexStub.called).toEqual(false); 109 | 110 | const logs = [ 111 | 'No log project to remove' 112 | ]; 113 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 114 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 115 | } 116 | }); 117 | }); 118 | 119 | 120 | it('should skip non-existing log store', () => { 121 | getLogProjectStub.returns(Promise.resolve({})); 122 | getLogStoreStub.returns(Promise.resolve(undefined)); 123 | deleteLogProjectStub.returns(Promise.resolve()); 124 | deleteLogStoreStub.returns(Promise.resolve()); 125 | deleteLogIndexStub.returns(Promise.resolve()); 126 | 127 | return aliyunRemove.removeLogProject(projectName, logStoreName).then(() => { 128 | expect(getLogProjectStub.calledWithExactly(projectName)).toEqual(true); 129 | expect(getLogStoreStub.calledWithExactly(projectName, logStoreName)).toEqual(true); 130 | expect(deleteLogProjectStub.called).toEqual(true); 131 | expect(deleteLogStoreStub.called).toEqual(false); 132 | expect(deleteLogIndexStub.called).toEqual(false); 133 | 134 | const logs = [ 135 | 'No log store to remove', 136 | 'Removing log project sls-accountid-cn-shanghai-logs...', 137 | 'Removed log project sls-accountid-cn-shanghai-logs' 138 | ]; 139 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 140 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 141 | } 142 | }); 143 | }); 144 | }); 145 | 146 | describe('#removeRoleAndPolicies()', () => { 147 | let consoleLogStub; 148 | let getLogStoreStub; 149 | let getLogProjectStub; 150 | let deleteLogIndexStub; 151 | let deleteLogStoreStub; 152 | let deleteLogProjectStub; 153 | 154 | beforeEach(() => { 155 | const options = { 156 | stage: 'dev', 157 | region: 'cn-shanghai' 158 | }; 159 | serverless.setProvider('aliyun', new AliyunProvider(serverless, options)); 160 | serverless.pluginManager.setCliOptions(options); 161 | aliyunRemove = new AliyunRemove(serverless, options); 162 | aliyunRemove.templates = { 163 | create: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-create.json')), 164 | update: require(path.join(__dirname, '..', '..', 'test', '.serverless', 'configuration-template-update.json')), 165 | }; 166 | consoleLogStub = sinon.stub(aliyunRemove.serverless.cli, 'log').returns(); 167 | 168 | getLogStoreStub = sinon.stub(aliyunRemove.provider, 'getLogStore'); 169 | getLogProjectStub = sinon.stub(aliyunRemove.provider, 'getLogProject'); 170 | deleteLogIndexStub = sinon.stub(aliyunRemove.provider, 'deleteLogIndex'); 171 | deleteLogStoreStub = sinon.stub(aliyunRemove.provider, 'deleteLogStore'); 172 | deleteLogProjectStub = sinon.stub(aliyunRemove.provider, 'deleteLogProject'); 173 | 174 | }); 175 | 176 | afterEach(() => { 177 | aliyunRemove.serverless.cli.log.restore(); 178 | aliyunRemove.provider.getLogStore.restore(); 179 | aliyunRemove.provider.getLogProject.restore(); 180 | aliyunRemove.provider.deleteLogIndex.restore(); 181 | aliyunRemove.provider.deleteLogStore.restore(); 182 | aliyunRemove.provider.deleteLogProject.restore(); 183 | }); 184 | 185 | it('should not do anything if there is no --remove-logstore', () => { 186 | getLogProjectStub.returns(Promise.resolve({})); 187 | getLogStoreStub.returns(Promise.resolve({})); 188 | deleteLogProjectStub.returns(Promise.resolve()); 189 | deleteLogStoreStub.returns(Promise.resolve()); 190 | deleteLogIndexStub.returns(Promise.resolve()); 191 | 192 | return aliyunRemove.removeLogProject(projectName, logStoreName).then(() => { 193 | expect(getLogProjectStub.called).toEqual(false); 194 | expect(getLogStoreStub.called).toEqual(false); 195 | expect(deleteLogProjectStub.called).toEqual(false); 196 | expect(deleteLogStoreStub.called).toEqual(false); 197 | expect(deleteLogIndexStub.called).toEqual(false); 198 | 199 | const logs = [ 200 | 'Skip removing log project' 201 | ]; 202 | for (var i = 0; i < consoleLogStub.callCount; ++i) { 203 | expect(consoleLogStub.getCall(i).args[0]).toEqual(logs[i]); 204 | } 205 | }); 206 | }); 207 | }); 208 | }); 209 | -------------------------------------------------------------------------------- /test/.serverless/configuration-template-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "Resources": { 3 | "sls-storage-bucket": { 4 | "Type": "ALIYUN::OSS:Bucket", 5 | "Properties": { 6 | "BucketName": "sls-accountid-cn-shanghai", 7 | "Region": "cn-shanghai" 8 | } 9 | }, 10 | "sls-function-service": { 11 | "Type": "ALIYUN::FC::Service", 12 | "Properties": { 13 | "name": "my-service-dev", 14 | "region": "cn-shanghai", 15 | "logConfig": { 16 | "logstore": "my-service-dev", 17 | "project": "sls-accountid-cn-shanghai-logs" 18 | } 19 | } 20 | }, 21 | "sls-log-project": { 22 | "Type": "ALIYUN::SLS::Project", 23 | "Properties": { 24 | "projectName": "sls-accountid-cn-shanghai-logs", 25 | "description": "Log project for serverless service my-service, generated by the Serverless framework" 26 | } 27 | }, 28 | "sls-log-store": { 29 | "Type": "ALIYUN::SLS::Store", 30 | "Properties": { 31 | "projectName": "sls-accountid-cn-shanghai-logs", 32 | "storeName": "my-service-dev", 33 | "description": "Log store for Function Compute service my-service-dev, generated by the Serverless framework", 34 | "ttl": 30, 35 | "shardCount": 2 36 | } 37 | }, 38 | "sls-log-index": { 39 | "Type": "ALIYUN::SLS::Index", 40 | "Properties": { 41 | "projectName": "sls-accountid-cn-shanghai-logs", 42 | "storeName": "my-service-dev", 43 | "ttl": 30, 44 | "keys": { 45 | "functionName": { 46 | "caseSensitive": false, 47 | "token": [ 48 | "\n", 49 | "\t", 50 | ";", 51 | ",", 52 | "=", 53 | ":" 54 | ], 55 | "type": "text" 56 | } 57 | } 58 | } 59 | }, 60 | "sls-fc-exec-role": { 61 | "Type": "ALIYUN::RAM::Role", 62 | "Properties": { 63 | "RoleName": "sls-my-service-dev-cn-shanghai-exec-role", 64 | "Description": "Allow Function Compute service my-service-dev to access other services, generated by the Serverless framework", 65 | "AssumeRolePolicyDocument": { 66 | "Version": "1", 67 | "Statement": [ 68 | { 69 | "Action": "sts:AssumeRole", 70 | "Effect": "Allow", 71 | "Principal": { 72 | "Service": [ 73 | "fc.aliyuncs.com" 74 | ] 75 | } 76 | } 77 | ] 78 | }, 79 | "Policies": [ 80 | { 81 | "PolicyName": "fc-my-service-dev-cn-shanghai-access", 82 | "Description": "Allow Function Compute service my-service-dev to access other services, generated by the Serverless framework", 83 | "PolicyDocument": { 84 | "Version": "1", 85 | "Statement": [ 86 | { 87 | "Action": [ 88 | "log:PostLogStoreLogs" 89 | ], 90 | "Resource": [ 91 | "acs:log:*:accountid:project/sls-accountid-cn-shanghai-logs/logstore/my-service-dev" 92 | ], 93 | "Effect": "Allow" 94 | }, 95 | { 96 | "Effect": "Allow", 97 | "Action": [ 98 | "oss:GetObject", 99 | "oss:PutObject" 100 | ], 101 | "Resource": [ 102 | "acs:oss:cn-shanghai:accountid:my-service-resource" 103 | ] 104 | } 105 | ] 106 | } 107 | } 108 | ] 109 | } 110 | }, 111 | "sls-storage-object": { 112 | "Type": "ALIYUN::OSS:Object", 113 | "Properties": { 114 | "BucketName": "sls-accountid-cn-shanghai", 115 | "ObjectName": "serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip", 116 | "LocalPath": "/projects/.serverless/my-service.zip" 117 | } 118 | }, 119 | "sls-my-service-dev-postTest": { 120 | "Type": "ALIYUN::FC::Function", 121 | "Properties": { 122 | "name": "my-service-dev-postTest", 123 | "service": "my-service-dev", 124 | "handler": "index.postHandler", 125 | "memorySize": 128, 126 | "timeout": 30, 127 | "runtime": "nodejs6", 128 | "code": { 129 | "ossBucketName": "sls-accountid-cn-shanghai", 130 | "ossObjectName": "serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip" 131 | } 132 | } 133 | }, 134 | "sls-api-group": { 135 | "Type": "ALIYUN::API::APIGroup", 136 | "Properties": { 137 | "GroupName": "my_service_dev_api", 138 | "Description": "API group for Function Compute service my-service-dev, generated by the Serverless framework.", 139 | "Region": "cn-shanghai" 140 | } 141 | }, 142 | "sls-fc-invoke-role": { 143 | "Type": "ALIYUN::RAM::Role", 144 | "Properties": { 145 | "RoleName": "sls-my-service-dev-cn-shanghai-invoke-role", 146 | "Description": "Allow Function Compute service my-service-dev to be triggered, generated by the Serverless framework", 147 | "AssumeRolePolicyDocument": { 148 | "Version": "1", 149 | "Statement": [ 150 | { 151 | "Action": "sts:AssumeRole", 152 | "Effect": "Allow", 153 | "Principal": { 154 | "Service": [ 155 | "apigateway.aliyuncs.com", 156 | "oss.aliyuncs.com" 157 | ] 158 | } 159 | } 160 | ] 161 | }, 162 | "Policies": [ 163 | { 164 | "PolicyType": "System", 165 | "PolicyName": "AliyunFCInvocationAccess", 166 | "RoleName": "sls-my-service-dev-cn-shanghai-invoke-role" 167 | } 168 | ] 169 | } 170 | }, 171 | "sls_http_my_service_dev_postTest": { 172 | "Type": "ALIYUN::API::HTTP", 173 | "Properties": { 174 | "GroupName": "my_service_dev_api", 175 | "ApiName": "sls_http_my_service_dev_postTest", 176 | "Visibility": "PUBLIC", 177 | "Description": "API for Function Compute function my-service-dev-postTest of service my-service-dev, triggered by http event, generated by the Serverless framework.", 178 | "AuthType": "ANONYMOUS", 179 | "RequestConfig": { 180 | "RequestProtocol": "HTTP", 181 | "RequestHttpMethod": "POST", 182 | "RequestMode": "PASSTHROUGH", 183 | "RequestPath": "/baz", 184 | "BodyFormat": "FORM", 185 | "PostBodyDescription": "" 186 | }, 187 | "ServiceConfig": { 188 | "ServiceProtocol": "FunctionCompute", 189 | "Mock": "FALSE", 190 | "ServiceTimeout": 3000, 191 | "FunctionComputeConfig": { 192 | "FcRegionId": "cn-shanghai", 193 | "ServiceName": "my-service-dev", 194 | "FunctionName": "my-service-dev-postTest" 195 | }, 196 | "ContentTypeValue": "application/json; charset=UTF-8" 197 | }, 198 | "RequestParameters": [ 199 | { 200 | "ApiParameterName": "foo", 201 | "ParameterType": "String", 202 | "Location": "Body", 203 | "Required": "OPTIONAL", 204 | "isHide": false, 205 | "DefaultValue": "bar", 206 | "DemoValue": "bar", 207 | "Description": "foo" 208 | } 209 | ], 210 | "ServiceParameters": [ 211 | { 212 | "ServiceParameterName": "foo", 213 | "Type": "String", 214 | "Location": "Body", 215 | "ParameterCatalog": "REQUEST" 216 | } 217 | ], 218 | "ServiceParametersMap": [ 219 | { 220 | "ServiceParameterName": "foo", 221 | "RequestParameterName": "foo" 222 | } 223 | ], 224 | "ResultType": "JSON", 225 | "ResultSample": "{}" 226 | } 227 | }, 228 | "sls-my-service-dev-getTest": { 229 | "Type": "ALIYUN::FC::Function", 230 | "Properties": { 231 | "name": "my-service-dev-getTest", 232 | "service": "my-service-dev", 233 | "handler": "index.getHandler", 234 | "memorySize": 128, 235 | "timeout": 30, 236 | "runtime": "nodejs6", 237 | "code": { 238 | "ossBucketName": "sls-accountid-cn-shanghai", 239 | "ossObjectName": "serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip" 240 | } 241 | } 242 | }, 243 | "sls_http_my_service_dev_getTest": { 244 | "Type": "ALIYUN::API::HTTP", 245 | "Properties": { 246 | "GroupName": "my_service_dev_api", 247 | "ApiName": "sls_http_my_service_dev_getTest", 248 | "Visibility": "PUBLIC", 249 | "Description": "API for Function Compute function my-service-dev-getTest of service my-service-dev, triggered by http event, generated by the Serverless framework.", 250 | "AuthType": "ANONYMOUS", 251 | "RequestConfig": { 252 | "RequestProtocol": "HTTP", 253 | "RequestHttpMethod": "GET", 254 | "RequestMode": "PASSTHROUGH", 255 | "RequestPath": "/quo", 256 | "BodyFormat": "", 257 | "PostBodyDescription": "" 258 | }, 259 | "ServiceConfig": { 260 | "ServiceProtocol": "FunctionCompute", 261 | "Mock": "FALSE", 262 | "ServiceTimeout": 3000, 263 | "FunctionComputeConfig": { 264 | "FcRegionId": "cn-shanghai", 265 | "ServiceName": "my-service-dev", 266 | "FunctionName": "my-service-dev-getTest" 267 | }, 268 | "ContentTypeValue": "application/json; charset=UTF-8" 269 | }, 270 | "RequestParameters": [], 271 | "ServiceParameters": [], 272 | "ServiceParametersMap": [], 273 | "ResultType": "JSON", 274 | "ResultSample": "{}" 275 | } 276 | }, 277 | "sls-my-service-dev-ossTriggerTest": { 278 | "Type": "ALIYUN::FC::Function", 279 | "Properties": { 280 | "name": "my-service-dev-ossTriggerTest", 281 | "service": "my-service-dev", 282 | "handler": "index.ossTriggerHandler", 283 | "memorySize": 128, 284 | "timeout": 30, 285 | "runtime": "nodejs6", 286 | "code": { 287 | "ossBucketName": "sls-accountid-cn-shanghai", 288 | "ossObjectName": "serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip" 289 | } 290 | } 291 | }, 292 | "sls_oss_my_service_dev_ossTriggerTest": { 293 | "Type": "ALIYUN::FC::Trigger", 294 | "Properties": { 295 | "sourceArn": "acs:oss:cn-shanghai:accountid:my-service-resource", 296 | "triggerConfig": { 297 | "events": [ 298 | "oss:ObjectCreated:PostObject", 299 | "oss:ObjectCreated:PutObject" 300 | ], 301 | "filter": { 302 | "key": { 303 | "prefix": "source/" 304 | } 305 | } 306 | }, 307 | "triggerName": "sls_oss_my_service_dev_ossTriggerTest", 308 | "triggerType": "oss", 309 | "functionName": "my-service-dev-ossTriggerTest", 310 | "serviceName": "my-service-dev" 311 | } 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /test/data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // should correspond to test/.serverless/configuration-template-update.json 3 | 4 | exports.apiGroup = { 5 | 'GroupName': 'my_service_dev_api', 6 | 'Description': 'API group for Function Compute service my-service-dev, generated by the Serverless framework.', 7 | 'Region': 'cn-shanghai' 8 | }; 9 | 10 | exports.fullGroup = { 11 | 'GroupName': 'my_service_dev_api', 12 | 'Description': 'API group for Function Compute service my-service-dev, generated by the Serverless framework.', 13 | 'GroupId': '523e8dc7bbe04613b5b1d726c2a7889d', 14 | 'SubDomain': '523e8dc7bbe04613b5b1d726c2a7889d-cn-shanghai.alicloudapi.com' 15 | }; 16 | 17 | exports.apis = [{ 18 | 'GroupName': 'my_service_dev_api', 19 | 'ApiName': 'sls_http_my_service_dev_postTest', 20 | 'Visibility': 'PUBLIC', 21 | 'Description': 'API for Function Compute function my-service-dev-postTest of service my-service-dev, triggered by http event, generated by the Serverless framework.', 22 | 'AuthType': 'ANONYMOUS', 23 | 'RequestConfig': { 24 | 'RequestProtocol': 'HTTP', 25 | 'RequestHttpMethod': 'POST', 26 | 'RequestMode': 'PASSTHROUGH', 27 | 'RequestPath': '/baz', 28 | 'BodyFormat': 'FORM', 29 | 'PostBodyDescription': '' 30 | }, 31 | 'ServiceConfig': { 32 | 'ServiceProtocol': 'FunctionCompute', 33 | 'Mock': 'FALSE', 34 | 'ServiceTimeout': 3000, 35 | 'FunctionComputeConfig': { 36 | 'FcRegionId': 'cn-shanghai', 37 | 'ServiceName': 'my-service-dev', 38 | 'FunctionName': 'my-service-dev-postTest' 39 | }, 40 | 'ContentTypeValue': 'application/json; charset=UTF-8' 41 | }, 42 | 'RequestParameters': [ 43 | { 44 | 'ApiParameterName': 'foo', 45 | 'ParameterType': 'String', 46 | 'Location': 'Body', 47 | 'Required': 'OPTIONAL', 48 | 'isHide': false, 49 | 'DefaultValue': 'bar', 50 | 'DemoValue': 'bar', 51 | 'Description': 'foo' 52 | } 53 | ], 54 | 'ServiceParameters': [ 55 | { 56 | 'ServiceParameterName': 'foo', 57 | 'Type': 'String', 58 | 'Location': 'Body', 59 | 'ParameterCatalog': 'REQUEST' 60 | } 61 | ], 62 | 'ServiceParametersMap': [ 63 | { 64 | 'ServiceParameterName': 'foo', 65 | 'RequestParameterName': 'foo' 66 | } 67 | ], 68 | 'ResultType': 'JSON', 69 | 'ResultSample': '{}' 70 | }, { 71 | 'GroupName': 'my_service_dev_api', 72 | 'ApiName': 'sls_http_my_service_dev_getTest', 73 | 'Visibility': 'PUBLIC', 74 | 'Description': 'API for Function Compute function my-service-dev-getTest of service my-service-dev, triggered by http event, generated by the Serverless framework.', 75 | 'AuthType': 'ANONYMOUS', 76 | 'RequestConfig': { 77 | 'RequestProtocol': 'HTTP', 78 | 'RequestHttpMethod': 'GET', 79 | 'RequestMode': 'PASSTHROUGH', 80 | 'RequestPath': '/quo', 81 | 'BodyFormat': '', 82 | 'PostBodyDescription': '' 83 | }, 84 | 'ServiceConfig': { 85 | 'ServiceProtocol': 'FunctionCompute', 86 | 'Mock': 'FALSE', 87 | 'ServiceTimeout': 3000, 88 | 'FunctionComputeConfig': { 89 | 'FcRegionId': 'cn-shanghai', 90 | 'ServiceName': 'my-service-dev', 91 | 'FunctionName': 'my-service-dev-getTest' 92 | }, 93 | 'ContentTypeValue': 'application/json; charset=UTF-8' 94 | }, 95 | 'RequestParameters': [], 96 | 'ServiceParameters': [], 97 | 'ServiceParametersMap': [], 98 | 'ResultType': 'JSON', 99 | 'ResultSample': '{}' 100 | }]; 101 | 102 | exports.fullApis = [{ 103 | 'ApiName': 'sls_http_my_service_dev_postTest', 104 | 'ApiId': '4134134134141' 105 | }, { 106 | 'ApiName': 'sls_http_my_service_dev_getTest', 107 | 'ApiId': '413243280141' 108 | }]; 109 | 110 | exports.role = { 111 | 'RoleName': 'sls-my-service-dev-cn-shanghai-invoke-role', 112 | 'Description': 'Allow Function Compute service my-service-dev to be triggered, generated by the Serverless framework', 113 | 'AssumeRolePolicyDocument': { 114 | 'Version': '1', 115 | 'Statement': [ 116 | { 117 | 'Action': 'sts:AssumeRole', 118 | 'Effect': 'Allow', 119 | 'Principal': { 120 | 'Service': [ 121 | 'apigateway.aliyuncs.com', 122 | 'oss.aliyuncs.com' 123 | ] 124 | } 125 | } 126 | ] 127 | }, 128 | 'Policies': [ 129 | { 130 | 'PolicyType': 'System', 131 | 'PolicyName': 'AliyunFCInvocationAccess', 132 | 'RoleName': 'sls-my-service-dev-cn-shanghai-invoke-role' 133 | } 134 | ] 135 | }; 136 | 137 | exports.execRole = { 138 | 'RoleName': 'sls-my-service-dev-cn-shanghai-exec-role', 139 | 'Description': 'Allow Function Compute service my-service-dev to access other services, generated by the Serverless framework', 140 | 'AssumeRolePolicyDocument': { 141 | 'Version': '1', 142 | 'Statement': [ 143 | { 144 | 'Action': 'sts:AssumeRole', 145 | 'Effect': 'Allow', 146 | 'Principal': { 147 | 'Service': [ 148 | 'fc.aliyuncs.com' 149 | ] 150 | } 151 | } 152 | ] 153 | }, 154 | 'Policies': [ 155 | { 156 | 'PolicyName': 'fc-my-service-dev-cn-shanghai-access', 157 | 'Description': 'Allow Function Compute service my-service-dev to access other services, generated by the Serverless framework', 158 | 'PolicyDocument': { 159 | 'Version': '1', 160 | 'Statement': [ 161 | { 162 | 'Action': [ 163 | 'log:PostLogStoreLogs' 164 | ], 165 | 'Resource': [ 166 | 'acs:log:*:accountid:project/sls-accountid-cn-shanghai-logs/logstore/my-service-dev' 167 | ], 168 | 'Effect': 'Allow' 169 | }, 170 | { 171 | 'Effect': 'Allow', 172 | 'Action': [ 173 | 'oss:GetObject', 174 | 'oss:PutObject' 175 | ], 176 | 'Resource': [ 177 | 'acs:oss:cn-shanghai:accountid:my-service-resource' 178 | ] 179 | } 180 | ] 181 | } 182 | } 183 | ] 184 | }; 185 | 186 | exports.fullRole = { 187 | 'RoleId': '901234567890123', 188 | 'RoleName': 'sls-my-service-dev-cn-shanghai-invoke-role', 189 | 'Arn': 'acs:ram::1234567890123456:role/sls-my-service-dev-cn-shanghai-invoke-role' 190 | }; 191 | 192 | exports.fullExecRole = { 193 | 'RoleId': '901234223890123', 194 | 'RoleName': 'sls-my-service-dev-cn-shanghai-exec-role', 195 | 'Arn': 'acs:ram::1234567890123456:role/sls-my-service-dev-cn-shanghai-exec-role' 196 | }; 197 | 198 | exports.service = { 199 | 'name': 'my-service-dev', 200 | 'region': 'cn-shanghai', 201 | 'logConfig': { 202 | 'logstore': 'my-service-dev', 203 | 'project': 'sls-accountid-cn-shanghai-logs' 204 | } 205 | }; 206 | 207 | exports.functions = [{ 208 | 'name': 'my-service-dev-postTest', 209 | 'service': 'my-service-dev', 210 | 'handler': 'index.postHandler', 211 | 'memorySize': 128, 212 | 'timeout': 30, 213 | 'runtime': 'nodejs6', 214 | 'code': { 215 | 'ossBucketName': 'sls-accountid-cn-shanghai', 216 | 'ossObjectName': 'serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip' 217 | } 218 | }, { 219 | 'name': 'my-service-dev-getTest', 220 | 'service': 'my-service-dev', 221 | 'handler': 'index.getHandler', 222 | 'memorySize': 128, 223 | 'timeout': 30, 224 | 'runtime': 'nodejs6', 225 | 'code': { 226 | 'ossBucketName': 'sls-accountid-cn-shanghai', 227 | 'ossObjectName': 'serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip' 228 | } 229 | }, { 230 | 'name': 'my-service-dev-ossTriggerTest', 231 | 'service': 'my-service-dev', 232 | 'handler': 'index.ossTriggerHandler', 233 | 'memorySize': 128, 234 | 'timeout': 30, 235 | 'runtime': 'nodejs6', 236 | 'code': { 237 | 'ossBucketName': 'sls-accountid-cn-shanghai', 238 | 'ossObjectName': 'serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip' 239 | } 240 | }]; 241 | 242 | exports.fullFunctions = [{ 243 | 'functionName': 'my-service-dev-postTest', 244 | 'lastModifiedTime': '2017-07-21T07:38:41.413Z' 245 | }, { 246 | 'functionName': 'my-service-dev-getTest', 247 | 'lastModifiedTime': '2017-07-21T07:38:41.413Z' 248 | }, { 249 | 'functionName': 'my-service-dev-ossTriggerTest', 250 | 'lastModifiedTime': '2017-07-21T07:38:41.413Z' 251 | }]; 252 | 253 | exports.bucket = { 254 | name: 'sls-accountid-cn-shanghai', 255 | region: 'cn-shanghai' 256 | }; 257 | 258 | exports.objects = [{ 259 | name: 'serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z/my-service.zip' 260 | },{ 261 | name: 'serverless/my-service/dev/1510622721413-2017-07-23T07:38:41.413Z/my-service.zip' 262 | },{ 263 | name: 'serverless/my-service/dev/1520622721413-2017-07-25T07:38:41.413Z/my-service.zip' 264 | }]; 265 | 266 | exports.directory = 'serverless/my-service/dev/1500622721413-2017-07-21T07:38:41.413Z'; 267 | 268 | exports.functionDefs = { 269 | postTest: { 270 | handler: 'index.postHandler', 271 | events: [{ 272 | http: { 273 | path: '/baz', 274 | method: 'post', 275 | bodyFormat: 'form', 276 | parameters: [{ 277 | name: 'foo', 278 | type: 'string', 279 | location: 'body', 280 | optional: true, 281 | default: 'bar', 282 | demo: 'bar', 283 | description: 'foo', 284 | }] 285 | } 286 | }] 287 | }, 288 | getTest: { 289 | handler: 'index.getHandler', 290 | events: [ 291 | { http: { 292 | path: '/quo', 293 | method: 'get' 294 | } }, 295 | ], 296 | }, 297 | ossTriggerTest: { 298 | handler: 'index.ossTriggerHandler', 299 | events: [{ 300 | oss: { 301 | sourceArn: 'acs:oss:cn-shanghai:accountid:my-service-resource', 302 | triggerConfig: { 303 | events: [ 304 | 'oss:ObjectCreated:PostObject', 305 | 'oss:ObjectCreated:PutObject' 306 | ], 307 | filter: { key: { prefix: 'source/' } } 308 | } 309 | }, 310 | }] 311 | } 312 | }; 313 | 314 | exports.logProject = { 315 | 'projectName': 'sls-accountid-cn-shanghai-logs', 316 | 'description': 'Log project for serverless service my-service, generated by the Serverless framework' 317 | }; 318 | 319 | exports.fullLogProject = { 320 | 'createTime': '2017-08-17 17:56:36', 321 | 'description': 'Log project for serverless service my-service, generated by the Serverless framework', 322 | 'lastModifyTime': '2017-08-17 17:56:36', 323 | 'owner': 'accountid', 324 | 'projectName': 'sls-accountid-cn-shanghai-logs', 325 | 'region': 'cn-shanghai', 326 | 'status': 'Normal' 327 | }; 328 | 329 | exports.logStore = { 330 | 'projectName': 'sls-accountid-cn-shanghai-logs', 331 | 'storeName': 'my-service-dev', 332 | 'description': 'Log store for Function Compute service my-service-dev, generated by the Serverless framework', 333 | 'ttl': 30, 334 | 'shardCount': 2 335 | }; 336 | 337 | exports.fullLogStore = { 338 | 'logstoreName': 'my-service-dev', 339 | 'ttl': 30, 340 | 'shardCount': 2, 341 | 'enable_tracking': false, 342 | 'createTime': 1502963801, 343 | 'lastModifyTime': 1502963801 344 | }; 345 | 346 | exports.logIndex = { 347 | 'projectName': 'sls-accountid-cn-shanghai-logs', 348 | 'storeName': 'my-service-dev', 349 | 'ttl': 30, 350 | 'keys': { 351 | 'functionName': { 352 | 'caseSensitive': false, 353 | 'token': [ 354 | '\n', 355 | '\t', 356 | ';', 357 | ',', 358 | '=', 359 | ':' 360 | ], 361 | 'type': 'text' 362 | } 363 | } 364 | }; 365 | exports.fullLogIndex = { 366 | 'index_mode': 'v2', 367 | 'keys': { 368 | 'functionName': { 369 | 'caseSensitive': false, 370 | 'token': [ 371 | '\n', 372 | '\t', 373 | ';', 374 | ',', 375 | '=', 376 | ':' 377 | ], 378 | 'type': 'text' 379 | } 380 | }, 381 | 'storage': 'pg', 382 | 'ttl': 30, 383 | 'lastModifyTime': 1502963801 384 | }; 385 | 386 | exports.logs = [{ 387 | __source__: '', 388 | __time__: '1503051506', 389 | __topic__: 'my-service-dev', 390 | functionName: 'my-service-dev-postTest', 391 | message: '2017-08-18T10:18:26.131Z [info] FunctionCompute nodejs runtime inited.\r', 392 | serviceName: 'my-service-dev' 393 | }, 394 | { 395 | __source__: '', 396 | __time__: '1503051506', 397 | __topic__: 'my-service-dev', 398 | functionName: 'my-service-dev-postTest', 399 | message: 'FC Invoke Start RequestId: 332425-41-143112-415219434\r', 400 | serviceName: 'my-service-dev' 401 | }, 402 | { 403 | __source__: '', 404 | __time__: '1503052461', 405 | __topic__: 'my-service-dev', 406 | functionName: 'my-service-dev-postTest', 407 | message: 'FC Invoke End RequestId: 25222ee9-41-143112-415219434', 408 | serviceName: 'my-service-dev' 409 | }]; 410 | 411 | exports.ramRoleStatements = [{ 412 | Effect: 'Allow', 413 | Action: ['oss:GetObject', 'oss:PutObject'], 414 | Resource: ['acs:oss:cn-shanghai:accountid:my-service-resource'] 415 | }]; 416 | 417 | exports.triggers = [{ 418 | sourceArn: 'acs:oss:cn-shanghai:accountid:my-service-resource', 419 | triggerConfig: { 420 | events: [ 421 | 'oss:ObjectCreated:PostObject', 422 | 'oss:ObjectCreated:PutObject' 423 | ], 424 | filter: { key: { prefix: 'source/' } } 425 | }, 426 | triggerName: 'sls_oss_my_service_dev_ossTriggerTest', 427 | triggerType: 'oss', 428 | functionName: 'my-service-dev-ossTriggerTest', 429 | serviceName: 'my-service-dev' 430 | }]; 431 | 432 | exports.fullTriggers = [{ 433 | createdTime: '2017-08-15T15:00:00.000+0000', 434 | invocationRole: 'acs:ram::1234567890123456:role/sls-my-service-dev-cn-shanghai-invoke-role', 435 | lastModifiedTime: '2017-08-15T15:00:00.000+0000', 436 | sourceArn: 'acs:oss:cn-shanghai:accountid:my-service-resource', 437 | triggerConfig: { 438 | events: [ 439 | 'oss:ObjectCreated:PostObject', 440 | 'oss:ObjectCreated:PutObject' 441 | ], 442 | filter: { key: { prefix: 'source/' } } 443 | }, 444 | triggerName: 'sls_oss_my_service_dev_ossTriggerTest', 445 | triggerType: 'oss' 446 | }]; 447 | 448 | exports.fullService = { 449 | serviceId: '15e2c99d57e1cbbf2e267', 450 | serviceName: 'my-service-dev' 451 | }; 452 | --------------------------------------------------------------------------------