Class: Vagrant::Action::Warden

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant/action/warden.rb

Overview

The action warden is a middleware which injects itself between every other middleware, watching for exceptions which are raised and performing proper cleanup on every action by calling the recover method. The warden therefore allows middlewares to not worry about exceptional events, and by providing a simple callback, can clean up in any erroneous case.

Warden will "just work" behind the scenes, and is not of particular interest except to those who are curious about the internal workings of Vagrant.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(actions, env) ⇒ Warden

Returns a new instance of Warden.



22
23
24
25
26
27
# File 'lib/vagrant/action/warden.rb', line 22

def initialize(actions, env)
  @stack      = []
  @actions    = actions.map { |m| finalize_action(m, env) }.flatten
  @logger     = Log4r::Logger.new("vagrant::action::warden")
  @last_error = nil
end

Instance Attribute Details

#actionsObject

Returns the value of attribute actions.



20
21
22
# File 'lib/vagrant/action/warden.rb', line 20

def actions
  @actions
end

#stackObject

Returns the value of attribute stack.



20
21
22
# File 'lib/vagrant/action/warden.rb', line 20

def stack
  @stack
end

Instance Method Details

#call(env) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/vagrant/action/warden.rb', line 29

def call(env)
  return if @actions.empty?

  begin
    # Call the next middleware in the sequence, appending to the stack
    # of "recoverable" middlewares in case something goes wrong!
    raise Errors::VagrantInterrupt if env[:interrupted]
    action = @actions.shift
    @logger.info("Calling IN action: #{action}")
    @stack.unshift(action).first.call(env)
    raise Errors::VagrantInterrupt if env[:interrupted]
    @logger.info("Calling OUT action: #{action}")
  rescue SystemExit, NoMemoryError
    # This means that an "exit" or "abort" was called, or we have run out
    # of memory. In these cases, we just exit immediately.
    raise
  rescue Exception => e
    # We guard this so that the Warden only outputs this once for
    # an exception that bubbles up.
    if e != @last_error
      @logger.error("Error occurred: #{e}")
      @last_error = e
    end

    env["vagrant.error"] = e

    # Something went horribly wrong. Start the rescue chain then
    # reraise the exception to properly kick us out of limbo here.
    recover(env)
    raise
  end
end

#finalize_action(action, env) ⇒ Object

A somewhat confusing function which simply initializes each middleware properly to call the next middleware in the sequence.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/vagrant/action/warden.rb', line 84

def finalize_action(action, env)
  if action.is_a?(Builder::StackItem)
    klass = action.middleware
    args = action.arguments.parameters
    keywords = action.arguments.keywords
    block = action.arguments.block
  else
    klass = action
    args = []
    keywords = {}
  end

  args = nil if args.empty?
  keywords = nil if keywords.empty?

  if klass.is_a?(Class)
    # NOTE: We need to detect if we are passing args and/or
    #       keywords and do it explicitly. Earlier versions
    #       are not as lax about splatting keywords when the
    #       target method is not expecting them.
    if args && keywords
      klass.new(self, env, *args, **keywords, &block)
    elsif args
      klass.new(self, env, *args, &block)
    elsif keywords
      klass.new(self, env, **keywords, &block)
    else
      klass.new(self, env, &block)
    end
  elsif klass.respond_to?(:call)
    # Make it a lambda which calls the item then forwards
    # up the chain
    lambda do |e|
      klass.call(e)
      self.call(e)
    end
  else
    raise "Invalid action: #{action.inspect}"
  end
end

#recover(env) ⇒ Object

We implement the recover method ourselves in case a Warden is embedded within another Warden. To recover, we just do our own recovery process on our stack.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/vagrant/action/warden.rb', line 65

def recover(env)
  @logger.info("Beginning recovery process...")

  @stack.each do |act|
    if act.respond_to?(:recover)
      @logger.info("Calling recover: #{act}")
      act.recover(env)
    end
  end

  @logger.info("Recovery complete.")

  # Clear stack so that warden down the middleware chain doesn't
  # rescue again.
  @stack.clear
end