Class: MotherBrain::CommandRunner

Inherits:
Object
  • Object
show all
Includes:
MB::Mixin::Services
Defined in:
lib/mb/command_runner.rb

Defined Under Namespace

Classes: CleanRoom, InvokableComponent

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(job, environment, scope, execute, node_filter, *args) ⇒ CommandRunner

Returns a new instance of CommandRunner.

Parameters:

  • environment (String)

    environment to run this command on

  • scope (MB::Plugin, MB::Component)

    scope to execute this command in.

    • executing the command in the scope of an entire plugin will give you easy access to component commands and other plugin level commands

    • executing the command in the scope of a component will give you easy access to the other commands available in that component

  • execute (Proc)

    the code to execute when the command runner is run

    @example

    proc {
      on("some_nodes") { service("nginx").run("stop") }
    }
    
  • node_filter (Array)

    a list of nodes to filter commands on

  • args (Array)

    any additional arguments to pass to the execution block



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/mb/command_runner.rb', line 36

def initialize(job, environment, scope, execute, node_filter, *args)
  @job         = job
  @environment = environment
  @scope       = scope
  @execute     = execute
  @node_filter = node_filter
  @args        = args

  @on_procs    = []
  @async       = false

  run
end

Instance Attribute Details

#argsArray (readonly)

Returns:

  • (Array)


14
15
16
# File 'lib/mb/command_runner.rb', line 14

def args
  @args
end

#environmentString (readonly)

Returns:

  • (String)


6
7
8
# File 'lib/mb/command_runner.rb', line 6

def environment
  @environment
end

#executeProc (readonly)

Returns:

  • (Proc)


10
11
12
# File 'lib/mb/command_runner.rb', line 10

def execute
  @execute
end

#node_filterArray (readonly)

Returns:

  • (Array)


12
13
14
# File 'lib/mb/command_runner.rb', line 12

def node_filter
  @node_filter
end

#scopeMB::Plugin, MB::Component (readonly)



8
9
10
# File 'lib/mb/command_runner.rb', line 8

def scope
  @scope
end

Instance Method Details

#applyObject

Run the stored procs created by on() that have not been ran yet.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/mb/command_runner.rb', line 60

def apply
  # TODO: This needs to happen in parallel but can't due to the way running multiple
  # actions on a single node works. Actions work on a node and don't know about other
  # actions which are being run on that node, so in a single node environment the
  # state of a node can get weird when actions stomp all over each other.
  #
  # We should refactor this to APPLY actions to nodes and then allow them to converge
  # together before we run them. This will allow us to execute multiple actions on a node at once
  # without getting weird race conditions.
  @on_procs.each do |on_proc|
    on_proc.call
  end

  @on_procs.clear
end

#async(&block) ⇒ Object

Run the block asynchronously.



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/mb/command_runner.rb', line 84

def async(&block)
  @nodes = []

  @async = true
  instance_eval(&block)
  @async = false

  apply

  node_querier.bulk_chef_run job, @nodes
end

#async?Boolean

Are we inside an async block?

Returns:

  • (Boolean)


79
80
81
# File 'lib/mb/command_runner.rb', line 79

def async?
  !!@async
end

#command(command_name) ⇒ Object



166
167
168
# File 'lib/mb/command_runner.rb', line 166

def command(command_name)
  scope.command!(command_name).invoke(job, environment, node_filter)
end

#component(component_name) ⇒ InvokableComponent

Select a component for the purposes of invoking a command. NB: returns a proxy object

Parameters:

  • component_name (String)

    the name of the component you want to invoke a command on

Returns:

  • (InvokableComponent)

    proxy for the actual component, only useful if you call #invoke on it



162
163
164
# File 'lib/mb/command_runner.rb', line 162

def component(component_name)
  InvokableComponent.new(job, environment, scope.component(component_name))
end

#on(*group_names, &block) ⇒ Object

Run the block specified on the nodes in the groups specified.

Examples:

running on masters and slaves, only 2 of them, 1 at a time


on("masters", "slaves", any: 2, max_concurrent: 1) do
  # actions
end

Parameters:

  • group_names (Array<String>)

    groups to run on

  • options (Hash)

    a customizable set of options



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/mb/command_runner.rb', line 110

def on(*group_names, &block)
  options = group_names.last.kind_of?(Hash) ? group_names.pop : {}

  unless block_given?
    raise PluginSyntaxError, "Block required"
  end

  clean_room = CleanRoom.new(scope)
  clean_room.instance_eval(&block)
  actions = clean_room.send(:actions)

  nodes = group_names.map do |name|
    scope.group!(name.to_s)
  end.flat_map do |group|
    group.nodes(environment)
  end.uniq

  nodes = MB::NodeFilter.filter(node_filter, nodes) if node_filter

  return unless nodes.any?

  if options[:any]
    nodes = nodes.sample(options[:any])
  end

  options[:max_concurrent] ||= nodes.count
  node_groups = nodes.each_slice(options[:max_concurrent]).to_a

  run_chef = !async?

  @on_procs << -> {
    node_groups.each do |nodes|
      actions.each do |action|
        action.run(job, environment, nodes, run_chef)
      end
    end
  }

  if async?
    @nodes |= nodes
  else
    apply
  end
end

#runObject



50
51
52
53
54
55
56
57
# File 'lib/mb/command_runner.rb', line 50

def run
  if execute.arity.nonzero?
    curried_execute = proc { execute.call(*args) }
    instance_eval(&curried_execute)
  else
    instance_eval(&execute)
  end
end