"Fossies" - the Fresh Open Source Software Archive

Member "vagrant-2.2.14/lib/vagrant/action/builder.rb" (20 Nov 2020, 11491 Bytes) of package /linux/misc/vagrant-2.2.14.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "builder.rb": 2.2.13_vs_2.2.14.

    1 module Vagrant
    2   module Action
    3     # Action builder which provides a nice DSL for building up
    4     # a middleware sequence for Vagrant actions. This code is based
    5     # heavily off of `Rack::Builder` and `ActionDispatch::MiddlewareStack`
    6     # in Rack and Rails, respectively.
    7     #
    8     # Usage
    9     #
   10     # Building an action sequence is very easy:
   11     #
   12     #     app = Vagrant::Action::Builder.new.tap do |b|
   13     #       b.use MiddlewareA
   14     #       b.use MiddlewareB
   15     #     end
   16     #
   17     #     Vagrant::Action.run(app)
   18     #
   19     class Builder
   20       # Container for Action arguments
   21       MiddlewareArguments = Struct.new(:parameters, :block, :keywords, keyword_init: true)
   22       # Item within the stack
   23       StackItem = Struct.new(:middleware, :arguments, keyword_init: true)
   24 
   25       # This is the stack of middlewares added. This should NOT be used
   26       # directly.
   27       #
   28       # @return [Array]
   29       attr_reader :stack
   30 
   31       # This is a shortcut for a middleware sequence with only one item
   32       # in it. For a description of the arguments and the documentation, please
   33       # see {#use} instead.
   34       #
   35       # @return [Builder]
   36       def self.build(middleware, *args, **keywords, &block)
   37         new.use(middleware, *args, **keywords, &block)
   38       end
   39 
   40       def initialize
   41         @stack = []
   42       end
   43 
   44       # Implement a custom copy that copies the stack variable over so that
   45       # we don't clobber that.
   46       def initialize_copy(original)
   47         super
   48 
   49         @stack = original.stack.dup
   50       end
   51 
   52       # Returns a mergeable version of the builder. If `use` is called with
   53       # the return value of this method, then the stack will merge, instead
   54       # of being treated as a separate single middleware.
   55       def flatten
   56         lambda do |env|
   57           self.call(env)
   58         end
   59       end
   60 
   61       # Adds a middleware class to the middleware stack. Any additional
   62       # args and a block, if given, are saved and passed to the initializer
   63       # of the middleware.
   64       #
   65       # @param [Class] middleware The middleware class
   66       def use(middleware, *args, **keywords, &block)
   67         item = StackItem.new(
   68           middleware: middleware,
   69           arguments: MiddlewareArguments.new(
   70             parameters: args,
   71             keywords: keywords,
   72             block: block
   73           )
   74         )
   75 
   76         if middleware.kind_of?(Builder)
   77           # Merge in the other builder's stack into our own
   78           self.stack.concat(middleware.stack)
   79         else
   80           self.stack << item
   81         end
   82 
   83         self
   84       end
   85 
   86       # Inserts a middleware at the given index or directly before the
   87       # given middleware object.
   88       def insert(idx_or_item, middleware, *args, **keywords, &block)
   89         item = StackItem.new(
   90           middleware: middleware,
   91           arguments: MiddlewareArguments.new(
   92             parameters: args,
   93             keywords: keywords,
   94             block: block
   95           )
   96         )
   97 
   98         if idx_or_item.is_a?(Integer)
   99           index = idx_or_item
  100         else
  101           index = self.index(idx_or_item)
  102         end
  103 
  104         raise "no such middleware to insert before: #{index.inspect}" unless index
  105 
  106         if middleware.kind_of?(Builder)
  107           middleware.stack.reverse.each do |stack_item|
  108             stack.insert(index, stack_item)
  109           end
  110         else
  111           stack.insert(index, item)
  112         end
  113       end
  114 
  115       alias_method :insert_before, :insert
  116 
  117       # Inserts a middleware after the given index or middleware object.
  118       def insert_after(idx_or_item, middleware, *args, **keywords, &block)
  119         if idx_or_item.is_a?(Integer)
  120           index = idx_or_item
  121         else
  122           index = self.index(idx_or_item)
  123         end
  124 
  125         raise "no such middleware to insert after: #{index.inspect}" unless index
  126         insert(index + 1, middleware, *args, &block)
  127       end
  128 
  129       # Replaces the given middlware object or index with the new
  130       # middleware.
  131       def replace(index, middleware, *args, **keywords, &block)
  132         if index.is_a?(Integer)
  133           delete(index)
  134           insert(index, middleware, *args, **keywords, &block)
  135         else
  136           insert_before(index, middleware, *args, **keywords, &block)
  137           delete(index)
  138         end
  139       end
  140 
  141       # Deletes the given middleware object or index
  142       def delete(index)
  143         index = self.index(index) unless index.is_a?(Integer)
  144         stack.delete_at(index)
  145       end
  146 
  147       # Runs the builder stack with the given environment.
  148       def call(env)
  149         to_app(env).call(env)
  150       end
  151 
  152       # Returns the numeric index for the given middleware object.
  153       #
  154       # @param [Object] object The item to find the index for
  155       # @return [Integer]
  156       def index(object)
  157         stack.each_with_index do |item, i|
  158           return i if item == object
  159           return i if item.middleware == object
  160           return i if item.middleware.respond_to?(:name) &&
  161             item.middleware.name == object
  162         end
  163 
  164         nil
  165       end
  166 
  167       # Converts the builder stack to a runnable action sequence.
  168       #
  169       # @param [Hash] env The action environment hash
  170       # @return [Warden] A callable object
  171       def to_app(env)
  172         # Start with a duplicate of ourself which can
  173         # be modified
  174         builder = self.dup
  175 
  176         # Apply all dynamic modifications of the stack. This
  177         # will generate dynamic hooks for all actions within
  178         # the stack, load any triggers for action classes, and
  179         # apply them to the builder's stack
  180         builder.apply_dynamic_updates(env)
  181 
  182         # Now that the stack is fully expanded, apply any
  183         # action hooks that may be defined so they are on
  184         # the outermost locations of the stack
  185         builder.apply_action_name(env)
  186 
  187         # Wrap the middleware stack with the Warden to provide a consistent
  188         # and predictable behavior upon exceptions.
  189         Warden.new(builder.stack.dup, env)
  190       end
  191 
  192       # Find any action hooks or triggers which have been defined
  193       # for items within the stack. Update the stack with any
  194       # hooks or triggers found.
  195       #
  196       # @param [Hash] env Call environment
  197       # @return [Builder] self
  198       def apply_dynamic_updates(env)
  199         if Vagrant::Util::Experimental.feature_enabled?("typed_triggers")
  200           triggers = env[:triggers]
  201         end
  202 
  203         # Use a Hook as a convenient interface for injecting
  204         # any applicable trigger actions within the stack
  205         machine_name = env[:machine].name if env[:machine]
  206 
  207         # Iterate over all items in the stack and apply new items
  208         # into the hook as they are found. Must be sure to dup the
  209         # stack here since we are modifying the stack in the loop.
  210         stack.dup.each do |item|
  211           hook = Hook.new
  212 
  213           action = item.first
  214           next if action.is_a?(Proc)
  215 
  216           # Start with adding any action triggers that may be defined
  217           if triggers && !triggers.find(action, :before, machine_name, :action).empty?
  218             hook.prepend(Vagrant::Action::Builtin::Trigger,
  219               action.name, triggers, :before, :action)
  220           end
  221 
  222           if triggers && !triggers.find(action, :after, machine_name, :action).empty?
  223             hook.append(Vagrant::Action::Builtin::Trigger,
  224               action.name, triggers, :after, :action)
  225           end
  226 
  227           # Next look for any hook triggers that may be defined against
  228           # the dynamically generated action class hooks
  229           if triggers && !triggers.find(action, :before, machine_name, :hook).empty?
  230             hook.prepend(Vagrant::Action::Builtin::Trigger,
  231               action.name, triggers, :before, :hook)
  232           end
  233 
  234           if triggers && !triggers.find(action, :after, machine_name, :hook).empty?
  235             hook.append(Vagrant::Action::Builtin::Trigger,
  236               action.name, triggers, :after, :hook)
  237           end
  238 
  239           # Finally load any registered hooks for dynamically generated
  240           # action class based hooks
  241           Vagrant.plugin("2").manager.find_action_hooks(action).each do |hook_proc|
  242             hook_proc.call(hook)
  243           end
  244 
  245           hook.apply(self, root: item)
  246         end
  247 
  248         # Apply the hook to ourself to update the stack
  249         self
  250       end
  251 
  252       # If action hooks have not already been set, this method
  253       # will perform three tasks:
  254       #   1. Load any hook triggers defined for the action_name
  255       #   2. Load any action_hooks defined from plugins
  256       #   3. Load any action triggers based on machine action called (not action classes)
  257       #
  258       # @param [Hash] env Call environment
  259       # @return [Builder]
  260       def apply_action_name(env)
  261         env[:builder_raw_applied] ||= []
  262         return self if !env[:action_name]
  263 
  264         hook = Hook.new
  265         machine_name = env[:machine].name if env[:machine]
  266 
  267         # Start with loading any hook triggers if applicable
  268         if Vagrant::Util::Experimental.feature_enabled?("typed_triggers") && env[:triggers]
  269           if !env[:triggers].find(env[:action_name], :before, machine_name, :hook).empty?
  270             hook.prepend(Vagrant::Action::Builtin::Trigger,
  271               env[:action_name], env[:triggers], :before, :hook)
  272           end
  273           if !env[:triggers].find(env[:action_name], :after, machine_name, :hook).empty?
  274             hook.append(Vagrant::Action::Builtin::Trigger,
  275               env[:action_name], env[:triggers], :after, :hook)
  276           end
  277         end
  278 
  279         # Next we load up all the action hooks that plugins may
  280         # have defined
  281         action_hooks = Vagrant.plugin("2").manager.action_hooks(env[:action_name])
  282         action_hooks.each do |hook_proc|
  283           hook_proc.call(hook)
  284         end
  285 
  286         # Finally load any action triggers defined. The action triggers
  287         # are the originally implemented trigger style. They run before
  288         # and after specific provider actions (like :up, :halt, etc) and
  289         # are different from true action triggers
  290         if env[:triggers] && !env[:builder_raw_applied].include?(env[:raw_action_name])
  291           env[:builder_raw_applied] << env[:raw_action_name]
  292 
  293           if !env[:triggers].find(env[:raw_action_name], :before, machine_name, :action, all: true).empty?
  294             hook.prepend(Vagrant::Action::Builtin::Trigger,
  295               env[:raw_action_name], env[:triggers], :before, :action, all: true)
  296           end
  297           if !env[:triggers].find(env[:raw_action_name], :after, machine_name, :action, all: true).empty?
  298             # NOTE: These after triggers need to be delayed before running to
  299             #       allow the rest of the call stack to complete before being
  300             #       run. The delayed action is prepended to the stack (not appended)
  301             #       to ensure it is called first, which results in it properly
  302             #       waiting for everything to finish before itself completing.
  303             builder = self.class.build(Vagrant::Action::Builtin::Trigger,
  304               env[:raw_action_name], env[:triggers], :after, :action, all: true)
  305             hook.prepend(Vagrant::Action::Builtin::Delayed, builder)
  306           end
  307         end
  308 
  309         # If the hooks are empty, then there was nothing to apply and
  310         # we can just send ourself back
  311         return self if hook.empty?
  312 
  313         # Apply all the hooks to the new builder instance
  314         hook.apply(self)
  315 
  316         self
  317       end
  318     end
  319   end
  320 end