├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── package.json ├── resources └── eb.config.yml ├── src ├── ElasticBeanstalkPlugin.ts ├── index.ts ├── lib │ ├── AWS.ts │ ├── build.ts │ ├── configure.ts │ ├── deploy.ts │ ├── getVersion.ts │ └── validate.ts └── types.ts ├── test ├── fixture │ ├── .serverless │ │ ├── .artifacts │ │ │ └── bundle-app-name-1.0.0.zip │ │ └── stack-config.json │ ├── create-eb-app-version-response.json │ ├── describe-app-versions-failed-response.json │ ├── describe-app-versions-response.json │ ├── describe-envs-response.json │ ├── serverless.yml │ ├── update-env-response.json │ └── upload-app-s3-response.json ├── src │ ├── ElasticBeanstalk.test.ts │ └── lib │ │ ├── AWS.test.ts │ │ ├── build.test.ts │ │ ├── configure.test.ts │ │ ├── deploy.test.ts │ │ └── getVersion.test.ts └── stubs │ └── context.js ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .serverless 4 | .elasticbeanstalk 5 | *.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | !dist 4 | .babelrc 5 | .eslintrc 6 | .eslintignore 7 | .npmignore 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: ['6', '7', '8'] 4 | 5 | install: 6 | - yarn install 7 | 8 | script: 9 | - yarn lint 10 | - yarn build 11 | - yarn test 12 | 13 | before_deploy: 14 | - echo "//registry.npmjs.org/:_authToken=\${NPM_API_KEY}" > ~/.npmrc 15 | 16 | deploy: 17 | provider: script 18 | skip_cleanup: true 19 | script: npm publish . 20 | on: 21 | tags: true 22 | node: '7' 23 | 24 | notifications: 25 | email: 26 | tom@x-c-o-d-e.com 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased][] 4 | 5 | ## [1.5.1][] - 2019-01-22 6 | 7 | ### Updated 8 | 9 | - Updated `bundle-bundler` dependency 10 | 11 | ## [1.5.0][] - 2018-10-22 12 | 13 | ### Updated 14 | 15 | * Changed the S3 methods to not use async methods as AWS-SDK provides promisified returns. 16 | 17 | ## [1.4.0][] - 2018-03-01 18 | 19 | ### Added 20 | 21 | * Added a handler for `FAILED` application creation. Previous behaviour would wait indefinitely if the creation failed. Now it throws an error. 22 | 23 | ### Updated 24 | 25 | * Updated AWS ElasticBeanstalk platform versions to latest versions 26 | 27 | ## [1.3.0][] - 2017-08-27 28 | 29 | ### Updated 30 | 31 | * Updated dependencies 32 | 33 | ## [1.2.2][] - 2017-08-26 34 | 35 | ### Fixed 36 | 37 | * Fixed default bundle name 38 | 39 | ## [1.2.1][] - 2017-08-26 40 | 41 | ### Fixed 42 | 43 | * Bundle source path 44 | 45 | ## [1.2.0][] - 2017-08-26 46 | 47 | ### Feature 48 | 49 | * Added optional `file` config property to `IPluginConfig` to allow custom names and/or adding a prefix 50 | 51 | ### Updated 52 | 53 | * Updated dependencies 54 | * Updated AWS ElasticBeanstalk platform versions 55 | 56 | [Unreleased]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.5.1...HEAD 57 | [1.5.1]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.5.0...v1.5.1 58 | [1.5.0]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.4.0...v1.5.0 59 | [1.4.0]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.3.0...v1.4.0 60 | [1.3.0]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.2.2...v1.3.0 61 | [1.2.2]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.2.1...v1.2.2 62 | [1.2.1]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.2.0...v1.2.1 63 | [1.2.0]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/compare/v1.1.0...v1.2.0 64 | [1.1.0]: https://github.com/rawphp/serverless-plugin-elastic-beanstalk/tree/v1.1.0 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elastic Beanstalk Deployment Plugin 2 | 3 | [![Build Status](https://travis-ci.org/rawphp/serverless-plugin-elastic-beanstalk.svg?branch=master)](https://travis-ci.org/rawphp/serverless-plugin-elastic-beanstalk) 4 | 5 | A serverless plugin to deploy applications to AWS ElasticBeanstalk. 6 | 7 | ## Dependencies 8 | 9 | * This plugin is dependent on the output of [Stack Config Plugin for Serverless](https://www.npmjs.com/package/serverless-plugin-stack-config) 10 | 11 | ## Features 12 | 13 | * `elastic-beanstalk` - This uploads an ElasticBeanstalk application. 14 | 15 | ## Install 16 | 17 | ```shell 18 | npm install --save serverless-plugin-elastic-beanstalk 19 | ``` 20 | 21 | ## Usage 22 | 23 | Add the plugin to your `serverless.yml` like the following: 24 | 25 | ### serverless.yml: 26 | 27 | ```yaml 28 | provider: 29 | ... 30 | 31 | plugins: 32 | - serverless-plugin-elastic-beanstalk 33 | 34 | custom: 35 | elastic-beanstalk: 36 | variables: 37 | applicationName: CartApplicationName 38 | environmentName: CartApplicationEvironmentName 39 | key: ${opt:key} 40 | file: 41 | prefix: bundles 42 | name: bundle-latest.zip 43 | platform: nodejs 44 | script: scripts/configure.js 45 | build: 46 | babel: true 47 | sourceMaps: true 48 | include: 49 | - .ebextensions/** 50 | - src/** 51 | - resources/schema/** 52 | - package.json 53 | 54 | functions: 55 | ... 56 | resources: 57 | Resources: 58 | CartApplication: 59 | Type: AWS::ElasticBeanstalk::Application 60 | Properties: 61 | ApplicationName: ${self:service} 62 | Description: Cart application 63 | CartEnvironment: 64 | Type: AWS::ElasticBeanstalk::Environment 65 | Properties: 66 | ApplicationName: 67 | Ref: CartApplication 68 | Description: Cart environment 69 | SolutionStackName: '64bit Amazon Linux 2017.03 v4.4.5 running Node.js' 70 | OptionSettings: 71 | - Namespace: aws:elasticbeanstalk:container:nodejs 72 | OptionName: NodeVersion 73 | Value: '7.6.0' 74 | - Namespace: aws:elasticbeanstalk:environment 75 | OptionName: EnvironmentType 76 | Value: SingleInstance 77 | ... 78 | Outputs: 79 | CartApplicationName: 80 | Description: Cart application name 81 | Value: 82 | Ref: CartApplication 83 | CartApplicationEvironmentName: 84 | Description: Cart environment name 85 | Value: 86 | Ref: CartEnvironment 87 | ... 88 | ``` 89 | 90 | **NOTE:** If providing a custom script, that script must be exported from the module using `module.exports`. 91 | 92 | ### shell command: 93 | 94 | ```shell 95 | serverless elastic-beanstalk --stage dev --region eu-west-1 --key ec2-key 96 | ``` 97 | 98 | ## License 99 | 100 | MIT 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-plugin-elastic-beanstalk", 3 | "description": "A serverless plugin to deploy applications to AWS ElasticBeanstalk.", 4 | "version": "1.5.1", 5 | "main": "dist/index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "prebuild": "yarn run clear", 9 | "build": "tsc", 10 | "prerelease": "yarn build && yarn version", 11 | "clear": "rm -rf dist", 12 | "watch": "npm-watch", 13 | "lint": "tslint -c tslint.json 'src/**/*.ts'", 14 | "test": "env TZ='UTC' NODE_ENV=test mocha --require ts-node/register test/src/*.test.ts test/src/**/*.test.ts", 15 | "version": "version-changelog CHANGELOG.md && changelog-verify CHANGELOG.md && git add CHANGELOG.md" 16 | }, 17 | "engines": { 18 | "node": ">=6.10" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:rawphp/serverless-plugin-elastic-beanstalk.git" 23 | }, 24 | "keywords": [ 25 | "serverless", 26 | "plugin", 27 | "aws", 28 | "elastic", 29 | "beanstalk", 30 | "command", 31 | "CLI" 32 | ], 33 | "dependencies": { 34 | "aws-sdk": "2.x.x", 35 | "bluebird": "3.x.x", 36 | "bundle-bundler": "0.2.x", 37 | "fs-promise": "2.x.x" 38 | }, 39 | "devDependencies": { 40 | "@types/bluebird": "3.x.x", 41 | "@types/chai": "4.x.x", 42 | "@types/fs-promise": "1.x.x", 43 | "@types/mocha": "2.x.x", 44 | "@types/node": "7.x.x", 45 | "awesome-typescript-loader": "3.x.x", 46 | "babel-cli": "6.x.x", 47 | "babel-core": "6.x.x", 48 | "babel-eslint": "7.x.x", 49 | "babel-loader": "6.x.x", 50 | "babel-preset-env": "1.x.x", 51 | "babel-preset-stage-3": "6.x.x", 52 | "chai": "3.x.x", 53 | "changelog-verify": "1.x.x", 54 | "esdoc": "0.x.x", 55 | "esdoc-es7-plugin": "0.x.x", 56 | "mocha": "3.x.x", 57 | "serverless": "^1.32.0", 58 | "sinon": "1.x.x", 59 | "ts-node": "3.x.x", 60 | "tslint": "5.x.x", 61 | "typescript": "2.x.x", 62 | "version-changelog": "2.x.x", 63 | "webpack": "2.x.x", 64 | "yamljs": "0.x.x", 65 | "zip-unzip-promise": "1.0.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /resources/eb.config.yml: -------------------------------------------------------------------------------- 1 | branch-defaults: 2 | master: 3 | environment: {{APPLICATION_ENVIRONMENT}} 4 | environment-defaults: 5 | {{APPLICATION_ENVIRONMENT}}: 6 | branch: master 7 | repository: null 8 | global: 9 | application_name: {{APPLICATION_NAME}} 10 | branch: null 11 | default_ec2_keyname: {{KEY}} 12 | default_platform: {{PLATFORM}} 13 | default_region: {{REGION}} 14 | instance_profile: null 15 | platform_name: null 16 | platform_version: null 17 | profile: {{ENV}} 18 | repository: null 19 | sc: git 20 | workspace_type: Application 21 | -------------------------------------------------------------------------------- /src/ElasticBeanstalkPlugin.ts: -------------------------------------------------------------------------------- 1 | import * as BPromise from 'bluebird'; 2 | import * as path from 'path'; 3 | import * as IServerless from 'serverless'; 4 | import CLI from 'serverless/lib/classes/CLI'; 5 | import { getElasticBeanstalkInstance, getS3Instance } from './lib/AWS'; 6 | import build from './lib/build'; 7 | import configure from './lib/configure'; 8 | import deploy from './lib/deploy'; 9 | import validate from './lib/validate'; 10 | import { 11 | IElasticBeanstalk, 12 | IElasticBeanstalkCommands, 13 | IElasticBeanstalkHooks, 14 | IElasticBeanstalkOptions, 15 | IPluginConfig, 16 | } from './types'; 17 | 18 | export default class ElasticBeanstalkPlugin implements IElasticBeanstalk { 19 | private serverless: IServerless; 20 | private servicePath: string; 21 | private options: IElasticBeanstalkOptions; 22 | private config: IPluginConfig; 23 | private logger: CLI; 24 | private service: any; 25 | private tmpDir: string; 26 | private artifactTmpDir: string; 27 | private commands: IElasticBeanstalkCommands; 28 | private hooks: IElasticBeanstalkHooks; 29 | private getS3Instance; 30 | private getElasticBeanstalkInstance; 31 | 32 | /** 33 | * Create a new instance. 34 | * 35 | * @param {IServerless} serverless the Serverless instance 36 | * @param {IElasticBeanstalkOptions} options passed in options 37 | */ 38 | constructor(serverless: IServerless, options: IElasticBeanstalkOptions) { 39 | this.serverless = serverless; 40 | this.options = options; 41 | this.servicePath = this.serverless.config.servicePath; 42 | this.logger = this.serverless.cli; 43 | this.service = this.serverless.service; 44 | 45 | this.tmpDir = path.join(this.servicePath, '/.serverless'); 46 | this.artifactTmpDir = path.join(this.tmpDir, './artifacts'); 47 | 48 | if (this.service.custom) { 49 | this.config = this.service.custom['elastic-beanstalk']; 50 | } 51 | 52 | this.commands = this.defineCommands(); 53 | this.hooks = this.defineHooks(); 54 | 55 | this.getS3Instance = getS3Instance; 56 | this.getElasticBeanstalkInstance = getElasticBeanstalkInstance; 57 | } 58 | 59 | /** 60 | * Define plugin commands. 61 | * 62 | * @returns {IElasticBeanstalkCommands} the commands 63 | */ 64 | public defineCommands(): IElasticBeanstalkCommands { 65 | const commonOptions = { 66 | region: { 67 | shortcut: 'r', 68 | usage: 'Region of the service', 69 | }, 70 | stage: { 71 | shortcut: 's', 72 | usage: 'Stage of the service', 73 | }, 74 | verbose: { 75 | shortcut: 'v', 76 | usage: 'Show all stack events during deployment', 77 | }, 78 | }; 79 | 80 | return { 81 | 'elastic-beanstalk': { 82 | lifecycleEvents: [ 83 | 'validate', 84 | 'configure', 85 | 'deploy', 86 | ], 87 | options: commonOptions, 88 | usage: 'Deploys the application to AWS ElasticBeanstalk', 89 | }, 90 | }; 91 | } 92 | 93 | /** 94 | * Define plugin hooks. 95 | * 96 | * @returns {IElasticBeanstalkHooks} the hooks 97 | */ 98 | public defineHooks(): IElasticBeanstalkHooks { 99 | return { 100 | 'elastic-beanstalk:deploy': () => BPromise.bind(this) 101 | .then(validate) 102 | .then(configure) 103 | .then(build) 104 | .then(deploy), 105 | }; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import ElasticBeanstalkPlugin from './ElasticBeanstalkPlugin'; 2 | 3 | export default ElasticBeanstalkPlugin; 4 | module.exports = ElasticBeanstalkPlugin; 5 | -------------------------------------------------------------------------------- /src/lib/AWS.ts: -------------------------------------------------------------------------------- 1 | import * as AWS from "aws-sdk"; 2 | import * as IServerless from 'serverless'; 3 | 4 | /** 5 | * Get S3 instance. 6 | * 7 | * @param {IServerless} serverless serverless instance 8 | * @param {String} region region name 9 | * 10 | * @returns {IS3} S3 instance 11 | */ 12 | export function getS3Instance(serverless: IServerless, region: string): AWS.S3 { 13 | const provider = serverless.getProvider(serverless.service.provider.name); 14 | 15 | return new provider.sdk.S3({ region, apiVersion: '2006-03-01' }); 16 | } 17 | 18 | /** 19 | * Get ElasticBeanstalk instance. 20 | * 21 | * @param {IServerless} serverless serverless instance 22 | * @param {String} region region name 23 | * 24 | * @returns {IEB} elastic beanstalk instance 25 | */ 26 | export function getElasticBeanstalkInstance(serverless: IServerless, region: string): AWS.ElasticBeanstalk { 27 | const provider = serverless.getProvider(serverless.service.provider.name); 28 | 29 | return new provider.sdk.ElasticBeanstalk({ region, apiVersion: '2010-12-01' }); 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/build.ts: -------------------------------------------------------------------------------- 1 | import Bundler from 'bundle-bundler'; 2 | import * as fsp from 'fs-promise'; 3 | import { IBuildConfig, IPluginConfig } from "../types"; 4 | import getVersion from './getVersion'; 5 | 6 | /** 7 | * Builds the application. 8 | * 9 | * @returns {undefined} 10 | */ 11 | export default async function build(): Promise { 12 | this.logger.log('Building Application Bundle...'); 13 | 14 | const configPath: string = `${process.cwd()}/.serverless/stack-config.json`; 15 | 16 | const config: IPluginConfig = await fsp.readJson(configPath); 17 | this.config.version = getVersion(this.config.version); 18 | 19 | const applicationName: string = config[this.config.variables.applicationName]; 20 | const versionLabel: string = `${applicationName}-${this.config.version}`; 21 | const fileName: string = `bundle-${versionLabel}.zip`; 22 | 23 | this.logger.log(`Creating ${fileName}`); 24 | 25 | // make sure artifact directory exists 26 | await fsp.ensureDir(this.artifactTmpDir); 27 | 28 | // get build configuration -- required 29 | const buildConfig: IBuildConfig = this.config.build; 30 | 31 | const bundler = new Bundler({ 32 | babel: buildConfig.babel || false, 33 | logger: this.logger, 34 | rootDir: process.cwd(), 35 | sourceMaps: buildConfig.sourceMaps || false, 36 | }); 37 | 38 | await bundler.bundle({ 39 | include: this.config.build.include, 40 | output: `${this.artifactTmpDir}/${fileName}`, 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/configure.ts: -------------------------------------------------------------------------------- 1 | import { ElasticBeanstalk, S3 as IS3 } from "aws-sdk"; 2 | import * as BPromise from 'bluebird'; 3 | import * as fsp from 'fs-promise'; 4 | import * as path from 'path'; 5 | import CLI from 'serverless/lib/classes/CLI'; 6 | 7 | /** 8 | * List of supported platforms. 9 | * 10 | * @see http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/concepts.platforms.html 11 | */ 12 | const platforms = { 13 | go: '64bit Amazon Linux 2017.09 v2.7.6 running Go 1.9', 14 | java8SE: '64bit Amazon Linux 2017.09 v2.6.6 running Java 8', 15 | java8Tomcat8: '64bit Amazon Linux 2017.09 v2.7.6 running Tomcat 8 Java 8', 16 | multiContainerDocker: '64bit Amazon Linux 2017.09 v2.8.4 running Multi-container Docker 17.09.1-ce (Generic)', 17 | netIIS85: '64bit Windows Server 2016 v1.2.0 running IIS 10.0', 18 | nodejs: '64bit Amazon Linux 2017.09 v4.4.5 running Node.js', 19 | packer: '64bit Amazon Linux 2017.09 v2.4.5 running Packer 1.0.3', 20 | php70: '64bit Amazon Linux 2017.09 v2.6.5 running PHP 7.0', 21 | php71: '64bit Amazon Linux 2017.09 v2.6.5 running PHP 7.1', 22 | python34: '64bit Amazon Linux 2017.09 v2.6.5 running Python 3.4', 23 | python36: '64bit Amazon Linux 2017.09 v2.6.5 running Python 3.6', 24 | ruby23: '64bit Amazon Linux 2017.09 v2.7.1 running Ruby 2.3 (Puma)', 25 | singleContainerDocker: '64bit Amazon Linux 2017.09 v2.8.4 running Docker 17.09.1-ce', 26 | }; 27 | 28 | /** 29 | * Create a new ElasticBeanstalk configuration file. 30 | * 31 | * @param {Object} config stack configuration object 32 | * @param {Object} logger log instance 33 | * 34 | * @returns {undefined} 35 | */ 36 | async function createEBConfig(config: any, logger: CLI): Promise { 37 | const templatePath = `${__dirname}/../../resources/eb.config.yml`; 38 | const filePath = `${process.cwd()}/.elasticbeanstalk/config.yml`; 39 | 40 | let content = await fsp.readFile(templatePath, 'utf-8'); 41 | 42 | // create output dir if not exists 43 | await fsp.ensureDir(`${process.cwd()}/.elasticbeanstalk`); 44 | 45 | const variables = { 46 | APPLICATION_ENVIRONMENT: config.environmentName, 47 | APPLICATION_NAME: config.applicationName, 48 | ENV: config.env, 49 | KEY: config.key, 50 | PLATFORM: platforms[config.platform], 51 | REGION: config.region, 52 | }; 53 | 54 | Object.keys(variables).forEach((key) => { 55 | content = content.replace(new RegExp(`{{${key}}}`, 'g'), variables[key]); 56 | }); 57 | 58 | try { 59 | await fsp.writeFile(filePath, content); 60 | } catch (error) { 61 | logger.log(error); 62 | } 63 | } 64 | 65 | /** 66 | * Configure docker run configuration file. 67 | * 68 | * @param {IS3} S3 s3 instance 69 | * @param {Object} config config object 70 | * @param {Object} logger logger instance 71 | * 72 | * @returns {undefined} 73 | */ 74 | async function configureDockerRun(S3: IS3, config: any, logger: CLI): Promise { 75 | const dockerRunFile = `${process.cwd()}/Dockerrun.aws.json`; 76 | const runtimeDockerRunFile = `${process.cwd()}/.serverless/Dockerrun.aws.json`; 77 | 78 | let content = await fsp.readFile(dockerRunFile, 'utf-8'); 79 | 80 | const variables = { 81 | BUCKET_NAME: config.bucketName, 82 | CONFIG_FILE: config.configFile, 83 | IMAGE: config.image, 84 | VERSION: config.version, 85 | }; 86 | 87 | Object.keys(variables).forEach((key) => { 88 | content = content.replace(new RegExp(`{{${key}}}`, 'g'), variables[key]); 89 | }); 90 | 91 | try { 92 | await fsp.writeFile(runtimeDockerRunFile, content); 93 | 94 | await S3.upload({ 95 | Body: content, 96 | Bucket: config.bucketName, 97 | Key: 'Dockerrun.aws.json', 98 | }).promise(); 99 | } catch (error) { 100 | logger.log(error); 101 | } 102 | } 103 | 104 | /** 105 | * Create a new application version. 106 | * 107 | * @param {ElasticBeanstalk} EB elastic beanstalk instance 108 | * @param {Object} params update environment parameters 109 | * 110 | * @returns {Object} update environment response 111 | */ 112 | async function deployApplicationVersion(EB: ElasticBeanstalk, params: any): Promise { 113 | return EB.createApplicationVersion(params).promise(); 114 | } 115 | 116 | /** 117 | * Configure service for deployment. 118 | * 119 | * @returns {undefined} 120 | */ 121 | export default async function configure(): Promise { 122 | this.logger.log('Configuring ElasticBeanstalk Deployment...'); 123 | 124 | const stackOutputs = await fsp.readJson(path.resolve(`${process.cwd()}/.serverless/stack-config.json`)); 125 | 126 | const options = { 127 | applicationName: stackOutputs[this.config.variables.applicationName], 128 | env: this.options.env, 129 | environmentName: stackOutputs[this.config.variables.environmentName], 130 | key: this.options.key, 131 | platform: this.config.platform, 132 | region: this.options.region, 133 | }; 134 | 135 | await createEBConfig(options, this.logger); 136 | 137 | if (this.config.docker) { 138 | let bucketName; 139 | let configFile; 140 | 141 | const docker = this.config.docker; 142 | 143 | const S3: IS3 = this.getS3Instance(this.serverless, this.options.region); 144 | 145 | if (docker && docker.auth) { 146 | bucketName = docker.auth.configBucketName; 147 | configFile = docker.auth.configFile; 148 | 149 | this.logger.log('Uploading docker auth file to S3...'); 150 | 151 | await S3.upload({ 152 | Body: await fsp.readFile(configFile, 'utf-8'), 153 | Bucket: bucketName, 154 | Key: configFile, 155 | }).promise(); 156 | 157 | this.logger.log('docker auth file uploaded to to S3 successfully'); 158 | } 159 | 160 | const dockerConfig = { 161 | bucketName, 162 | configFile, 163 | image: docker.image, 164 | version: docker.version, 165 | }; 166 | 167 | await configureDockerRun(S3, dockerConfig, this.logger); 168 | 169 | const EB: ElasticBeanstalk = this.getElasticBeanstalkInstance(this.serverless, this.options.region); 170 | 171 | const params = { 172 | ApplicationName: stackOutputs[this.config.variables.applicationName], 173 | SourceBundle: { 174 | S3Bucket: dockerConfig.bucketName, 175 | S3Key: 'Dockerrun.aws.json', 176 | }, 177 | VersionLabel: dockerConfig.version, 178 | }; 179 | 180 | await deployApplicationVersion(EB, params); 181 | } 182 | 183 | // execute custom script if provided 184 | if (this.config.script) { 185 | this.logger.log(`Executing custom script command: ${this.config.script}`); 186 | 187 | const script = require(`${process.cwd()}/${this.config.script}`); 188 | 189 | await script(this.serverless, stackOutputs); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/lib/deploy.ts: -------------------------------------------------------------------------------- 1 | import { ElasticBeanstalk, S3 as IS3 } from "aws-sdk"; 2 | import * as BPromise from 'bluebird'; 3 | import * as fsp from 'fs-promise'; 4 | import * as path from 'path'; 5 | import { IPluginConfig } from '../types'; 6 | import getVersion from './getVersion'; 7 | 8 | /** 9 | * Retrieves stack Ouputs from AWS. 10 | * 11 | * @returns {undefined} 12 | */ 13 | export default async function deploy() { 14 | this.logger.log('Deploying Application to ElasticBeanstalk...'); 15 | 16 | const configPath = `${process.cwd()}/.serverless/stack-config.json`; 17 | 18 | const ebConfig: IPluginConfig = this.config; 19 | 20 | const config = await fsp.readJson(configPath); 21 | ebConfig.version = getVersion(ebConfig.version); 22 | 23 | const applicationName = config[ebConfig.variables.applicationName]; 24 | const environmentName = config[ebConfig.variables.environmentName]; 25 | const versionLabel = `${applicationName}-${ebConfig.version}`; 26 | 27 | let fileName = `bundle-${versionLabel}.zip`; 28 | 29 | if (ebConfig.file) { 30 | fileName = ebConfig.file.prefix ? `${ebConfig.file.prefix}/` : ''; 31 | fileName += ebConfig.file.name ? `${ebConfig.file.name}` : `bundle-${versionLabel}.zip`; 32 | } 33 | 34 | const bundlePath = path.resolve(this.artifactTmpDir, `bundle-${versionLabel}.zip`); 35 | 36 | process.env.PATH = `/root/.local/bin:${process.env.PATH}`; 37 | 38 | const S3: IS3 = this.getS3Instance(this.serverless, this.options.region); 39 | 40 | this.logger.log('Uploading Application Bundle to S3...'); 41 | 42 | this.logger.log( 43 | JSON.stringify( 44 | await S3.upload({ 45 | Body: fsp.createReadStream(bundlePath), 46 | Bucket: ebConfig.bucket, 47 | Key: fileName, 48 | }).promise(), 49 | ), 50 | ); 51 | 52 | this.logger.log('Application Bundle Uploaded to S3 Successfully'); 53 | 54 | const EB: ElasticBeanstalk = this.getElasticBeanstalkInstance(this.serverless, this.options.region); 55 | 56 | this.logger.log('Creating New Application Version...'); 57 | 58 | this.logger.log( 59 | JSON.stringify( 60 | await EB.createApplicationVersion({ 61 | ApplicationName: applicationName, 62 | Process: true, 63 | SourceBundle: { 64 | S3Bucket: ebConfig.bucket, 65 | S3Key: fileName, 66 | }, 67 | VersionLabel: versionLabel, 68 | }).promise(), 69 | ), 70 | ); 71 | 72 | this.logger.log('Waiting for application version...'); 73 | 74 | let updated = false; 75 | 76 | while (!updated) { 77 | const response = await EB.describeApplicationVersions({ 78 | VersionLabels: [versionLabel], 79 | }).promise(); 80 | 81 | this.logger.log(JSON.stringify(response)); 82 | 83 | if (response.ApplicationVersions[0].Status === 'PROCESSED') { 84 | updated = true; 85 | } else if (response.ApplicationVersions[0].Status === 'FAILED') { 86 | throw new Error('Creating Application Version Failed'); 87 | } else { 88 | await BPromise.delay(5000); 89 | } 90 | } 91 | 92 | this.logger.log('New Application Version Created Successfully'); 93 | this.logger.log('Updating Application Environment...'); 94 | 95 | this.logger.log( 96 | JSON.stringify( 97 | await EB.updateEnvironment({ 98 | ApplicationName: applicationName, 99 | EnvironmentName: environmentName, 100 | VersionLabel: versionLabel, 101 | }).promise(), 102 | ), 103 | ); 104 | 105 | this.logger.log('Waiting for environment...'); 106 | 107 | updated = false; 108 | 109 | while (!updated) { 110 | const response = await EB.describeEnvironments({ 111 | EnvironmentNames: [environmentName], 112 | }).promise(); 113 | 114 | this.logger.log(JSON.stringify(response)); 115 | 116 | if (response.Environments[0].Status === 'Ready') { 117 | updated = true; 118 | } else { 119 | await BPromise.delay(5000); 120 | } 121 | } 122 | 123 | this.logger.log('Application Environment Updated Successfully'); 124 | this.logger.log('Application Deployed Successfully'); 125 | } 126 | -------------------------------------------------------------------------------- /src/lib/getVersion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get version from config. 3 | * 4 | * @param {String} version configuration object 5 | * 6 | * @returns {String} version string 7 | */ 8 | export default function getVersion(version: string): string { 9 | if (version === 'latest') { 10 | return Math.floor(new Date().valueOf() / 1000).toString(); 11 | } 12 | 13 | return version; 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/validate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Validate configuration. 3 | * 4 | * @returns {undefined} 5 | */ 6 | export default async function validate() { 7 | if (!this.serverless.config.servicePath) { 8 | throw new this.serverless.classes 9 | .Error('This command can only be run inside a service directory'); 10 | } 11 | 12 | this.options.stage = this.options.stage 13 | || (this.serverless.service.provider && this.serverless.service.provider.stage) 14 | || 'dev'; 15 | this.options.region = this.options.region 16 | || (this.serverless.service.provider && this.serverless.service.provider.region) 17 | || 'us-east-1'; 18 | } 19 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import CLI from 'serverless/lib/classes/CLI'; 2 | 3 | export interface IBuildConfig { 4 | babel?: boolean; 5 | sourceMaps?: boolean; 6 | include?: string[]; 7 | } 8 | 9 | export interface IProjectVariables { 10 | applicationName: string; 11 | environmentName: string; 12 | } 13 | 14 | export interface IPluginConfig { 15 | variables: IProjectVariables; 16 | key: string; 17 | platform: string; 18 | bucket: string; 19 | file?: { 20 | prefix: string; 21 | name: string; 22 | }; 23 | version: string; 24 | build: IBuildConfig; 25 | } 26 | 27 | export interface IElasticBeanstalkCommands { 28 | 'elastic-beanstalk'; 29 | } 30 | 31 | export interface IElasticBeanstalkHooks { 32 | 'elastic-beanstalk:deploy'; 33 | } 34 | 35 | export interface IElasticBeanstalk { 36 | /** 37 | * Define plugin commands. 38 | * 39 | * @returns {IElasticBeanstalkCommands} the commands 40 | */ 41 | defineCommands(): IElasticBeanstalkCommands; 42 | /** 43 | * Define plugin hooks. 44 | * 45 | * @returns {IElasticBeanstalkHooks} the hooks 46 | */ 47 | defineHooks(): IElasticBeanstalkHooks; 48 | } 49 | 50 | export interface IElasticBeanstalkOptions { 51 | env: string; 52 | key: string; 53 | region: string; 54 | } 55 | -------------------------------------------------------------------------------- /test/fixture/.serverless/.artifacts/bundle-app-name-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rawphp/serverless-plugin-elastic-beanstalk/ecff425ae5963fbdfbbe7eb69787dc9178e4046d/test/fixture/.serverless/.artifacts/bundle-app-name-1.0.0.zip -------------------------------------------------------------------------------- /test/fixture/.serverless/stack-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testAppApplicationName": "app-name", 3 | "testAppEnvironmentName": "app-environment-name" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixture/create-eb-app-version-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "ResponseMetadata": { 3 | "RequestId": "1acda101-65e1-11e7-b532-93585e5f184b" 4 | }, 5 | "ApplicationVersion": { 6 | "ApplicationName": "app-name-dev", 7 | "VersionLabel": "app-name-dev-1499740253", 8 | "SourceBundle": { 9 | "S3Bucket": "app-name-dev", 10 | "S3Key": "bundle-app-name-dev-1499740253.zip" 11 | }, 12 | "DateCreated": "2017-07-11T02:31:55.176Z", 13 | "DateUpdated": "2017-07-11T02:31:55.176Z", 14 | "Status": "PROCESSING" 15 | } 16 | } -------------------------------------------------------------------------------- /test/fixture/describe-app-versions-failed-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "ResponseMetadata": { 3 | "RequestId": "273205b2-7b02-11e7-b133-43b3f92a6e4b" 4 | }, 5 | "ApplicationVersions": [ 6 | { 7 | "ApplicationName": "track-service", 8 | "VersionLabel": "app-name-dev-1499740253", 9 | "SourceBundle": { 10 | "S3Bucket": "app-name-dev", 11 | "S3Key": "bundle-track-service-1499740253.zip" 12 | }, 13 | "DateCreated": "2017-08-06T23:51:15.650Z", 14 | "DateUpdated": "2017-08-06T23:51:15.650Z", 15 | "Status": "FAILED" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /test/fixture/describe-app-versions-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "ResponseMetadata": { 3 | "RequestId": "273205b2-7b02-11e7-b133-43b3f92a6e4b" 4 | }, 5 | "ApplicationVersions": [ 6 | { 7 | "ApplicationName": "track-service", 8 | "VersionLabel": "app-name-dev-1499740253", 9 | "SourceBundle": { 10 | "S3Bucket": "app-name-dev", 11 | "S3Key": "bundle-track-service-1499740253.zip" 12 | }, 13 | "DateCreated": "2017-08-06T23:51:15.650Z", 14 | "DateUpdated": "2017-08-06T23:51:15.650Z", 15 | "Status": "PROCESSED" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /test/fixture/describe-envs-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "ResponseMetadata": { 3 | "RequestId": "07dcbb48-65e2-11e7-a2d0-3bea6a5ed968" 4 | }, 5 | "Environments": [ 6 | { 7 | "EnvironmentName": "prox-Prox-EJMRVSUYF473", 8 | "EnvironmentId": "e-ypydtywbnk", 9 | "ApplicationName": "app-name-dev", 10 | "VersionLabel": "app-name-dev-1499740636", 11 | "SolutionStackName": "64bit Amazon Linux 2017.03 v4.1.1 running Node.js", 12 | "PlatformArn": "arn:aws:elasticbeanstalk:eu-west-1::platform/Node.js running on 64bit Amazon Linux/4.1.1", 13 | "Description": "Mock Server Environment", 14 | "EndpointURL": "awseb-e-y-AWSEBLoa-1OTMT3RZCQQET-659295223.eu-west-1.elb.amazonaws.com", 15 | "CNAME": "prox-Prox-EJMRVSUYF473.t4bemkmt2z.eu-west-1.elasticbeanstalk.com", 16 | "DateCreated": "2017-07-06T00:33:22.366Z", 17 | "DateUpdated": "2017-07-11T02:38:32.542Z", 18 | "Status": "Ready", 19 | "AbortableOperationInProgress": false, 20 | "Health": "Green", 21 | "Tier": { 22 | "Name": "WebServer", 23 | "Type": "Standard", 24 | "Version": "" 25 | }, 26 | "EnvironmentLinks": [] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /test/fixture/serverless.yml: -------------------------------------------------------------------------------- 1 | service: test-app 2 | description: The AWS CloudFormation template for Phoenix Mock Server 3 | 4 | frameworkVersion: ">= 1.0.0" 5 | 6 | plugins: 7 | # - serverless-plugin-stack-config 8 | # - serverless-plugin-elastic-beanstalk 9 | 10 | custom: 11 | elastic-beanstalk: 12 | variables: 13 | applicationName: ProxyApplicationName 14 | environmentName: ProxyEnvironmentName 15 | key: ${opt:key} 16 | platform: nodejs 17 | bucket: ${self:service}-${opt:env} 18 | version: ${opt:version} 19 | build: 20 | babel: true 21 | sourceMaps: true 22 | include: 23 | - .ebextensions/** 24 | - src/** 25 | - resources/schema/** 26 | - package.json 27 | 28 | provider: 29 | name: aws 30 | stage: ${opt:env} 31 | region: ${opt:region} 32 | 33 | resources: 34 | Resources: 35 | Bucket: 36 | Type: AWS::S3::Bucket 37 | Properties: 38 | BucketName: ${self:service}-${opt:env} -------------------------------------------------------------------------------- /test/fixture/update-env-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "ResponseMetadata": { 3 | "RequestId": "1ba27cb6-65e1-11e7-ac70-69652c5735b6" 4 | }, 5 | "EnvironmentName": "prox-Prox-EJMRVSUYF473", 6 | "EnvironmentId": "e-ypydtywbnk", 7 | "ApplicationName": "app-name-dev", 8 | "VersionLabel": "app-name-dev-1499740253", 9 | "SolutionStackName": "64bit Amazon Linux 2017.03 v4.1.1 running Node.js", 10 | "PlatformArn": "arn:aws:elasticbeanstalk:eu-west-1::platform/Node.js running on 64bit Amazon Linux/4.1.1", 11 | "Description": "Mock Server Environment", 12 | "EndpointURL": "awseb-e-y-AWSEBLoa-1OTMT3RZCQQET-659295223.eu-west-1.elb.amazonaws.com", 13 | "CNAME": "prox-Prox-EJMRVSUYF473.t4bemkmt2z.eu-west-1.elasticbeanstalk.com", 14 | "DateCreated": "2017-07-06T00:33:22.366Z", 15 | "DateUpdated": "2017-07-11T02:31:56.929Z", 16 | "Status": "Updating", 17 | "AbortableOperationInProgress": true, 18 | "Health": "Grey", 19 | "Tier": { 20 | "Name": "WebServer", 21 | "Type": "Standard", 22 | "Version": "" 23 | }, 24 | "EnvironmentLinks": [] 25 | } -------------------------------------------------------------------------------- /test/fixture/upload-app-s3-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "Location": "https://app-name-dev.s3-eu-west-1.amazonaws.com/bundle-app-name-dev-1499740253.zip", 3 | "Bucket": "app-name-dev", 4 | "Key": "bundle-app-name-dev-1499740253.zip", 5 | "ETag": "\"f4c1feec6a89b9dfd8ceaf863be5281f-3\"" 6 | } -------------------------------------------------------------------------------- /test/src/ElasticBeanstalk.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as path from 'path'; 3 | import * as Serverless from 'serverless'; 4 | import * as sinon from 'sinon'; 5 | import ElasticBeanstalkPlugin from './../../src/ElasticBeanstalkPlugin'; 6 | 7 | const fixturePath = path.resolve(`${process.cwd()}/test/fixture`); 8 | 9 | describe('ElasticBeanstalkPlugin', function() { 10 | this.timeout(5000); 11 | 12 | let plugin; 13 | const sandbox = sinon.sandbox.create(); 14 | let serverless; 15 | 16 | const options = { 17 | env: 'dev', 18 | key: 'ec2-key', 19 | region: 'eu-west-1', 20 | }; 21 | 22 | beforeEach(async () => { 23 | serverless = new Serverless({}); 24 | serverless.config.update({ servicePath: fixturePath }); 25 | serverless.pluginManager.cliOptions = { 26 | stage: 'dev', 27 | }; 28 | 29 | await serverless.init(); 30 | 31 | plugin = new ElasticBeanstalkPlugin(serverless, options); 32 | }); 33 | 34 | it('new ElasticBeanstalk', () => { 35 | expect(plugin).to.be.an.instanceOf(ElasticBeanstalkPlugin); 36 | expect(plugin.options).to.deep.equal(options); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/src/lib/AWS.test.ts: -------------------------------------------------------------------------------- 1 | import * as AWS from 'aws-sdk'; 2 | import * as BPromise from 'bluebird'; 3 | import { expect } from 'chai'; 4 | import * as fsp from 'fs-promise'; 5 | import * as path from 'path'; 6 | import * as Serverless from 'serverless'; 7 | import * as zipper from 'zip-unzip-promise'; 8 | import getContext from '../../stubs/context'; 9 | import { getElasticBeanstalkInstance, getS3Instance } from './../../../src/lib/AWS'; 10 | 11 | describe('AWS', function() { 12 | this.timeout(5000); 13 | 14 | const fixturePath = path.resolve(`${process.cwd()}/test/fixture`); 15 | 16 | describe('getElasticBeanstalkInstance', () => { 17 | let serverless; 18 | 19 | beforeEach(async () => { 20 | serverless = new Serverless({}); 21 | serverless.config.update({ servicePath: fixturePath }); 22 | serverless.pluginManager.cliOptions = { 23 | stage: 'dev', 24 | }; 25 | 26 | await serverless.init(); 27 | }); 28 | 29 | it('correctly returns an ElasticBeanstalk instance', () => { 30 | const eb = getElasticBeanstalkInstance(serverless, 'eu-west-1'); 31 | 32 | expect(eb instanceof AWS.ElasticBeanstalk); 33 | }).timeout(5000); 34 | }); 35 | 36 | describe('S3', () => { 37 | let serverless; 38 | 39 | beforeEach(async () => { 40 | serverless = new Serverless({}); 41 | serverless.config.update({ servicePath: fixturePath }); 42 | serverless.pluginManager.cliOptions = { 43 | stage: 'dev', 44 | }; 45 | 46 | await serverless.init(); 47 | }); 48 | 49 | it('correctly returns an S3 instance', () => { 50 | const eb = getS3Instance(serverless, 'eu-west-1'); 51 | 52 | expect(eb instanceof AWS.S3); 53 | }).timeout(5000); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/src/lib/build.test.ts: -------------------------------------------------------------------------------- 1 | import * as BPromise from 'bluebird'; 2 | import { expect } from 'chai'; 3 | import * as fsp from 'fs-promise'; 4 | import * as zipper from 'zip-unzip-promise'; 5 | import build from './../../../src/lib/build'; 6 | 7 | describe('build', () => { 8 | const rootDir = `${process.cwd()}/.serverless`; 9 | const configPath = `${rootDir}/stack-config.json`; 10 | 11 | const context = { 12 | artifactTmpDir: `${rootDir}/.artifacts`, 13 | build, 14 | config: { 15 | build: { 16 | babel: true, 17 | include: [ 18 | 'test/fixture/es6-sample-project/src/js/**', 19 | ], 20 | sourceMaps: true, 21 | }, 22 | variables: { 23 | applicationName: 'testAppApplicationName', 24 | environmentName: 'testAppEnvironmentName', 25 | }, 26 | version: '1.0.0', 27 | }, 28 | logger: { log: () => this }, 29 | }; 30 | 31 | const config = { 32 | testAppApplicationName: 'app-name', 33 | testAppEnvironmentName: 'app-environment-name', 34 | }; 35 | 36 | beforeEach(async () => { 37 | await fsp.ensureDir(rootDir); 38 | await fsp.writeJson(configPath, config); 39 | }); 40 | 41 | afterEach(async () => { 42 | await fsp.remove(context.artifactTmpDir); 43 | }); 44 | 45 | it('is function', () => { 46 | expect(typeof build).to.equal('function'); 47 | }); 48 | 49 | it('builds the application bundle successfully', async () => { 50 | // tslint:disable-next-line:max-line-length 51 | const artifactPath = `${context.artifactTmpDir}/bundle-${config.testAppApplicationName}-${context.config.version}.zip`; 52 | const extractPath = `${context.artifactTmpDir}/${config.testAppApplicationName}`; 53 | 54 | process.chdir(`${__dirname}/../../../`); 55 | 56 | await context.build(); 57 | 58 | expect(await fsp.exists(artifactPath)).to.equal(true); 59 | 60 | await zipper.unzip(artifactPath, extractPath); 61 | 62 | expect(await fsp.exists(`${extractPath}/node_modules`)); 63 | expect(await fsp.exists(`${extractPath}/test`)); 64 | expect(await fsp.exists(`${extractPath}/test/fixture`)); 65 | expect(await fsp.exists(`${extractPath}/test/fixture/es6-sample-project`)); 66 | expect(await fsp.exists(`${extractPath}/test/fixture/es6-sample-project/src`)); 67 | expect(await fsp.exists(`${extractPath}/test/fixture/es6-sample-project/src/js`)); 68 | expect(await fsp.exists(`${extractPath}/test/fixture/es6-sample-project/src/js/drag.js`)); 69 | expect(await fsp.exists(`${extractPath}/test/fixture/es6-sample-project/src/js/drag.js.map`)); 70 | }).timeout(100000); 71 | }); 72 | -------------------------------------------------------------------------------- /test/src/lib/configure.test.ts: -------------------------------------------------------------------------------- 1 | import * as BPromise from 'bluebird'; 2 | import { expect } from 'chai'; 3 | import * as fsp from 'fs-promise'; 4 | import * as YAML from 'yamljs'; 5 | import configure from './../../../src/lib/configure'; 6 | 7 | describe('configure', () => { 8 | const ebDir = `${process.cwd()}/.elasticbeanstalk`; 9 | 10 | const context = { 11 | config: { 12 | build: { 13 | babel: true, 14 | include: [ 15 | 'test/fixture/es6-sample-project/src/js/**', 16 | ], 17 | sourceMaps: true, 18 | }, 19 | platform: 'nodejs', 20 | variables: { 21 | applicationName: 'testAppApplicationName', 22 | environmentName: 'testAppEnvironmentName', 23 | }, 24 | version: '1.0.0', 25 | }, 26 | configure, 27 | logger: { log: () => this }, 28 | options: { 29 | env: 'dev', 30 | key: 'ec2-key', 31 | region: 'eu-west-1', 32 | }, 33 | }; 34 | 35 | it('is function', () => { 36 | expect(typeof configure).to.equal('function'); 37 | }); 38 | 39 | it('configures the application for deployment correctly', async () => { 40 | await context.configure(); 41 | 42 | const yaml = (await fsp.readFile(`${ebDir}/config.yml`)).toString(); 43 | 44 | const obj = YAML.parse(yaml); 45 | 46 | expect(obj['branch-defaults'].master.environment).to.equal('app-environment-name'); 47 | expect(obj['environment-defaults']['app-environment-name'].branch).to.equal('master'); 48 | expect(obj['environment-defaults']['app-environment-name'].repository).to.equal(null); 49 | expect(obj.global.application_name).to.equal('app-name'); 50 | expect(obj.global.branch).to.equal(null); 51 | expect(obj.global.default_ec2_keyname).to.equal('ec2-key'); 52 | expect(obj.global.default_platform).to.equal('64bit Amazon Linux 2017.09 v4.4.5 running Node.js'); 53 | expect(obj.global.default_region).to.equal('eu-west-1'); 54 | expect(obj.global.instance_profile).to.equal(null); 55 | expect(obj.global.platform_name).to.equal(null); 56 | expect(obj.global.platform_version).to.equal(null); 57 | expect(obj.global.profile).to.equal('dev'); 58 | expect(obj.global.repository).to.equal(null); 59 | expect(obj.global.sc).to.equal('git'); 60 | expect(obj.global.workspace_type).to.equal('Application'); 61 | }); 62 | 63 | describe('docker', () => { 64 | it('is not tested', () => { 65 | // tslint:disable-next-line:no-console 66 | console.log('NOTE:: DOCKER NOT TESTED'); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/src/lib/deploy.test.ts: -------------------------------------------------------------------------------- 1 | import * as AWS from 'aws-sdk'; 2 | import * as BPromise from 'bluebird'; 3 | import { expect } from 'chai'; 4 | import * as fsp from 'fs-promise'; 5 | import * as path from 'path'; 6 | import * as Serverless from 'serverless'; 7 | import * as sinon from 'sinon'; 8 | import * as YAML from 'yamljs'; 9 | import deploy from './../../../src/lib/deploy'; 10 | 11 | const logger = console; 12 | 13 | /** 14 | * Stub AWS function response, that returns an object with promise function 15 | * Promise function return the file, read with fsp.readJson 16 | * @param {string} filePath - 17 | * @return {object} - { promise() } 18 | */ 19 | function stubAWSFunc(filePath){ 20 | return { 21 | promise: async () => { 22 | return await fsp.readJson(filePath); 23 | } 24 | } 25 | } 26 | 27 | describe('deploy', function() { 28 | this.timeout(5000); 29 | const sandbox = sinon.sandbox.create(); 30 | const fixturePath = path.resolve(`${process.cwd()}/test/fixture`); 31 | 32 | const rootDir = `${process.cwd()}/test/fixture/.serverless`; 33 | const ebDir = `${process.cwd()}/.elasticbeanstalk`; 34 | let serverless; 35 | 36 | const S3 = BPromise.promisifyAll(new AWS.S3({ region: 'eu-west-1', apiVersion: '2006-03-01' })); 37 | 38 | const EB = BPromise.promisifyAll(new AWS.ElasticBeanstalk({ region: 'eu-west-1' })); 39 | 40 | const context: any = { 41 | artifactTmpDir: `${rootDir}/.artifacts`, 42 | config: { 43 | bucket: 'test-bucket', 44 | file: { 45 | prefix: 'bundles', 46 | file: 'bundle-latest.zip', 47 | }, 48 | build: { 49 | babel: true, 50 | include: ['test/fixture/es6-sample-project/src/js/**'], 51 | sourceMaps: true, 52 | }, 53 | platform: 'nodejs', 54 | variables: { 55 | applicationName: 'testAppApplicationName', 56 | environmentName: 'testAppEnvironmentName', 57 | }, 58 | version: '1.0.0', 59 | }, 60 | deploy, 61 | getElasticBeanstalkInstance: sandbox.stub(), 62 | getS3Instance: sandbox.stub(), 63 | logger: { log: (message) => logger.log(message) }, 64 | options: { 65 | env: 'dev', 66 | key: 'ec2-key', 67 | region: 'eu-west-1', 68 | }, 69 | }; 70 | 71 | const uploadStub = sandbox.stub(S3, 'upload'); 72 | const createApplicationVersionStub = sandbox.stub(EB, 'createApplicationVersion'); 73 | const updateEnvironmentStub = sandbox.stub(EB, 'updateEnvironment'); 74 | const describeApplicationVersionsStub = sandbox.stub(EB, 'describeApplicationVersions'); 75 | const describeEnvironmentsStub = sandbox.stub(EB, 'describeEnvironments'); 76 | 77 | beforeEach(async () => { 78 | serverless = new Serverless({}); 79 | serverless.config.update({ servicePath: fixturePath }); 80 | serverless.pluginManager.cliOptions = { 81 | stage: 'dev', 82 | }; 83 | 84 | context.serverless = serverless; 85 | 86 | await serverless.init(); 87 | 88 | context.getS3Instance.returns(S3); 89 | context.getElasticBeanstalkInstance.returns(EB); 90 | 91 | await fsp.ensureDir(rootDir); 92 | }); 93 | 94 | afterEach(() => { 95 | sandbox.reset(); 96 | }); 97 | 98 | it('is function', () => { 99 | expect(typeof deploy).to.equal('function'); 100 | }); 101 | 102 | it('deploys the application successfully', async () => { 103 | uploadStub.returns(stubAWSFunc(`${fixturePath}/upload-app-s3-response.json`)); 104 | createApplicationVersionStub.returns( 105 | stubAWSFunc(`${fixturePath}/create-eb-app-version-response.json`) 106 | ); 107 | updateEnvironmentStub.returns(stubAWSFunc(`${fixturePath}/update-env-response.json`)); 108 | describeApplicationVersionsStub.returns( 109 | stubAWSFunc(`${fixturePath}/describe-app-versions-response.json`) 110 | ); 111 | describeEnvironmentsStub.returns(stubAWSFunc(`${fixturePath}/describe-envs-response.json`)); 112 | 113 | await context.deploy(); 114 | 115 | expect(uploadStub.calledOnce).to.equal(true); 116 | expect(createApplicationVersionStub.calledOnce).to.equal(true); 117 | expect(updateEnvironmentStub.calledOnce).to.equal(true); 118 | expect(describeEnvironmentsStub.calledOnce).to.equal(true); 119 | }).timeout(10000); 120 | 121 | it('fails to deploy the application', async () => { 122 | uploadStub.returns(stubAWSFunc(`${fixturePath}/upload-app-s3-response.json`)); 123 | createApplicationVersionStub.returns( 124 | stubAWSFunc(`${fixturePath}/create-eb-app-version-response.json`) 125 | ); 126 | describeApplicationVersionsStub.returns( 127 | stubAWSFunc(`${fixturePath}/describe-app-versions-failed-response.json`), 128 | ); 129 | 130 | try { 131 | await context.deploy(); 132 | } catch (error) { 133 | // console.log(error); 134 | } 135 | 136 | expect(uploadStub.calledOnce).to.equal(true); 137 | expect(createApplicationVersionStub.calledOnce).to.equal(true); 138 | expect(updateEnvironmentStub.called).to.equal(false); 139 | expect(describeEnvironmentsStub.called).to.equal(false); 140 | }).timeout(10000); 141 | }); 142 | -------------------------------------------------------------------------------- /test/src/lib/getVersion.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import getVersion from './../../../src/lib/getVersion'; 3 | 4 | const logger = console; 5 | 6 | describe('getVersion', () => { 7 | it('is function', () => { 8 | expect(typeof getVersion).to.equal('function'); 9 | }); 10 | 11 | it('returns the passed in version if valid', () => { 12 | const version = '1.0.0'; 13 | 14 | expect(getVersion(version)).to.equal(version); 15 | }); 16 | 17 | it('returns a timestamp string if passed in `latest`', () => { 18 | const version = 'latest'; 19 | 20 | const result = getVersion(version); 21 | 22 | logger.log(result); 23 | 24 | expect(typeof result).to.equal('string'); 25 | expect(result).to.not.equal(version); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/stubs/context.js: -------------------------------------------------------------------------------- 1 | import * as sinon from 'sinon'; 2 | 3 | /** 4 | * Returns a new context. 5 | * 6 | * @returns {Object} new test context 7 | */ 8 | function getContext() { 9 | const sandbox = sinon.sandbox.create(); 10 | const context = sandbox.mock(); 11 | 12 | context.logSpy = sandbox.spy(); 13 | context.serverless = { 14 | cli: {}, 15 | service: { service: 'test-service' }, 16 | provider: {}, 17 | variables: {}, 18 | }; 19 | context.service = { service: 'test-service' }; 20 | context.CF = { 21 | describeStacksAsync: sandbox.stub(), 22 | }; 23 | // context.S3 = { 24 | // getObjectAsync: sandbox.stub(), 25 | // putObjectAsync: sandbox.stub(), 26 | // }; 27 | context.options = { stage: 'dev', path: '/tmp' }; 28 | context.logger = { 29 | log: args => context.logSpy(args), 30 | error: args => context.errorSpy(args), 31 | }; 32 | context.backup = { 33 | s3: { 34 | key: 'serverless-config.json', 35 | bucket: 'my-test-bucket', 36 | shallow: true, 37 | }, 38 | }; 39 | 40 | return context; 41 | } 42 | 43 | export default getContext; 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "allowJs": true, 5 | "module": "commonjs", 6 | "target": "es6", 7 | "watch": false, 8 | "lib": [ 9 | "es2015" 10 | ] 11 | }, 12 | "include": [ 13 | "./src/**/*" 14 | ] 15 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "quotemark": [ 9 | "single" 10 | ], 11 | "no-var-requires": false, 12 | "object-literal-sort-keys": false 13 | }, 14 | "rulesDirectory": [] 15 | } --------------------------------------------------------------------------------