floe 0.7.1 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88cfa362fcb949aeaa6e9b0b799bff4bbb0e6b7158c95920545300ba063d6a0d
4
- data.tar.gz: 14df3cddf7a1811e945f533b88fcacd6611566e212cacb88bebf2da0bae9fe67
3
+ metadata.gz: c121968c8c3f9c70edaa20c0ab3e7937206623b7c06c0cf0f300aceddcde7814
4
+ data.tar.gz: 943d9e77a9e8309a771e57fee5d6f70e5026a4d254b0da5072b768a99f427d1a
5
5
  SHA512:
6
- metadata.gz: 90a3cdfdb241242d5b52ad403e7bbbf409476efec6d01d16f8f6f223b6f5fa3e227647da98ab2fb9466993982793be32684521ca5a268e73ab191ff7b6a025fb
7
- data.tar.gz: c1194b741493a3db2f3abdf083ef2858b25af61442584c6a2001d222579c78e5057f0a7efd3c6c904e02d898c296f543c5af1889c2e4bb91c95f9197d327fa35
6
+ metadata.gz: 4fa820c364e8dc3266c2fc1eb29d682e72a9d2ab4d8f154aac3a88ff6d4c35e45e124fe714b80110b52e78a0ab654e25f956bc8a39f45f05f3990c0ab26d01ed
7
+ data.tar.gz: b62ac48c4105497c1d3dd2f2ef9d8aec60c25bcadf71046e8488175705365d0d84facde34c11f0047660b5ab2f4390f13babbe8ba2ab6becf31027f027e0b833
data/CHANGELOG.md CHANGED
@@ -4,8 +4,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- ## [0.7.1] - 2024-01-17
7
+ ## [0.8.0] - 2024-01-17
8
+ ### Added
9
+ - Add CLI shorthand options for docker runner ([#147](https://github.com/ManageIQ/floe/pull/147))
10
+ - Run multiple workflows in exe/floe ([#149](https://github.com/ManageIQ/floe/pull/149))
11
+ - Add secure options for passing credentials via command-line ([#151](https://github.com/ManageIQ/floe/pull/151))
12
+ - Add a Docker Runner pull-policy option ([#155](https://github.com/ManageIQ/floe/pull/155))
13
+
8
14
  ### Fixed
15
+ - Fix podman with empty output ([#150](https://github.com/ManageIQ/floe/pull/150))
16
+ - Fix run_container logger saying docker when using podman ([#154](https://github.com/ManageIQ/floe/pull/154))
9
17
  - Ensure that workflow credentials is not-nil ([#156](https://github.com/ManageIQ/floe/pull/156))
10
18
 
11
19
  ## [0.7.0] - 2023-12-18
@@ -118,8 +126,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
118
126
  ### Added
119
127
  - Initial release
120
128
 
121
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.7.1...HEAD
122
- [0.7.1]: https://github.com/ManageIQ/floe/compare/v0.7.0...v0.7.1
129
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.8.0...HEAD
130
+ [0.8.0]: https://github.com/ManageIQ/floe/compare/v0.7.0...v0.8.0
123
131
  [0.7.0]: https://github.com/ManageIQ/floe/compare/v0.6.1...v0.7.0
124
132
  [0.6.1]: https://github.com/ManageIQ/floe/compare/v0.6.0...v0.6.1
125
133
  [0.6.0]: https://github.com/ManageIQ/floe/compare/v0.5.0...v0.6.0
data/README.md CHANGED
@@ -51,6 +51,16 @@ You can provide that at runtime via the `--credentials` parameter:
51
51
  bundle exec ruby exe/floe --workflow my-workflow.asl --credentials='{"roleArn": "arn:aws:iam::111122223333:role/LambdaRole"}'
52
52
  ```
53
53
 
54
+ Or if you are running the floe command programmatically you can securely provide the credentials via a stdin pipe via `--credentials=-`:
55
+ ```
56
+ echo '{"roleArn": "arn:aws:iam::111122223333:role/LambdaRole"}' | bundle exec ruby exe/floe --workflow my-workflow.asl --credentials -
57
+ ```
58
+
59
+ Or you can pass a file path with the `--credentials-file` parameter:
60
+ ```
61
+ bundle exec ruby exe/floe --workflow my-workflow.asl --credentials-file /tmp/20231218-80537-kj494t
62
+ ```
63
+
54
64
  If you need to set a credential at runtime you can do that by using the `"ResultPath": "$.Credentials"` directive, for example to user a username/password to login and get a Bearer token:
55
65
 
56
66
  ```
@@ -152,6 +162,7 @@ end
152
162
  Options supported by the Docker docker runner are:
153
163
 
154
164
  * `network` - What docker to connect the container to, defaults to `"bridge"`. If you need access to host resources for development you can pass `network=host`.
165
+ * `pull-policy` - Pull image policy. The default is missing. Allowed values: always, missing, never
155
166
 
156
167
  #### Podman
157
168
 
@@ -161,6 +172,7 @@ Options supported by the podman docker runner are:
161
172
  * `log-level=string` - Log messages above specified level (trace, debug, info, warn, warning, error, fatal, panic)
162
173
  * `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`.
163
174
  * `noout=boolean` - do not output to stdout
175
+ * `pull-policy=string` - Pull image policy. The default is missing. Allowed values: always, missing, never, newer
164
176
  * `root=string` - Path to the root directory in which data, including images, is stored
165
177
  * `runroot=string` - Path to the 'run directory' where all state information is stored
166
178
  * `runtime=string` - Path to the OCI-compatible binary used to run containers
@@ -179,6 +191,7 @@ Options supported by the kubernetes docker runner are:
179
191
  * `kubeconfig` - Path to a kubeconfig file, defaults to `KUBECONFIG` environment variable or `~/.kube/config`
180
192
  * `kubeconfig_context` - Context to use in the kubeconfig file, defaults to `"default"`
181
193
  * `namespace` - Namespace to use when creating kubernetes resources, defaults to `"default"`
194
+ * `pull-policy` - Pull image policy. The default is Always. Allowed values: IfNotPresent, Always, Never
182
195
  * `server` - A kubernetes API Server URL, overrides anything in your kubeconfig file. If set `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` will be used
183
196
  * `token` - A bearer_token to use to authenticate to the kubernetes API, overrides anything in your kubeconfig file. If present, `/run/secrets/kubernetes.io/serviceaccount/token` will be used
184
197
  * `ca_file` - Path to a certificate-authority file for the kubernetes API, only valid if server and token are passed. If present `/run/secrets/kubernetes.io/serviceaccount/ca.crt` will be used
data/exe/floe CHANGED
@@ -6,15 +6,31 @@ require "optimist"
6
6
 
7
7
  opts = Optimist.options do
8
8
  version("v#{Floe::VERSION}\n")
9
- opt :workflow, "Path to your workflow json", :type => :string, :required => true
10
- opt :input, "JSON payload to input to the workflow", :default => '{}'
11
- opt :credentials, "JSON payload with credentials", :default => "{}"
12
- opt :docker_runner, "Type of runner for docker images", :default => "docker"
13
- opt :docker_runner_options, "Options to pass to the runner", :type => :strings
9
+ usage("[options] workflow input [workflow2 input2]")
10
+
11
+ opt :workflow, "Path to your workflow json (legacy)", :type => :string
12
+ opt :input, "JSON payload to input to the workflow (legacy)", :type => :string
13
+ opt :credentials, "JSON payload with credentials", :type => :string
14
+ opt :credentials_file, "Path to a file with credentials", :type => :string
15
+ opt :docker_runner, "Type of runner for docker images", :type => :string, :short => 'r'
16
+ opt :docker_runner_options, "Options to pass to the runner", :type => :strings, :short => 'o'
17
+
18
+ opt :docker, "Use docker to run images (short for --docker_runner=docker)", :type => :boolean
19
+ opt :podman, "Use podman to run images (short for --docker_runner=podman)", :type => :boolean
20
+ opt :kubernetes, "Use kubernetes to run images (short for --docker_runner=kubernetes)", :type => :boolean
14
21
  end
15
22
 
16
23
  Optimist.die(:docker_runner, "must be one of #{Floe::Workflow::Runner::TYPES.join(", ")}") unless Floe::Workflow::Runner::TYPES.include?(opts[:docker_runner])
17
24
 
25
+ # legacy support for --workflow
26
+ args = ARGV.empty? ? [opts[:workflow], opts[:input]] : ARGV
27
+ Optimist.die(:workflow, "must be specified") if args.empty?
28
+
29
+ # shortcut support
30
+ opts[:docker_runner] ||= "docker" if opts[:docker]
31
+ opts[:docker_runner] ||= "podman" if opts[:podman]
32
+ opts[:docker_runner] ||= "kubernetes" if opts[:kubernetes]
33
+
18
34
  require "logger"
19
35
  Floe.logger = Logger.new($stdout)
20
36
 
@@ -31,10 +47,36 @@ runner_options = opts[:docker_runner_options].to_h { |opt| opt.split("=", 2) }
31
47
 
32
48
  Floe::Workflow::Runner.docker_runner = runner_klass.new(runner_options)
33
49
 
34
- context = Floe::Workflow::Context.new(:input => opts[:input])
35
- workflow = Floe::Workflow.load(opts[:workflow], context, opts[:credentials])
50
+ credentials =
51
+ if opts[:credentials_given]
52
+ opts[:credentials] == "-" ? $stdin.read : opts[:credentials]
53
+ elsif opts[:credentials_file_given]
54
+ File.read(opts[:credentials_file])
55
+ end
56
+
57
+ workflows =
58
+ args.each_slice(2).map do |workflow, input|
59
+ context = Floe::Workflow::Context.new(:input => input || opts[:input] || "{}")
60
+ Floe::Workflow.load(workflow, context, credentials)
61
+ end
62
+
63
+ # run
64
+
65
+ outstanding = workflows.dup
66
+ until outstanding.empty?
67
+ ready = outstanding.select(&:step_nonblock_ready?)
68
+ ready.map(&:run_nonblock)
69
+ outstanding -= ready.select(&:end?)
70
+ sleep(1) if !outstanding.empty?
71
+ end
72
+
73
+ # display status
74
+
75
+ workflows.each do |workflow|
76
+ puts "", "#{workflow.name}#{" (#{workflow.status})" unless workflow.context.success?}", "===" if workflows.size > 1
77
+ puts workflow.output.inspect
78
+ end
36
79
 
37
- workflow.run!
80
+ # exit status
38
81
 
39
- puts workflow.output.inspect
40
- exit workflow.status == "success" ? 0 : 1
82
+ exit workflows.all? { |workflow| workflow.context.success? } ? 0 : 1
data/lib/floe/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Floe
4
- VERSION = "0.7.1".freeze
4
+ VERSION = "0.8.0".freeze
5
5
  end
@@ -74,6 +74,10 @@ module Floe
74
74
  end
75
75
  end
76
76
 
77
+ def success?
78
+ status == "success"
79
+ end
80
+
77
81
  def state=(val)
78
82
  @context["State"] = val
79
83
  end
@@ -14,7 +14,8 @@ module Floe
14
14
 
15
15
  super
16
16
 
17
- @network = options.fetch("network", "bridge")
17
+ @network = options.fetch("network", "bridge")
18
+ @pull_policy = options["pull-policy"]
18
19
  end
19
20
 
20
21
  def run_async!(resource, env = {}, secrets = {})
@@ -72,7 +73,7 @@ module Floe
72
73
  def run_container(image, env, secrets_file)
73
74
  params = run_container_params(image, env, secrets_file)
74
75
 
75
- logger.debug("Running #{AwesomeSpawn.build_command_line("docker", params)}")
76
+ logger.debug("Running #{AwesomeSpawn.build_command_line(self.class::DOCKER_COMMAND, params)}")
76
77
 
77
78
  result = docker!(*params)
78
79
  result.output
@@ -83,6 +84,7 @@ module Floe
83
84
  params << :detach
84
85
  params += env.map { |k, v| [:e, "#{k}=#{v}"] }
85
86
  params << [:e, "_CREDENTIALS=/run/secrets"] if secrets_file
87
+ params << [:pull, @pull_policy] if @pull_policy
86
88
  params << [:net, "host"] if @network == "host"
87
89
  params << [:v, "#{secrets_file}:/run/secrets:z"] if secrets_file
88
90
  params << [:name, container_name(image)]
@@ -40,6 +40,7 @@ module Floe
40
40
 
41
41
  @namespace = options.fetch("namespace", "default")
42
42
 
43
+ @pull_policy = options["pull-policy"]
43
44
  @task_service_account = options["task_service_account"]
44
45
 
45
46
  super
@@ -143,6 +144,7 @@ module Floe
143
144
  }
144
145
  }
145
146
 
147
+ spec[:spec][:imagePullPolicy] = @pull_policy if @pull_policy
146
148
  spec[:spec][:serviceAccountName] = @task_service_account if @task_service_account
147
149
 
148
150
  if secret
@@ -16,6 +16,7 @@ module Floe
16
16
  @log_level = options["log-level"]
17
17
  @network = options["network"]
18
18
  @noout = options["noout"].to_s == "true" if options.key?("noout")
19
+ @pull_policy = options["pull-policy"]
19
20
  @root = options["root"]
20
21
  @runroot = options["runroot"]
21
22
  @runtime = options["runtime"]
@@ -35,7 +36,8 @@ module Floe
35
36
  params << :detach
36
37
  params += env.map { |k, v| [:e, "#{k}=#{v}"] }
37
38
  params << [:e, "_CREDENTIALS=/run/secrets/#{secret}"] if secret
38
- params << [:net, "host"] if @network == "host"
39
+ params << [:pull, @pull_policy] if @pull_policy
40
+ params << [:net, "host"] if @network == "host"
39
41
  params << [:secret, secret] if secret
40
42
  params << [:name, container_name(image)]
41
43
  params << image
@@ -33,10 +33,6 @@ module Floe
33
33
  raise Floe::InvalidWorkflowError, "State name [#{name}] must be less than or equal to 80 characters" if name.length > 80
34
34
  end
35
35
 
36
- def run!(_input = nil)
37
- wait until run_nonblock! == 0
38
- end
39
-
40
36
  def wait(timeout: 5)
41
37
  start = Time.now.utc
42
38
 
@@ -137,8 +137,8 @@ module Floe
137
137
  end
138
138
 
139
139
  def parse_output(output)
140
- return if output.nil?
141
140
  return output if output.kind_of?(Hash)
141
+ return if output.nil? || output.empty?
142
142
 
143
143
  JSON.parse(output.split("\n").last)
144
144
  rescue JSON::ParserError
data/lib/floe/workflow.rb CHANGED
@@ -8,9 +8,12 @@ module Floe
8
8
  include Logging
9
9
 
10
10
  class << self
11
- def load(path_or_io, context = nil, credentials = {})
11
+ def load(path_or_io, context = nil, credentials = {}, name = nil)
12
12
  payload = path_or_io.respond_to?(:read) ? path_or_io.read : File.read(path_or_io)
13
- new(payload, context, credentials)
13
+ # default the name if it is a filename and none was passed in
14
+ name ||= path_or_io.respond_to?(:read) ? "stream" : path_or_io.split("/").last.split(".").first
15
+
16
+ new(payload, context, credentials, name)
14
17
  end
15
18
 
16
19
  def wait(workflows, timeout: 5)
@@ -31,9 +34,9 @@ module Floe
31
34
  end
32
35
  end
33
36
 
34
- attr_reader :context, :credentials, :payload, :states, :states_by_name, :start_at
37
+ attr_reader :context, :credentials, :payload, :states, :states_by_name, :start_at, :name
35
38
 
36
- def initialize(payload, context = nil, credentials = {})
39
+ def initialize(payload, context = nil, credentials = {}, name = nil)
37
40
  payload = JSON.parse(payload) if payload.kind_of?(String)
38
41
  credentials = JSON.parse(credentials) if credentials.kind_of?(String)
39
42
  context = Context.new(context) unless context.kind_of?(Context)
@@ -42,12 +45,13 @@ module Floe
42
45
  raise Floe::InvalidWorkflowError, "Missing field \"StartAt\"" if payload["StartAt"].nil?
43
46
  raise Floe::InvalidWorkflowError, "\"StartAt\" not in the \"States\" field" unless payload["States"].key?(payload["StartAt"])
44
47
 
48
+ @name = name
45
49
  @payload = payload
46
50
  @context = context
47
51
  @credentials = credentials || {}
48
52
  @start_at = payload["StartAt"]
49
53
 
50
- @states = payload["States"].to_a.map { |name, state| State.build!(self, name, state) }
54
+ @states = payload["States"].to_a.map { |state_name, state| State.build!(self, state_name, state) }
51
55
  @states_by_name = @states.each_with_object({}) { |state, result| result[state.name] = state }
52
56
 
53
57
  unless context.state.key?("Name")
@@ -58,16 +62,6 @@ module Floe
58
62
  raise Floe::InvalidWorkflowError, err.message
59
63
  end
60
64
 
61
- def run!
62
- step until end?
63
- self
64
- end
65
-
66
- def step
67
- step_nonblock_wait until step_nonblock == 0
68
- self
69
- end
70
-
71
65
  def run_nonblock
72
66
  loop while step_nonblock == 0 && !end?
73
67
  self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: floe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Developers