floe 0.12.0 → 0.13.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 +10 -1
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/choice_rule/data.rb +17 -5
- data/lib/floe/workflow/intrinsic_function/parser.rb +9 -1
- data/lib/floe/workflow/intrinsic_function/transformer.rb +76 -6
- data/lib/floe/workflow/path.rb +12 -1
- data/lib/floe/workflow/state.rb +21 -3
- data/lib/floe.rb +1 -0
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: abe15498a790293b2a375c4538ed649bfa577edd1b7eaa38d02266fc4f7fc576
         | 
| 4 | 
            +
              data.tar.gz: 733e0e6687ca143de8a74214f13b4a78118333a99f89a9a09a9998ab0319768a
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d5dfc65c86e68d39b7c9bbeedd20e2a3ea844f158d601bb72bf35595e6d6597ac6571f7bc9ad6802f8ed33d6753b4906e7745a3e46cb38790492869aa95e6a02
         | 
| 7 | 
            +
              data.tar.gz: 913032cb3a042e8b3464f05c7e0e65401f8532b28ded1cebd57df310899dcc8bd1bc26d9307a6b824837474c0e3ee9144477147779e0b40b6fd9c9fecfc7bddc
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -4,6 +4,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). | |
| 4 4 |  | 
| 5 5 | 
             
            ## [Unreleased]
         | 
| 6 6 |  | 
| 7 | 
            +
            ## [0.13.0] - 2024-08-12
         | 
| 8 | 
            +
            ### Added
         | 
