Skip to content

Commit 10d884b

Browse files
committed
Feat: Docker and Fn::GetAtt support
- Added support for running emulator in Docker - Resolves Fn::GetAtt in Task type reference
1 parent b62e226 commit 10d884b

File tree

4 files changed

+421
-4161
lines changed

4 files changed

+421
-4161
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ This is a plugin for the [Serverless Framework](https://serverless.com/). It us
66
## Requirements
77

88
- The [serverless-offline](https://www.npmjs.com/package/serverless-offline) plugin
9-
- The [serverless-offline-lambda](https://www.npmjs.com/package/serverless-offline-lambda) plugin
109
- The [serverless-step-functions](https://www.npmjs.com/package/serverless-step-functions) plugin
11-
- Java Runtime Engine (JRE) version 6.x or newer
10+
- (optional) Java Runtime Engine (JRE) version 6.x or newer if you don't run your own AWS Step Functions emulator
1211

1312
## Install
1413

@@ -52,6 +51,8 @@ It also adds an environment variable for each created state machine that contain
5251
- `lambdaEndpoint` (defaults to `http://localhost:4000`) the endpoint for the lambda service
5352
- `path` (defaults to `./.step-functions-local`) the path to store the downloaded step function executables
5453
- `TaskResourceMapping` allows for Resource ARNs to be configured differently for local development
54+
- `startStepFunctionsLocalApp` (default: true) starts AWS step function emulator. Set to false if it's running already. It needs to run on port 8083.
55+
5556

5657
### Full Config Example
5758

@@ -76,6 +77,7 @@ custom:
7677
TaskResourceMapping:
7778
FirstState: arn:aws:lambda:us-east-1:101010101010:function:hello
7879
FinalState: arn:aws:lambda:us-east-1:101010101010:function:hello
80+
startStepFunctionsLocalApp: true
7981
8082
functions:
8183
hello:
@@ -101,3 +103,13 @@ stepFunctions:
101103
Resource: Fn::GetAtt: [hello, Arn]
102104
End: true
103105
```
106+
107+
### Running AWS Step functions emulator in Docker
108+
```bash
109+
$ docker run \
110+
-p 8083:8083 \
111+
-e "AWS_ACCOUNT_ID=101010101010" \
112+
-e "AWS_DEFAULT_REGION=us-east-1" \
113+
-e "LAMBDA_ENDPOINT=http://localhost:3002" \
114+
amazon/aws-stepfunctions-local
115+
```

index.js

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,28 @@ class ServerlessStepFunctionsLocal {
3030
this.config.path = './.step-functions-local';
3131
}
3232

33+
if (this.config.startStepFunctionsLocalApp === undefined) {
34+
this.config.startStepFunctionsLocalApp = true
35+
}
36+
3337
this.stepfunctionsServer = new StepFunctionsLocal(this.config);
3438

3539
this.stepfunctionsAPI = new AWS.StepFunctions({endpoint: 'http://localhost:8083', region: this.config.region});
3640

3741
this.hooks = {
3842
'offline:start:init': async () => {
39-
await this.installStepFunctions();
43+
if (this.config.startStepFunctionsLocalApp) {
44+
await this.installStepFunctions();
45+
}
46+
4047
await this.startStepFunctions();
4148
await this.getStepFunctionsFromConfig();
4249
await this.createEndpoints();
4350
},
4451
'before:offline:start:end': async () => {
45-
await this.stopStepFunctions();
52+
if (this.config.startStepFunctionsLocalApp) {
53+
await this.stopStepFunctions();
54+
}
4655
}
4756
};
4857
}
@@ -52,15 +61,20 @@ class ServerlessStepFunctionsLocal {
5261
}
5362

5463
async startStepFunctions() {
55-
this.stepfunctionsServer.start({
56-
account: this.config.accountId.toString(),
57-
lambdaEndpoint: this.config.lambdaEndpoint
58-
}).on('data', data => {
59-
console.log(chalk.blue('[Serverless Step Functions Local]'), data.toString());
60-
});
64+
if (this.config.startStepFunctionsLocalApp) {
65+
this.stepfunctionsServer.start({
66+
account: this.config.accountId.toString(),
67+
lambdaEndpoint: this.config.lambdaEndpoint
68+
}).on('data', data => {
69+
console.log(chalk.blue('[Serverless Step Functions Local]'), data.toString());
70+
});
71+
} else {
72+
console.log(chalk.blue('[Serverless Step Functions Local]'), 'Waiting for AWS Step Functions emulator on port 8083');
73+
}
6174

6275
// Wait for server to start
6376
await tcpPortUsed.waitUntilUsed(8083, 200, 10000);
77+
console.log(chalk.blue('[Serverless Step Functions Local]'), 'AWS Step Functions emulator detected on 8083');
6478
}
6579

6680
stopStepFunctions() {
@@ -80,6 +94,19 @@ class ServerlessStepFunctionsLocal {
8094

8195
this.stateMachines = parsed.stepFunctions.stateMachines;
8296

97+
// replace Fn::GetAtt
98+
Object.keys(this.stateMachines).map(stateMachineName => {
99+
const machine = this.stateMachines[stateMachineName]
100+
Object.keys(machine.definition.States).map(stateName => {
101+
const state = machine.definition.States[stateName]
102+
if(state.Type === 'Task') {
103+
if(state.Resource && state.Resource['Fn::GetAtt'] && Array.isArray(state.Resource['Fn::GetAtt'])) {
104+
state.Resource = `arn:aws:lambda:${this.config.region}:${this.config.accountId}:function:${this.service.service}-${this.serverless.stage ? this.serverless.stage : 'dev'}-${state.Resource['Fn::GetAtt'][0]}`
105+
}
106+
}
107+
})
108+
})
109+
83110
if (parsed.custom
84111
&& parsed.custom.stepFunctionsLocal
85112
&& parsed.custom.stepFunctionsLocal.TaskResourceMapping) {
@@ -104,6 +131,51 @@ class ServerlessStepFunctionsLocal {
104131
}
105132

106133
async createEndpoints() {
134+
// Delete existing state machines
135+
const EMPTY = Symbol('empty')
136+
let nextToken = EMPTY
137+
const knownStateMachines = Object.keys(this.stateMachines)
138+
// A state machine is eventually deleted.
139+
// We need to wait until it's actually deleted because otherwise
140+
// the new state machine created later is deleted as well and is not
141+
// available.
142+
while (true) {
143+
let hasRunningMachine = false
144+
while (nextToken) {
145+
const data = await this.stepfunctionsAPI.listStateMachines({
146+
nextToken: (nextToken === EMPTY ? undefined : nextToken)
147+
}).promise()
148+
149+
nextToken = data.nextToken
150+
for (const machine of data.stateMachines) {
151+
if(!knownStateMachines.includes(machine.name)) {
152+
continue
153+
}
154+
hasRunningMachine = true
155+
156+
await this.stepfunctionsAPI.deleteStateMachine({
157+
stateMachineArn: machine.stateMachineArn
158+
})
159+
.promise()
160+
.catch(err => {
161+
// state machine was not found
162+
if(err && err.code === 400) {
163+
return
164+
}
165+
166+
throw err
167+
})
168+
}
169+
}
170+
171+
if(!hasRunningMachine) {
172+
break
173+
}
174+
175+
await new Promise(resolve => setTimeout(resolve, 1000))
176+
console.log(chalk.blue('[Serverless Step Functions Local]'), 'Retrying old state machine removal');
177+
}
178+
107179
const endpoints = await Promise.all(Object.keys(this.stateMachines).map(stateMachineName => this.stepfunctionsAPI.createStateMachine({
108180
definition: JSON.stringify(this.stateMachines[stateMachineName].definition),
109181
name: stateMachineName,

0 commit comments

Comments
 (0)