floe 0.11.2 → 0.12.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/.codeclimate.yml +16 -0
- data/.yamllint +1 -3
- data/CHANGELOG.md +51 -1
- data/Gemfile +1 -1
- data/LICENSE.txt +202 -0
- data/README.md +5 -1
- data/exe/floe +3 -72
- data/floe.gemspec +5 -4
- data/lib/floe/cli.rb +86 -0
- data/lib/floe/container_runner/docker_mixin.rb +3 -0
- data/lib/floe/runner.rb +9 -4
- data/lib/floe/validation_mixin.rb +49 -0
- data/lib/floe/version.rb +1 -1
- data/lib/floe/workflow/catcher.rb +17 -3
- data/lib/floe/workflow/choice_rule.rb +14 -9
- data/lib/floe/workflow/context.rb +27 -5
- data/lib/floe/workflow/error_matcher_mixin.rb +17 -0
- data/lib/floe/workflow/intrinsic_function/parser.rb +100 -0
- data/lib/floe/workflow/intrinsic_function/transformer.rb +196 -0
- data/lib/floe/workflow/intrinsic_function.rb +34 -0
- data/lib/floe/workflow/path.rb +7 -1
- data/lib/floe/workflow/payload_template.rb +7 -4
- data/lib/floe/workflow/reference_path.rb +2 -5
- data/lib/floe/workflow/retrier.rb +11 -4
- data/lib/floe/workflow/state.rb +33 -46
- data/lib/floe/workflow/states/choice.rb +12 -11
- data/lib/floe/workflow/states/fail.rb +3 -3
- data/lib/floe/workflow/states/input_output_mixin.rb +8 -8
- data/lib/floe/workflow/states/non_terminal_mixin.rb +6 -6
- data/lib/floe/workflow/states/pass.rb +7 -6
- data/lib/floe/workflow/states/succeed.rb +12 -3
- data/lib/floe/workflow/states/task.rb +35 -30
- data/lib/floe/workflow/states/wait.rb +8 -7
- data/lib/floe/workflow.rb +75 -23
- data/lib/floe.rb +6 -0
- metadata +31 -22
    
        data/lib/floe/workflow/state.rb
    CHANGED
    
    | @@ -4,114 +4,101 @@ module Floe | |
| 4 4 | 
             
              class Workflow
         | 
| 5 5 | 
             
                class State
         | 
| 6 6 | 
             
                  include Logging
         | 
| 7 | 
            +
                  include ValidationMixin
         | 
| 7 8 |  | 
| 8 9 | 
             
                  class << self
         | 
| 9 10 | 
             
                    def build!(workflow, name, payload)
         | 
| 10 11 | 
             
                      state_type = payload["Type"]
         | 
| 11 | 
            -
                       | 
| 12 | 
            +
                      missing_field_error!(name, "Type") if payload["Type"].nil?
         | 
| 13 | 
            +
                      invalid_field_error!(name[0..-2], "Name", name.last, "must be less than or equal to 80 characters") if name.last.length > 80
         | 
| 12 14 |  | 
| 13 15 | 
             
                      begin
         | 
| 14 16 | 
             
                        klass = Floe::Workflow::States.const_get(state_type)
         | 
| 15 17 | 
             
                      rescue NameError
         | 
| 16 | 
            -
                         | 
| 18 | 
            +
                        invalid_field_error!(name, "Type", state_type, "is not valid")
         | 
| 17 19 | 
             
                      end
         | 
| 18 20 |  | 
| 19 21 | 
             
                      klass.new(workflow, name, payload)
         | 
| 20 22 | 
             
                    end
         | 
| 21 23 | 
             
                  end
         | 
| 22 24 |  | 
| 23 | 
            -
                  attr_reader : | 
| 25 | 
            +
                  attr_reader :comment, :name, :type, :payload
         | 
