simplerubysteps 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +64 -24
- data/exe/simplerubysteps +3 -3
- data/exe/srs +6 -0
- data/lib/function.rb +4 -1
- data/lib/simplerubysteps/dsl.rb +86 -0
- data/lib/simplerubysteps/model.rb +186 -0
- data/lib/{statemachine.yaml → simplerubysteps/statemachine.yaml} +2 -0
- data/lib/simplerubysteps/tool.rb +454 -0
- data/lib/simplerubysteps/version.rb +1 -1
- data/lib/simplerubysteps.rb +2 -273
- data/simplerubysteps.gemspec +2 -4
- metadata +9 -15
- data/.gitignore +0 -9
- data/Gemfile +0 -5
- data/LICENSE.txt +0 -21
- data/bin/console +0 -7
- data/bin/setup +0 -6
- data/lib/tool.rb +0 -448
- data/samples/sample1/sample-task-worker.sh +0 -5
- data/samples/sample1/start-callbackbranch.sh +0 -3
- data/samples/sample1/start-directbranch.sh +0 -3
- data/samples/sample1/workflow.rb +0 -39
- data/samples/sample2/workflow.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2eca7ec6018c9962f0d8186ed64c50fc068550705f9105eeb67e73069435964a
|
4
|
+
data.tar.gz: 8a17b50a46fe226c1d55cd235ab25c5540356c08517345f2323a2708c131d265
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5424b35119552d84924e1741c7b1ecfd8964058ad203db7fa0bceb5bf5dcd9880507a3d47aa1129b4664f9200da00b5e1275afb6a8725e1450119d99a2f3c6e
|
7
|
+
data.tar.gz: 59d20879dcdddaad13bad9744e92334b89efb7e5eb432bb66a2c0fba7c4f09894d56b34cebf378f4ffd28f3951d35f16202c03bfaf2618ef2dbabf39187e0916
|
data/README.md
CHANGED
@@ -1,55 +1,94 @@
|
|
1
1
|
# Simplerubysteps
|
2
2
|
|
3
|
-
Simplerubysteps
|
3
|
+
Simplerubysteps makes it easy to manage AWS Step Functions with ruby (this is an early alpha version and should not really be used by anyone).
|
4
4
|
|
5
|
-
|
5
|
+
## Installation and Usage
|
6
6
|
|
7
|
-
|
7
|
+
### Prerequisites
|
8
8
|
|
9
|
-
|
10
|
-
* AWS CLI
|
9
|
+
* AWS CLI installed (mainly for debugging privileges)
|
10
|
+
* Configured AWS CLI profile with sufficient permissions to create IAM roles and policies, create Lambda functions, create and run Step Functions state machines, run CloudWatch log queries, etc.
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
### Install the gem and the simplerubysteps CLI
|
12
|
+
### Install the gem and the srs CLI
|
15
13
|
|
16
14
|
```
|
17
15
|
gem install simplerubysteps
|
18
16
|
```
|
19
17
|
|
20
|
-
### Create AWS Step
|
18
|
+
### Create an AWS Step Function State Machine with the simplerubysteps Ruby DSL
|
21
19
|
|
22
20
|
```
|
23
|
-
|
21
|
+
mkdir -p samples/hello-world
|
22
|
+
cd samples/hello-world
|
23
|
+
|
24
24
|
vi workflow.rb
|
25
25
|
```
|
26
26
|
|
27
|
-
|
27
|
+
#### Hello World State Machine (workflow.rb)
|
28
|
+
|
29
|
+
```
|
30
|
+
require "simplerubysteps"
|
31
|
+
include Simplerubysteps
|
32
|
+
|
33
|
+
kind "EXPRESS"
|
34
|
+
|
35
|
+
task :start do
|
36
|
+
transition_to :even do |data|
|
37
|
+
data["number"] % 2 == 0
|
38
|
+
end
|
39
|
+
|
40
|
+
default_transition_to :odd
|
41
|
+
end
|
42
|
+
|
43
|
+
task :even do
|
44
|
+
action do |data|
|
45
|
+
number = data["number"]
|
46
|
+
|
47
|
+
puts "Just for the record, I've discovered an even number: #{number}"
|
48
|
+
|
49
|
+
{
|
50
|
+
result: "#{number} is even",
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
task :odd do
|
56
|
+
action do |data|
|
57
|
+
{
|
58
|
+
result: "#{data["number"]} is odd",
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
### Deploy the Step Functions State Machine and the Lambda function that implements the Task Actions.
|
28
65
|
|
29
66
|
```
|
30
|
-
export AWS_PROFILE
|
31
|
-
cd samples/
|
32
|
-
|
67
|
+
export AWS_PROFILE=<AWS CLI profile name with sufficient privileges>
|
68
|
+
cd samples/hello-world
|
69
|
+
|
70
|
+
srs deploy
|
33
71
|
```
|
34
72
|
|
35
|
-
### Trigger State Machine
|
73
|
+
### Trigger State Machine executions
|
36
74
|
|
37
75
|
```
|
38
|
-
export AWS_PROFILE
|
39
|
-
cd samples/
|
76
|
+
export AWS_PROFILE=<AWS CLI profile name with sufficient privileges>
|
77
|
+
cd samples/hello-world
|
40
78
|
|
41
|
-
|
79
|
+
echo "Enter a number"
|
80
|
+
read NUMBER
|
42
81
|
|
43
|
-
|
44
|
-
|
82
|
+
echo "{\"number\":$NUMBER}" | srs start | jq -r ".output" | jq -r ".result"
|
83
|
+
# Above: will print "123 is odd" for input "123"
|
45
84
|
```
|
46
85
|
|
47
86
|
### Delete CloudFormation stack
|
48
87
|
|
49
88
|
```
|
50
|
-
export AWS_PROFILE
|
51
|
-
|
52
|
-
|
89
|
+
export AWS_PROFILE=<AWS CLI profile name with sufficient privileges>
|
90
|
+
|
91
|
+
srs destroy
|
53
92
|
```
|
54
93
|
|
55
94
|
## Development
|
@@ -58,8 +97,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
58
97
|
|
59
98
|
### TODOs
|
60
99
|
|
61
|
-
* Custom IAM policies per Lambda task
|
100
|
+
* Custom IAM policies per Lambda task (e.g. to allow a task to send a message to an SQS queue)
|
62
101
|
* Workflow action unit test support
|
102
|
+
* Better error handling and reporting
|
63
103
|
* ...
|
64
104
|
|
65
105
|
## Contributing
|
data/exe/simplerubysteps
CHANGED
data/exe/srs
ADDED
data/lib/function.rb
CHANGED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Simplerubysteps
|
4
|
+
$sm = StateMachine.new
|
5
|
+
$tasks = []
|
6
|
+
|
7
|
+
def kind(k)
|
8
|
+
$sm.kind = k
|
9
|
+
end
|
10
|
+
|
11
|
+
def task(name)
|
12
|
+
t = $sm.add Task.new(name)
|
13
|
+
|
14
|
+
$tasks.last.next = t if $tasks.last
|
15
|
+
|
16
|
+
$tasks.push t
|
17
|
+
yield if block_given?
|
18
|
+
$tasks.pop
|
19
|
+
end
|
20
|
+
|
21
|
+
def callback(name)
|
22
|
+
t = $sm.add Callback.new(name)
|
23
|
+
|
24
|
+
$tasks.last.next = t if $tasks.last
|
25
|
+
|
26
|
+
$tasks.push t
|
27
|
+
yield if block_given?
|
28
|
+
$tasks.pop
|
29
|
+
end
|
30
|
+
|
31
|
+
def action(&action_block)
|
32
|
+
$tasks.last.action &action_block
|
33
|
+
end
|
34
|
+
|
35
|
+
def transition(state)
|
36
|
+
$tasks.last.next = state
|
37
|
+
end
|
38
|
+
|
39
|
+
def transition_to(state, &condition_block)
|
40
|
+
choice = $tasks.last.implicit_choice
|
41
|
+
|
42
|
+
c = ChoiceItem.new({
|
43
|
+
:Variable => "$.#{choice.name}_#{state}",
|
44
|
+
:StringMatches => "yes",
|
45
|
+
})
|
46
|
+
c.next = state
|
47
|
+
c.implicit_condition_block = condition_block
|
48
|
+
|
49
|
+
choice.add c
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_transition_to(state)
|
53
|
+
choice = $tasks.last.implicit_choice
|
54
|
+
|
55
|
+
choice.default = state
|
56
|
+
end
|
57
|
+
|
58
|
+
def choice(name)
|
59
|
+
t = $sm.add Choice.new(name)
|
60
|
+
|
61
|
+
$tasks.last.next = t if $tasks.last
|
62
|
+
|
63
|
+
$tasks.push t
|
64
|
+
yield if block_given?
|
65
|
+
$tasks.pop
|
66
|
+
end
|
67
|
+
|
68
|
+
def string_matches(var, match)
|
69
|
+
c = ChoiceItem.new({
|
70
|
+
:Variable => var,
|
71
|
+
:StringMatches => match,
|
72
|
+
})
|
73
|
+
|
74
|
+
$tasks.last.add c
|
75
|
+
|
76
|
+
$tasks.push c
|
77
|
+
yield if block_given?
|
78
|
+
$tasks.pop
|
79
|
+
end
|
80
|
+
|
81
|
+
def default
|
82
|
+
$tasks.push $tasks.last
|
83
|
+
yield if block_given?
|
84
|
+
$tasks.pop
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module Simplerubysteps
|
2
|
+
$FUNCTION_ARN = ENV["LAMBDA_FUNCTION_ARN"] ? ENV["LAMBDA_FUNCTION_ARN"] : "unknown"
|
3
|
+
|
4
|
+
def function_name
|
5
|
+
return "unknown" unless $FUNCTION_ARN =~ /.+\:function\:(.+)/
|
6
|
+
$1
|
7
|
+
end
|
8
|
+
|
9
|
+
class StateMachine
|
10
|
+
attr_reader :states
|
11
|
+
attr_reader :start_at
|
12
|
+
attr_accessor :kind
|
13
|
+
|
14
|
+
def initialize()
|
15
|
+
@states = {}
|
16
|
+
@kind = "STANDARD"
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(state)
|
20
|
+
@start_at = state.name unless @start_at
|
21
|
+
|
22
|
+
@states[state.name] = state
|
23
|
+
state.state_machine = self
|
24
|
+
|
25
|
+
state
|
26
|
+
end
|
27
|
+
|
28
|
+
def render
|
29
|
+
{
|
30
|
+
:StartAt => @start_at,
|
31
|
+
:States => @states.map { |name, state| [name, state.render] }.to_h,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class State
|
37
|
+
attr_reader :name
|
38
|
+
attr_accessor :state_machine
|
39
|
+
|
40
|
+
def initialize(name)
|
41
|
+
@name = name
|
42
|
+
@dict = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def []=(key, value)
|
46
|
+
@dict[key] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def next=(state)
|
50
|
+
@dict[:Next] = (state.is_a? Symbol) ? state : state.name
|
51
|
+
end
|
52
|
+
|
53
|
+
def render
|
54
|
+
dict = @dict
|
55
|
+
dict[:End] = true unless dict[:Next]
|
56
|
+
dict
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Task < State
|
61
|
+
def initialize(name)
|
62
|
+
super
|
63
|
+
@dict[:Type] = "Task"
|
64
|
+
@dict[:Resource] = $FUNCTION_ARN
|
65
|
+
@dict[:Parameters] = {
|
66
|
+
:Task => name,
|
67
|
+
"Input.$" => "$",
|
68
|
+
}
|
69
|
+
|
70
|
+
action { |input| input } # default: pass through
|
71
|
+
end
|
72
|
+
|
73
|
+
def action(&action_block)
|
74
|
+
@action_block = action_block
|
75
|
+
end
|
76
|
+
|
77
|
+
def perform_action(input)
|
78
|
+
output = nil
|
79
|
+
output = @action_block.call(input) if @action_block
|
80
|
+
|
81
|
+
if @implicit_choice
|
82
|
+
output = {} unless output
|
83
|
+
@implicit_choice.perform_action(output)
|
84
|
+
end
|
85
|
+
|
86
|
+
output
|
87
|
+
end
|
88
|
+
|
89
|
+
def implicit_choice
|
90
|
+
unless @implicit_choice
|
91
|
+
@implicit_choice = Choice.new("#{name}_choice")
|
92
|
+
$sm.add @implicit_choice
|
93
|
+
self.next = @implicit_choice
|
94
|
+
end
|
95
|
+
@implicit_choice
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Callback < State
|
100
|
+
def initialize(name)
|
101
|
+
super
|
102
|
+
@dict[:Type] = "Task"
|
103
|
+
@dict[:Resource] = "arn:aws:states:::lambda:invoke.waitForTaskToken"
|
104
|
+
@dict[:Parameters] = {
|
105
|
+
:FunctionName => function_name,
|
106
|
+
:Payload => {
|
107
|
+
:Task => name,
|
108
|
+
"Input.$" => "$",
|
109
|
+
"Token.$" => "$$.Task.Token",
|
110
|
+
},
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def action(&action_block)
|
115
|
+
@action_block = action_block
|
116
|
+
end
|
117
|
+
|
118
|
+
def perform_action(input, token)
|
119
|
+
@action_block.call(input, token) if @action_block
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class ChoiceItem
|
124
|
+
attr_accessor :implicit_condition_block
|
125
|
+
|
126
|
+
def initialize(dict = {}, state = nil)
|
127
|
+
@dict = dict
|
128
|
+
self.next = state if state
|
129
|
+
end
|
130
|
+
|
131
|
+
def next=(state)
|
132
|
+
@dict[:Next] = (state.is_a? Symbol) ? state : state.name
|
133
|
+
end
|
134
|
+
|
135
|
+
def render
|
136
|
+
@dict
|
137
|
+
end
|
138
|
+
|
139
|
+
def perform_action(choice_name, output)
|
140
|
+
if @implicit_condition_block
|
141
|
+
output["#{choice_name}_#{@dict[:Next]}"] = @implicit_condition_block.call(output) ? "yes" : "no"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Choice < State
|
147
|
+
attr_reader :choices
|
148
|
+
|
149
|
+
def initialize(name)
|
150
|
+
super
|
151
|
+
@choices = []
|
152
|
+
@dict[:Type] = "Choice"
|
153
|
+
end
|
154
|
+
|
155
|
+
def add(item)
|
156
|
+
@choices.push item
|
157
|
+
end
|
158
|
+
|
159
|
+
def add_string_matches(var, match, state)
|
160
|
+
add ChoiceItem.new({
|
161
|
+
:Variable => var,
|
162
|
+
:StringMatches => match,
|
163
|
+
}, state)
|
164
|
+
end
|
165
|
+
|
166
|
+
def default=(state)
|
167
|
+
@dict[:Default] = (state.is_a? Symbol) ? state : state.name
|
168
|
+
end
|
169
|
+
|
170
|
+
def next=(state)
|
171
|
+
self.default = state
|
172
|
+
end
|
173
|
+
|
174
|
+
def render
|
175
|
+
dict = @dict.clone
|
176
|
+
dict[:Choices] = @choices.map { |item| item.render }
|
177
|
+
dict
|
178
|
+
end
|
179
|
+
|
180
|
+
def perform_action(output)
|
181
|
+
@choices.each do |choice|
|
182
|
+
choice.perform_action name, output
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|