floe 0.11.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/floe/workflow.rb CHANGED
@@ -6,6 +6,7 @@ require "json"
6
6
  module Floe
7
7
  class Workflow
8
8
  include Logging
9
+ include ValidationMixin
9
10
 
10
11
  class << self
11
12
  def load(path_or_io, context = nil, credentials = {}, name = nil)
@@ -85,61 +86,73 @@ module Floe
85
86
  end
86
87
  end
87
88
 
88
- attr_reader :context, :credentials, :payload, :states, :states_by_name, :start_at, :name, :comment
89
+ attr_reader :context, :payload, :states, :states_by_name, :start_at, :name, :comment
89
90
 
90
- def initialize(payload, context = nil, credentials = {}, name = nil)
91
+ def initialize(payload, context = nil, credentials = nil, name = nil)
91
92
  payload = JSON.parse(payload) if payload.kind_of?(String)
92
93
  credentials = JSON.parse(credentials) if credentials.kind_of?(String)
93
94
  context = Context.new(context) unless context.kind_of?(Context)
94
95
 
95
- raise Floe::InvalidWorkflowError, "Missing field \"States\"" if payload["States"].nil?
96
- raise Floe::InvalidWorkflowError, "Missing field \"StartAt\"" if payload["StartAt"].nil?
97
- raise Floe::InvalidWorkflowError, "\"StartAt\" not in the \"States\" field" unless payload["States"].key?(payload["StartAt"])
96
+ # backwards compatibility
97
+ # caller should really put credentials into context and not pass that variable
98
+ context.credentials = credentials if credentials
98
99
 
99
- @name = name
100
+ # NOTE: this is a string, and states use an array
101
+ @name = name || "State Machine"
100
102
  @payload = payload
101
103
  @context = context
102
- @credentials = credentials || {}
103
104
  @comment = payload["Comment"]
104
105
  @start_at = payload["StartAt"]
105
106
 
106
- @states = payload["States"].to_a.map { |state_name, state| State.build!(self, state_name, state) }
107
- @states_by_name = @states.each_with_object({}) { |state, result| result[state.name] = state }
107
+ # NOTE: Everywhere else we include our name (i.e.: parent name) when building the child name.
108
+ # When creating the states, we are dropping our name (i.e.: the workflow name)
109
+ @states = payload["States"].to_a.map { |state_name, state| State.build!(self, ["States", state_name], state) }
108
110
 
109
- unless context.state.key?("Name")
110
- context.state["Name"] = start_at
111
- context.state["Input"] = context.execution["Input"].dup
112
- end
113
- rescue StandardError => err
111
+ validate_workflow
112
+
113
+ @states_by_name = @states.each_with_object({}) { |state, result| result[state.short_name] = state }
114
+ rescue Floe::Error
115
+ raise
116
+ rescue => err
114
117
  raise Floe::InvalidWorkflowError, err.message
115
118
  end
116
119
 
117
120
  def run_nonblock
121
+ start_workflow
118
122
  loop while step_nonblock == 0 && !end?
119
123
  self
120
124
  end
121
125
 
126
+ # NOTE: If running manually, make sure to call start_workflow at startup
122
127
  def step_nonblock
123
128
  return Errno::EPERM if end?
124
129
 
125
- step_next
126
- current_state.run_nonblock!
130
+ result = current_state.run_nonblock!(context)
131
+ return result if result != 0
132
+
133
+ # if it completed the step
134
+ context.state_history << context.state
135
+ context.next_state ? step! : end_workflow!
136
+
137
+ result
127
138
  end
128
139
 
140
+ # if this hasn't started (and we have no current_state yet), assume it is ready
129
141
  def step_nonblock_wait(timeout: nil)
130
- current_state.wait(:timeout => timeout)
142
+ context.started? ? current_state.wait(context, :timeout => timeout) : 0
131
143
  end
132
144
 
145
+ # if this hasn't started (and we have no current_state yet), assume it is ready
133
146
  def step_nonblock_ready?
134
- current_state.ready?
147
+ !context.started? || current_state.ready?(context)
135
148
  end
136
149
 
137
150
  def waiting?
138
- current_state.waiting?
151
+ current_state.waiting?(context)
139
152
  end
140
153
 
141
154
  def wait_until
142
- current_state.wait_until
155
+ current_state.wait_until(context)
143
156
  end
144
157
 
145
158
  def status
@@ -147,21 +160,60 @@ module Floe
147
160
  end
148
161
 
149
162
  def output
150
- context.output if end?
163
+ context.json_output if end?
151
164
  end
152
165
 
153
166
  def end?
154
167
  context.ended?
155
168
  end
156
169
 
170
+ # setup a workflow
171
+ def start_workflow
172
+ return if context.state_name
173
+
174
+ context.state["Name"] = start_at
175
+ context.state["Input"] = context.execution["Input"].dup
176
+ context.state["Guid"] = SecureRandom.uuid
177
+
178
+ context.execution["Id"] = SecureRandom.uuid
179
+ context.execution["StartTime"] = Time.now.utc.iso8601
180
+
181
+ self
182
+ end
183
+
184
+ # NOTE: Expecting the context to be initialized (via start_workflow) before this
157
185
  def current_state
158
186
  @states_by_name[context.state_name]
159
187
  end
160
188
 
189
+ # backwards compatibility. Caller should access directly from context
190
+ def credentials
191
+ @context.credentials
192
+ end
193
+
161
194
  private
162
195
 