| 24 26 |  | 
| 25 | 
            -
                  def initialize( | 
| 26 | 
            -
                    @workflow = workflow
         | 
| 27 | 
            +
                  def initialize(_workflow, name, payload)
         | 
| 27 28 | 
             
                    @name     = name
         | 
| 28 29 | 
             
                    @payload  = payload
         | 
| 29 30 | 
             
                    @type     = payload["Type"]
         | 
| 30 31 | 
             
                    @comment  = payload["Comment"]
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                    raise Floe::InvalidWorkflowError, "Missing \"Type\" field in state [#{name}]" if payload["Type"].nil?
         | 
| 33 | 
            -
                    raise Floe::InvalidWorkflowError, "State name [#{name}] must be less than or equal to 80 characters" if name.length > 80
         | 
| 34 32 | 
             
                  end
         | 
| 35 33 |  | 
| 36 | 
            -
                  def wait(timeout: nil)
         | 
| 34 | 
            +
                  def wait(context, timeout: nil)
         | 
| 37 35 | 
             
                    start = Time.now.utc
         | 
| 38 36 |  | 
| 39 37 | 
             
                    loop do
         | 
| 40 | 
            -
                      return 0             if ready?
         | 
| 38 | 
            +
                      return 0             if ready?(context)
         | 
| 41 39 | 
             
                      return Errno::EAGAIN if timeout && (timeout.zero? || Time.now.utc - start > timeout)
         | 
| 42 40 |  | 
| 43 41 | 
             
                      sleep(1)
         | 
| 44 42 | 
             
                    end
         | 
| 45 43 | 
             
                  end
         | 
| 46 44 |  | 
| 47 | 
            -
                   | 
| 48 | 
            -
             | 
| 49 | 
            -
                     | 
| 45 | 
            +
                  # @return for incomplete Errno::EAGAIN, for completed 0
         | 
| 46 | 
            +
                  def run_nonblock!(context)
         | 
| 47 | 
            +
                    start(context) unless context.state_started?
         | 
| 48 | 
            +
                    return Errno::EAGAIN unless ready?(context)
         | 
| 50 49 |  | 
| 51 | 
            -
                    finish
         | 
| 50 | 
            +
                    finish(context)
         | 
| 52 51 | 
             
                  end
         | 
| 53 52 |  | 
| 54 | 
            -
                  def start( | 
| 55 | 
            -
                     | 
| 56 | 
            -
             | 
| 57 | 
            -
                    context.execution["StartTime"] ||= start_time
         | 
| 58 | 
            -
                    context.state["Guid"]            = SecureRandom.uuid
         | 
| 59 | 
            -
                    context.state["EnteredTime"]     = start_time
         | 
| 53 | 
            +
                  def start(context)
         | 
| 54 | 
            +
                    context.state["EnteredTime"] = Time.now.utc.iso8601
         | 
| 60 55 |  | 
| 61 | 
            -
                    logger.info("Running state: [#{long_name}] with input [#{context. | 
| 56 | 
            +
                    logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...")
         | 
| 62 57 | 
             
                  end
         | 
| 63 58 |  | 
| 64 | 
            -
                  def finish
         | 
| 59 | 
            +
                  def finish(context)
         | 
| 65 60 | 
             
                    finished_time     = Time.now.utc
         | 
| 66 | 
            -
                    finished_time_iso = finished_time.iso8601
         | 
| 67 61 | 
             
                    entered_time      = Time.parse(context.state["EnteredTime"])
         | 
| 68 62 |  | 
| 69 | 
            -
                    context.state["FinishedTime"] ||=  | 
| 63 | 
            +
                    context.state["FinishedTime"] ||= finished_time.iso8601
         | 
| 70 64 | 
             
                    context.state["Duration"]       = finished_time - entered_time
         | 
| 71 | 
            -
                    context.execution["EndTime"]    = finished_time_iso if context.next_state.nil?
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                    level = context.output&.[]("Error") ? :error : :info
         | 
| 74 | 
            -
                    logger.public_send(level, "Running state: [#{long_name}] with input [#{context.input}]...Complete #{context.next_state ? "- next state [#{context.next_state}]" : "workflow -"} output: [#{context.output}]")
         | 
| 75 65 |  | 
| 76 | 
            -
                    context. | 
| 66 | 
            +
                    level = context.failed? ? :error : :info
         | 
| 67 | 
            +
                    logger.public_send(level, "Running state: [#{long_name}] with input [#{context.json_input}]...Complete #{context.next_state ? "- next state [#{context.next_state}]" : "workflow -"} output: [#{context.json_output}]")
         | 
| 77 68 |  | 
| 78 69 | 
             
                    0
         | 
| 79 70 | 
             
                  end
         | 
| 80 71 |  | 
| 81 | 
            -
                  def context
         | 
| 82 | 
            -
                     | 
| 83 | 
            -
                  end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                  def started?
         | 
| 86 | 
            -
                    context.state.key?("EnteredTime")
         | 
| 72 | 
            +
                  def ready?(context)
         | 
| 73 | 
            +
                    !context.state_started? || !running?(context)
         | 
| 87 74 | 
             
                  end
         | 
| 88 75 |  | 
| 89 | 
            -
                  def  | 
| 90 | 
            -
                     | 
| 76 | 
            +
                  def running?(context)
         | 
| 77 | 
            +
                    raise NotImplementedError, "Must be implemented in a subclass"
         | 
| 91 78 | 
             
                  end
         | 
| 92 79 |  | 
| 93 | 
            -
                  def  | 
| 94 | 
            -
                    context.state.key?("FinishedTime")
         | 
| 95 | 
            -
                  end
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                  def waiting?
         | 
| 80 | 
            +
                  def waiting?(context)
         | 
| 98 81 | 
             
                    context.state["WaitUntil"] && Time.now.utc <= Time.parse(context.state["WaitUntil"])
         | 
| 99 82 | 
             
                  end
         | 
| 100 83 |  | 
| 101 | 
            -
                  def wait_until
         | 
| 84 | 
            +
                  def wait_until(context)
         | 
| 102 85 | 
             
                    context.state["WaitUntil"] && Time.parse(context.state["WaitUntil"])
         | 
| 103 86 | 
             
                  end
         | 
| 104 87 |  | 
| 88 | 
            +
                  def short_name
         | 
| 89 | 
            +
                    name.last
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 105 92 | 
             
                  def long_name
         | 
| 106 | 
            -
                    "#{ | 
| 93 | 
            +
                    "#{type}:#{short_name}"
         | 
| 107 94 | 
             
                  end
         | 
| 108 95 |  | 
| 109 96 | 
             
                  private
         | 
| 110 97 |  | 
| 111 | 
            -
                  def wait_until!(seconds: nil, time: nil)
         | 
| 98 | 
            +
                  def wait_until!(context, seconds: nil, time: nil)
         | 
| 112 99 | 
             
                    context.state["WaitUntil"] =
         | 
| 113 100 | 
             
                      if seconds
         | 
| 114 | 
            -
                        (Time. | 
| 101 | 
            +
                        (Time.now + seconds).iso8601
         | 
| 115 102 | 
             
                      elsif time.kind_of?(String)
         | 
| 116 103 | 
             
                        time
         | 
| 117 104 | 
             
                      else
         | 
| @@ -9,17 +9,18 @@ module Floe | |
| 9 9 | 
             
                    def initialize(workflow, name, payload)
         | 
| 10 10 | 
             
                      super
         | 
| 11 11 |  | 
| 12 | 
            -
                      validate_state!
         | 
| 12 | 
            +
                      validate_state!(workflow)
         | 
| 13 13 |  | 
| 14 | 
            -
                      @choices = payload["Choices"].map { |choice| ChoiceRule.build(choice) }
         | 
| 14 | 
            +
                      @choices = payload["Choices"].map.with_index { |choice, i| ChoiceRule.build(workflow, name + ["Choices", i.to_s], choice) }
         | 
| 15 15 | 
             
                      @default = payload["Default"]
         | 
| 16 16 |  | 
| 17 17 | 
             
                      @input_path  = Path.new(payload.fetch("InputPath", "$"))
         | 
| 18 18 | 
             
                      @output_path = Path.new(payload.fetch("OutputPath", "$"))
         | 
| 19 19 | 
             
                    end
         | 
| 20 20 |  | 
| 21 | 
            -
                    def finish
         | 
| 22 | 
            -
                       | 
| 21 | 
            +
                    def finish(context)
         | 
| 22 | 
            +
                      input      = input_path.value(context, context.input)
         | 
| 23 | 
            +
                      output     = output_path.value(context, input)
         | 
| 23 24 | 
             
                      next_state = choices.detect { |choice| choice.true?(context, output) }&.next || default
         | 
| 24 25 |  | 
| 25 26 | 
             
                      context.next_state = next_state
         | 
| @@ -27,7 +28,7 @@ module Floe | |
| 27 28 | 
             
                      super
         | 
| 28 29 | 
             
                    end
         | 
| 29 30 |  | 
| 30 | 
            -
                    def running?
         | 
| 31 | 
            +
                    def running?(_)
         | 
| 31 32 | 
             
                      false
         | 
| 32 33 | 
             
                    end
         | 
| 33 34 |  | 
| @@ -37,18 +38,18 @@ module Floe | |
| 37 38 |  | 
| 38 39 | 
             
                    private
         | 
| 39 40 |  | 
| 40 | 
            -
                    def validate_state!
         | 
| 41 | 
            +
                    def validate_state!(workflow)
         | 
| 41 42 | 
             
                      validate_state_choices!
         | 
| 42 | 
            -
                      validate_state_default!
         | 
| 43 | 
            +
                      validate_state_default!(workflow)
         | 
| 43 44 | 
             
                    end
         | 
| 44 45 |  | 
| 45 46 | 
             
                    def validate_state_choices!
         | 
| 46 | 
            -
                       | 
| 47 | 
            -
                       | 
| 47 | 
            +
                      missing_field_error!("Choices") unless payload.key?("Choices")
         | 
| 48 | 
            +
                      invalid_field_error!("Choices", nil, "must be a non-empty array") unless payload["Choices"].kind_of?(Array) && !payload["Choices"].empty?
         | 
| 48 49 | 
             
                    end
         | 
| 49 50 |  | 
| 50 | 
            -
                    def validate_state_default!
         | 
| 51 | 
            -
                       | 
| 51 | 
            +
                    def validate_state_default!(workflow)
         | 
| 52 | 
            +
                      invalid_field_error!("Default", payload["Default"], "is not found in \"States\"") if payload["Default"] && !workflow_state?(payload["Default"], workflow)
         | 
| 52 53 | 
             
                    end
         | 
| 53 54 | 
             
                  end
         | 
| 54 55 | 
             
                end
         | 
| @@ -4,7 +4,7 @@ module Floe | |
| 4 4 | 
             
              class Workflow
         | 
| 5 5 | 
             
                module States
         | 
| 6 6 | 
             
                  class Fail < Floe::Workflow::State
         | 
| 7 | 
            -
                    attr_reader :cause, :error
         | 
| 7 | 
            +
                    attr_reader :cause, :error, :cause_path, :error_path
         | 
| 8 8 |  | 
| 9 9 | 
             
                    def initialize(workflow, name, payload)
         | 
| 10 10 | 
             
                      super
         | 
| @@ -15,7 +15,7 @@ module Floe | |
| 15 15 | 
             
                      @error_path = Path.new(payload["ErrorPath"]) if payload["ErrorPath"]
         | 
| 16 16 | 
             
                    end
         | 
| 17 17 |  | 
| 18 | 
            -
                    def finish
         | 
| 18 | 
            +
                    def finish(context)
         | 
| 19 19 | 
             
                      context.next_state = nil
         | 
| 20 20 | 
             
                      # TODO: support intrinsic functions here
         | 
| 21 21 | 
             
                      # see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-fail-state.html
         | 
| @@ -27,7 +27,7 @@ module Floe | |
| 27 27 | 
             
                      super
         | 
| 28 28 | 
             
                    end
         | 
| 29 29 |  | 
| 30 | 
            -
                    def running?
         | 
| 30 | 
            +
                    def running?(_)
         | 
| 31 31 | 
             
                      false
         | 
| 32 32 | 
             
                    end
         | 
| 33 33 |  | 
| @@ -4,23 +4,23 @@ module Floe | |
| 4 4 | 
             
              class Workflow
         | 
| 5 5 | 
             
                module States
         | 
| 6 6 | 
             
                  module InputOutputMixin
         | 
| 7 | 
            -
                    def process_input( | 
| 8 | 
            -
                      input = input_path.value(context, input)
         | 
| 7 | 
            +
                    def process_input(context)
         | 
| 8 | 
            +
                      input = input_path.value(context, context.input)
         | 
| 9 9 | 
             
                      input = parameters.value(context, input) if parameters
         | 
| 10 10 | 
             
                      input
         | 
| 11 11 | 
             
                    end
         | 
| 12 12 |  | 
| 13 | 
            -
                    def process_output( | 
| 14 | 
            -
                      return input if results.nil?
         | 
| 13 | 
            +
                    def process_output(context, results)
         | 
| 14 | 
            +
                      return context.input.dup if results.nil?
         | 
| 15 15 | 
             
                      return if output_path.nil?
         | 
| 16 16 |  | 
| 17 17 | 
             
                      results = result_selector.value(context, results) if @result_selector
         | 
| 18 18 | 
             
                      if result_path.payload.start_with?("$.Credentials")
         | 
| 19 | 
            -
                        credentials = result_path.set( | 
| 20 | 
            -
                         | 
| 21 | 
            -
                        output = input
         | 
| 19 | 
            +
                        credentials = result_path.set(context.credentials, results)["Credentials"]
         | 
| 20 | 
            +
                        context.credentials.merge!(credentials)
         | 
| 21 | 
            +
                        output = context.input.dup
         | 
| 22 22 | 
             
                      else
         | 
| 23 | 
            -
                        output = result_path.set(input, results)
         | 
| 23 | 
            +
                        output = result_path.set(context.input.dup, results)
         | 
| 24 24 | 
             
                      end
         | 
| 25 25 |  | 
| 26 26 | 
             
                      output_path.value(context, output)
         | 
| @@ -4,16 +4,16 @@ module Floe | |
| 4 4 | 
             
              class Workflow
         | 
| 5 5 | 
             
                module States
         | 
| 6 6 | 
             
                  module NonTerminalMixin
         | 
| 7 | 
            -
                    def finish
         | 
| 8 | 
            -
                      # If this state is failed or  | 
| 9 | 
            -
                      context.next_state  | 
| 7 | 
            +
                    def finish(context)
         | 
| 8 | 
            +
                      # If this state is failed or this is an end state, next_state to nil
         | 
| 9 | 
            +
                      context.next_state ||= end? || context.failed? ? nil : @next
         | 
| 10 10 |  | 
| 11 11 | 
             
                      super
         | 
| 12 12 | 
             
                    end
         | 
| 13 13 |  | 
| 14 | 
            -
                    def validate_state_next!
         | 
| 15 | 
            -
                       | 
| 16 | 
            -
                       | 
| 14 | 
            +
                    def validate_state_next!(workflow)
         | 
| 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)
         | 
| 17 17 | 
             
                    end
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 | 
             
                end
         | 
| @@ -21,15 +21,16 @@ module Floe | |
| 21 21 | 
             
                      @output_path = Path.new(payload.fetch("OutputPath", "$"))
         | 
| 22 22 | 
             
                      @result_path = ReferencePath.new(payload.fetch("ResultPath", "$"))
         | 
| 23 23 |  | 
| 24 | 
            -
                      validate_state!
         | 
| 24 | 
            +
                      validate_state!(workflow)
         | 
| 25 25 | 
             
                    end
         | 
| 26 26 |  | 
| 27 | 
            -
                    def finish
         | 
| 28 | 
            -
                       | 
| 27 | 
            +
                    def finish(context)
         | 
| 28 | 
            +
                      input = result.nil? ? process_input(context) : result
         | 
| 29 | 
            +
                      context.output = process_output(context, input)
         | 
| 29 30 | 
             
                      super
         | 
| 30 31 | 
             
                    end
         | 
| 31 32 |  | 
| 32 | 
            -
                    def running?
         | 
| 33 | 
            +
                    def running?(_)
         | 
| 33 34 | 
             
                      false
         | 
| 34 35 | 
             
                    end
         | 
| 35 36 |  | 
| @@ -39,8 +40,8 @@ module Floe | |
| 39 40 |  | 
| 40 41 | 
             
                    private
         | 
| 41 42 |  | 
| 42 | 
            -
                    def validate_state!
         | 
| 43 | 
            -
                      validate_state_next!
         | 
| 43 | 
            +
                    def validate_state!(workflow)
         | 
| 44 | 
            +
                      validate_state_next!(workflow)
         | 
| 44 45 | 
             
                    end
         | 
| 45 46 | 
             
                  end
         | 
| 46 47 | 
             
                end
         | 
| @@ -6,13 +6,22 @@ module Floe | |
| 6 6 | 
             
                  class Succeed < Floe::Workflow::State
         | 
| 7 7 | 
             
                    attr_reader :input_path, :output_path
         | 
| 8 8 |  | 
| 9 | 
            -
                    def  | 
| 9 | 
            +
                    def initialize(workflow, name, payload)
         | 
| 10 | 
            +
                      super
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      @input_path  = Path.new(payload.fetch("InputPath", "$"))
         | 
| 13 | 
            +
                      @output_path = Path.new(payload.fetch("OutputPath", "$"))
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    def finish(context)
         | 
| 17 | 
            +
                      input              = input_path.value(context, context.input)
         | 
| 18 | 
            +
                      context.output     = output_path.value(context, input)
         | 
| 10 19 | 
             
                      context.next_state = nil
         | 
| 11 | 
            -
             | 
| 20 | 
            +
             | 
| 12 21 | 
             
                      super
         | 
| 13 22 | 
             
                    end
         | 
| 14 23 |  | 
| 15 | 
            -
                    def running?
         | 
| 24 | 
            +
                    def running?(_)
         | 
| 16 25 | 
             
                      false
         | 
| 17 26 | 
             
                    end
         | 
| 18 27 |  | 
| @@ -18,10 +18,13 @@ module Floe | |
| 18 18 | 
             
                      @next              = payload["Next"]
         | 
| 19 19 | 
             
                      @end               = !!payload["End"]
         | 
| 20 20 | 
             
                      @resource          = payload["Resource"]
         | 
| 21 | 
            -
             | 
| 21 | 
            +
             | 
| 22 | 
            +
                      missing_field_error!("Resource") unless @resource.kind_of?(String)
         | 
| 23 | 
            +
                      @runner = wrap_parser_error("Resource", @resource) { Floe::Runner.for_resource(@resource) }
         | 
| 24 | 
            +
             | 
| 22 25 | 
             
                      @timeout_seconds   = payload["TimeoutSeconds"]
         | 
| 23 | 
            -
                      @retry             = payload["Retry"].to_a.map { |retrier| Retrier.new(retrier) }
         | 
| 24 | 
            -
                      @catch             = payload["Catch"].to_a.map { |catcher| Catcher.new(catcher) }
         | 
| 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) }
         | 
| 25 28 | 
             
                      @input_path        = Path.new(payload.fetch("InputPath", "$"))
         | 
| 26 29 | 
             
                      @output_path       = Path.new(payload.fetch("OutputPath", "$"))
         | 
| 27 30 | 
             
                      @result_path       = ReferencePath.new(payload.fetch("ResultPath", "$"))
         | 
| @@ -29,36 +32,35 @@ module Floe | |
| 29 32 | 
             
                      @result_selector   = PayloadTemplate.new(payload["ResultSelector"]) if payload["ResultSelector"]
         | 
| 30 33 | 
             
                      @credentials       = PayloadTemplate.new(payload["Credentials"])    if payload["Credentials"]
         | 
| 31 34 |  | 
| 32 | 
            -
                      validate_state!
         | 
| 35 | 
            +
                      validate_state!(workflow)
         | 
| 33 36 | 
             
                    end
         | 
| 34 37 |  | 
| 35 | 
            -
                    def start( | 
| 38 | 
            +
                    def start(context)
         | 
| 36 39 | 
             
                      super
         | 
| 37 40 |  | 
| 38 | 
            -
                      input          = process_input( | 
| 39 | 
            -
                      runner_context = runner.run_async!(resource, input, credentials&.value({},  | 
| 41 | 
            +
                      input          = process_input(context)
         | 
| 42 | 
            +
                      runner_context = runner.run_async!(resource, input, credentials&.value({}, context.credentials), context)
         | 
| 40 43 |  | 
| 41 44 | 
             
                      context.state["RunnerContext"] = runner_context
         | 
| 42 45 | 
             
                    end
         | 
| 43 46 |  | 
| 44 | 
            -
                    def finish
         | 
| 47 | 
            +
                    def finish(context)
         | 
| 45 48 | 
             
                      output = runner.output(context.state["RunnerContext"])
         | 
| 46 49 |  | 
| 47 | 
            -
                      if success?
         | 
| 50 | 
            +
                      if success?(context)
         | 
| 48 51 | 
             
                        output = parse_output(output)
         | 
| 49 | 
            -
                        context.output = process_output(context | 
| 50 | 
            -
                        super
         | 
| 52 | 
            +
                        context.output = process_output(context, output)
         | 
| 51 53 | 
             
                      else
         | 
| 52 | 
            -
                         | 
| 53 | 
            -
                         | 
| 54 | 
            -
                        retry_state!(error) || catch_error!(error) || fail_workflow!(error)
         | 
| 54 | 
            +
                        error = parse_error(output)
         | 
| 55 | 
            +
                        retry_state!(context, error) || catch_error!(context, error) || fail_workflow!(context, error)
         | 
| 55 56 | 
             
                      end
         | 
| 57 | 
            +
                      super
         | 
| 56 58 | 
             
                    ensure
         | 
| 57 59 | 
             
                      runner.cleanup(context.state["RunnerContext"])
         | 
| 58 60 | 
             
                    end
         | 
| 59 61 |  | 
| 60 | 
            -
                    def running?
         | 
| 61 | 
            -
                      return true if waiting?
         | 
| 62 | 
            +
                    def running?(context)
         | 
| 63 | 
            +
                      return true if waiting?(context)
         | 
| 62 64 |  | 
| 63 65 | 
             
                      runner.status!(context.state["RunnerContext"])
         | 
| 64 66 | 
             
                      runner.running?(context.state["RunnerContext"])
         | 
| @@ -72,23 +74,23 @@ module Floe | |
| 72 74 |  | 
| 73 75 | 
             
                    attr_reader :runner
         | 
| 74 76 |  | 
| 75 | 
            -
                    def validate_state!
         | 
| 76 | 
            -
                      validate_state_next!
         | 
| 77 | 
            +
                    def validate_state!(workflow)
         | 
| 78 | 
            +
                      validate_state_next!(workflow)
         | 
| 77 79 | 
             
                    end
         | 
| 78 80 |  | 
| 79 | 
            -
                    def success?
         | 
| 81 | 
            +
                    def success?(context)
         | 
| 80 82 | 
             
                      runner.success?(context.state["RunnerContext"])
         | 
| 81 83 | 
             
                    end
         | 
| 82 84 |  | 
| 83 85 | 
             
                    def find_retrier(error)
         | 
| 84 | 
            -
                      self.retry.detect { |r|  | 
| 86 | 
            +
                      self.retry.detect { |r| r.match_error?(error) }
         | 
| 85 87 | 
             
                    end
         | 
| 86 88 |  | 
| 87 89 | 
             
                    def find_catcher(error)
         | 
| 88 | 
            -
                      self.catch.detect { |c|  | 
| 90 | 
            +
                      self.catch.detect { |c| c.match_error?(error) }
         | 
| 89 91 | 
             
                    end
         | 
| 90 92 |  | 
| 91 | 
            -
                    def retry_state!(error)
         | 
| 93 | 
            +
                    def retry_state!(context, error)
         | 
| 92 94 | 
             
                      retrier = find_retrier(error["Error"]) if error
         | 
| 93 95 | 
             
                      return if retrier.nil?
         | 
| 94 96 |  | 
| @@ -102,27 +104,30 @@ module Floe | |
| 102 104 |  | 
| 103 105 | 
             
                      return if context["State"]["RetryCount"] > retrier.max_attempts
         | 
| 104 106 |  | 
| 105 | 
            -
                      wait_until!(:seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
         | 
| 107 | 
            +
                      wait_until!(context, :seconds => retrier.sleep_duration(context["State"]["RetryCount"]))
         | 
| 106 108 | 
             
                      context.next_state = context.state_name
         | 
| 107 | 
            -
                       | 
| 109 | 
            +
                      context.output     = error
         | 
| 110 | 
            +
                      logger.info("Running state: [#{long_name}] with input [#{context.json_input}] got error[#{context.json_output}]...Retry - delay: #{wait_until(context)}")
         | 
| 108 111 | 
             
                      true
         | 
| 109 112 | 
             
                    end
         | 
| 110 113 |  | 
| 111 | 
            -
                    def catch_error!(error)
         | 
| 114 | 
            +
                    def catch_error!(context, error)
         | 
| 112 115 | 
             
                      catcher = find_catcher(error["Error"]) if error
         | 
| 113 116 | 
             
                      return if catcher.nil?
         | 
| 114 117 |  | 
| 115 118 | 
             
                      context.next_state = catcher.next
         | 
| 116 119 | 
             
                      context.output     = catcher.result_path.set(context.input, error)
         | 
| 117 | 
            -
                      logger.info("Running state: [#{long_name}] with input [#{context. | 
| 120 | 
            +
                      logger.info("Running state: [#{long_name}] with input [#{context.json_input}]...CatchError - next state: [#{context.next_state}] output: [#{context.json_output}]")
         | 
| 118 121 |  | 
| 119 122 | 
             
                      true
         | 
| 120 123 | 
             
                    end
         | 
| 121 124 |  | 
| 122 | 
            -
                    def fail_workflow!(error)
         | 
| 123 | 
            -
                       | 
| 124 | 
            -
                       | 
| 125 | 
            -
                       | 
| 125 | 
            +
                    def fail_workflow!(context, error)
         | 
| 126 | 
            +
                      # next_state is nil, and will be set to nil again in super
         | 
| 127 | 
            +
                      # keeping in here for completeness
         | 
| 128 | 
            +
                      context.next_state = nil
         | 
| 129 | 
            +
                      context.output = error
         | 
| 130 | 
            +
                      logger.error("Running state: [#{long_name}] with input [#{context.json_input}]...Complete workflow - output: [#{context.json_output}]")
         | 
| 126 131 | 
             
                    end
         | 
| 127 132 |  | 
| 128 133 | 
             
                    def parse_error(output)
         | 
| @@ -23,28 +23,29 @@ module Floe | |
| 23 23 | 
             
                      @input_path  = Path.new(payload.fetch("InputPath", "$"))
         | 
| 24 24 | 
             
                      @output_path = Path.new(payload.fetch("OutputPath", "$"))
         | 
| 25 25 |  | 
| 26 | 
            -
                      validate_state!
         | 
| 26 | 
            +
                      validate_state!(workflow)
         | 
| 27 27 | 
             
                    end
         | 
| 28 28 |  | 
| 29 | 
            -
                    def start( | 
| 29 | 
            +
                    def start(context)
         | 
| 30 30 | 
             
                      super
         | 
| 31 31 |  | 
| 32 32 | 
             
                      input = input_path.value(context, context.input)
         | 
| 33 33 |  | 
| 34 34 | 
             
                      wait_until!(
         | 
| 35 | 
            +
                        context,
         | 
| 35 36 | 
             
                        :seconds => seconds_path ? seconds_path.value(context, input).to_i : seconds,
         | 
| 36 37 | 
             
                        :time    => timestamp_path ? timestamp_path.value(context, input) : timestamp
         | 
| 37 38 | 
             
                      )
         | 
| 38 39 | 
             
                    end
         | 
| 39 40 |  | 
| 40 | 
            -
                    def finish
         | 
| 41 | 
            +
                    def finish(context)
         | 
| 41 42 | 
             
                      input          = input_path.value(context, context.input)
         | 
| 42 43 | 
             
                      context.output = output_path.value(context, input)
         | 
| 43 44 | 
             
                      super
         | 
| 44 45 | 
             
                    end
         | 
| 45 46 |  | 
| 46 | 
            -
                    def running?
         | 
| 47 | 
            -
                      waiting?
         | 
| 47 | 
            +
                    def running?(context)
         | 
| 48 | 
            +
                      waiting?(context)
         | 
| 48 49 | 
             
                    end
         | 
| 49 50 |  | 
| 50 51 | 
             
                    def end?
         | 
| @@ -53,8 +54,8 @@ module Floe | |
| 53 54 |  | 
| 54 55 | 
             
                    private
         | 
| 55 56 |  | 
| 56 | 
            -
                    def validate_state!
         | 
| 57 | 
            -
                      validate_state_next!
         | 
| 57 | 
            +
                    def validate_state!(workflow)
         | 
| 58 | 
            +
                      validate_state_next!(workflow)
         | 
| 58 59 | 
             
                    end
         | 
| 59 60 | 
             
                  end
         | 
| 60 61 | 
             
                end
         |