simplerubysteps 0.0.7 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tool.rb DELETED
@@ -1,448 +0,0 @@
1
- require "aws-sdk-cloudformation"
2
- require "aws-sdk-s3"
3
- require "aws-sdk-states"
4
- require "digest"
5
- require "zip"
6
- require "tempfile"
7
- require "json"
8
- require "optparse"
9
- require "aws-sdk-cloudwatchlogs"
10
- require "time"
11
-
12
- $cloudformation_client = Aws::CloudFormation::Client.new
13
- $s3_client = Aws::S3::Client.new
14
- $states_client = Aws::States::Client.new
15
- $logs_client = Aws::CloudWatchLogs::Client.new
16
-
17
- def tail_follow_logs(log_group_name, extract_pattern = nil) # FIXME too hacky and not really working
18
- Signal.trap("INT") do
19
- exit
20
- end
21
-
22
- first_event_time = Time.now.to_i * 1000
23
-
24
- next_tokens = {}
25
- first_round = true
26
- loop do
27
- log_streams = $logs_client.describe_log_streams(
28
- log_group_name: log_group_name,
29
- order_by: "LastEventTime",
30
- descending: true,
31
- ).log_streams
32
-
33
- log_streams.each do |log_stream|
34
- get_log_events_params = {
35
- log_group_name: log_group_name,
36
- log_stream_name: log_stream.log_stream_name,
37
- }
38
-
39
- if next_tokens.key?(log_stream.log_stream_name)
40
- get_log_events_params[:next_token] = next_tokens[log_stream.log_stream_name]
41
- else
42
- get_log_events_params[:start_time] = first_round ? log_stream.last_event_timestamp : first_event_time
43
- end
44
-
45
- response = $logs_client.get_log_events(get_log_events_params)
46
-
47
- response.events.each do |event|
48
- if event.timestamp >= first_event_time
49
- if extract_pattern
50
- if /#{extract_pattern}/ =~ event.message
51
- puts $1
52
- exit
53
- end
54
- else
55
- puts "#{Time.at(event.timestamp / 1000).utc} - #{log_stream.log_stream_name} - #{event.message}"
56
- end
57
- end
58
- end
59
-
60
- next_tokens[log_stream.log_stream_name] = response.next_forward_token
61
- end
62
-
63
- sleep 5
64
-
65
- first_round = false
66
- end
67
- end
68
-
69
- def stack_outputs(stack_name)
70
- begin
71
- response = $cloudformation_client.describe_stacks(stack_name: stack_name)
72
- outputs = {}
73
- response.stacks.first.outputs.each do |output|
74
- outputs[output.output_key] = output.output_value
75
- end
76
- outputs
77
- rescue Aws::CloudFormation::Errors::ServiceError => error
78
- return nil if error.message =~ /Stack .* does not exist/
79
- raise error
80
- end
81
- end
82
-
83
- def stack_params(stack_name, template, parameters)
84
- params = {
85
- stack_name: stack_name,
86
- template_body: template,
87
- capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"],
88
- parameters: [],
89
- }
90
- parameters.each do |k, v|
91
- params[:parameters].push({
92
- parameter_key: k,
93
- parameter_value: v,
94
- })
95
- end
96
- params
97
- end
98
-
99
- def stack_create(stack_name, template, parameters)
100
- $cloudformation_client.create_stack(stack_params(stack_name, template, parameters))
101
- $cloudformation_client.wait_until(:stack_create_complete, stack_name: stack_name)
102
- stack_outputs(stack_name)
103
- end
104
-
105
- def stack_update(stack_name, template, parameters)
106
- begin
107
- $cloudformation_client.update_stack(stack_params(stack_name, template, parameters))
108
- $cloudformation_client.wait_until(:stack_update_complete, stack_name: stack_name)
109
- stack_outputs(stack_name)
110
- rescue Aws::CloudFormation::Errors::ServiceError => error
111
- return stack_outputs(stack_name).merge({ :no_update => true }) if error.message =~ /No updates are to be performed/
112
- raise unless error.message =~ /No updates are to be performed/
113
- end
114
- end
115
-
116
- def upload_to_s3(bucket, key, body)
117
- $s3_client.put_object(
118
- bucket: bucket,
119
- key: key,
120
- body: body,
121
- )
122
- end
123
-
124
- def upload_file_to_s3(bucket, key, file_path)
125
- File.open(file_path, "rb") do |file|
126
- upload_to_s3(bucket, key, file)
127
- end
128
- end
129
-
130
- def empty_s3_bucket(bucket_name)
131
- $s3_client.list_objects_v2(bucket: bucket_name).contents.each do |object|
132
- $s3_client.delete_object(bucket: bucket_name, key: object.key)
133
- end
134
- end
135
-
136
- def create_zip(zip_file, files_by_name)
137
- Zip::File.open(zip_file, create: true) do |zipfile|
138
- base_dir = File.expand_path(File.dirname(__FILE__))
139
- files_by_name.each do |n, f|
140
- zipfile.add n, f
141
- end
142
- end
143
- end
144
-
145
- def dir_files(base_dir, glob)
146
- files_by_name = {}
147
- base_dir = File.expand_path(base_dir)
148
- Dir.glob("#{base_dir}/#{glob}").select { |path| File.file?(path) }.each do |f|
149
- files_by_name[File.expand_path(f)[base_dir.length + 1..-1]] = f
150
- end
151
- files_by_name
152
- end
153
-
154
- def stack_name_from_current_dir
155
- File.basename(File.expand_path("."))
156
- end
157
-
158
- def workflow_files
159
- dir_files ".", "**/*.rb"
160
- end
161
-
162
- def my_lib_files
163
- dir_files File.dirname(__FILE__), "**/*.rb"
164
- end
165
-
166
- def cloudformation_template
167
- File.open("#{File.dirname(__FILE__)}/statemachine.yaml", "r") do |file|
168
- return file.read
169
- end
170
- end
171
-
172
- def log(extract_pattern = nil)
173
- current_stack_outputs = stack_outputs(stack_name_from_current_dir)
174
- function_name = current_stack_outputs["LambdaFunctionName"]
175
- rause "State Machine is not deployed" unless function_name
176
-
177
- tail_follow_logs "/aws/lambda/#{function_name}", extract_pattern
178
- end
179
-
180
- def destroy
181
- current_stack_outputs = stack_outputs(stack_name_from_current_dir)
182
- deploy_bucket = current_stack_outputs["DeployBucket"]
183
- rause "No CloudFormation stack to destroy" unless deploy_bucket
184
-
185
- empty_s3_bucket deploy_bucket
186
-
187
- puts "Bucket emptied: #{deploy_bucket}"
188
-
189
- $cloudformation_client.delete_stack(stack_name: stack_name_from_current_dir)
190
- $cloudformation_client.wait_until(:stack_delete_complete, stack_name: stack_name_from_current_dir)
191
-
192
- puts "Stack deleted"
193
- end
194
-
195
- def deploy(workflow_type)
196
- current_stack_outputs = stack_outputs(stack_name_from_current_dir)
197
-
198
- unless current_stack_outputs
199
- current_stack_outputs = stack_create(stack_name_from_current_dir, cloudformation_template, {
200
- "DeployLambda" => "no",
201
- "DeployStepfunctions" => "no",
202
- "LambdaS3" => "",
203
- "StepFunctionsS3" => "",
204
- "StateMachineType" => "",
205
- })
206
-
207
- puts "Deployment bucket created"
208
- end
209
-
210
- deploy_bucket = current_stack_outputs["DeployBucket"]
211
- puts "Deployment bucket: #{deploy_bucket}"
212
-
213
- function_zip_temp = Tempfile.new("function")
214
- create_zip function_zip_temp.path, my_lib_files.merge(workflow_files)
215
- lambda_sha = Digest::SHA1.file function_zip_temp.path
216
- lambda_zip_name = "function-#{lambda_sha}.zip"
217
- upload_file_to_s3 deploy_bucket, lambda_zip_name, function_zip_temp.path
218
- puts "Uploaded: #{lambda_zip_name}"
219
-
220
- unless current_stack_outputs["LambdaFunctionARN"]
221
- current_stack_outputs = stack_update(stack_name_from_current_dir, cloudformation_template, {
222
- "DeployLambda" => "yes",
223
- "DeployStepfunctions" => "no",
224
- "LambdaS3" => lambda_zip_name,
225
- "StepFunctionsS3" => "",
226
- "StateMachineType" => "",
227
- })
228
-
229
- puts "Lambda function created"
230
- end
231
-
232
- lambda_arn = current_stack_outputs["LambdaFunctionARN"]
233
- puts "Lambda function: #{lambda_arn}"
234
-
235
- state_machine_json = JSON.parse(`LAMBDA_FUNCTION_ARN=#{lambda_arn} ruby -e 'require "./workflow.rb";puts $sm.render.to_json'`).to_json
236
- state_machine_json_sha = Digest::SHA1.hexdigest state_machine_json
237
- state_machine_json_name = "statemachine-#{state_machine_json_sha}.json"
238
- upload_to_s3 deploy_bucket, state_machine_json_name, state_machine_json
239
- puts "Uploaded: #{state_machine_json_name}"
240
-
241
- current_stack_outputs = stack_update(stack_name_from_current_dir, cloudformation_template, {
242
- "DeployLambda" => "yes",
243
- "DeployStepfunctions" => "yes",
244
- "LambdaS3" => lambda_zip_name,
245
- "StepFunctionsS3" => state_machine_json_name,
246
- "StateMachineType" => workflow_type,
247
- })
248
-
249
- if current_stack_outputs[:no_update]
250
- puts "Stack not updated"
251
- else
252
- puts "Stack updated"
253
- end
254
-
255
- puts "State machine: #{current_stack_outputs["StepFunctionsStateMachineARN"]}"
256
- end
257
-
258
- def start_sync_execution(state_machine_arn, input)
259
- $states_client.start_sync_execution(
260
- state_machine_arn: state_machine_arn,
261
- input: input,
262
- )
263
- end
264
-
265
- def start_async_execution(state_machine_arn, input)
266
- $states_client.start_execution(
267
- state_machine_arn: state_machine_arn,
268
- input: input,
269
- )
270
- end
271
-
272
- def describe_execution(execution_arn)
273
- $states_client.describe_execution(
274
- execution_arn: execution_arn,
275
- )
276
- end
277
-
278
- def wait_for_async_execution_completion(execution_arn)
279
- response = nil
280
-
281
- loop do
282
- response = describe_execution(execution_arn)
283
- status = response.status
284
-
285
- break if %w[SUCCEEDED FAILED TIMED_OUT].include?(status)
286
-
287
- sleep 5
288
- end
289
-
290
- response
291
- end
292
-
293
- def start(workflow_type, wait = true, input = $stdin)
294
- current_stack_outputs = stack_outputs(stack_name_from_current_dir)
295
- state_machine_arn = current_stack_outputs["StepFunctionsStateMachineARN"]
296
- raise "State Machine is not deployed" unless state_machine_arn
297
-
298
- input_json = JSON.parse(input.read).to_json
299
-
300
- if workflow_type == "STANDARD"
301
- start_response = start_async_execution(state_machine_arn, input_json)
302
-
303
- unless wait
304
- puts start_response.to_json
305
- else
306
- execution_arn = start_response.execution_arn
307
-
308
- puts wait_for_async_execution_completion(execution_arn).to_json
309
- end
310
- elsif workflow_type == "EXPRESS"
311
- puts start_sync_execution(state_machine_arn, input_json).to_json
312
- else
313
- raise "Unknown state machine type: #{workflow_type}"
314
- end
315
- end
316
-
317
- def send_task_success(task_token, output = $stdin)
318
- raise "No token" unless task_token
319
-
320
- output_json = JSON.parse(output.read).to_json
321
-
322
- puts $states_client.send_task_success(
323
- task_token: task_token,
324
- output: output_json,
325
- ).to_json
326
- end
327
-
328
- options = {
329
- :workflow_type => "STANDARD",
330
- :wait => false,
331
- :input => $stdin,
332
- }
333
-
334
- subcommands = {
335
- "deploy" => OptionParser.new do |opts|
336
- opts.banner = "Usage: #{$0} deploy [options]"
337
-
338
- opts.on("--type VALUE", "STANDARD (default) or EXPRESS") do |value|
339
- options[:workflow_type] = value
340
- end
341
-
342
- opts.on("-h", "--help", "Display this help message") do
343
- puts opts
344
- exit
345
- end
346
- end,
347
- "destroy" => OptionParser.new do |opts|
348
- opts.banner = "Usage: #{$0} destroy [options]"
349
-
350
- opts.on("-h", "--help", "Display this help message") do
351
- puts opts
352
- exit
353
- end
354
- end,
355
- "log" => OptionParser.new do |opts|
356
- opts.banner = "Usage: #{$0} log [options]"
357
-
358
- opts.on("--extract_pattern VALUE", "Waits for and extracts pattern") do |value|
359
- options[:extract_pattern] = value
360
- end
361
-
362
- opts.on("-h", "--help", "Display this help message") do
363
- puts opts
364
- exit
365
- end
366
- end,
367
- "start" => OptionParser.new do |opts|
368
- opts.banner = "Usage: #{$0} start [options]"
369
-
370
- opts.on("--type VALUE", "STANDARD (default) or EXPRESS") do |value|
371
- options[:workflow_type] = value
372
- end
373
-
374
- opts.on("--wait VALUE", "true (default and always true for EXPRESS) or false (default for STANDARD)") do |value|
375
- options[:wait] = "true" == value
376
- end
377
-
378
- opts.on("--input VALUE", "/path/to/file (STDIN will be used per default)") do |value|
379
- options[:input] = File.new(value)
380
- end
381
-
382
- opts.on("-h", "--help", "Display this help message") do
383
- puts opts
384
- exit
385
- end
386
- end,
387
- "task-success" => OptionParser.new do |opts|
388
- opts.banner = "Usage: #{$0} task-success [options]"
389
-
390
- opts.on("--input VALUE", "/path/to/file (STDIN will be used per default)") do |value|
391
- options[:input] = File.new(value)
392
- end
393
-
394
- opts.on("--token VALUE", "The task token") do |value|
395
- options[:token] = value
396
- end
397
-
398
- opts.on("-h", "--help", "Display this help message") do
399
- puts opts
400
- exit
401
- end
402
- end,
403
- }
404
-
405
- global = OptionParser.new do |opts|
406
- opts.banner = "Usage: #{$0} [command] [options]"
407
- opts.separator ""
408
- opts.separator "Commands:"
409
- opts.separator " deploy Create Step Functions State Machine"
410
- opts.separator " destroy Delete Step Functions State Machine"
411
- opts.separator " log Continuously prints Lambda function log output"
412
- opts.separator " start Start State Machine execution"
413
- opts.separator " task-success Continue Start State Machine execution"
414
- opts.separator ""
415
-
416
- opts.on_tail("-h", "--help", "Display this help message") do
417
- puts opts
418
- exit
419
- end
420
- end
421
-
422
- begin
423
- global.order!(ARGV)
424
- command = ARGV.shift
425
- options[:command] = command
426
- subcommands.fetch(command).parse!(ARGV)
427
- rescue KeyError
428
- puts "Unknown command: '#{command}'"
429
- puts
430
- puts global
431
- exit 1
432
- rescue OptionParser::ParseError => error
433
- puts error.message
434
- puts subcommands.fetch(command)
435
- exit 1
436
- end
437
-
438
- if options[:command] == "deploy"
439
- deploy options[:workflow_type]
440
- elsif options[:command] == "start"
441
- start options[:workflow_type], options[:wait], options[:input]
442
- elsif options[:command] == "log"
443
- log options[:extract_pattern]
444
- elsif options[:command] == "task-success"
445
- send_task_success options[:token], options[:input]
446
- elsif options[:command] == "destroy"
447
- destroy
448
- end
@@ -1,5 +0,0 @@
1
- #!/bin/bash
2
-
3
- TOKEN=$(simplerubysteps log --extract_pattern 'callback_token=(.+)')
4
-
5
- echo '{"willo":"billo"}'|simplerubysteps task-success --token $TOKEN
@@ -1,3 +0,0 @@
1
- #!/bin/bash
2
-
3
- echo '{"foo": "John Wick"}'|simplerubysteps start --wait true|jq
@@ -1,3 +0,0 @@
1
- #!/bin/bash
2
-
3
- echo '{"foo": "James Bond"}'|simplerubysteps start --wait true|jq -r ".output"|jq
@@ -1,39 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- require "simplerubysteps"
4
- include Simplerubysteps
5
-
6
- task :t1 do
7
- action do |input|
8
- puts "Task t1: #{input}"
9
- input.merge({ "Foo1" => (input["foo"] == "John Wick" ? "ja" : "nein") })
10
- end
11
-
12
- choice :t2 do
13
- string_matches "$.Foo1", "ja" do
14
- callback :t3 do
15
- action do |input, token|
16
- puts "Callback t3: #{input}, callback_token=#{token}" # The logged token is picked up by continue-callbackbranch.sh
17
- end
18
-
19
- transition :t5
20
- end
21
- end
22
-
23
- default do
24
- task :t4 do
25
- action do |input|
26
- puts "Task t4: #{input}"
27
- input.merge({ "Foo4": "Bar4xy" })
28
- end
29
- end
30
- end
31
- end
32
- end
33
-
34
- task :t5 do
35
- action do |input|
36
- puts "Task t5: #{input}"
37
- input.merge({ "Foo5" => "Bar5" })
38
- end
39
- end
@@ -1,42 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- require "simplerubysteps"
4
- include Simplerubysteps
5
-
6
- kind "EXPRESS"
7
-
8
- task :t1 do
9
- action do |input|
10
- puts "Task t1: #{input}"
11
- input.merge({ "t1" => (input["bar"] ? input["bar"] : "") + "t1" })
12
- end
13
-
14
- transition_to :t2 do |output|
15
- output["t1"] == "foot1"
16
- end
17
-
18
- default_transition_to :t3
19
- end
20
-
21
- task :t2 do
22
- action do |input|
23
- puts "Task t2: #{input}"
24
- input.merge({ "t2" => "yes" })
25
- end
26
-
27
- transition :t4
28
- end
29
-
30
- task :t3 do
31
- action do |input|
32
- puts "Task t3: #{input}"
33
- input.merge({ "t3" => "yes" })
34
- end
35
-
36
- task :t4 do
37
- action do |input|
38
- puts "Task t4: #{input}"
39
- input.merge({ "t4" => "yes" })
40
- end
41
- end
42
- end