| 9 | 
            +
            - Choice rule payload validation path ([#253](https://github.com/ManageIQ/floe/pull/253))
         | 
| 10 | 
            +
            - Intrinsics JsonToString and StringToJson ([#256](https://github.com/ManageIQ/floe/pull/256))
         | 
| 11 | 
            +
            - Add States.Format intrinsic function ([#258](https://github.com/ManageIQ/floe/pull/258))
         | 
| 12 | 
            +
            - Intrinsics States.JsonMerge ([#255](https://github.com/ManageIQ/floe/pull/255))
         | 
| 13 | 
            +
            - Enable support for Hashes in States.Hash ([#260](https://github.com/ManageIQ/floe/pull/260))
         | 
| 14 | 
            +
             | 
| 7 15 | 
             
            ## [0.12.0] - 2024-07-31
         | 
| 8 16 | 
             
            ### Added
         | 
| 9 17 | 
             
            - Set Floe.logger.level if DEBUG env var set ([#234](https://github.com/ManageIQ/floe/pull/234))
         | 
| @@ -225,7 +233,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). | |
| 225 233 | 
             
            ### Added
         | 
| 226 234 | 
             
            - Initial release
         | 
| 227 235 |  | 
| 228 | 
            -
            [Unreleased]: https://github.com/ManageIQ/floe/compare/v0. | 
| 236 | 
            +
            [Unreleased]: https://github.com/ManageIQ/floe/compare/v0.13.0...HEAD
         | 
| 237 | 
            +
            [0.13.0]: https://github.com/ManageIQ/floe/compare/v0.12.0...v0.13.0
         | 
| 229 238 | 
             
            [0.12.0]: https://github.com/ManageIQ/floe/compare/v0.11.3...v0.12.0
         | 
| 230 239 | 
             
            [0.11.3]: https://github.com/ManageIQ/floe/compare/v0.11.2...v0.11.3
         | 
| 231 240 | 
             
            [0.11.2]: https://github.com/ManageIQ/floe/compare/v0.11.1...v0.11.2
         | 
    
        data/lib/floe/version.rb
    CHANGED
    
    
| @@ -5,14 +5,13 @@ module Floe | |
| 5 5 | 
             
                class ChoiceRule
         | 
| 6 6 | 
             
                  class Data < Floe::Workflow::ChoiceRule
         | 
| 7 7 | 
             
                    def true?(context, input)
         | 
| 8 | 
            +
                      return presence_check(context, input) if compare_key == "IsPresent"
         | 
| 9 | 
            +
             | 
| 8 10 | 
             
                      lhs = variable_value(context, input)
         | 
| 9 11 | 
             
                      rhs = compare_value(context, input)
         | 
| 10 12 |  | 
| 11 | 
            -
                      validate!(lhs)
         | 
| 12 | 
            -
             | 
| 13 13 | 
             
                      case compare_key
         | 
| 14 14 | 
             
                      when "IsNull" then is_null?(lhs)
         | 
| 15 | 
            -
                      when "IsPresent" then is_present?(lhs)
         | 
| 16 15 | 
             
                      when "IsNumeric" then is_numeric?(lhs)
         | 
| 17 16 | 
             
                      when "IsString" then is_string?(lhs)
         | 
| 18 17 | 
             
                      when "IsBoolean" then is_boolean?(lhs)
         | 
| @@ -47,8 +46,21 @@ module Floe | |
| 47 46 |  | 
| 48 47 | 
             
                    private
         | 
| 49 48 |  | 
| 50 | 
            -
                    def  | 
| 51 | 
            -
                       | 
| 49 | 
            +
                    def presence_check(context, input)
         | 
| 50 | 
            +
                      # Get the right hand side for {"Variable": "$.foo", "IsPresent": true} i.e.: true
         | 
| 51 | 
            +
                      # If true  then return true when present.
         | 
| 52 | 
            +
                      # If false then return true when not present.
         | 
| 53 | 
            +
                      rhs = compare_value(context, input)
         | 
| 54 | 
            +
                      # Don't need the variable_value, just need to see if the path finds the value.
         | 
| 55 | 
            +
                      variable_value(context, input)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      # The variable_value is present
         | 
| 58 | 
            +
                      # If rhs is true, then presence check was successful, return true.
         | 
| 59 | 
            +
                      rhs
         | 
| 60 | 
            +
                    rescue Floe::PathError
         | 
| 61 | 
            +
                      # variable_value is not present. (the path lookup threw an error)
         | 
| 62 | 
            +
                      # If rhs is false, then it successfully wasn't present, return true.
         | 
| 63 | 
            +
                      !rhs
         | 
| 52 64 | 
             
                    end
         | 
| 53 65 |  | 
| 54 66 | 
             
                    def is_null?(value) # rubocop:disable Naming/PredicateName
         | 
| @@ -54,6 +54,9 @@ module Floe | |
| 54 54 | 
             
                    end
         | 
| 55 55 |  | 
| 56 56 | 
             
                    [
         | 
| 57 | 
            +
                      :states_format,          "States.Format",
         | 
| 58 | 
            +
                      :states_string_to_json,  "States.StringToJson",
         | 
| 59 | 
            +
                      :states_json_to_string,  "States.JsonToString",
         | 
| 57 60 | 
             
                      :states_array,           "States.Array",
         | 
| 58 61 | 
             
                      :states_array_partition, "States.ArrayPartition",
         | 
| 59 62 | 
             
                      :states_array_contains,  "States.ArrayContains",
         | 
| @@ -64,6 +67,7 @@ module Floe | |
| 64 67 | 
             
                      :states_base64_encode,   "States.Base64Encode",
         | 
| 65 68 | 
             
                      :states_base64_decode,   "States.Base64Decode",
         | 
| 66 69 | 
             
                      :states_hash,            "States.Hash",
         | 
| 70 | 
            +
                      :states_json_merge,      "States.JsonMerge",
         | 
| 67 71 | 
             
                      :states_math_random,     "States.MathRandom",
         | 
| 68 72 | 
             
                      :states_math_add,        "States.MathAdd",
         | 
| 69 73 | 
             
                      :states_string_split,    "States.StringSplit",
         | 
| @@ -77,7 +81,10 @@ module Floe | |
| 77 81 | 
             
                    end
         | 
| 78 82 |  | 
| 79 83 | 
             
                    rule(:expression) do
         | 
| 80 | 
            -
                       | 
| 84 | 
            +
                      states_format |
         | 
| 85 | 
            +
                        states_string_to_json |
         | 
| 86 | 
            +
                        states_json_to_string |
         | 
| 87 | 
            +
                        states_array |
         | 
| 81 88 | 
             
                        states_array_partition |
         | 
| 82 89 | 
             
                        states_array_contains |
         | 
| 83 90 | 
             
                        states_array_range |
         | 
| @@ -87,6 +94,7 @@ module Floe | |
| 87 94 | 
             
                        states_base64_encode |
         | 
| 88 95 | 
             
                        states_base64_decode |
         | 
| 89 96 | 
             
                        states_hash |
         | 
| 97 | 
            +
                        states_json_merge |
         | 
| 90 98 | 
             
                        states_math_random |
         | 
| 91 99 | 
             
                        states_math_add |
         | 
| 92 100 | 
             
                        states_string_split |
         | 
| @@ -9,6 +9,7 @@ module Floe | |
| 9 9 | 
             
                class IntrinsicFunction
         | 
| 10 10 | 
             
                  class Transformer < Parslet::Transform
         | 
| 11 11 | 
             
                    OptionalArg = Struct.new(:type)
         | 
| 12 | 
            +
                    VariadicArgs = Struct.new(:type)
         | 
| 12 13 |  | 
| 13 14 | 
             
                    class << self
         | 
| 14 15 | 
             
                      def process_args(args, function, signature = nil)
         | 
| @@ -37,20 +38,33 @@ module Floe | |
| 37 38 |  | 
| 38 39 | 
             
                      def check_arity(args, function, signature)
         | 
| 39 40 | 
             
                        if signature.any?(OptionalArg)
         | 
| 40 | 
            -
                           | 
| 41 | 
            -
                          signature_size = ( | 
| 41 | 
            +
                          signature_required = signature.reject { |a| a.kind_of?(OptionalArg) }
         | 
| 42 | 
            +
                          signature_size = (signature_required.size..signature.size)
         | 
| 42 43 |  | 
| 43 44 | 
             
                          raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature_size})" unless signature_size.include?(args.size)
         | 
| 45 | 
            +
                        elsif signature.any?(VariadicArgs)
         | 
| 46 | 
            +
                          signature_required = signature.reject { |a| a.kind_of?(VariadicArgs) }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                          raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected at least #{signature_required.size})" unless args.size >= signature_required.size
         | 
| 44 49 | 
             
                        else
         | 
| 45 50 | 
             
                          raise ArgumentError, "wrong number of arguments to #{function} (given #{args.size}, expected #{signature.size})" unless signature.size == args.size
         | 
| 46 51 | 
             
                        end
         | 
| 47 52 | 
             
                      end
         | 
| 48 53 |  | 
| 49 54 | 
             
                      def check_types(args, function, signature)
         | 
| 55 | 
            +
                        # Adjust the signature for VariadicArgs to create a copy of the expected type for each given arg
         | 
| 56 | 
            +
                        if signature.last.kind_of?(VariadicArgs)
         | 
| 57 | 
            +
                          signature = signature[0..-2] + Array.new(args.size - signature.size + 1, signature.last.type)
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
             | 
| 50 60 | 
             
                        args.zip(signature).each_with_index do |(arg, type), index|
         | 
| 51 61 | 
             
                          type = type.type if type.kind_of?(OptionalArg)
         | 
| 52 62 |  | 
| 53 | 
            -
                           | 
| 63 | 
            +
                          if type.kind_of?(Array)
         | 
| 64 | 
            +
                            raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected one of #{type.join(", ")})" unless type.any? { |t| arg.kind_of?(t) }
         | 
| 65 | 
            +
                          else
         | 
| 66 | 
            +
                            raise ArgumentError, "wrong type for argument #{index + 1} to #{function} (given #{arg.class}, expected #{type})" unless arg.kind_of?(type)
         | 
| 67 | 
            +
                          end
         | 
| 54 68 | 
             
                        end
         | 
| 55 69 | 
             
                      end
         | 
| 56 70 | 
             
                    end
         | 
| @@ -63,6 +77,50 @@ module Floe | |
| 63 77 | 
             
                    rule(:number   => simple(:v)) { v.match(/[eE.]/) ? Float(v) : Integer(v) }
         | 
| 64 78 | 
             
                    rule(:jsonpath => simple(:v)) { Floe::Workflow::Path.value(v.to_s, context, input) }
         | 
| 65 79 |  | 
| 80 | 
            +
                    STATES_FORMAT_PLACEHOLDER = /(?<!\\)\{\}/.freeze
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    rule(:states_format => {:args => subtree(:args)}) do
         | 
| 83 | 
            +
                      args = Transformer.process_args(args(), "States.Format", [String, VariadicArgs[[String, TrueClass, FalseClass, Integer, Float, NilClass]]])
         | 
| 84 | 
            +
                      str, *rest = *args
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      # TODO: Handle templates with escaped characters, including invalid templates
         | 
| 87 | 
            +
                      #   See https://states-language.net/#intrinsic-functions (number 6)
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                      expected_args = str.scan(STATES_FORMAT_PLACEHOLDER).size
         | 
| 90 | 
            +
                      actual_args = rest.size
         | 
| 91 | 
            +
                      if expected_args != actual_args
         | 
| 92 | 
            +
                        raise ArgumentError, "number of arguments to States.Format do not match the occurrences of {} (given #{actual_args}, expected #{expected_args})"
         | 
| 93 | 
            +
                      end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                      rest.each do |arg|
         | 
| 96 | 
            +
                        str = str.sub(STATES_FORMAT_PLACEHOLDER, arg.nil? ? "null" : arg.to_s)
         | 
| 97 | 
            +
                      end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                      # TODO: Handle arguments that have escape characters within them but are interpolated
         | 
| 100 | 
            +
                      str.gsub!("\\'", "'")
         | 
| 101 | 
            +
                      str.gsub!("\\{", "{")
         | 
| 102 | 
            +
                      str.gsub!("\\}", "}")
         | 
| 103 | 
            +
                      str.gsub!("\\\\", "\\")
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                      str
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    rule(:states_json_to_string => {:args => subtree(:args)}) do
         | 
| 109 | 
            +
                      args = Transformer.process_args(args(), "States.JsonToString", [Object])
         | 
| 110 | 
            +
                      json = args.first
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                      JSON.generate(json)
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    rule(:states_string_to_json => {:args => subtree(:args)}) do
         | 
| 116 | 
            +
                      args = Transformer.process_args(args(), "States.StringToJson", [String])
         | 
| 117 | 
            +
                      str = args.first
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                      JSON.parse(str)
         | 
| 120 | 
            +
                    rescue JSON::ParserError => e
         | 
| 121 | 
            +
                      raise ArgumentError, "invalid value for argument 1 to States.StringToJson (invalid json: #{e.message})"
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
             | 
| 66 124 | 
             
                    rule(:states_array => {:args => subtree(:args)}) do
         | 
| 67 125 | 
             
                      Transformer.process_args(args, "States.Array")
         | 
| 68 126 | 
             
                    end
         | 
| @@ -135,7 +193,7 @@ module Floe | |
| 135 193 | 
             
                    rule(:states_hash => {:args => subtree(:args)}) do
         | 
| 136 194 | 
             
                      args = Transformer.process_args(args(), "States.Hash", [Object, String])
         | 
| 137 195 | 
             
                      data, algorithm = *args
         | 
| 138 | 
            -
             | 
| 196 | 
            +
             | 
| 139 197 | 
             
                      if data.nil?
         | 
| 140 198 | 
             
                        raise ArgumentError, "invalid value for argument 1 to States.Hash (given null, expected non-null)"
         | 
| 141 199 | 
             
                      end
         | 
| @@ -147,8 +205,20 @@ module Floe | |
| 147 205 |  | 
| 148 206 | 
             
                      require "openssl"
         | 
| 149 207 | 
             
                      algorithm = algorithm.sub("-", "")
         | 
| 150 | 
            -
                      data = data | 
| 151 | 
            -
                      OpenSSL::Digest.hexdigest(algorithm, data)
         | 
| 208 | 
            +
                      data = JSON.generate(data) unless data.kind_of?(String)
         | 
| 209 | 
            +
                      OpenSSL::Digest.hexdigest(algorithm, data).force_encoding("UTF-8")
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                    rule(:states_json_merge => {:args => subtree(:args)}) do
         | 
| 213 | 
            +
                      args = Transformer.process_args(args(), "States.JsonMerge", [Hash, Hash, [TrueClass, FalseClass]])
         | 
| 214 | 
            +
                      left, right, deep = *args
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                      if deep
         | 
| 217 | 
            +
                        # NOTE: not implemented by AWS Step Functions and nuances not defined in docs
         | 
| 218 | 
            +
                        left.merge(right) { |_key, l, r| l.kind_of?(Hash) && r.kind_of?(Hash) ? l.merge(r) : r }
         | 
| 219 | 
            +
                      else
         | 
| 220 | 
            +
                        left.merge(right)
         | 
| 221 | 
            +
                      end
         | 
| 152 222 | 
             
                    end
         | 
| 153 223 |  | 
| 154 224 | 
             
                    rule(:states_math_random => {:args => subtree(:args)}) do
         | 
    
        data/lib/floe/workflow/path.rb
    CHANGED
    
    | @@ -34,7 +34,18 @@ module Floe | |
| 34 34 | 
             
                    return obj if path == "$"
         | 
| 35 35 |  | 
| 36 36 | 
             
                    results = JsonPath.on(obj, path)
         | 
| 37 | 
            -
                    results.count | 
| 37 | 
            +
                    case results.count
         | 
| 38 | 
            +
                    when 0
         | 
| 39 | 
            +
                      raise Floe::PathError, "Path [#{payload}] references an invalid value"
         | 
| 40 | 
            +
                    when 1
         | 
| 41 | 
            +
                      results.first
         | 
| 42 | 
            +
                    else
         | 
| 43 | 
            +
                      results
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def to_s
         | 
| 48 | 
            +
                    payload
         | 
| 38 49 | 
             
                  end
         | 
| 39 50 | 
             
                end
         | 
| 40 51 | 
             
              end
         | 
    
        data/lib/floe/workflow/state.rb
    CHANGED
    
    | @@ -48,17 +48,27 @@ module Floe | |
| 48 48 | 
             
                    return Errno::EAGAIN unless ready?(context)
         | 
| 49 49 |  | 
| 50 50 | 
             
                    finish(context)
         | 
| 51 | 
            +
                  rescue Floe::Error => e
         | 
| 52 | 
            +
                    mark_error(context, e)
         | 
| 51 53 | 
             
                  end
         | 
| 52 54 |  | 
| 53 55 | 
             
                  def start(context)
         | 
| 56 | 
            +
                    mark_started(context)
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def finish(context)
         | 
| 60 | 
            +
                    mark_finished(context)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def mark_started(context)
         | 
| 54 64 | 
             
                    context.state["EnteredTime"] = Time.now.utc.iso8601
         | 
| 55 65 |  | 
| 56 66 | 
             
                    logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...")
         | 
| 57 67 | 
             
                  end
         | 
| 58 68 |  | 
| 59 | 
            -
                  def  | 
| 60 | 
            -
                    finished_time | 
| 61 | 
            -
                    entered_time | 
| 69 | 
            +
                  def mark_finished(context)
         | 
| 70 | 
            +
                    finished_time = Time.now.utc
         | 
| 71 | 
            +
                    entered_time  = Time.parse(context.state["EnteredTime"])
         | 
| 62 72 |  | 
| 63 73 | 
             
                    context.state["FinishedTime"] ||= finished_time.iso8601
         | 
| 64 74 | 
             
                    context.state["Duration"]       = finished_time - entered_time
         | 
| @@ -69,6 +79,14 @@ module Floe | |
| 69 79 | 
             
                    0
         | 
| 70 80 | 
             
                  end
         | 
| 71 81 |  | 
| 82 | 
            +
                  def mark_error(context, exception)
         | 
| 83 | 
            +
                    # InputPath or OutputPath were bad.
         | 
| 84 | 
            +
                    context.next_state = nil
         | 
| 85 | 
            +
                    context.output     = {"Error" => "States.Runtime", "Cause" => exception.message}
         | 
| 86 | 
            +
                    # Since finish threw an exception, super was never called. Calling that now.
         | 
| 87 | 
            +
                    mark_finished(context)
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 72 90 | 
             
                  def ready?(context)
         | 
| 73 91 | 
             
                    !context.state_started? || !running?(context)
         | 
| 74 92 | 
             
                  end
         | 
    
        data/lib/floe.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: floe
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.13.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - ManageIQ Developers
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024-08- | 
| 11 | 
            +
            date: 2024-08-12 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: awesome_spawn
         |