floe 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +12 -1
- data/Gemfile +3 -0
- data/README.md +57 -3
- data/exe/floe +3 -3
- data/floe.gemspec +0 -4
- data/lib/floe/null_logger.rb +1 -1
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/choice_rule/and.rb +13 -0
- data/lib/floe/workflow/choice_rule/data.rb +6 -6
- data/lib/floe/workflow/choice_rule/not.rb +14 -0
- data/lib/floe/workflow/choice_rule/or.rb +13 -0
- data/lib/floe/workflow/choice_rule.rb +16 -12
- data/lib/floe/workflow/context.rb +51 -3
- data/lib/floe/workflow/payload_template.rb +56 -15
- data/lib/floe/workflow/runner/docker.rb +84 -15
- data/lib/floe/workflow/runner/kubernetes.rb +47 -15
- data/lib/floe/workflow/runner/podman.rb +119 -14
- data/lib/floe/workflow/runner.rb +21 -1
- data/lib/floe/workflow/state.rb +60 -0
- data/lib/floe/workflow/states/choice.rb +6 -4
- data/lib/floe/workflow/states/fail.rb +8 -4
- data/lib/floe/workflow/states/map.rb +1 -0
- data/lib/floe/workflow/states/parallel.rb +1 -0
- data/lib/floe/workflow/states/pass.rb +6 -4
- data/lib/floe/workflow/states/succeed.rb +6 -4
- data/lib/floe/workflow/states/task.rb +61 -16
- data/lib/floe/workflow/states/wait.rb +13 -6
- data/lib/floe/workflow.rb +60 -36
- data/lib/floe.rb +3 -1
- metadata +5 -45
- data/lib/floe/workflow/choice_rule/boolean.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7a1c4e51ef3ec3052e8c3c150070464ed1a8b463a3b1237774b8f1b2ff416dc
|
4
|
+
data.tar.gz: 8cd6fd6e8c7bed14635d906699f13dde1f5e666018f0c62709972dbddc1999cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73bf61a41d240922786746b0ace87c616311c4bd3f63ac59883096e53b0fce766ef791db3270a29cf257cd5315f5764d69c91bcfd4817efcadfaae72be4c226b
|
7
|
+
data.tar.gz: 3cb8f1ad58c7b1b7895363f46871b38b300a66eb40ebb69d2fc8ffd1b5ec303c982c0f49f89f9575b209b5e0671c2570e54670078da4e4916a3718050c9e0b9d
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
4
|
|
5
5
|
## [Unreleased]
|
6
6
|
|
7
|
+
## [0.4.0] - 2023-09-26
|
8
|
+
### Added
|
9
|
+
- Add ability to run workflows asynchronously ([#52](https://github.com/ManageIQ/floe/pull/92))
|
10
|
+
- Add Workflow.wait, Workflow#step_nonblock, Workflow#step_nonblock_wait ([#92](https://github.com/ManageIQ/floe/pull/92))
|
11
|
+
|
12
|
+
## [0.3.1] - 2023-08-29
|
13
|
+
### Added
|
14
|
+
- Add more global podman runner options ([#90])(https://github.com/ManageIQ/floe/pull/90)
|
15
|
+
|
7
16
|
## [0.3.0] - 2023-08-07
|
8
17
|
### Added
|
9
18
|
- Add --network=host option to Docker/Podman runners ([#81])(https://github.com/ManageIQ/floe/pull/81)
|
@@ -54,7 +63,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
54
63
|
### Added
|
55
64
|
- Initial release
|
56
65
|
|
57
|
-
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.
|
66
|
+
[Unreleased]: https://github.com/ManageIQ/floe/compare/v0.4.0...HEAD
|
67
|
+
[0.4.0]: https://github.com/ManageIQ/floe/compare/v0.3.1...v0.4.0
|
68
|
+
[0.3.1]: https://github.com/ManageIQ/floe/compare/v0.3.0...v0.3.1
|
58
69
|
[0.3.0]: https://github.com/ManageIQ/floe/compare/v0.2.3...v0.3.0
|
59
70
|
[0.2.3]: https://github.com/ManageIQ/floe/compare/v0.2.2...v0.2.3
|
60
71
|
[0.2.2]: https://github.com/ManageIQ/floe/compare/v0.2.1...v0.2.2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -56,7 +56,7 @@ bundle exec ruby exe/floe --workflow my-workflow.asl --credentials='{"roleArn":
|
|
56
56
|
```ruby
|
57
57
|
require 'floe'
|
58
58
|
|
59
|
-
workflow = Floe::Workflow.load(
|
59
|
+
workflow = Floe::Workflow.load("workflow.asl")
|
60
60
|
workflow.run!
|
61
61
|
```
|
62
62
|
|
@@ -68,10 +68,51 @@ Floe::Workflow::Runner.docker_runner = Floe::Workflow::Runner::Podman.new
|
|
68
68
|
# Or
|
69
69
|
Floe::Workflow::Runner.docker_runner = Floe::Workflow::Runner::Kubernetes.new("namespace" => "default", "server" => "https://k8s.example.com:6443", "token" => "my-token")
|
70
70
|
|
71
|
-
workflow = Floe::Workflow.load(
|
71
|
+
workflow = Floe::Workflow.load("workflow.asl")
|
72
72
|
workflow.run!
|
73
73
|
```
|
74
74
|
|
75
|
+
### Non-Blocking Workflow Execution
|
76
|
+
|
77
|
+
It is also possible to step through a workflow without blocking, and any state which
|
78
|
+
would block will return `Errno::EAGAIN`.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require 'floe'
|
82
|
+
|
83
|
+
workflow = Floe::Workflow.load("workflow.asl")
|
84
|
+
|
85
|
+
# Step through the workflow while it would not block
|
86
|
+
workflow.run_nonblock
|
87
|
+
|
88
|
+
# Go off and do some other task
|
89
|
+
|
90
|
+
# Continue stepping until the workflow is finished
|
91
|
+
workflow.run_nonblock
|
92
|
+
```
|
93
|
+
|
94
|
+
You can also use the `Floe::Workflow.wait` class method to wait on multiple workflows
|
95
|
+
and return all that are ready to be stepped through.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'floe'
|
99
|
+
|
100
|
+
workflow1 = Floe::Workflow.load("workflow1.asl")
|
101
|
+
workflow2 = Floe::Workflow.load("workflow2.asl")
|
102
|
+
|
103
|
+
running_workflows = [workflow1, workflow2]
|
104
|
+
until running_workflows.empty?
|
105
|
+
# Wait for any of the running workflows to be ready (up to the timeout)
|
106
|
+
ready_workflows = Floe::Workflow.wait(running_workflows)
|
107
|
+
# Step through the ready workflows until they would block
|
108
|
+
ready_workflows.each do |workflow|
|
109
|
+
loop while workflow.step_nonblock == 0
|
110
|
+
end
|
111
|
+
# Remove any finished workflows from the list of running_workflows
|
112
|
+
running_workflows.reject!(&:end?)
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
75
116
|
### Docker Runner Options
|
76
117
|
|
77
118
|
#### Docker
|
@@ -84,7 +125,20 @@ Options supported by the Docker docker runner are:
|
|
84
125
|
|
85
126
|
Options supported by the podman docker runner are:
|
86
127
|
|
87
|
-
* `
|
128
|
+
* `identity=string` - path to SSH identity file, (CONTAINER_SSHKEY)
|
129
|
+
* `log-level=string` - Log messages above specified level (trace, debug, info, warn, warning, error, fatal, panic)
|
130
|
+
* `network=string` - What docker to connect the container to, defaults to `"bridge"`. If you need access to host resources for development you can pass `network=host`.
|
131
|
+
* `noout=boolean` - do not output to stdout
|
132
|
+
* `root=string` - Path to the root directory in which data, including images, is stored
|
133
|
+
* `runroot=string` - Path to the 'run directory' where all state information is stored
|
134
|
+
* `runtime=string` - Path to the OCI-compatible binary used to run containers
|
135
|
+
* `runtime-flag=stringArray` - add global flags for the container runtime
|
136
|
+
* `storage-driver=string` - Select which storage driver is used to manage storage of images and containers
|
137
|
+
* `storage-opt=stringArray` - Used to pass an option to the storage driver
|
138
|
+
* `syslog=boolean` - Output logging information to syslog as well as the console
|
139
|
+
* `tmpdir=string` - Path to the tmp directory for libpod state content
|
140
|
+
* `transient-store=boolean` - Enable transient container storage
|
141
|
+
* `volumepath=string` - Path to the volume directory in which volume data is stored
|
88
142
|
|
89
143
|
#### Kubernetes
|
90
144
|
|
data/exe/floe
CHANGED
@@ -18,9 +18,6 @@ Optimist.die(:docker_runner, "must be one of #{Floe::Workflow::Runner::TYPES.joi
|
|
18
18
|
require "logger"
|
19
19
|
Floe.logger = Logger.new($stdout)
|
20
20
|
|
21
|
-
context = Floe::Workflow::Context.new(input: opts[:input])
|
22
|
-
workflow = Floe::Workflow.load(opts[:workflow], context, opts[:credentials])
|
23
|
-
|
24
21
|
runner_klass = case opts[:docker_runner]
|
25
22
|
when "docker"
|
26
23
|
Floe::Workflow::Runner::Docker
|
@@ -34,6 +31,9 @@ runner_options = opts[:docker_runner_options].to_h { |opt| opt.split("=", 2) }
|
|
34
31
|
|
35
32
|
Floe::Workflow::Runner.docker_runner = runner_klass.new(runner_options)
|
36
33
|
|
34
|
+
context = Floe::Workflow::Context.new(:input => opts[:input])
|
35
|
+
workflow = Floe::Workflow.load(opts[:workflow], context, opts[:credentials])
|
36
|
+
|
37
37
|
workflow.run!
|
38
38
|
|
39
39
|
puts workflow.output.inspect
|
data/floe.gemspec
CHANGED
@@ -34,8 +34,4 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency "kubeclient", "~>4.7"
|
35
35
|
spec.add_dependency "more_core_extensions"
|
36
36
|
spec.add_dependency "optimist", "~>3.0"
|
37
|
-
|
38
|
-
spec.add_development_dependency "manageiq-style"
|
39
|
-
spec.add_development_dependency "rspec"
|
40
|
-
spec.add_development_dependency "rubocop"
|
41
37
|
end
|
data/lib/floe/null_logger.rb
CHANGED
data/lib/floe/version.rb
CHANGED
@@ -51,27 +51,27 @@ module Floe
|
|
51
51
|
raise "No such variable [#{variable}]" if value.nil? && !%w[IsNull IsPresent].include?(compare_key)
|
52
52
|
end
|
53
53
|
|
54
|
-
def is_null?(value)
|
54
|
+
def is_null?(value) # rubocop:disable Naming/PredicateName
|
55
55
|
value.nil?
|
56
56
|
end
|
57
57
|
|
58
|
-
def is_present?(value)
|
58
|
+
def is_present?(value) # rubocop:disable Naming/PredicateName
|
59
59
|
!value.nil?
|
60
60
|
end
|
61
61
|
|
62
|
-
def is_numeric?(value)
|
62
|
+
def is_numeric?(value) # rubocop:disable Naming/PredicateName
|
63
63
|
value.kind_of?(Integer) || value.kind_of?(Float)
|
64
64
|
end
|
65
65
|
|
66
|
-
def is_string?(value)
|
66
|
+
def is_string?(value) # rubocop:disable Naming/PredicateName
|
67
67
|
value.kind_of?(String)
|
68
68
|
end
|
69
69
|
|
70
|
-
def is_boolean?(value)
|
70
|
+
def is_boolean?(value) # rubocop:disable Naming/PredicateName
|
71
71
|
[true, false].include?(value)
|
72
72
|
end
|
73
73
|
|
74
|
-
def is_timestamp?(value)
|
74
|
+
def is_timestamp?(value) # rubocop:disable Naming/PredicateName
|
75
75
|
require "date"
|
76
76
|
|
77
77
|
DateTime.rfc3339(value)
|
@@ -4,24 +4,28 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
class ChoiceRule
|
6
6
|
class << self
|
7
|
-
def true?(payload, context, input)
|
8
|
-
build(payload).true?(context, input)
|
9
|
-
end
|
10
|
-
|
11
7
|
def build(payload)
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
if (sub_payloads = payload["Not"])
|
9
|
+
Floe::Workflow::ChoiceRule::Not.new(payload, build_children([sub_payloads]))
|
10
|
+
elsif (sub_payloads = payload["And"])
|
11
|
+
Floe::Workflow::ChoiceRule::And.new(payload, build_children(sub_payloads))
|
12
|
+
elsif (sub_payloads = payload["Or"])
|
13
|
+
Floe::Workflow::ChoiceRule::Or.new(payload, build_children(sub_payloads))
|
15
14
|
else
|
16
|
-
Floe::Workflow::ChoiceRule::
|
15
|
+
Floe::Workflow::ChoiceRule::Data.new(payload)
|
17
16
|
end
|
18
17
|
end
|
18
|
+
|
19
|
+
def build_children(sub_payloads)
|
20
|
+
sub_payloads.map { |payload| build(payload) }
|
21
|
+
end
|
19
22
|
end
|
20
23
|
|
21
|
-
attr_reader :next, :payload, :variable
|
24
|
+
attr_reader :next, :payload, :variable, :children
|
22
25
|
|
23
|
-
def initialize(payload)
|
24
|
-
@payload
|
26
|
+
def initialize(payload, children = nil)
|
27
|
+
@payload = payload
|
28
|
+
@children = children
|
25
29
|
|
26
30
|
@next = payload["Next"]
|
27
31
|
@variable = payload["Variable"]
|
@@ -34,7 +38,7 @@ module Floe
|
|
34
38
|
private
|
35
39
|
|
36
40
|
def variable_value(context, input)
|
37
|
-
|
41
|
+
Path.value(variable, context, input)
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|
@@ -11,7 +11,7 @@ module Floe
|
|
11
11
|
"Input" => input
|
12
12
|
},
|
13
13
|
"State" => {},
|
14
|
-
"
|
14
|
+
"StateHistory" => [],
|
15
15
|
"StateMachine" => {},
|
16
16
|
"Task" => {}
|
17
17
|
}
|
@@ -21,16 +21,64 @@ module Floe
|
|
21
21
|
@context["Execution"]
|
22
22
|
end
|
23
23
|
|
24
|
+
def started?
|
25
|
+
execution.key?("StartTime")
|
26
|
+
end
|
27
|
+
|
28
|
+
def running?
|
29
|
+
started? && !ended?
|
30
|
+
end
|
31
|
+
|
32
|
+
def ended?
|
33
|
+
execution.key?("EndTime")
|
34
|
+
end
|
35
|
+
|
24
36
|
def state
|
25
37
|
@context["State"]
|
26
38
|
end
|
27
39
|
|
40
|
+
def input
|
41
|
+
state["Input"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def output
|
45
|
+
state["Output"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def output=(val)
|
49
|
+
state["Output"] = val
|
50
|
+
end
|
51
|
+
|
52
|
+
def state_name
|
53
|
+
state["Name"]
|
54
|
+
end
|
55
|
+
|
56
|
+
def next_state
|
57
|
+
state["NextState"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def next_state=(val)
|
61
|
+
state["NextState"] = val
|
62
|
+
end
|
63
|
+
|
64
|
+
def status
|
65
|
+
if !started?
|
66
|
+
"pending"
|
67
|
+
elsif running?
|
68
|
+
"running"
|
69
|
+
elsif state["Error"]
|
70
|
+
"failure"
|
71
|
+
else
|
72
|
+
"success"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
28
76
|
def state=(val)
|
29
77
|
@context["State"] = val
|
30
78
|
end
|
31
79
|
|
32
|
-
def
|
33
|
-
@context["
|
80
|
+
def state_history
|
81
|
+
@context["StateHistory"]
|
34
82
|
end
|
35
83
|
|
36
84
|
def state_machine
|
@@ -4,35 +4,76 @@ module Floe
|
|
4
4
|
class Workflow
|
5
5
|
class PayloadTemplate
|
6
6
|
def initialize(payload)
|
7
|
-
@
|
7
|
+
@payload_template = parse_payload(payload)
|
8
8
|
end
|
9
9
|
|
10
10
|
def value(context, inputs = {})
|
11
|
-
|
11
|
+
interpolate_value(payload_template, context, inputs)
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
attr_reader :
|
16
|
+
attr_reader :payload_template
|
17
17
|
|
18
|
-
def
|
18
|
+
def parse_payload(value)
|
19
19
|
case value
|
20
|
-
when Array
|
21
|
-
|
22
|
-
when
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
when Array then parse_payload_array(value)
|
21
|
+
when Hash then parse_payload_hash(value)
|
22
|
+
when String then parse_payload_string(value)
|
23
|
+
else
|
24
|
+
value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_payload_array(value)
|
29
|
+
value.map { |val| parse_payload(val) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_payload_hash(value)
|
33
|
+
value.to_h do |key, val|
|
34
|
+
if key.end_with?(".$")
|
35
|
+
check_key_conflicts(key, value)
|
36
|
+
|
37
|
+
[key, parse_payload(val)]
|
38
|
+
else
|
39
|
+
[key, val]
|
29
40
|
end
|
30
|
-
|
31
|
-
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_payload_string(value)
|
45
|
+
value.start_with?("$") ? Path.new(value) : value
|
46
|
+
end
|
47
|
+
|
48
|
+
def interpolate_value(value, context, inputs)
|
49
|
+
case value
|
50
|
+
when Array then interpolate_value_array(value, context, inputs)
|
51
|
+
when Hash then interpolate_value_hash(value, context, inputs)
|
52
|
+
when Path then value.value(context, inputs)
|
32
53
|
else
|
33
54
|
value
|
34
55
|
end
|
35
56
|
end
|
57
|
+
|
58
|
+
def interpolate_value_array(value, context, inputs)
|
59
|
+
value.map { |val| interpolate_value(val, context, inputs) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def interpolate_value_hash(value, context, inputs)
|
63
|
+
value.to_h do |key, val|
|
64
|
+
if key.end_with?(".$")
|
65
|
+
[key.chomp(".$"), interpolate_value(val, context, inputs)]
|
66
|
+
else
|
67
|
+
[key, val]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_key_conflicts(key, value)
|
73
|
+
if value.key?(key.chomp(".$"))
|
74
|
+
raise Floe::InvalidWorkflowError, "both #{key} and #{key.chomp(".$")} present"
|
75
|
+
end
|
76
|
+
end
|
36
77
|
end
|
37
78
|
end
|
38
79
|
end
|
@@ -18,34 +18,103 @@ module Floe
|
|
18
18
|
|
19
19
|
image = resource.sub("docker://", "")
|
20
20
|
|
21
|
-
params = ["run", :rm]
|
22
|
-
params += [[:net, "host"]] if network == "host"
|
23
|
-
params += env.map { |k, v| [:e, "#{k}=#{v}"] } if env
|
24
|
-
|
25
21
|
secrets_file = nil
|
22
|
+
if secrets && !secrets.empty?
|
23
|
+
secrets_file = create_secret(secrets)
|
24
|
+
env["_CREDENTIALS"] = "/run/secrets"
|
25
|
+
end
|
26
|
+
|
27
|
+
output = run_container(image, env, secrets_file)
|
28
|
+
|
29
|
+
{"exit_code" => 0, "output" => output}
|
30
|
+
ensure
|
31
|
+
cleanup({"secrets_ref" => secrets_file})
|
32
|
+
end
|
33
|
+
|
34
|
+
def run_async!(resource, env = {}, secrets = {})
|
35
|
+
raise ArgumentError, "Invalid resource" unless resource&.start_with?("docker://")
|
36
|
+
|
37
|
+
image = resource.sub("docker://", "")
|
38
|
+
|
39
|
+
runner_context = {}
|
26
40
|
|
27
41
|
if secrets && !secrets.empty?
|
28
|
-
|
29
|
-
|
30
|
-
|
42
|
+
runner_context["secrets_ref"] = create_secret(secrets)
|
43
|
+
env["_CREDENTIALS"] = "/run/secrets"
|
44
|
+
end
|
31
45
|
|
32
|
-
|
33
|
-
|
46
|
+
begin
|
47
|
+
runner_context["container_ref"] = run_container(image, env, runner_context["secrets_ref"], :detached => true)
|
48
|
+
rescue
|
49
|
+
cleanup(runner_context)
|
50
|
+
raise
|
34
51
|
end
|
35
52
|
|
36
|
-
|
53
|
+
runner_context
|
54
|
+
end
|
37
55
|
|
38
|
-
|
39
|
-
|
56
|
+
def cleanup(runner_context)
|
57
|
+
container_id, secrets_file = runner_context.values_at("container_ref", "secrets_ref")
|
40
58
|
|
41
|
-
|
42
|
-
|
43
|
-
|
59
|
+
delete_container(container_id) if container_id
|
60
|
+
File.unlink(secrets_file) if secrets_file && File.exist?(secrets_file)
|
61
|
+
end
|
62
|
+
|
63
|
+
def status!(runner_context)
|
64
|
+
runner_context["container_state"] = inspect_container(runner_context["container_ref"]).first&.dig("State")
|
65
|
+
end
|
66
|
+
|
67
|
+
def running?(runner_context)
|
68
|
+
runner_context.dig("container_state", "Running")
|
69
|
+
end
|
70
|
+
|
71
|
+
def success?(runner_context)
|
72
|
+
runner_context.dig("container_state", "ExitCode") == 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def output(runner_context)
|
76
|
+
output = docker!("logs", runner_context["container_ref"]).output
|
77
|
+
runner_context["output"] = output
|
44
78
|
end
|
45
79
|
|
46
80
|
private
|
47
81
|
|
48
82
|
attr_reader :network
|
83
|
+
|
84
|
+
def run_container(image, env, secrets_file, detached: false)
|
85
|
+
params = ["run"]
|
86
|
+
params << (detached ? :detach : :rm)
|
87
|
+
params += env.map { |k, v| [:e, "#{k}=#{v}"] }
|
88
|
+
params << [:net, "host"] if @network == "host"
|
89
|
+
params << [:v, "#{secrets_file}:/run/secrets:z"] if secrets_file
|
90
|
+
params << image
|
91
|
+
|
92
|
+
logger.debug("Running docker: #{AwesomeSpawn.build_command_line("docker", params)}")
|
93
|
+
|
94
|
+
result = docker!(*params)
|
95
|
+
result.output
|
96
|
+
end
|
97
|
+
|
98
|
+
def inspect_container(container_id)
|
99
|
+
JSON.parse(docker!("inspect", container_id).output)
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete_container(container_id)
|
103
|
+
docker!("rm", container_id)
|
104
|
+
rescue
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_secret(secrets)
|
109
|
+
secrets_file = Tempfile.new
|
110
|
+
secrets_file.write(secrets.to_json)
|
111
|
+
secrets_file.close
|
112
|
+
secrets_file.path
|
113
|
+
end
|
114
|
+
|
115
|
+
def docker!(*params, **kwargs)
|
116
|
+
AwesomeSpawn.run!("docker", :params => params, **kwargs)
|
117
|
+
end
|
49
118
|
end
|
50
119
|
end
|
51
120
|
end
|