163
- def step_next
164
- context.state = {"Name" => context.next_state, "Input" => context.output} if context.next_state
196
+ def validate_workflow
197
+ missing_field_error!("States") if @states.empty?
198
+ missing_field_error!("StartAt") if @start_at.nil?
199
+ invalid_field_error!("StartAt", @start_at, "is not found in \"States\"") unless workflow_state?(@start_at, self)
200
+ end
201
+
202
+ def step!
203
+ next_state = {"Name" => context.next_state, "Guid" => SecureRandom.uuid, "PreviousStateGuid" => context.state["Guid"]}
204
+
205
+ # if rerunning due to an error (and we are using Retry)
206
+ if context.state_name == context.next_state && context.failed? && context.state.key?("Retrier")
207
+ next_state.merge!(context.state.slice("RetryCount", "Input", "Retrier"))
208
+ else
209
+ next_state["Input"] = context.output
210
+ end
211
+
212
+ context.state = next_state
213
+ end
214
+
215
+ def end_workflow!
216
+ context.execution["EndTime"] = context.state["FinishedTime"]
165
217
  end
166
218
  end
167
219
  end
data/lib/floe.rb CHANGED
@@ -7,7 +7,9 @@ require_relative "floe/logging"
7
7
 
8
8
  require_relative "floe/runner"
9
9
 
10
+ require_relative "floe/validation_mixin"
10
11
  require_relative "floe/workflow"
12
+ require_relative "floe/workflow/error_matcher_mixin"
11
13
  require_relative "floe/workflow/catcher"
12
14
  require_relative "floe/workflow/choice_rule"
13
15
  require_relative "floe/workflow/choice_rule/not"
@@ -15,6 +17,9 @@ require_relative "floe/workflow/choice_rule/or"
15
17
  require_relative "floe/workflow/choice_rule/and"
16
18
  require_relative "floe/workflow/choice_rule/data"
17
19
  require_relative "floe/workflow/context"
20
+ require_relative "floe/workflow/intrinsic_function"
21
+ require_relative "floe/workflow/intrinsic_function/parser"
22
+ require_relative "floe/workflow/intrinsic_function/transformer"
18
23
  require_relative "floe/workflow/path"
19
24
  require_relative "floe/workflow/payload_template"
20
25
  require_relative "floe/workflow/reference_path"
@@ -37,6 +42,7 @@ require "time"
37
42
  module Floe
38
43
  class Error < StandardError; end
39
44
  class InvalidWorkflowError < Error; end
45
+ class InvalidExecutionInput < Error; end
40
46
 
41
47
  def self.logger
42
48
  @logger ||= NullLogger.new
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: floe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Developers
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-24 00:00:00.000000000 Z
11
+ date: 2024-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_spawn
@@ -80,20 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: parslet
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: manageiq-style
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - ">="
88
102
  - !ruby/object:Gem::Version
89
- version: '0'
103
+ version: 1.5.2
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
- version: '0'
110
+ version: 1.5.2
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rake
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -122,20 +136,6 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: rubocop
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: simplecov
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -164,13 +164,14 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
- description: Simple Workflow Runner.
167
+ description: Floe is a runner for Amazon States Language workflows.
168
168
  email:
169
169
  executables:
170
170
  - floe
171
171
  extensions: []
172
172
  extra_rdoc_files: []
173
173
  files:
174
+ - ".codeclimate.yml"
174
175
  - ".rspec"
175
176
  - ".rubocop.yml"
176
177
  - ".rubocop_cc.yml"
@@ -178,6 +179,7 @@ files:
178
179
  - ".yamllint"
179
180
  - CHANGELOG.md
180
181
  - Gemfile
182
+ - LICENSE.txt
181
183
  - README.md
182
184
  - Rakefile
183
185
  - examples/set-credential.asl
@@ -185,6 +187,7 @@ files:
185
187
  - exe/floe
186
188
  - floe.gemspec
187
189
  - lib/floe.rb
190
+ - lib/floe/cli.rb
188
191
  - lib/floe/container_runner.rb
189
192
  - lib/floe/container_runner/docker.rb
190
193
  - lib/floe/container_runner/docker_mixin.rb
@@ -193,6 +196,7 @@ files:
193
196
  - lib/floe/logging.rb
194
197
  - lib/floe/null_logger.rb
195
198
  - lib/floe/runner.rb
199
+ - lib/floe/validation_mixin.rb
196
200
  - lib/floe/version.rb
197
201
  - lib/floe/workflow.rb
198
202
  - lib/floe/workflow/catcher.rb
@@ -202,6 +206,10 @@ files:
202
206
  - lib/floe/workflow/choice_rule/not.rb
203
207
  - lib/floe/workflow/choice_rule/or.rb
204
208
  - lib/floe/workflow/context.rb
209
+ - lib/floe/workflow/error_matcher_mixin.rb
210
+ - lib/floe/workflow/intrinsic_function.rb
211
+ - lib/floe/workflow/intrinsic_function/parser.rb
212
+ - lib/floe/workflow/intrinsic_function/transformer.rb
205
213
  - lib/floe/workflow/path.rb
206
214
  - lib/floe/workflow/payload_template.rb
207
215
  - lib/floe/workflow/reference_path.rb
@@ -220,7 +228,8 @@ files:
220
228
  - renovate.json
221
229
  - sig/floe.rbs/floe.rbs
222
230
  homepage: https://github.com/ManageIQ/floe
223
- licenses: []
231
+ licenses:
232
+ - Apache-2.0
224
233
  metadata:
225
234
  allowed_push_host: https://rubygems.org
226
235
  rubygems_mfa_required: 'true'
@@ -242,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
251
  - !ruby/object:Gem::Version
243
252
  version: '0'
244
253
  requirements: []
245
- rubygems_version: 3.5.10
254
+ rubygems_version: 3.4.20
246
255
  signing_key:
247
256
  specification_version: 4
248
- summary: Simple Workflow Runner.
257
+ summary: Floe is a runner for Amazon States Language workflows.
249
258
  test_files: []