floe 0.17.1 → 0.19.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: 7631c084cea78319880f47959e5bc883a5406caaf2623b894004172c4d01ff96
4
- data.tar.gz: 58416b08ffcd26360eeaa8e8f8056279ed57440f8a65c6650fb26e95a3564c28
3
+ metadata.gz: d7ec87295c6283b53c25c1791a9f8a2a953e48bec4059bf67fd5487875c114ce
4
+ data.tar.gz: a56a431253dd0a4a4c4470363ce0dfa83aa6f8ec834239a43266c6e41ccc0b61
5
5
  SHA512:
6
- metadata.gz: 72f82e4eafec1ea8c1317f4766424e55c821c5f360e5e9db04037032552cd76dfafcdd2c5ec842bc2fc58acb1137adeaafed8306fbbf6a18f2b3c8b6e45efafa
7
- data.tar.gz: 976265735ea69bb1844bce7ff07b1abd28b9a40fbd01c62dd47fb40d33374333bd7147fc85bbb52760eb2b9903b2727db4c8edf7f3f38371028cf4827a9a78ee
6
+ metadata.gz: c3d888a3713b5e3291e9fb7a04ea3254268e2ee11ccdc1a49af71f992cab61d5752d689f70292d67f3c9d3b69f146c47c0a86423a424c18220e813c2997c4402
7
+ data.tar.gz: f54adf8264d7ed09c05d2c78b8f5029d0ed5c472ac365e75a3656cc66fa29db0dd79eded1136b0893114d75eaf9a5e4637e76a2c347d1b8917ac8d3cf4238ec2
data/CHANGELOG.md CHANGED
@@ -4,6 +4,33 @@ This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.19.0] - 2025-12-16
8
+ ### Fixed
9
+ - Fix builtin_runner/runner spec file name ([#329](https://github.com/ManageIQ/floe/pull/329))
10
+ - Check WaitUntil before starting a State ([#336](https://github.com/ManageIQ/floe/pull/336))
11
+ - Task TimeoutSeconds and TimeoutSecondsPath not implemented ([#330](https://github.com/ManageIQ/floe/pull/330))
12
+
13
+ ### Added
14
+ - Add floe://log builtin method ([#328](https://github.com/ManageIQ/floe/pull/328))
15
+
16
+ ### Changed
17
+ - Raise `States.Timeout` in `running?` and remove duplicate `timed_out?` check in `finished?` ([#337](https://github.com/ManageIQ/floe/pull/337))
18
+
19
+ ## [0.18.0] - 2025-10-24
20
+ ### Added
21
+ - Declare active support dependency ([#316](https://github.com/ManageIQ/floe/pull/316))
22
+ - Add Context equality comparison ([#317](https://github.com/ManageIQ/floe/pull/317))
23
+ - Choice rule payload validation ([#189](https://github.com/ManageIQ/floe/pull/189))
24
+
25
+ ### Changed
26
+ - In ChoiceRule use Regexp.union ([#323](https://github.com/ManageIQ/floe/pull/323))
27
+ - Remove special case for choice rule IsPresent ([#322](https://github.com/ManageIQ/floe/pull/322))
28
+ - Rename data operation parameters ([#324](https://github.com/ManageIQ/floe/pull/324))
29
+ - Refactor workflow state check to not use payload ([#326](https://github.com/ManageIQ/floe/pull/326))
30
+
31
+ ### Fixed
32
+ - Fix missing parameters in item_batcher_spec ([#325](https://github.com/ManageIQ/floe/pull/325))
33
+
7
34
  ## [0.17.1] - 2025-08-25
8
35
  ### Changed
9
36
  - Default Method=GET for floe://http method parameter ([#315](https://github.com/ManageIQ/floe/pull/315))
@@ -302,7 +329,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
302
329
  ### Added
303
330
  - Initial release
304
331
 
305
- [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.17.0...HEAD
332
+ [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.19.0...HEAD
333
+ [0.19.0]: https://github.com/ManageIQ/floe/compare/v0.18.0...v0.19.0
334
+ [0.18.0]: https://github.com/ManageIQ/floe/compare/v0.17.1...v0.18.0
335
+ [0.17.1]: https://github.com/ManageIQ/floe/compare/v0.17.0...v0.17.1
336
+ [0.17.0]: https://github.com/ManageIQ/floe/compare/v0.16.0...v0.17.0
306
337
  [0.16.0]: https://github.com/ManageIQ/floe/compare/v0.16.0...v0.17.0
307
338
  [0.16.0]: https://github.com/ManageIQ/floe/compare/v0.15.1...v0.16.0
308
339
  [0.15.1]: https://github.com/ManageIQ/floe/compare/v0.15.0...v0.15.1
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Floe
2
2
 
3
3
  [![CI](https://github.com/ManageIQ/floe/actions/workflows/ci.yaml/badge.svg)](https://github.com/ManageIQ/floe/actions/workflows/ci.yaml)
4
- [![Code Climate](https://codeclimate.com/github/ManageIQ/floe.svg)](https://codeclimate.com/github/ManageIQ/floe)
5
- [![Test Coverage](https://codeclimate.com/github/ManageIQ/floe/badges/coverage.svg)](https://codeclimate.com/github/ManageIQ/floe/coverage)
6
4
 
7
5
  ## Overview
8
6
 
@@ -167,6 +165,31 @@ Task Runner Types:
167
165
 
168
166
  This is the "builtin" runner and exposes methods that are executed internally without having to call out to a docker image.
169
167
 
168
+ ##### Log builtin method
169
+
170
+ `floe://log` allows you to write a message to the logger.
171
+
172
+ Example:
173
+ ```json
174
+ {
175
+ "Comment": "Print log message",
176
+ "StartAt": "Log",
177
+ "States": {
178
+ "Log": {
179
+ "Type": "Task",
180
+ "Resource": "floe://log",
181
+ "Parameters": {
182
+ "Level": "INFO",
183
+ "Message": "Hello, Floe!"
184
+ },
185
+ "End": true
186
+ }
187
+ ```
188
+
189
+ Log Parameters:
190
+ * `Level` (required) - Log level. Permitted values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, or `UNKNOWN`. Defaults to `INFO`.
191
+ * `Message` (required) - The message to log
192
+
170
193
  ##### HTTP builtin method
171
194
 
172
195
  `floe://http` allows you to execute HTTP method calls.
data/examples/log.asl ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "Comment": "Print log messages",
3
+ "StartAt": "Log Info",
4
+ "States": {
5
+ "Log Info": {
6
+ "Type": "Task",
7
+ "Resource": "floe://log",
8
+ "Parameters": {
9
+ "Level": "INFO",
10
+ "Message": "Hello, Floe!"
11
+ },
12
+ "Next": "Log Debug"
13
+ },
14
+ "Log Debug": {
15
+ "Type": "Task",
16
+ "Resource": "floe://log",
17
+ "Parameters": {
18
+ "Level": "DEBUG",
19
+ "Message": "Hello, Floe!"
20
+ },
21
+ "Next": "Log From Input"
22
+ },
23
+ "Log From Input": {
24
+ "Type": "Task",
25
+ "Resource": "floe://log",
26
+ "Parameters": {
27
+ "Level": "INFO",
28
+ "Message.$": "States.Format('Hello, {}!', $.name)"
29
+ },
30
+ "End": true
31
+ }
32
+ }
33
+ }
data/floe.gemspec CHANGED
@@ -31,15 +31,16 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ["lib"]
33
33
 
34
+ spec.add_dependency "activesupport", ">5.2"
34
35
  spec.add_dependency "awesome_spawn", "~>1.6"
36
+ spec.add_dependency "faraday"
37
+ spec.add_dependency "faraday-follow_redirects"
35
38
  spec.add_dependency "io-wait"
39
+ spec.add_dependency "json", "~>2.10"
36
40
  spec.add_dependency "jsonpath", "~>1.1"
37
41
  spec.add_dependency "kubeclient", "~>4.7"
38
42
  spec.add_dependency "optimist", "~>3.0"
39
43
  spec.add_dependency "parslet", "~>2.0"
40
- spec.add_dependency "json", "~>2.10"
41
- spec.add_dependency "faraday"
42
- spec.add_dependency "faraday-follow_redirects"
43
44
 
44
45
  spec.add_development_dependency "manageiq-style", ">= 1.5.2"
45
46
  spec.add_development_dependency "rake", "~> 13.0"
@@ -1,6 +1,35 @@
1
+ require "logger"
2
+
1
3
  module Floe
2
4
  module BuiltinRunner
3
5
  class Methods < BasicObject
6
+ def self.log(params, _secrets, context)
7
+ params["Level"] = (params["Level"] ||= "INFO").upcase
8
+
9
+ error = log_verify_params(params)
10
+ return BuiltinRunner.error!({}, :cause => error) if error
11
+
12
+ level, message = params.values_at("Level", "Message")
13
+
14
+ context.logger.add(::Logger::Severity.const_get(level), message)
15
+
16
+ BuiltinRunner.success!({}, :output => context.input)
17
+ end
18
+
19
+ LOG_SEVERITIES = ::Logger::Severity.constants.sort_by { |s| ::Logger::Severity.const_get(s) }.map(&:to_s)
20
+ LOG_SEVERITIES_S = "#{LOG_SEVERITIES[0..-2].join(", ")}, or #{LOG_SEVERITIES[-1]}"
21
+
22
+ private_class_method def self.log_verify_params(params)
23
+ return "Missing Parameter: Message" if params["Message"].nil?
24
+ return "Invalid Parameter: Level: [#{params["Level"]}], must be one of #{LOG_SEVERITIES_S}" unless LOG_SEVERITIES.include?(params["Level"])
25
+
26
+ nil
27
+ end
28
+
29
+ private_class_method def self.log_status!(runner_context)
30
+ runner_context
31
+ end
32
+
4
33
  def self.http(params, _secrets, _context)
5
34
  params["Method"] ||= "GET"
6
35
 
@@ -59,8 +88,8 @@ module Floe
59
88
  connection.response(:follow_redirects)
60
89
  end
61
90
 
62
- response = connection.send(method.downcase) do |request|
63
- request.body = body if body
91
+ response = connection.send(method.downcase) do |faraday_request|
92
+ faraday_request.body = body if body
64
93
  end
65
94
 
66
95
  output = {"Status" => response.status, "Body" => response.body, "Headers" => response.headers}
@@ -23,7 +23,7 @@ module Floe
23
23
  method_name = runner_context["method"]
24
24
  raise ArgumentError if method_name.nil?
25
25
 
26
- cleanup_method = "#{method_name}_cleanup"
26
+ cleanup_method = :"#{method_name}_cleanup"
27
27
  return unless Methods.respond_to?(cleanup_method, true)
28
28
 
29
29
  Methods.send(cleanup_method, runner_context)
@@ -34,7 +34,7 @@ module Floe
34
34
  raise ArgumentError if method_name.nil?
35
35
  return if runner_context["running"] == false
36
36
 
37
- Methods.send("#{method_name}_status!", runner_context)
37
+ Methods.send(:"#{method_name}_status!", runner_context)
38
38
  end
39
39
 
40
40
  def running?(runner_context)
@@ -105,10 +105,14 @@ module Floe
105
105
  end
106
106
 
107
107
  def running?(runner_context)
108
+ return false if runner_context.key?("Error")
109
+
108
110
  !!runner_context.dig("container_state", "Running")
109
111
  end
110
112
 
111
113
  def success?(runner_context)
114
+ return false if runner_context.key?("Error")
115
+
112
116
  runner_context.dig("container_state", "ExitCode") == 0
113
117
  end
114
118
 
@@ -176,18 +180,21 @@ module Floe
176
180
  when "die", "destroy"
177
181
  :delete
178
182
  else
179
- :unkonwn
183
+ :unknown
180
184
  end
181
185
  end
182
186
 
183
187
  def inspect_container(container_id)
184
188
  JSON.parse(docker!("inspect", container_id).output).first
185
189
  rescue AwesomeSpawn::CommandResultError => err
186
- raise Floe::ExecutionError.new("Failed to get status for container #{container_id}: #{err}")
190
+ raise Floe::ExecutionError, "Failed to get status for container #{container_id}: #{err}"
187
191
  end
188
192
 
189
- def delete_container(container_id)
190
- docker!("rm", container_id)
193
+ def delete_container(container_id, force: true)
194
+ params = ["rm", container_id]
195
+ params << "--force" if force
196
+
197
+ docker!(*params)
191
198
  rescue
192
199
  nil
193
200
  end
@@ -11,7 +11,7 @@ module Floe
11
11
  FAILURE_REASONS = %w[CrashLoopBackOff ImagePullBackOff ErrImagePull].freeze
12
12
 
13
13
  def initialize(options = {})
14
- require "active_support/core_ext/hash/keys"
14
+ require "active_support/core_ext/hash/keys" # deep_stringify_keys
15
15
  require "awesome_spawn"
16
16
  require "securerandom"
17
17
  require "base64"
@@ -70,6 +70,7 @@ module Floe
70
70
  end
71
71
 
72
72
  def running?(runner_context)
73
+ return false if runner_context.key?("Error")
73
74
  return false unless pod_running?(runner_context)
74
75
  # If a pod is Pending and the containers are waiting with a failure
75
76
  # reason such as ImagePullBackOff or CrashLoopBackOff then the pod
@@ -80,6 +81,8 @@ module Floe
80
81
  end
81
82
 
82
83
  def success?(runner_context)
84
+ return false if runner_context.key?("Error")
85
+
83
86
  runner_context.dig("container_state", "phase") == "Succeeded"
84
87
  end
85
88
 
@@ -156,7 +159,7 @@ module Floe
156
159
  def pod_info(pod_name)
157
160
  kubeclient.get_pod(pod_name, namespace)
158
161
  rescue Kubeclient::HttpError => err
159
- raise Floe::ExecutionError.new("Failed to get status for pod #{namespace}/#{pod_name}: #{err}")
162
+ raise Floe::ExecutionError, "Failed to get status for pod #{namespace}/#{pod_name}: #{err}"
160
163
  end
161
164
 
162
165
  def pod_running?(context)
@@ -22,10 +22,6 @@ module Floe
22
22
  raise Floe::ExecutionError.new(self.class.field_error_text(name, field_name, field_value, comment), floe_error)
23
23
  end
24
24
 
25
- def workflow_state?(field_value, workflow)
26
- workflow.payload["States"] ? workflow.payload["States"].include?(field_value) : true
27
- end
28
-
29
25
  def wrap_parser_error(field_name, field_value)
30
26
  yield
31
27
  rescue ArgumentError, InvalidWorkflowError => error
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.17.1"
4
+ VERSION = "0.19.0"
5
5
  end
@@ -24,7 +24,7 @@ module Floe
24
24
 
25
25
  def validate_state_next!(workflow)
26
26
  missing_field_error!("Next") if @next.nil?
27
- invalid_field_error!("Next", @next, "is not found in \"States\"") if @next && !workflow_state?(@next, workflow)
27
+ invalid_field_error!("Next", @next, "is not found in \"States\"") if @next && !workflow.state?(@next)
28
28
  end
29
29
  end
30
30
  end
@@ -6,12 +6,14 @@ module Floe
6
6
  class Data < Floe::Workflow::ChoiceRule
7
7
  TYPES = ["String", "Numeric", "Boolean", "Timestamp", "Present", "Null"].freeze
8
8
  COMPARES = ["Equals", "LessThan", "GreaterThan", "LessThanEquals", "GreaterThanEquals", "Matches"].freeze
9
+ OPERATIONS = TYPES.each_with_object({}) { |dt, a| a[dt] = :"is_#{dt.downcase}?" }
10
+ .merge(COMPARES.each_with_object({}) { |op, a| a[op] = :"op_#{op.downcase}?" }).freeze
9
11
  # e.g.: (Is)(String), (Is)(Present)
10
- TYPE_CHECK = /^(Is)(#{TYPES.join("|")})$/.freeze
12
+ TYPE_CHECK = /^Is(#{Regexp.union(TYPES)})$/
11
13
  # e.g.: (String)(LessThan)(Path), (Numeric)(GreaterThanEquals)()
12
- OPERATION = /^(#{(TYPES - %w[Null Present]).join("|")})(#{COMPARES.join("|")})(Path)?$/.freeze
14
+ OPERATION = /^(#{Regexp.union(TYPES - %w[Null Present])})(#{Regexp.union(COMPARES)})(Path)?$/
13
15
 
14
- attr_reader :variable, :compare_key, :operation, :type, :compare_predicate, :path
16
+ attr_reader :variable, :compare_key, :operator, :type, :compare_predicate, :path
15
17
 
16
18
  def initialize(_workflow, _name, payload)
17
19
  super
@@ -20,62 +22,80 @@ module Floe
20
22
  parse_compare_key
21
23
  end
22
24
 
25
+ # Evaluate whether this rule is true for the given context and input (runtime)
26
+ #
27
+ # @param context [Context] The workflow execution context
28
+ # @param input [Hash] The current state input
29
+ # @return [Boolean] true if the rule evaluate to true
23
30
  def true?(context, input)
24
- return presence_check(context, input) if compare_key == "IsPresent"
25
-
26
- lhs = variable_value(context, input)
31
+ # Payload pattern is: {"Variable": $lhs, $operator: $rhs}
32
+ # Example:
33
+ #
34
+ # {"Variable": "$.foo", "IsNumeric": true}
35
+ # lhs = input["$.foo"]
36
+ # rhs = true
37
+ # is_numeric?(lhs, rhs)
38
+ #
39
+ # {"Variable": "$.foo", "GreaterThanString": "aaa"}
40
+ # lhs = input["$.foo"]
41
+ # rhs = "aaa"
42
+ # op_greaterthan?(lhs, rhs)
43
+ #
44
+ # {"Variable": "$.foo", "GreaterThanNumericPath": "$.bar"}
45
+ # lhs = input["$.foo"]
46
+ # rhs = input["$.bar"]
47
+ # op_greaterthan?(lhs, rhs)
48
+ #
49
+ # NOTE: IsPresent works a little differently as lhs might raise a PathError.
50
+ # See the exception handler below. This is why we process the rhs before the lhs.
27
51
  rhs = compare_value(context, input)
28
- send(operation, lhs, rhs)
52
+ lhs = variable_value(context, input)
53
+ send(OPERATIONS[operator], lhs, rhs)
54
+ rescue Floe::PathError
55
+ # For IsPresent, we can expect the lhs to not be present in some cases,
56
+ # This throws a PathError. We handle that special case here.
57
+ # Example:
58
+ #
59
+ # {"Variable": "$.foo", "IsPresent": false}
60
+ # lhs = input["$.foo"], but variable is not present. (The variable lookup threw PathError)
61
+ # rhs = false
62
+ return is_present?(:not_present, rhs) if operator == "Present"
63
+
64
+ # for non "IsPresent" checks, share that lhs or rhs is not found.
65
+ raise
29
66
  end
30
67
 
31
68
  private
32
69
 
33
- def presence_check(context, input)
34
- # Get the right hand side for {"Variable": "$.foo", "IsPresent": true} i.e.: true
35
- # If true then return true when present.
36
- # If false then return true when not present.
37
- predicate = compare_value(context, input)
38
- # Don't need the variable_value, just need to see if the path finds the value.
39
- variable_value(context, input)
40
-
41
- # The variable_value is present
42
- # If predicate is true, then presence check was successful, return true.
43
- predicate
44
- rescue Floe::PathError
45
- # variable_value is not present. (the path lookup threw an error)
46
- # If predicate is false, then it successfully wasn't present, return true.
47
- !predicate
48
- end
49
-
50
70
  # rubocop:disable Naming/PredicateName
51
71
  # rubocop:disable Style/OptionalBooleanParameter
52
- def is_null?(value, predicate = true)
53
- value.nil? == predicate
72
+ def is_null?(value, expectation)
73
+ value.nil? == expectation
54
74
  end
55
75
 
56
- def is_present?(value, predicate = true)
57
- !value.nil? == predicate
76
+ def is_present?(value, expectation)
77
+ (value != :not_present) == expectation
58
78
  end
59
79
 
60
- def is_numeric?(value, predicate = true)
61
- value.kind_of?(Numeric) == predicate
80
+ def is_numeric?(value, expectation)
81
+ value.kind_of?(Numeric) == expectation
62
82
  end
63
83
 
64
- def is_string?(value, predicate = true)
65
- value.kind_of?(String) == predicate
84
+ def is_string?(value, expectation)
85
+ value.kind_of?(String) == expectation
66
86
  end
67
87
 
68
- def is_boolean?(value, predicate = true)
69
- [true, false].include?(value) == predicate
88
+ def is_boolean?(value, expectation)
89
+ [true, false].include?(value) == expectation
70
90
  end
71
91
 
72
- def is_timestamp?(value, predicate = true)
92
+ def is_timestamp?(value, expectation)
73
93
  require "date"
74
94
 
75
95
  DateTime.rfc3339(value)
76
- predicate
96
+ expectation
77
97
  rescue TypeError, Date::Error
78
- !predicate
98
+ !expectation
79
99
  end
80
100
  # rubocop:enable Naming/PredicateName
81
101
  # rubocop:enable Style/OptionalBooleanParameter
@@ -100,8 +120,8 @@ module Floe
100
120
  lhs >= rhs
101
121
  end
102
122
 
103
- def op_matches?(lhs, rhs)
104
- lhs.match?(Regexp.escape(rhs).gsub('\*', '.*?'))
123
+ def op_matches?(value, pattern)
124
+ value.match?(Regexp.escape(pattern).gsub('\*', '.*?'))
105
125
  end
106
126
 
107
127
  # parse the compare key at initialization time
@@ -110,26 +130,26 @@ module Floe
110
130
  # e.g. (String)(GreaterThan)(Path)
111
131
  if (match_values = OPERATION.match(key))
112
132
  @compare_key = key
113
- @type, operator, @path = match_values.captures
114
- @operation = "op_#{operator.downcase}?".to_sym
133
+ @type, @operator, @path = match_values.captures
115
134
  @compare_predicate = parse_predicate(type)
116
135
  break
117
- end
118
136
  # e.g. (Is)(String)
119
- if (match_value = TYPE_CHECK.match(key))
137
+ elsif (match_value = TYPE_CHECK.match(key))
120
138
  @compare_key = key
121
- _operator, type = match_value.captures
139
+ @operator = match_value.captures.first
122
140
  # type: nil means no runtime type checking.
123
141
  @type = @path = nil
124
- @operation = "is_#{type.downcase}?".to_sym
125
142
  @compare_predicate = parse_predicate("Boolean")
126
143
  break
127
144
  end
128
145
  end
129
- parser_error!("requires a compare key") if compare_key.nil? || operation.nil?
146
+ parser_error!("requires a compare key") if compare_key.nil? || operator.nil?
130
147
  end
131
148
 
132
- # parse predicate at initilization time
149
+ # parse predicate at initialization time
150
+ # @param data_type [String] the data type of the variable
151
+ # When parsing operations (IntegerGreaterThan), this will be the operation data type (e.g.: Integer)
152
+ # When parsing type checks (IsString), this will always be a Boolean
133
153
  # @return the right predicate attached to the compare key
134
154
  def parse_predicate(data_type)
135
155
  path ? parse_path(compare_key) : parse_field(compare_key, data_type)
@@ -140,13 +160,13 @@ module Floe
140
160
  path ? fetch_path(compare_key, compare_predicate, context, input) : compare_predicate
141
161
  end
142
162
 
143
- # feth the variable value at runtime
144
- # @return variable value (left hand side )
163
+ # fetch the variable value at runtime
164
+ # @return variable value (left hand side)
145
165
  def variable_value(context, input)
146
166
  fetch_path("Variable", variable, context, input)
147
167
  end
148
168
 
149
- # parse path at initilization time
169
+ # parse path at initialization time
150
170
  # helper method to parse a path from the payload
151
171
  def parse_path(field_name)
152
172
  value = payload[field_name]
@@ -155,6 +175,10 @@ module Floe
155
175
  end
156
176
 
157
177
  # parse predicate field at initialization time
178
+ # @param field_name [String] the compare key
179
+ # @param data_type [String] the data type of the variable
180
+ # When parsing operations (IntegerGreaterThan), this will be the operation data type (e.g.: Integer)
181
+ # When parsing type checks (IsString), this will always be a Boolean
158
182
  def parse_field(field_name, data_type)
159
183
  value = payload[field_name]
160
184
  return value if correct_type?(value, data_type)
@@ -165,6 +189,7 @@ module Floe
165
189
  # fetch a path at runtime
166
190
  def fetch_path(field_name, field_path, context, input)
167
191
  value = field_path.value(context, input)
192
+ # if this is an operation (GreaterThanPath), ensure the value is the correct type
168
193
  return value if type.nil? || correct_type?(value, type)
169
194
 
170
195
  runtime_field_error!(field_name, field_path.to_s, "required to point to a #{type}")
@@ -173,7 +198,7 @@ module Floe
173
198
  # if we have runtime checking, check against that type
174
199
  # otherwise assume checking a TYPE_CHECK predicate and check against Boolean
175
200
  def correct_type?(value, data_type)
176
- send("is_#{data_type.downcase}?".to_sym, value)
201
+ send(OPERATIONS[data_type], value, true)
177
202
  end
178
203
  end
179
204
  end
@@ -51,7 +51,7 @@ module Floe
51
51
  elsif !@next
52
52
  # top level nodes require a next
53
53
  missing_field_error!("Next")
54
- elsif !workflow_state?(@next, workflow)
54
+ elsif !workflow.state?(@next)
55
55
  # top level nodes require a next field that is found
56
56
  invalid_field_error!("Next", @next, "is not found in \"States\"")
57
57
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
4
+
3
5
  module Floe
4
6
  class Workflow
5
7
  class Context
@@ -25,6 +27,17 @@ module Floe
25
27
  raise Floe::InvalidExecutionInput, "Invalid State Machine Execution Input: #{err}: was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')"
26
28
  end
27
29
 
30
+ def prepare_start(start_at)
31
+ return if started?
32
+
33
+ state["Name"] = start_at
34
+ state["Input"] = execution["Input"].dup
35
+ state["Guid"] = SecureRandom.uuid
36
+
37
+ execution["Id"] ||= SecureRandom.uuid
38
+ execution["StartTime"] = Time.now.utc.iso8601
39
+ end
40
+
28
41
  def execution
29
42
  @context["Execution"]
30
43
  end
@@ -140,13 +153,22 @@ module Floe
140
153
  end
141
154
 
142
155
  def inspect
143
- format("#<%s: %s>", self.class.name, safe_context.inspect)
156
+ "#<#{self.class.name}: #{safe_context.inspect}>"
144
157
  end
145
158
 
146
159
  def to_h
147
160
  safe_context
148
161
  end
149
162
 
163
+ def ==(other)
164
+ other.kind_of?(self.class) && other.instance_variable_get(:@context) == @context
165
+ end
166
+ alias eql? ==
167
+
168
+ def hash
169
+ @context.hash
170
+ end
171
+
150
172
  private
151
173
 
152
174
  def safe_context
@@ -77,7 +77,7 @@ module Floe
77
77
  rule(:number => simple(:v)) { v.match(/[eE.]/) ? Float(v) : Integer(v) }
78
78
  rule(:jsonpath => simple(:v)) { Floe::Workflow::Path.value(v.to_s, context, input) }
79
79
 
80
- STATES_FORMAT_PLACEHOLDER = /(?<!\\)\{\}/.freeze
80
+ STATES_FORMAT_PLACEHOLDER = /(?<!\\)\{\}/
81
81
 
82
82
  rule(:states_format => {:args => subtree(:args)}) do
83
83
  args = Transformer.process_args(args(), "States.Format", [String, VariadicArgs[[String, TrueClass, FalseClass, Numeric, NilClass]]])
@@ -236,15 +236,15 @@ module Floe
236
236
 
237
237
  rule(:states_string_split => {:args => subtree(:args)}) do
238
238
  args = Transformer.process_args(args(), "States.StringSplit", [String, String])
239
- str, delimeter = *args
239
+ str, delimiter = *args
240
240
 
241
- case delimeter.size
241
+ case delimiter.size
242
242
  when 0
243
243
  str.empty? ? [] : [str]
244
244
  when 1
245
- str.split(delimeter)
245
+ str.split(delimiter)
246
246
  else
247
- str.split(/[#{Regexp.escape(delimeter)}]+/)
247
+ str.split(/[#{Regexp.escape(delimiter)}]+/)
248
248
  end
249
249
  end
250
250
 
@@ -48,7 +48,7 @@ module Floe
48
48
  end
49
49
 
50
50
  def check_dynamic_datatype(key, value)
51
- unless value.is_a?(String)
51
+ unless value.kind_of?(String)
52
52
  raise Floe::InvalidWorkflowError, "The value for the field \"#{key}\" must be a String that contains a valid Reference Path or Intrinsic Function expression"
53
53
  end
54
54
  end
@@ -43,7 +43,9 @@ module Floe
43
43
 
44
44
  # @return for incomplete Errno::EAGAIN, for completed 0
45
45
  def run_nonblock!(context)
46
- start(context) unless context.state_started?
46
+ # Only start the state if it isn't already started and it isn't waiting
47
+ # from a prior Retry.
48
+ start(context) unless started?(context) || waiting?(context)
47
49
  return Errno::EAGAIN unless ready?(context)
48
50
 
49
51
  finish(context)
@@ -89,13 +91,13 @@ module Floe
89
91
  def mark_error(context, exception)
90
92
  # InputPath or OutputPath were bad.
91
93
  context.next_state = nil
92
- context.output = {"Error" => exception.floe_error, "Cause" => exception.message}
94
+ context.output = exception.to_output
93
95
  # Since finish threw an exception, super was never called. Calling that now.
94
96
  mark_finished(context)
95
97
  end
96
98
 
97
99
  def ready?(context)
98
- !started?(context) || !running?(context)
100
+ (started?(context) && !running?(context)) || !waiting?(context)
99
101
  end
100
102
 
101
103
  def running?(context)
@@ -49,7 +49,7 @@ module Floe
49
49
  end
50
50
 
51
51
  def validate_state_default!(workflow)
52
- invalid_field_error!("Default", @default, "is not found in \"States\"") if @default && !workflow_state?(@default, workflow)
52
+ invalid_field_error!("Default", @default, "is not found in \"States\"") if @default && !workflow.state?(@default)
53
53
  end
54
54
  end
55
55
  end
@@ -58,7 +58,7 @@ module Floe
58
58
  "Execution" => {
59
59
  "Id" => context.execution["Id"]
60
60
  },
61
- "Map" => {
61
+ "Map" => {
62
62
  "Item" => {"Index" => index, "Value" => item}
63
63
  }
64
64
  }
@@ -13,7 +13,7 @@ module Floe
13
13
 
14
14
  def validate_state_next!(workflow)
15
15
  missing_field_error!("Next") if @next.nil? && !@end
16
- invalid_field_error!("Next", @next, "is not found in \"States\"") if @next && !workflow_state?(@next, workflow)
16
+ invalid_field_error!("Next", @next, "is not found in \"States\"") if @next && !workflow.state?(@next)
17
17
  end
18
18
  end
19
19
  end
@@ -9,29 +9,29 @@ module Floe
9
9
  include RetryCatchMixin
10
10
 
11
11
  attr_reader :credentials, :end, :heartbeat_seconds, :next, :parameters,
12
- :result_selector, :resource, :timeout_seconds, :retry, :catch,
13
- :input_path, :output_path, :result_path
12
+ :result_selector, :resource, :timeout_seconds, :timeout_seconds_path,
13
+ :retry, :catch, :input_path, :output_path, :result_path
14
14
 
15
15
  def initialize(workflow, name, payload)
16
16
  super
17
17
 
18
- @heartbeat_seconds = payload["HeartbeatSeconds"]
19
- @next = payload["Next"]
20
- @end = !!payload["End"]
21
- @resource = payload["Resource"]
22
-
18
+ @resource = payload["Resource"]
23
19
  missing_field_error!("Resource") unless @resource.kind_of?(String)
24
20
  @runner = wrap_parser_error("Resource", @resource) { Floe::Runner.for_resource(@resource) }
25
21
 
26
- @timeout_seconds = payload["TimeoutSeconds"]
27
- @retry = payload["Retry"].to_a.map.with_index { |retrier, i| Retrier.new(workflow, name + ["Retry", i.to_s], retrier) }
28
- @catch = payload["Catch"].to_a.map.with_index { |catcher, i| Catcher.new(workflow, name + ["Catch", i.to_s], catcher) }
29
- @input_path = Path.new(payload.fetch("InputPath", "$"))
30
- @output_path = Path.new(payload.fetch("OutputPath", "$"))
31
- @result_path = ReferencePath.new(payload.fetch("ResultPath", "$"))
32
- @parameters = PayloadTemplate.new(payload["Parameters"]) if payload["Parameters"]
33
- @result_selector = PayloadTemplate.new(payload["ResultSelector"]) if payload["ResultSelector"]
34
- @credentials = PayloadTemplate.new(payload["Credentials"]) if payload["Credentials"]
22
+ @next = payload["Next"]
23
+ @end = !!payload["End"]
24
+ @timeout_seconds = payload["TimeoutSeconds"]
25
+ @heartbeat_seconds = payload["HeartbeatSeconds"]
26
+ @retry = payload["Retry"].to_a.map.with_index { |retrier, i| Retrier.new(workflow, name + ["Retry", i.to_s], retrier) }
27
+ @catch = payload["Catch"].to_a.map.with_index { |catcher, i| Catcher.new(workflow, name + ["Catch", i.to_s], catcher) }
28
+ @input_path = Path.new(payload.fetch("InputPath", "$"))
29
+ @output_path = Path.new(payload.fetch("OutputPath", "$"))
30
+ @result_path = ReferencePath.new(payload.fetch("ResultPath", "$"))
31
+ @timeout_seconds_path = ReferencePath.new(payload["TimeoutSecondsPath"]) if payload["TimeoutSecondsPath"]
32
+ @parameters = PayloadTemplate.new(payload["Parameters"]) if payload["Parameters"]
33
+ @result_selector = PayloadTemplate.new(payload["ResultSelector"]) if payload["ResultSelector"]
34
+ @credentials = PayloadTemplate.new(payload["Credentials"]) if payload["Credentials"]
35
35
 
36
36
  validate_state!(workflow)
37
37
  end
@@ -39,6 +39,9 @@ module Floe
39
39
  def start(context)
40
40
  super
41
41
 
42
+ # Wakeup no later than timeout_seconds to check if the Resource has timed out
43
+ wait_until!(context, :seconds => timeout_seconds) if timeout_seconds
44
+
42
45
  input = process_input(context)
43
46
  secrets = credentials&.value(context, context.input)
44
47
  runner_context = runner.run_async!(resource, input, secrets, context)
@@ -48,27 +51,31 @@ module Floe
48
51
 
49
52
  def finish(context)
50
53
  output = runner.output(context.state["RunnerContext"])
54
+ raise Floe::ExecutionError.from_output(parse_error(output)) unless success?(context)
55
+
56
+ output = parse_output(output)
57
+ context.output = process_output(context, output)
51
58
 
52
- if success?(context)
53
- output = parse_output(output)
54
- context.output = process_output(context, output)
55
- else
56
- error = parse_error(output)
57
- retry_state!(context, error) || catch_error!(context, error) || fail_workflow!(context, error)
58
- end
59
59
  super
60
60
  ensure
61
61
  runner.cleanup(context.state["RunnerContext"])
62
62
  end
63
63
 
64
64
  def running?(context)
65
- return true if waiting?(context)
65
+ raise Floe::TimeoutError if timed_out?(context)
66
66
  return false if finished?(context)
67
67
 
68
68
  runner.status!(context.state["RunnerContext"])
69
69
  runner.running?(context.state["RunnerContext"])
70
70
  end
71
71
 
72
+ def mark_error(context, exception)
73
+ error = exception.to_output
74
+
75
+ retry_state!(context, error) || catch_error!(context, error) || fail_workflow!(context, error)
76
+ mark_finished(context)
77
+ end
78
+
72
79
  def end?
73
80
  @end
74
81
  end
@@ -79,12 +86,43 @@ module Floe
79
86
 
80
87
  def validate_state!(workflow)
81
88
  validate_state_next!(workflow)
89
+ validate_state_timeout_seconds!(workflow)
90
+ validate_state_timeout_seconds_path!(workflow)
91
+ end
92
+
93
+ def validate_state_timeout_seconds!(workflow)
94
+ return if @timeout_seconds.nil?
95
+ return if @timeout_seconds.kind_of?(Integer) && @timeout_seconds > 0
96
+
97
+ invalid_field_error!("TimeoutSeconds", @timeout_seconds, "must be positive, non-zero integer")
98
+ end
99
+
100
+ def validate_state_timeout_seconds_path!(workflow)
101
+ return if @timeout_seconds_path.nil? || @timeout_seconds.nil?
102
+
103
+ invalid_field_error!("TimeoutSecondsPath", nil, "cannot specify both \"TimeoutSeconds\" and \"TimeoutSecondsPath\"")
82
104
  end
83
105
 
84
106
  def success?(context)
85
107
  runner.success?(context.state["RunnerContext"])
86
108
  end
87
109
 
110
+ def timed_out?(context)
111
+ return false if timeout_seconds.nil? && timeout_seconds_path.nil?
112
+
113
+ timeout = timeout_seconds || timeout_seconds_path.value(context, context.input)
114
+ entered_time = Time.parse(context.state["EnteredTime"])
115
+
116
+ Time.now.utc > entered_time + timeout
117
+ end
118
+
119
+ def task_timed_out!(context)
120
+ context.state["RunnerContext"]["Error"] = "States.Timeout"
121
+ context.state["RunnerContext"]["Cause"] = "Task timed out"
122
+
123
+ false
124
+ end
125
+
88
126
  def parse_error(output)
89
127
  return if output.nil?
90
128
  return output if output.kind_of?(Hash)
data/lib/floe/workflow.rb CHANGED
@@ -150,15 +150,7 @@ module Floe
150
150
 
151
151
  # setup a workflow
152
152
  def start_workflow
153
- return if context.state_name
154
-
155
- context.state["Name"] = start_at
156
- context.state["Input"] = context.execution["Input"].dup
157
- context.state["Guid"] = SecureRandom.uuid
158
-
159
- context.execution["Id"] ||= SecureRandom.uuid
160
- context.execution["StartTime"] = Time.now.utc.iso8601
161
-
153
+ context.prepare_start(start_at)
162
154
  self
163
155
  end
164
156
 
@@ -183,7 +175,7 @@ module Floe
183
175
 
184
176
  # if rerunning due to an error (and we are using Retry)
185
177
  if context.state_name == context.next_state && context.failed? && context.state.key?("Retrier")
186
- next_state.merge!(context.state.slice("RetryCount", "Input", "Retrier"))
178
+ next_state.merge!(context.state.slice("RetryCount", "Input", "Retrier", "WaitUntil"))
187
179
  else
188
180
  next_state["Input"] = context.output
189
181
  end
@@ -77,6 +77,10 @@ module Floe
77
77
  context.output.to_json if end?(context)
78
78
  end
79
79
 
80
+ def state?(state_name)
81
+ @payload["States"].include?(state_name)
82
+ end
83
+
80
84
  private
81
85
 
82
86
  def step!(context)
@@ -102,7 +106,7 @@ module Floe
102
106
  def validate_workflow!
103
107
  missing_field_error!("States") if @states.empty?
104
108
  missing_field_error!("StartAt") if @start_at.nil?
105
- invalid_field_error!("StartAt", @start_at, "is not found in \"States\"") unless workflow_state?(@start_at, self)
109
+ invalid_field_error!("StartAt", @start_at, "is not found in \"States\"") unless state?(@start_at)
106
110
  end
107
111
  end
108
112
  end
data/lib/floe.rb CHANGED
@@ -54,17 +54,36 @@ module Floe
54
54
  class InvalidExecutionInput < Error; end
55
55
 
56
56
  class ExecutionError < Error
57
+ def self.from_output(output)
58
+ raise ArgumentError unless output.kind_of?(Hash) && output.key?("Error")
59
+
60
+ new(output["Cause"], output["Error"])
61
+ end
62
+
57
63
  attr_reader :floe_error
58
64
 
59
65
  def initialize(message, floe_error = "States.Runtime")
60
66
  super(message)
61
67
  @floe_error = floe_error
62
68
  end
69
+
70
+ def to_output
71
+ {"Error" => floe_error}.tap do |output|
72
+ # If there is no "Cause" then ::Exception will use the exception class name
73
+ output["Cause"] = message if message != self.class.name.to_s
74
+ end
75
+ end
63
76
  end
64
77
 
65
78
  class PathError < ExecutionError
66
79
  end
67
80
 
81
+ class TimeoutError < ExecutionError
82
+ def initialize(message = nil)
83
+ super(message, "States.Timeout")
84
+ end
85
+ end
86
+
68
87
  def self.logger
69
88
  @logger ||= NullLogger.new
70
89
  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.17.1
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Developers
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.2'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: awesome_spawn
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -24,7 +38,7 @@ dependencies:
24
38
  - !ruby/object:Gem::Version
25
39
  version: '1.6'
26
40
  - !ruby/object:Gem::Dependency
27
- name: io-wait
41
+ name: faraday
28
42
  requirement: !ruby/object:Gem::Requirement
29
43
  requirements:
30
44
  - - ">="
@@ -38,103 +52,103 @@ dependencies:
38
52
  - !ruby/object:Gem::Version
39
53
  version: '0'
40
54
  - !ruby/object:Gem::Dependency
41
- name: jsonpath
55
+ name: faraday-follow_redirects
42
56
  requirement: !ruby/object:Gem::Requirement
43
57
  requirements:
44
- - - "~>"
58
+ - - ">="
45
59
  - !ruby/object:Gem::Version
46
- version: '1.1'
60
+ version: '0'
47
61
  type: :runtime
48
62
  prerelease: false
49
63
  version_requirements: !ruby/object:Gem::Requirement
50
64
  requirements:
51
- - - "~>"
65
+ - - ">="
52
66
  - !ruby/object:Gem::Version
53
- version: '1.1'
67
+ version: '0'
54
68
  - !ruby/object:Gem::Dependency
55
- name: kubeclient
69
+ name: io-wait
56
70
  requirement: !ruby/object:Gem::Requirement
57
71
  requirements:
58
- - - "~>"
72
+ - - ">="
59
73
  - !ruby/object:Gem::Version
60
- version: '4.7'
74
+ version: '0'
61
75
  type: :runtime
62
76
  prerelease: false
63
77
  version_requirements: !ruby/object:Gem::Requirement
64
78
  requirements:
65
- - - "~>"
79
+ - - ">="
66
80
  - !ruby/object:Gem::Version
67
- version: '4.7'
81
+ version: '0'
68
82
  - !ruby/object:Gem::Dependency
69
- name: optimist
83
+ name: json
70
84
  requirement: !ruby/object:Gem::Requirement
71
85
  requirements:
72
86
  - - "~>"
73
87
  - !ruby/object:Gem::Version
74
- version: '3.0'
88
+ version: '2.10'
75
89
  type: :runtime
76
90
  prerelease: false
77
91
  version_requirements: !ruby/object:Gem::Requirement
78
92
  requirements:
79
93
  - - "~>"
80
94
  - !ruby/object:Gem::Version
81
- version: '3.0'
95
+ version: '2.10'
82
96
  - !ruby/object:Gem::Dependency
83
- name: parslet
97
+ name: jsonpath
84
98
  requirement: !ruby/object:Gem::Requirement
85
99
  requirements:
86
100
  - - "~>"
87
101
  - !ruby/object:Gem::Version
88
- version: '2.0'
102
+ version: '1.1'
89
103
  type: :runtime
90
104
  prerelease: false
91
105
  version_requirements: !ruby/object:Gem::Requirement
92
106
  requirements:
93
107
  - - "~>"
94
108
  - !ruby/object:Gem::Version
95
- version: '2.0'
109
+ version: '1.1'
96
110
  - !ruby/object:Gem::Dependency
97
- name: json
111
+ name: kubeclient
98
112
  requirement: !ruby/object:Gem::Requirement
99
113
  requirements:
100
114
  - - "~>"
101
115
  - !ruby/object:Gem::Version
102
- version: '2.10'
116
+ version: '4.7'
103
117
  type: :runtime
104
118
  prerelease: false
105
119
  version_requirements: !ruby/object:Gem::Requirement
106
120
  requirements:
107
121
  - - "~>"
108
122
  - !ruby/object:Gem::Version
109
- version: '2.10'
123
+ version: '4.7'
110
124
  - !ruby/object:Gem::Dependency
111
- name: faraday
125
+ name: optimist
112
126
  requirement: !ruby/object:Gem::Requirement
113
127
  requirements:
114
- - - ">="
128
+ - - "~>"
115
129
  - !ruby/object:Gem::Version
116
- version: '0'
130
+ version: '3.0'
117
131
  type: :runtime
118
132
  prerelease: false
119
133
  version_requirements: !ruby/object:Gem::Requirement
120
134
  requirements:
121
- - - ">="
135
+ - - "~>"
122
136
  - !ruby/object:Gem::Version
123
- version: '0'
137
+ version: '3.0'
124
138
  - !ruby/object:Gem::Dependency
125
- name: faraday-follow_redirects
139
+ name: parslet
126
140
  requirement: !ruby/object:Gem::Requirement
127
141
  requirements:
128
- - - ">="
142
+ - - "~>"
129
143
  - !ruby/object:Gem::Version
130
- version: '0'
144
+ version: '2.0'
131
145
  type: :runtime
132
146
  prerelease: false
133
147
  version_requirements: !ruby/object:Gem::Requirement
134
148
  requirements:
135
- - - ">="
149
+ - - "~>"
136
150
  - !ruby/object:Gem::Version
137
- version: '0'
151
+ version: '2.0'
138
152
  - !ruby/object:Gem::Dependency
139
153
  name: manageiq-style
140
154
  requirement: !ruby/object:Gem::Requirement
@@ -211,10 +225,8 @@ executables:
211
225
  extensions: []
212
226
  extra_rdoc_files: []
213
227
  files:
214
- - ".codeclimate.yml"
215
228
  - ".rspec"
216
229
  - ".rubocop.yml"
217
- - ".rubocop_cc.yml"
218
230
  - ".rubocop_local.yml"
219
231
  - ".yamllint"
220
232
  - CHANGELOG.md
@@ -224,6 +236,7 @@ files:
224
236
  - Rakefile
225
237
  - examples/everything.asl
226
238
  - examples/http.asl
239
+ - examples/log.asl
227
240
  - examples/map.asl
228
241
  - examples/parallel.asl
229
242
  - examples/set-credential.asl
data/.codeclimate.yml DELETED
@@ -1,36 +0,0 @@
1
- version: '2'
2
- prepare:
3
- fetch:
4
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/.rubocop_base.yml
5
- path: ".rubocop_base.yml"
6
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/.rubocop_cc_base.yml
7
- path: ".rubocop_cc_base.yml"
8
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/styles/base.yml
9
- path: styles/base.yml
10
- - url: https://raw.githubusercontent.com/ManageIQ/manageiq-style/master/styles/cc_base.yml
11
- path: styles/cc_base.yml
12
- checks:
13
- argument-count:
14
- enabled: false
15
- complex-logic:
16
- enabled: false
17
- file-lines:
18
- enabled: false
19
- method-complexity:
20
- config:
21
- threshold: 11
22
- method-count:
23
- enabled: false
24
- method-lines:
25
- enabled: false
26
- nested-control-flow:
27
- enabled: false
28
- return-statements:
29
- enabled: false
30
- plugins:
31
- rubocop:
32
- enabled: true
33
- config: ".rubocop_cc.yml"
34
- channel: rubocop-1-56-3
35
- exclude_patterns:
36
- - spec/
data/.rubocop_cc.yml DELETED
@@ -1,4 +0,0 @@
1
- inherit_from:
2
- - ".rubocop_base.yml"
3
- - ".rubocop_cc_base.yml"
4
- - ".rubocop_local.yml"