manageiq-floe 0.1.0
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 +7 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +11 -0
- data/README.md +46 -0
- data/Rakefile +4 -0
- data/examples/workflow.json +89 -0
- data/exe/manageiq-floe +21 -0
- data/lib/manageiq/floe/logging.rb +15 -0
- data/lib/manageiq/floe/null_logger.rb +15 -0
- data/lib/manageiq/floe/version.rb +7 -0
- data/lib/manageiq/floe/workflow/catcher.rb +19 -0
- data/lib/manageiq/floe/workflow/choice_rule/boolean.rb +21 -0
- data/lib/manageiq/floe/workflow/choice_rule/data.rb +96 -0
- data/lib/manageiq/floe/workflow/choice_rule.rb +43 -0
- data/lib/manageiq/floe/workflow/path.rb +36 -0
- data/lib/manageiq/floe/workflow/payload_template.rb +39 -0
- data/lib/manageiq/floe/workflow/reference_path.rb +46 -0
- data/lib/manageiq/floe/workflow/retrier.rb +24 -0
- data/lib/manageiq/floe/workflow/runner/docker.rb +45 -0
- data/lib/manageiq/floe/workflow/runner/kubernetes.rb +118 -0
- data/lib/manageiq/floe/workflow/runner/podman.rb +42 -0
- data/lib/manageiq/floe/workflow/runner.rb +33 -0
- data/lib/manageiq/floe/workflow/state.rb +78 -0
- data/lib/manageiq/floe/workflow/states/choice.rb +55 -0
- data/lib/manageiq/floe/workflow/states/fail.rb +39 -0
- data/lib/manageiq/floe/workflow/states/map.rb +15 -0
- data/lib/manageiq/floe/workflow/states/parallel.rb +15 -0
- data/lib/manageiq/floe/workflow/states/pass.rb +33 -0
- data/lib/manageiq/floe/workflow/states/succeed.rb +28 -0
- data/lib/manageiq/floe/workflow/states/task.rb +84 -0
- data/lib/manageiq/floe/workflow/states/wait.rb +27 -0
- data/lib/manageiq/floe/workflow.rb +84 -0
- data/lib/manageiq/floe.rb +46 -0
- data/lib/manageiq-floe.rb +3 -0
- data/manageiq-floe.gemspec +39 -0
- data/sig/manageiq/floe.rbs +6 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a6a8fe345f9e54f72394a96fab04b30c611df88d75c37312c0c1ca4fabb19b94
|
4
|
+
data.tar.gz: e912605c61ee9cfb3e299d730aa2612ca152013095635d6e43bb7ba70f95d2e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 85afb6b3c99ec3c21500a51c04eacd4023231bc89fafb81ad9648077f84dcb37d65fc0545affe66db93209ee935fed81060b51aa34a92171d68ef215bda94a7e
|
7
|
+
data.tar.gz: a31a038245135c3c2e16647ab26209e3c16a40ca91c3d3491e6c6e167a4824acd3e1e16471779cc82009fd2eaec2a2466cb048171583360318e88e6e5bdf93c0
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
This project adheres to [Semantic Versioning](http://semver.org/).
|
4
|
+
|
5
|
+
## [Unreleased]
|
6
|
+
|
7
|
+
## [0.1.0] - 2023-03-13
|
8
|
+
### Added
|
9
|
+
- Initial release
|
10
|
+
|
11
|
+
[Unreleased]: https://github.com/ManageIQ/manageiq-floe/compare/v0.1.0...HEAD
|
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
plugin "bundler-inject", "~> 2.0"
|
6
|
+
require File.join(Bundler::Plugin.index.load_paths("bundler-inject")[0], "bundler-inject") rescue nil
|
7
|
+
|
8
|
+
# Specify your gem's dependencies in manageiq-floe.gemspec
|
9
|
+
gemspec
|
10
|
+
|
11
|
+
gem "rake", "~> 13.0"
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# ManageIQ::Floe
|
2
|
+
|
3
|
+
[](https://github.com/ManageIQ/manageiq-floe/actions/workflows/ci.yaml)
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Floe is a runner for [Amazon States Language](https://states-language.net/) workflows with support for Docker resources and running on Docker, Podman, or Kubernetes.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
12
|
+
|
13
|
+
$ bundle add manageiq-floe
|
14
|
+
|
15
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
|
+
|
17
|
+
$ gem install manageiq-floe
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Floe can be run as a command-line utility or as a ruby class.
|
22
|
+
|
23
|
+
### Command Line
|
24
|
+
|
25
|
+
```
|
26
|
+
bundle exec ruby exe/manageiq-floe --workflow examples/workflow.json --inputs='{"foo": 1}'
|
27
|
+
```
|
28
|
+
|
29
|
+
### Ruby Library
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'manageiq-floe'
|
33
|
+
|
34
|
+
workflow = ManageIQ::Floe::Workflow.load(File.read("workflow.json"))
|
35
|
+
workflow.run!
|
36
|
+
```
|
37
|
+
|
38
|
+
## Development
|
39
|
+
|
40
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
41
|
+
|
42
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ManageIQ/manageiq-floe.
|
data/Rakefile
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
{
|
2
|
+
"Comment": "An example of the Amazon States Language using a choice state.",
|
3
|
+
"StartAt": "FirstState",
|
4
|
+
"States": {
|
5
|
+
"FirstState": {
|
6
|
+
"Type": "Task",
|
7
|
+
"Resource": "docker://agrare/hello-world:latest",
|
8
|
+
"Credentials": {
|
9
|
+
"mysecret": "dont tell anyone"
|
10
|
+
},
|
11
|
+
"Retry": [
|
12
|
+
{
|
13
|
+
"ErrorEquals": [ "States.Timeout" ],
|
14
|
+
"IntervalSeconds": 3,
|
15
|
+
"MaxAttempts": 2,
|
16
|
+
"BackoffRate": 1.5
|
17
|
+
}
|
18
|
+
],
|
19
|
+
"Catch": [
|
20
|
+
{
|
21
|
+
"ErrorEquals": [ "States.ALL" ],
|
22
|
+
"Next": "FailState"
|
23
|
+
}
|
24
|
+
],
|
25
|
+
"Next": "ChoiceState"
|
26
|
+
},
|
27
|
+
|
28
|
+
"ChoiceState": {
|
29
|
+
"Type" : "Choice",
|
30
|
+
"Choices": [
|
31
|
+
{
|
32
|
+
"Variable": "$.foo",
|
33
|
+
"NumericEquals": 1,
|
34
|
+
"Next": "FirstMatchState"
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"Variable": "$.foo",
|
38
|
+
"NumericEquals": 2,
|
39
|
+
"Next": "SecondMatchState"
|
40
|
+
},
|
41
|
+
{
|
42
|
+
"Variable": "$.foo",
|
43
|
+
"NumericEquals": 3,
|
44
|
+
"Next": "SuccessState"
|
45
|
+
}
|
46
|
+
],
|
47
|
+
"Default": "FailState"
|
48
|
+
},
|
49
|
+
|
50
|
+
"FirstMatchState": {
|
51
|
+
"Type" : "Task",
|
52
|
+
"Resource": "docker://agrare/hello-world:latest",
|
53
|
+
"Next": "PassState"
|
54
|
+
},
|
55
|
+
|
56
|
+
"SecondMatchState": {
|
57
|
+
"Type" : "Task",
|
58
|
+
"Resource": "docker://agrare/hello-world:latest",
|
59
|
+
"Next": "NextState"
|
60
|
+
},
|
61
|
+
|
62
|
+
"PassState": {
|
63
|
+
"Type": "Pass",
|
64
|
+
"Result": {
|
65
|
+
"foo": "bar",
|
66
|
+
"bar": "baz"
|
67
|
+
},
|
68
|
+
"ResultPath": "$.result",
|
69
|
+
"Next": "NextState"
|
70
|
+
},
|
71
|
+
|
72
|
+
"FailState": {
|
73
|
+
"Type": "Fail",
|
74
|
+
"Error": "FailStateError",
|
75
|
+
"Cause": "No Matches!"
|
76
|
+
},
|
77
|
+
|
78
|
+
"SuccessState": {
|
79
|
+
"Type": "Succeed"
|
80
|
+
},
|
81
|
+
|
82
|
+
"NextState": {
|
83
|
+
"Type": "Task",
|
84
|
+
"Resource": "docker://agrare/hello-world:latest",
|
85
|
+
"Secrets": ["vmdb:aaa-bbb-ccc"],
|
86
|
+
"End": true
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
data/exe/manageiq-floe
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "manageiq-floe"
|
5
|
+
require "optimist"
|
6
|
+
|
7
|
+
opts = Optimist.options do
|
8
|
+
version "v#{ManageIQ::Floe::VERSION}\n"
|
9
|
+
opt :workflow, "Path to your workflow json", :type => :string, :required => true
|
10
|
+
opt :inputs, "JSON payload to input to the workflow", :type => :string, :default => '{}'
|
11
|
+
opt :credentials, "JSON payload with credentials", :type => :string, :default => '{}'
|
12
|
+
end
|
13
|
+
|
14
|
+
require "logger"
|
15
|
+
ManageIQ::Floe.logger = Logger.new(STDOUT)
|
16
|
+
|
17
|
+
workflow = ManageIQ::Floe::Workflow.load(opts[:workflow], opts[:inputs], opts[:credentials])
|
18
|
+
|
19
|
+
output = workflow.run!
|
20
|
+
|
21
|
+
puts output.inspect
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class Catcher
|
7
|
+
attr_reader :error_equals, :next, :result_path
|
8
|
+
|
9
|
+
def initialize(payload)
|
10
|
+
@payload = payload
|
11
|
+
|
12
|
+
@error_equals = payload["ErrorEquals"]
|
13
|
+
@next = payload["Next"]
|
14
|
+
@result_path = payload.fetch("ResultPath", "$")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class ChoiceRule
|
7
|
+
class Boolean < ManageIQ::Floe::Workflow::ChoiceRule
|
8
|
+
def true?(context, input)
|
9
|
+
if payload.key?("Not")
|
10
|
+
!ChoiceRule.true?(payload["Not"], context, input)
|
11
|
+
elsif payload.key?("And")
|
12
|
+
payload["And"].all? { |choice| ChoiceRule.true?(choice, context, input) }
|
13
|
+
else
|
14
|
+
payload["Or"].any? { |choice| ChoiceRule.true?(choice, context, input) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class ChoiceRule
|
7
|
+
class Data < ManageIQ::Floe::Workflow::ChoiceRule
|
8
|
+
def true?(context, input)
|
9
|
+
|
10
|
+
lhs = variable_value(context, input)
|
11
|
+
rhs = compare_value(context, input)
|
12
|
+
|
13
|
+
validate!(lhs)
|
14
|
+
|
15
|
+
case compare_key
|
16
|
+
when "IsNull"; is_null?(lhs)
|
17
|
+
when "IsPresent"; is_present?(lhs)
|
18
|
+
when "IsNumeric"; is_numeric?(lhs)
|
19
|
+
when "IsString"; is_string?(lhs)
|
20
|
+
when "IsBoolean"; is_boolean?(lhs)
|
21
|
+
when "IsTimestamp"; is_timestamp?(lhs)
|
22
|
+
when "StringEquals", "StringEqualsPath",
|
23
|
+
"NumericEquals", "NumericEqualsPath",
|
24
|
+
"BooleanEquals", "BooleanEqualsPath",
|
25
|
+
"TimestampEquals", "TimestampEqualsPath"
|
26
|
+
lhs == rhs
|
27
|
+
when "StringLessThan", "StringLessThanPath",
|
28
|
+
"NumericLessThan", "NumericLessThanPath",
|
29
|
+
"TimestampLessThan", "TimestampLessThanPath"
|
30
|
+
lhs < rhs
|
31
|
+
when "StringGreaterThan", "StringGreaterThanPath",
|
32
|
+
"NumericGreaterThan", "NumericGreaterThanPath",
|
33
|
+
"TimestampGreaterThan", "TimestampGreaterThanPath"
|
34
|
+
lhs > rhs
|
35
|
+
when "StringLessThanEquals", "StringLessThanEqualsPath",
|
36
|
+
"NumericLessThanEquals", "NumericLessThanEqualsPath",
|
37
|
+
"TimestampLessThanEquals", "TimestampLessThanEqualsPath"
|
38
|
+
lhs <= rhs
|
39
|
+
when "StringGreaterThanEquals", "StringGreaterThanEqualsPath",
|
40
|
+
"NumericGreaterThanEquals", "NumericGreaterThanEqualsPath",
|
41
|
+
"TimestampGreaterThanEquals", "TimestampGreaterThanEqualsPath"
|
42
|
+
lhs >= rhs
|
43
|
+
when "StringMatches"
|
44
|
+
lhs.match?(Regexp.escape(rhs).gsub('\*','.*?'))
|
45
|
+
else
|
46
|
+
raise ManageIQ::Floe::InvalidWorkflowError, "Invalid choice [#{compare_key}]"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def validate!(value)
|
53
|
+
raise RuntimeError, "No such variable [#{variable}]" if value.nil? && !%w[IsNull IsPresent].include?(compare_key)
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_null?(value)
|
57
|
+
value.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def is_present?(value)
|
61
|
+
!value.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def is_numeric?(value)
|
65
|
+
value.kind_of?(Integer) || value.kind_of?(Float)
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_string?(value)
|
69
|
+
value.kind_of?(String)
|
70
|
+
end
|
71
|
+
|
72
|
+
def is_boolean?(value)
|
73
|
+
[true, false].include?(value)
|
74
|
+
end
|
75
|
+
|
76
|
+
def is_timestamp?(value)
|
77
|
+
require "date"
|
78
|
+
|
79
|
+
DateTime.rfc3339(value)
|
80
|
+
true
|
81
|
+
rescue TypeError, Date::Error
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
def compare_key
|
86
|
+
@compare_key ||= payload.keys.detect { |key| key.match?(/^(IsNull|IsPresent|IsNumeric|IsString|IsBoolean|IsTimestamp|String|Numeric|Boolean|Timestamp)/) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def compare_value(context, input)
|
90
|
+
compare_key.end_with?("Path") ? Path.value(payload[compare_key], context, input) : payload[compare_key]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class ChoiceRule
|
7
|
+
class << self
|
8
|
+
def true?(payload, context, input)
|
9
|
+
build(payload).true?(context, input)
|
10
|
+
end
|
11
|
+
|
12
|
+
def build(payload)
|
13
|
+
data_expression = (payload.keys & %w[And Not Or]).empty?
|
14
|
+
if data_expression
|
15
|
+
ManageIQ::Floe::Workflow::ChoiceRule::Data.new(payload)
|
16
|
+
else
|
17
|
+
ManageIQ::Floe::Workflow::ChoiceRule::Boolean.new(payload)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :next, :payload, :variable
|
23
|
+
|
24
|
+
def initialize(payload)
|
25
|
+
@payload = payload
|
26
|
+
|
27
|
+
@next = payload["Next"]
|
28
|
+
@variable = payload["Variable"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def true?(*)
|
32
|
+
raise NotImplementedError, "Must be implemented in a subclass"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def variable_value(context, input)
|
38
|
+
@variable_value ||= Path.value(variable, context, input)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class Path
|
7
|
+
class << self
|
8
|
+
def value(payload, context, input = {})
|
9
|
+
new(payload).value(context, input)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(payload)
|
14
|
+
@payload = payload
|
15
|
+
end
|
16
|
+
|
17
|
+
def value(context, input = {})
|
18
|
+
obj, path =
|
19
|
+
if payload.start_with?("$$")
|
20
|
+
[context, payload[1..]]
|
21
|
+
else
|
22
|
+
[input, payload]
|
23
|
+
end
|
24
|
+
|
25
|
+
results = JsonPath.on(obj, path)
|
26
|
+
|
27
|
+
results.count < 2 ? results.first : results
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :payload
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class PayloadTemplate
|
7
|
+
def initialize(payload)
|
8
|
+
@payload = payload
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(context, inputs = {})
|
12
|
+
interpolate_value_nested(payload, context, inputs)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :payload
|
18
|
+
|
19
|
+
def interpolate_value_nested(value, context, inputs)
|
20
|
+
case value
|
21
|
+
when Array
|
22
|
+
value.map { |val| interpolate_value_nested(val, context, inputs) }
|
23
|
+
when Hash
|
24
|
+
value.to_h do |key, val|
|
25
|
+
val = interpolate_value_nested(val, context, inputs)
|
26
|
+
key = key.gsub(/\.\$$/, "") if key.end_with?(".$")
|
27
|
+
|
28
|
+
[key, val]
|
29
|
+
end
|
30
|
+
when String
|
31
|
+
value.start_with?("$") ? Path.value(value, context, inputs) : value
|
32
|
+
else
|
33
|
+
value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class ReferencePath < Path
|
7
|
+
class << self
|
8
|
+
def set (payload, context, value)
|
9
|
+
new(payload).set(context, value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
require "more_core_extensions/core_ext/hash/nested"
|
15
|
+
require "more_core_extensions/core_ext/array/nested"
|
16
|
+
|
17
|
+
super
|
18
|
+
|
19
|
+
raise ManageIQ::Floe::InvalidWorkflowError, "Invalid Reference Path" if payload.match?(/@|,|:|\?/)
|
20
|
+
end
|
21
|
+
|
22
|
+
def set(context, value)
|
23
|
+
result = context.dup
|
24
|
+
|
25
|
+
path = JsonPath.new(payload)
|
26
|
+
.path[1..]
|
27
|
+
.map { |v| v.match(/\[(?<name>.+)\]/)["name"] }
|
28
|
+
.map { |v| v[0] == "'" ? v.gsub("'", "") : v.to_i }
|
29
|
+
.compact
|
30
|
+
|
31
|
+
# If the payload is '$' then merge the value into the context
|
32
|
+
# otherwise use store path to set the value to a sub-key
|
33
|
+
#
|
34
|
+
# TODO: how to handle non-hash values, raise error if path=$ and value not a hash?
|
35
|
+
if path.empty?
|
36
|
+
result.merge!(value)
|
37
|
+
else
|
38
|
+
result.store_path(path, value)
|
39
|
+
end
|
40
|
+
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ManageIQ
|
4
|
+
module Floe
|
5
|
+
class Workflow
|
6
|
+
class Retrier
|
7
|
+
attr_reader :error_equals, :interval_seconds, :max_attempts, :backoff_rate
|
8
|
+
|
9
|
+
def initialize(payload)
|
10
|
+
@payload = payload
|
11
|
+
|
12
|
+
@error_equals = payload["ErrorEquals"]
|
13
|
+
@interval_seconds = payload["IntervalSeconds"] || 1.0
|
14
|
+
@max_attempts = payload["MaxAttempts"] || 3
|
15
|
+
@backoff_rate = payload["BackoffRate"] || 2.0
|
16
|
+
end
|
17
|
+
|
18
|
+
def sleep_duration(attempt)
|
19
|
+
interval_seconds * (backoff_rate * attempt)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ManageIQ
|
2
|
+
module Floe
|
3
|
+
class Workflow
|
4
|
+
class Runner
|
5
|
+
class Docker < ManageIQ::Floe::Workflow::Runner
|
6
|
+
def initialize(*)
|
7
|
+
require "awesome_spawn"
|
8
|
+
require "tempfile"
|
9
|
+
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def run!(resource, env = {}, secrets = {})
|
14
|
+
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
15
|
+
|
16
|
+
image = resource.sub("docker://", "")
|
17
|
+
|
18
|
+
params = ["run", :rm, [:net, "host"]]
|
19
|
+
params += env.map { |k, v| [:e, "#{k}=#{v}"] } if env
|
20
|
+
|
21
|
+
secrets_file = nil
|
22
|
+
|
23
|
+
if secrets && !secrets.empty?
|
24
|
+
secrets_file = Tempfile.new
|
25
|
+
secrets_file.write(secrets.to_json)
|
26
|
+
secrets_file.flush
|
27
|
+
|
28
|
+
params << [:e, "SECRETS=/run/secrets"]
|
29
|
+
params << [:v, "#{secrets_file.path}:/run/secrets:z"]
|
30
|
+
end
|
31
|
+
|
32
|
+
params << image
|
33
|
+
|
34
|
+
logger.debug("Running docker: #{AwesomeSpawn.build_command_line("docker", params)}")
|
35
|
+
result = AwesomeSpawn.run!("docker", :params => params)
|
36
|
+
|
37
|
+
[result.exit_status, result.output]
|
38
|
+
ensure
|
39
|
+
secrets_file&.close!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|