Skip to content

Commit fde3f81

Browse files
committed
Initial version without CloudFormation implementation
0 parents  commit fde3f81

9 files changed

+427
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
Icon*
3+
node_modules
4+
npm-debug.log
5+
.serverless

README.md

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Additional Stacks Plugin for Serverless 1.x
2+
Created by Kenneth Falck <<[email protected]>> in 2017.
3+
Copyright [SC5 Online](https://sc5.io). Released under the MIT license.
4+
5+
## Overview and purpose
6+
7+
This plugin provides support for managing multiple AWS CloudFormation stacks
8+
in your Serverless 1.x service (serverless.yml).
9+
10+
Normally, Serverless creates a single CloudFormation stack which contains
11+
all your CloudFormation resources. If you ever delete your service, these
12+
resources are also deleted, including data stored in S3 or DynamoDB.
13+
14+
The Additional Stacks Plugin allows you to move your "permanent"
15+
resources into a separate CloudFormation stack, which is not tightly coupled
16+
with the service. If you delete your Serverless service and deploy it again from
17+
scratch, the permanent resources and their stored data remain untouched.
18+
19+
The cost of separation is that `Ref:` no longer works for directly referencing
20+
resources between stacks. If you need such references, you'll need to define
21+
exported stack output values and then use `Fn::ImportValue` to refer to the
22+
value in the other stack.
23+
24+
However, you can also use shared Serverless variables in stack definitions. So
25+
you can define resource names in `serverless.yml` and then refer to them
26+
using variables like `${self:custom.tableName}`.
27+
28+
You can use the plugin to manage as many additional stacks as you like
29+
in your serverless.yml, placed under the `custom.additionalStacks` section.
30+
31+
## Installation
32+
33+
To install with npm, run this in your service directory:
34+
35+
npm install --save serverless-plugin-additional-stacks
36+
37+
To install with yarn, run this in your service directory:
38+
39+
yarn add serverless-plugin-additional-stacks
40+
41+
Then add this to your `serverless.yml`:
42+
43+
```yml
44+
plugins:
45+
- serverless-plugin-additional-stacks
46+
```
47+
48+
## Configuration
49+
50+
To define additional stacks, add an `additionalStacks` section like this
51+
in your `serverless.yml`:
52+
53+
```yml
54+
custom:
55+
additionalStacks:
56+
permanent:
57+
Resources:
58+
S3BucketData:
59+
Type: AWS::S3::Bucket
60+
Properties:
61+
Name: ${self:service}-data
62+
```
63+
64+
If you'd like to place the additional resource definitions at the end of
65+
`serverless.yml` but keep the `custom` section elsewhere, you can use a
66+
Serverless variable to split the configuration like this:
67+
68+
```yml
69+
custom:
70+
additionalStacks:
71+
permanent: ${self:permanentResources}
72+
73+
permanentResources:
74+
Resources:
75+
S3BucketData:
76+
Type: AWS::S3::Bucket
77+
Properties:
78+
Name: ${self:service}-data
79+
```
80+
81+
82+
The full name of the deployed CloudFormation stack will include the service
83+
name. If your service is called `my-service` and you deploy it to the `dev`
84+
stage, the additional stack would be called `my-service-permanent-dev`.
85+
86+
The syntax of the Resources section is identical to the normal resources
87+
in `serverless.yml`, so you can just cut-paste resource definitions around.
88+
89+
### Using Deploy: After stacks
90+
91+
By default, additional stacks are deployed *before* any other CloudFormation
92+
resources. This ensures they are immediately available when your Lambda
93+
functions run.
94+
95+
If you need to deploy an additional stack *after* other CloudFormation
96+
resources, you can add `Deploy: After` to its definition. Here's an example:
97+
98+
```yml
99+
custom:
100+
additionalStacks:
101+
secondary:
102+
Deploy: After
103+
Resources:
104+
S3BucketData:
105+
Type: AWS::S3::Bucket
106+
Properties:
107+
Name: ${self:service}-data
108+
109+
```
110+
111+
## Command Line Usage
112+
113+
Your additional stacks will be deployed automatically when you run:
114+
115+
sls deploy
116+
117+
To deploy an additional stack individually, you can use:
118+
119+
sls deploy additionalstack [stackname]
120+
121+
If you want to remove an additional stack, you need to run:
122+
123+
sls remove additionalstack [stackname]
124+
125+
Or you can remove all additional stacks in the service with:
126+
127+
sls remove additionalstack -a
128+
129+
Alternatively, you can remove stacks manually in AWS CloudFormation Console.

index.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use strict'
2+
3+
const path = require('path')
4+
5+
class ServerlessPlugin {
6+
constructor(serverless, options) {
7+
this.serverless = serverless
8+
this.options = options
9+
this.provider = this.serverless.getProvider('aws')
10+
11+
this.commands = {
12+
deploy: {
13+
commands: {
14+
additionalstack: {
15+
usage: 'Deploy additional stacks',
16+
lifecycleEvents: [
17+
'deploy',
18+
],
19+
options: {
20+
all: {
21+
usage: 'Deploy all additional stacks',
22+
shortcut: 'a',
23+
required: false,
24+
},
25+
stack: {
26+
usage: 'Additional stack name to deploy',
27+
shortcut: 'k',
28+
required: false,
29+
},
30+
},
31+
},
32+
},
33+
},
34+
}
35+
36+
this.hooks = {
37+
'before:deploy:deploy': this.beforeDeployGlobal.bind(this),
38+
'after:deploy:deploy': this.afterDeployGlobal.bind(this),
39+
'deploy:additionalstack:deploy': this.deployAdditionalStackDeploy.bind(this),
40+
}
41+
}
42+
43+
getAdditionalStacks() {
44+
return this.serverless.service.custom && this.serverless.service.custom.additionalStacks || {}
45+
}
46+
47+
getAdditionalBeforeStacks() {
48+
const beforeStacks = {}
49+
const stacks = this.getAdditionalStacks()
50+
Object.keys(stacks).map(stackName => {
51+
if (!stacks[stackName].Deploy || stacks[stackName].Deploy.toLowerCase() === 'before') {
52+
beforeStacks[stackName] = stacks[stackName]
53+
}
54+
})
55+
return beforeStacks
56+
}
57+
58+
getAdditionalAfterStacks() {
59+
const afterStacks = {}
60+
const stacks = this.getAdditionalStacks()
61+
Object.keys(stacks).map(stackName => {
62+
if (stacks[stackName].Deploy && stacks[stackName].Deploy.toLowerCase() === 'after') {
63+
afterStacks[stackName] = stacks[stackName]
64+
}
65+
})
66+
return afterStacks
67+
}
68+
69+
// Deploy additional stacks befpre deploying the main stack
70+
// These are stacks with Deploy: Before, which is the default
71+
beforeDeployGlobal() {
72+
const stacks = this.getAdditionalBeforeStacks()
73+
if (Object.keys(stacks).length > 0) {
74+
this.serverless.cli.log('Deploying additional stacks...')
75+
return this.deployStacks(stacks)
76+
}
77+
}
78+
79+
// Deploy additional stacks after deploying the main stack
80+
// These are stacks with Deploy: After
81+
afterDeployGlobal() {
82+
const stacks = this.getAdditionalAfterStacks()
83+
if (Object.keys(stacks).length > 0) {
84+
this.serverless.cli.log('Deploying additional stacks...')
85+
return this.deployStacks(stacks)
86+
}
87+
}
88+
89+
// Deploy additional stacks specified with sls deploy stack [name]
90+
deployAdditionalStackDeploy() {
91+
const stacks = this.getAdditionalStacks()
92+
93+
if (this.options.all) {
94+
// Deploy all additional stacks
95+
if (Object.keys(stacks).length > 0) {
96+
this.serverless.cli.log('Deploying all additional stacks...')
97+
return this.deployStacks(stacks)
98+
} else {
99+
this.serverless.cli.log('No additional stacks defined. Add a custom.additionalStacks section to serverless.yml.')
100+
return Promise.resolve()
101+
}
102+
} else if (this.options.stack) {
103+
const stack = stacks[this.options.stack]
104+
if (stack) {
105+
this.serverless.cli.log('Deploying additional stack ' + this.options.stack + '...')
106+
return this.deployStack(this.options.stack, stack)
107+
} else {
108+
return Promise.reject(new Error('Additional stack not found: ' + this.options.stack))
109+
}
110+
} else {
111+
return Promise.reject(new Error('Please specify either sls deploy additionalstack -a to deploy all additional stacks or -k [stackName] to deploy a single stack'))
112+
}
113+
}
114+
115+
// This deploys all the specified stacks
116+
deployStacks(stacks) {
117+
let promise = Promise.resolve()
118+
Object.keys(stacks).map(stackName => {
119+
promise = promise
120+
.then(() => {
121+
return this.deployStack(stackName, stacks[stackName])
122+
})
123+
})
124+
return promise
125+
}
126+
127+
// This is where we actually handle the deployment to AWS
128+
deployStack(stackName, stack) {
129+
this.serverless.cli.log('DEPLOYING NOW ' + stackName + ' ' + JSON.stringify(stack))
130+
}
131+
}
132+
133+
module.exports = ServerlessPlugin

package.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "serverless-plugin-additional-stacks",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"repository": {},
6+
"license": "MIT"
7+
}

test/.npmignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# package directories
2+
node_modules
3+
jspm_packages
4+
5+
# Serverless directories
6+
.serverless

test/handler.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
module.exports.hello = (event, context, callback) => {
4+
const response = {
5+
statusCode: 200,
6+
body: JSON.stringify({
7+
message: 'Go Serverless v1.0! Your function executed successfully!',
8+
input: event,
9+
}),
10+
};
11+
12+
callback(null, response);
13+
14+
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
15+
// callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event });
16+
};

test/package.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "serverless-plugin-additional-stacks-test",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"repository": {},
6+
"license": "MIT",
7+
"scripts": {
8+
"install": "mkdir -p node_modules/serverless-plugin-additional-stacks && cp ../{index.js,package.json,README.md} node_modules/serverless-plugin-additional-stacks/"
9+
}
10+
}

0 commit comments

Comments
 (0)