floe 0.19.2 → 0.20.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: e1e0936f8b333d83b40601da291196c63d9fef4357dcd86c54d0b762051c3ca7
4
- data.tar.gz: 5f084c9783bacc4ea7dd0c33aca1ae89001c097218c04b57d7f7be67e32e16d6
3
+ metadata.gz: 7276764c838fbe619e9dfbd03ef63ee48d654cb51e1c90338bd221312c35bac3
4
+ data.tar.gz: 61541bf22ba9193628978471a418d351aac5576cbdf3dc186374da04319dbec1
5
5
  SHA512:
6
- metadata.gz: 62ab51ba7cea145cf5c8abb9d2f89074810eacae5c645321116bc93239887749e4685594adc064780920fcc86adf4cfc1ad41caad23517271a73210aa8ed33f1
7
- data.tar.gz: 4486619b555718e0244a65b437cf419e92f3b94f1c802c05f9fd00b39142f9933b7ee97145b50310fb1bc4257f1545a2e3bf823c95b921469be75a9ef12409b6
6
+ metadata.gz: 4f295717a57a70e71683ec8e82b0755452cb5e740ffad204c40aa8d6970e78f159559c784541694982326a09e7437fea579202e77a34caa2f075293b4c11c421
7
+ data.tar.gz: 6ec1d9c222569c4bfa9d5decab841aa87fd8206bf69f753aa77dad6b182e680f9e0252f8be29cd8a93965ef91e1a92a290bc13bcb941c802dcb983ab289582a9
data/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.20.0] - 2026-07-08
8
+ ### Fixed
9
+ - Update to support io-wait v0.4 which drops the `IO#ready?` method ([#340](https://github.com/ManageIQ/floe/pull/340))
10
+ - Return States.TaskFailed for failed http request ([#343](https://github.com/ManageIQ/floe/pull/343))
11
+ - `kill -TERM` the docker wait process before exiting ([#344](https://github.com/ManageIQ/floe/pull/344))
12
+ - Use Kernel.spawn directly to ensure no `/bin/sh -c` shell is created ([#345](https://github.com/ManageIQ/floe/pull/345))
13
+
14
+ ### Added
15
+ - Add logging of the execution_id to exe/floe ([#341](https://github.com/ManageIQ/floe/pull/341))
16
+
7
17
  ## [0.19.2] - 2026-01-06
8
18
  ### Fixed
9
19
  - Lockdown io-wait to v0.3.* for `IO#ready?` ([#339](https://github.com/ManageIQ/floe/pull/339))
@@ -337,7 +347,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
337
347
  ### Added
338
348
  - Initial release
339
349
 
340
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.19.2...HEAD
350
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.20.0...HEAD
351
+ [0.20.0]: https://github.com/ManageIQ/floe/compare/v0.19.2...v0.20.0
341
352
  [0.19.2]: https://github.com/ManageIQ/floe/compare/v0.19.1...v0.19.2
342
353
  [0.19.1]: https://github.com/ManageIQ/floe/compare/v0.19.0...v0.19.1
343
354
  [0.19.0]: https://github.com/ManageIQ/floe/compare/v0.18.0...v0.19.0
data/README.md CHANGED
@@ -246,6 +246,27 @@ HTTP Parameters:
246
246
  * `User` - String - User for the proxy.
247
247
  * `Password` - String - Pasword for the proxy
248
248
 
249
+ Output:
250
+ A successful HTTP request will return the response Status, Body, and Headers. For example,
251
+ ```json
252
+ {
253
+ "Status": 200,
254
+ "Body": {"result": {"foo": "bar"}},
255
+ "Headers": {"content-type": "application/json"}
256
+ }
257
+ ```
258
+
259
+ Errors:
260
+ If an HTTP request fails, the `floe://http` builtin method will return an error payload that includes
261
+ the error code, the reason, and the response Status, Body, and Headers. For example,
262
+ ```json
263
+ {
264
+ "Error": "States.Http.StatusCode.404",
265
+ "Cause": "Not Found",
266
+ "Details": {"Status": 404, "Body": {"code": 404, "reason": "Page not found"}, "Headers": {"content-type": "application/json"}}
267
+ }
268
+ ```
269
+
249
270
  #### Docker resource
250
271
 
251
272
  The docker resource runner takes a docker image URI, including the registry, name, and tag.
@@ -94,7 +94,13 @@ module Floe
94
94
 
95
95
  output = {"Status" => response.status, "Body" => response.body, "Headers" => response.headers}
96
96
 
97
- BuiltinRunner.success!({}, :output => output)
97
+ if response.success?
98
+ BuiltinRunner.success!({}, :output => output)
99
+ else
100
+ BuiltinRunner.error!({}, :error => "States.Http.StatusCode.#{response.status}", :cause => response.reason_phrase, :details => output)
101
+ end
102
+ rescue ::Faraday::ConnectionFailed => err
103
+ BuiltinRunner.error!({}, :cause => err.to_s)
98
104
  end
99
105
 
100
106
  private_class_method def self.http_verify_params(params)
@@ -7,9 +7,11 @@ module Floe
7
7
  SCHEME_PREFIX = "#{SCHEME}://".freeze
8
8
 
9
9
  class << self
10
- def error!(runner_context = {}, cause:, error: "States.TaskFailed")
10
+ def error!(runner_context = {}, cause: nil, error: "States.TaskFailed", details: nil)
11
+ output = {"Error" => error, "Cause" => cause, "Details" => details}.compact
12
+
11
13
  runner_context.merge!(
12
- "running" => false, "success" => false, "output" => {"Error" => error, "Cause" => cause}
14
+ "running" => false, "success" => false, "output" => output
13
15
  )
14
16
  end
15
17
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Floe
6
+ class CLI
7
+ class Logger < ::Logger
8
+ attr_accessor :execution_id
9
+
10
+ def initialize(...)
11
+ super
12
+
13
+ original_formatter = formatter || ::Logger::Formatter.new
14
+ self.formatter = proc do |severity, time, progname, msg|
15
+ prefix = execution_id ? "[#{execution_id}] " : ""
16
+ original_formatter.call(severity, time, progname, "#{prefix}#{msg}")
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/floe/cli.rb CHANGED
@@ -1,44 +1,38 @@
1
1
  require "floe"
2
+ require "floe/cli/logger"
2
3
  require "floe/container_runner"
4
+ require "optimist"
5
+ require "stringio"
3
6
 
4
7
  module Floe
5
8
  class CLI
6
9
  include Logging
7
10
 
8
11
  def initialize
9
- require "optimist"
10
- require "logger"
11
-
12
- Floe.logger = Logger.new($stdout)
12
+ Floe.logger = ::Logger.new($stdout)
13
13
  Floe.logger.level = 0 if ENV["DEBUG"]
14
14
  end
15
15
 
16
16
  def run(args = ARGV)
17
17
  workflows_inputs, opts = parse_options!(args)
18
18
 
19
+ show_execution_id = workflows_inputs.size > 1
20
+
19
21
  credentials = create_credentials(opts)
20
22
 
21
23
  workflows =
22
- workflows_inputs.each_slice(2).map do |workflow, input|
23
- create_workflow(workflow, opts[:context], input, credentials)
24
+ workflows_inputs.map do |workflow, input|
25
+ wf_logger = create_logger(show_execution_id)
26
+ create_workflow(workflow, opts[:context], input, credentials, wf_logger)
24
27
  end
25
28
 
26
- output_streams = create_loggers(workflows, opts[:segment_output])
27
-
28
29
  logger.info("Checking #{workflows.count} workflows...")
29
30
  ready = Floe::Workflow.wait(workflows, &:run_nonblock)
30
31
  logger.info("Checking #{workflows.count} workflows...Complete - #{ready.count} ready")
31
32
 
32
33
  # Display status
33
34
  workflows.each do |workflow|
34
- if workflows.size > 1
35
- logger.info("")
36
- logger.info("#{workflow.name}#{" (#{workflow.status})" unless workflow.context.success?}")
37
- logger.info("===")
38
- end
39
-
40
- logger.info(output_streams[workflow].string) if output_streams[workflow]
41
- logger.info(workflow.output)
35
+ logger.info("[#{workflow.context.execution_id}] #{workflow.name} (#{workflow.status}) - output: #{workflow.output}")
42
36
  end
43
37
 
44
38
  workflows.all? { |workflow| workflow.context.success? }
@@ -62,7 +56,6 @@ module Floe
62
56
  opt :context, "JSON payload of the Context", :type => :string
63
57
  opt :credentials, "JSON payload with Credentials", :type => :string
64
58
  opt :credentials_file, "Path to a file with Credentials", :type => :string
65
- opt :segment_output, "Segment output by each worker", :default => false
66
59
 
67
60
  Floe::ContainerRunner.cli_options(self)
68
61
 
@@ -70,20 +63,20 @@ module Floe
70
63
  banner("General options:")
71
64
  end
72
65
 
73
- # Create workflow/input pairs from the various combinations of paramaters
66
+ # Create workflow/input pairs from the various combinations of parameters
74
67
  workflows_inputs =
75
68
  if opts[:workflow_given]
76
69
  Optimist.die("cannot specify both --workflow and bare workflows") if args.any?
77
70
 
78
- [opts[:workflow], opts.fetch(:input, "{}")]
71
+ [[opts[:workflow], opts.fetch(:input, "{}")]]
79
72
  elsif opts[:input_given]
80
73
  Optimist.die("workflow(s) must be specified") if args.empty?
81
74
 
82
- args.flat_map { |w| [w, opts[:input].dup] }
75
+ args.map { |w| [w, opts[:input].dup] }
83
76
  else
84
77
  Optimist.die("workflow/input pairs must be specified") if args.empty? || (args.size > 1 && args.size.odd?)
85
78
 
86
- args
79
+ args.each_slice(2).to_a
87
80
  end
88
81
 
89
82
  Floe::ContainerRunner.resolve_cli_options!(opts)
@@ -102,20 +95,15 @@ module Floe
102
95
  JSON.parse(credentials_str)
103
96
  end
104
97
 
105
- def create_workflow(workflow, context_payload, input, credentials)
106
- context = Floe::Workflow::Context.new(context_payload, :input => input, :credentials => credentials)
98
+ def create_workflow(workflow, context_payload, input, credentials, logger)
99
+ context = Floe::Workflow::Context.new(context_payload, :input => input, :credentials => credentials, :logger => logger)
107
100
  Floe::Workflow.load(workflow, context)
108
101
  end
109
102
 
110
- def create_loggers(workflows, segment_output)
111
- if workflows.size == 1 || !segment_output
112
- # no extra work necessary
113
- {}
114
- else
115
- workflows.each_with_object({}) do |workflow, h|
116
- workflow.context.logger = Logger.new(output = StringIO.new)
117
- h[workflow] = output
118
- end
103
+ def create_logger(show_execution_id)
104
+ logger_class = show_execution_id ? Floe::CLI::Logger : ::Logger
105
+ logger_class.new($stdout).tap do |logger|
106
+ logger.level = 0 if ENV["DEBUG"]
119
107
  end
120
108
  end
121
109
  end
@@ -46,13 +46,12 @@ module Floe
46
46
  end
47
47
 
48
48
  def wait(timeout: nil, events: %i[create update delete], &block)
49
- until_timestamp = Time.now.utc + timeout if timeout
49
+ until_timestamp = (Time.now.utc + timeout).to_i if timeout
50
50
 
51
51
  r, w = IO.pipe
52
52
 
53
- pid = AwesomeSpawn.run_detached(
54
- self.class::DOCKER_COMMAND, :err => :out, :out => w, :params => wait_params(until_timestamp)
55
- )
53
+ pid = Kernel.spawn({}, self.class::DOCKER_COMMAND, *wait_params(until_timestamp), :err => :out, :out => w)
54
+ Process.detach(pid)
56
55
 
57
56
  w.close
58
57
 
@@ -65,7 +64,7 @@ module Floe
65
64
 
66
65
  # Get all events while the pipe is readable
67
66
  notices = []
68
- while r.ready?
67
+ while r.wait_readable(0)
69
68
  notice = r.gets
70
69
 
71
70
  # If the process has exited `r.gets` returns `nil` and the pipe is
@@ -92,10 +91,12 @@ module Floe
92
91
  Process.kill(0, pid)
93
92
  rescue Errno::ESRCH
94
93
  # Break out of the loop if the `docker events` process has exited
94
+ pid = nil
95
95
  break
96
96
  end
97
97
  ensure
98
98
  r.close
99
+ sigterm(pid) if pid
99
100
  end
100
101
 
101
102
  def status!(runner_context)
@@ -150,8 +151,10 @@ module Floe
150
151
  end
151
152
 
152
153
  def wait_params(until_timestamp)
153
- params = ["events", [:format, "{{json .}}"], [:filter, "type=container"], [:since, Time.now.utc.to_i]]
154
- params << [:until, until_timestamp.to_i] if until_timestamp
154
+ since_timestamp = Time.now.utc.to_i
155
+
156
+ params = ["events", "--format", "{{json .}}", "--filter", "type=container", "--since", since_timestamp.to_s]
157
+ params += ["--until", until_timestamp.to_s] if until_timestamp
155
158
  params
156
159
  end
157
160
 
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.19.2"
4
+ VERSION = "0.20.0"
5
5
  end
@@ -36,12 +36,20 @@ module Floe
36
36
 
37
37
  execution["Id"] ||= SecureRandom.uuid
38
38
  execution["StartTime"] = Time.now.utc.iso8601
39
+
40
+ if logger.respond_to?(:execution_id=)
41
+ logger.execution_id = execution["Id"]
42
+ end
39
43
  end
40
44
 
41
45
  def execution
42
46
  @context["Execution"]
43
47
  end
44
48
 
49
+ def execution_id
50
+ execution["Id"]
51
+ end
52
+
45
53
  def credentials
46
54
  @context["Credentials"]
47
55
  end
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.19.2
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Developers
@@ -69,16 +69,22 @@ dependencies:
69
69
  name: io-wait
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - "~>"
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: 0.3.6
75
+ - - "<"
76
+ - !ruby/object:Gem::Version
77
+ version: 0.5.0
75
78
  type: :runtime
76
79
  prerelease: false
77
80
  version_requirements: !ruby/object:Gem::Requirement
78
81
  requirements:
79
- - - "~>"
82
+ - - ">="
80
83
  - !ruby/object:Gem::Version
81
84
  version: 0.3.6
85
+ - - "<"
86
+ - !ruby/object:Gem::Version
87
+ version: 0.5.0
82
88
  - !ruby/object:Gem::Dependency
83
89
  name: json
84
90
  requirement: !ruby/object:Gem::Requirement
@@ -155,14 +161,14 @@ dependencies:
155
161
  requirements:
156
162
  - - ">="
157
163
  - !ruby/object:Gem::Version
158
- version: 1.5.2
164
+ version: '0'
159
165
  type: :development
160
166
  prerelease: false
161
167
  version_requirements: !ruby/object:Gem::Requirement
162
168
  requirements:
163
169
  - - ">="
164
170
  - !ruby/object:Gem::Version
165
- version: 1.5.2
171
+ version: '0'
166
172
  - !ruby/object:Gem::Dependency
167
173
  name: rake
168
174
  requirement: !ruby/object:Gem::Requirement
@@ -242,12 +248,12 @@ files:
242
248
  - examples/set-credential.asl
243
249
  - examples/workflow.asl
244
250
  - exe/floe
245
- - floe.gemspec
246
251
  - lib/floe.rb
247
252
  - lib/floe/builtin_runner.rb
248
253
  - lib/floe/builtin_runner/methods.rb
249
254
  - lib/floe/builtin_runner/runner.rb
250
255
  - lib/floe/cli.rb
256
+ - lib/floe/cli/logger.rb
251
257
  - lib/floe/container_runner.rb
252
258
  - lib/floe/container_runner/docker.rb
253
259
  - lib/floe/container_runner/docker_mixin.rb
data/floe.gemspec DELETED
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/floe/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "floe"
7
- spec.version = Floe::VERSION
8
- spec.authors = ["ManageIQ Developers"]
9
-
10
- spec.summary = "Floe is a runner for Amazon States Language workflows."
11
- spec.description = spec.summary
12
- spec.homepage = "https://github.com/ManageIQ/floe"
13
- spec.licenses = ["Apache-2.0"]
14
- spec.required_ruby_version = ">= 3.0.0"
15
-
16
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
- spec.metadata['rubygems_mfa_required'] = "true"
18
-
19
- spec.metadata["homepage_uri"] = spec.homepage
20
- spec.metadata["source_code_uri"] = spec.homepage
21
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
22
-
23
- # Specify which files should be added to the gem when it is released.
24
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
- spec.files = Dir.chdir(__dir__) do
26
- `git ls-files -z`.split("\x0").reject do |f|
27
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
- end
29
- end
30
- spec.bindir = "exe"
31
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
- spec.require_paths = ["lib"]
33
-
34
- spec.add_dependency "activesupport", ">5.2"
35
- spec.add_dependency "awesome_spawn", "~>1.6"
36
- spec.add_dependency "faraday"
37
- spec.add_dependency "faraday-follow_redirects"
38
- spec.add_dependency "io-wait", "~> 0.3.6"
39
- spec.add_dependency "json", "~>2.10"
40
- spec.add_dependency "jsonpath", "~>1.1"
41
- spec.add_dependency "kubeclient", "~>4.7"
42
- spec.add_dependency "optimist", "~>3.0"
43
- spec.add_dependency "parslet", "~>2.0"
44
-
45
- spec.add_development_dependency "manageiq-style", ">= 1.5.2"
46
- spec.add_development_dependency "rake", "~> 13.0"
47
- spec.add_development_dependency "rspec"
48
- spec.add_development_dependency "simplecov", ">= 0.21.2"
49
- spec.add_development_dependency "timecop"
50
- end