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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41e3a9ed5b8d6c32357fa880c2826cdf18a16fb9195260c5d10c7063b7dcb474
4
- data.tar.gz: '07180493d6f6456f41164cd74f1410fb8bcd6649a645d109011c85ec9133c463'
3
+ metadata.gz: 2eca7ec6018c9962f0d8186ed64c50fc068550705f9105eeb67e73069435964a
4
+ data.tar.gz: 8a17b50a46fe226c1d55cd235ab25c5540356c08517345f2323a2708c131d265
5
5
  SHA512:
6
- metadata.gz: 0620c89b84893c3e58078ab8dc66a211aaef5a52101a45e7392bde32bfdab3be3cc9d83bdee6e9adcfd1f3ecdc1e3420dd9068f58bea07550aad1993d0775a90
7
- data.tar.gz: 50f9d29b51926176a2a3edc900694233f38d44511ef5115433cc756f88c74df5ffca90bc924406644580c43e1f9ca6e2b1e2f79187aa0b3c9eca003f14b2daae
6
+ metadata.gz: b5424b35119552d84924e1741c7b1ecfd8964058ad203db7fa0bceb5bf5dcd9880507a3d47aa1129b4664f9200da00b5e1275afb6a8725e1450119d99a2f3c6e
7
+ data.tar.gz: 59d20879dcdddaad13bad9744e92334b89efb7e5eb432bb66a2c0fba7c4f09894d56b34cebf378f4ffd28f3951d35f16202c03bfaf2618ef2dbabf39187e0916
data/README.md CHANGED
@@ -1,55 +1,94 @@
1
1
  # Simplerubysteps
2
2
 
3
- Simplerubysteps simplifies the administration of AWS Step Functions with ruby (eventually - this is an early alpha version)
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
- This is software in the experimental stage and should not really be used by anyone. Be warned.
5
+ ## Installation and Usage
6
6
 
7
- ## Installation
7
+ ### Prerequisites
8
8
 
9
- Prerequisites (for alpha version):
10
- * AWS CLI installed
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
- ## Usage
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 Functions State Machine with ruby DSL
18
+ ### Create an AWS Step Function State Machine with the simplerubysteps Ruby DSL
21
19
 
22
20
  ```
23
- cd samples/sample1
21
+ mkdir -p samples/hello-world
22
+ cd samples/hello-world
23
+
24
24
  vi workflow.rb
25
25
  ```
26
26
 
27
- ### Create CloudFormation stack with Step Functions State Machine and supporting Lambda function
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/sample1
32
- simplerubysteps deploy
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 Execution and wait for completion
73
+ ### Trigger State Machine executions
36
74
 
37
75
  ```
38
- export AWS_PROFILE=...
39
- cd samples/sample1
76
+ export AWS_PROFILE=<AWS CLI profile name with sufficient privileges>
77
+ cd samples/hello-world
40
78
 
41
- ./start-directbranch.sh
79
+ echo "Enter a number"
80
+ read NUMBER
42
81
 
43
- ./sample-task-worker.sh &
44
- ./start-callbackbranch.sh
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
- cd samples/sample1
52
- simplerubysteps destroy
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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- bin_dir = File.expand_path(File.dirname(__FILE__))
4
- include_path = File.join(bin_dir, "../lib/tool.rb")
3
+ require "simplerubysteps"
4
+ require "simplerubysteps/tool"
5
5
 
6
- require include_path
6
+ Simplerubysteps::Tool.new.run
data/exe/srs ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "simplerubysteps"
4
+ require "simplerubysteps/tool"
5
+
6
+ Simplerubysteps::Tool.new.run
data/lib/function.rb CHANGED
@@ -1,6 +1,9 @@
1
- require "json"
1
+ require "simplerubysteps"
2
+
2
3
  require "./workflow.rb"
3
4
 
5
+ include Simplerubysteps
6
+
4
7
  def handler(event:, context:)
5
8
  puts ENV.inspect if ENV["DEBUG"]
6
9
  puts event if ENV["DEBUG"]
@@ -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
@@ -125,3 +125,5 @@ Outputs:
125
125
  StepFunctionsStateMachineARN:
126
126
  Value: !GetAtt StepFunctionsStateMachine.Arn
127
127
  Condition: CreateStepfunctions
128
+ StateMachineType:
129
+ Value: !Ref StateMachineType