Class: Blower::Context

Inherits:
Object show all
Extended by:
Forwardable
Defined in:
lib/blower/context.rb

Overview

Blower tasks are executed within a context.

The context can be used to share information between tasks by storing it in instance variables.

Defined Under Namespace

Classes: HostHash

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Context

Create a new Context.

Parameters:

  • path (Array)

    The search path for tasks.


48
49
50
51
52
53
# File 'lib/blower/context.rb', line 48

def initialize (path)
  @path = path
  @data = {}
  @hosts = []
  @failures = []
end

Instance Attribute Details

#failuresObject

The failed hosts.


37
38
39
# File 'lib/blower/context.rb', line 37

def failures
  @failures
end

#fileObject

The file name of the currently running task. Context#cp interprets relative file names relative to this file name's directory component.


44
45
46
# File 'lib/blower/context.rb', line 44

def file
  @file
end

#hostsObject

The target hosts.


34
35
36
# File 'lib/blower/context.rb', line 34

def hosts
  @hosts
end

#pathObject

Search path for tasks.


31
32
33
# File 'lib/blower/context.rb', line 31

def path
  @path
end

#userObject

Username override. If not-nil, this user is used for all remote accesses.


40
41
42
# File 'lib/blower/context.rb', line 40

def user
  @user
end

Instance Method Details

#as(user, quiet: false) ⇒ Object

Yield with a temporary username override

Parameters:

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.


105
106
107
108
109
110
111
# File 'lib/blower/context.rb', line 105

def as (user, quiet: false)
  let :@user => user do
    log.info "as #{user}", quiet: quiet do
      yield
    end
  end
end

#cp(readable, to, as: user, on: hosts, quiet: false) ⇒ Object #cp(filename, to, as: user, on: hosts, quiet: false) ⇒ Object

Copy a file or readable to the host filesystems.

Overloads:

  • #cp(readable, to, as: user, on: hosts, quiet: false) ⇒ Object

    Parameters:

    • from (#read)

      An object from which to read the contents of the new file.

    • to (String)

      The file name to write the string to.

    • on (Array) (defaults to: hosts)

      The hosts to operate on. Defaults to the context's current host list.

    • as (#to_s) (defaults to: user)

      The remote user to operate as. Defaults to the context's current user if that is not nil, and the host's configured user otherwise.

    • quiet (Boolean) (defaults to: false)

      Whether to suppress log messages.

    • once (String)

      If non-nil, only perform the operation if it hasn't been done before as per #once.

  • #cp(filename, to, as: user, on: hosts, quiet: false) ⇒ Object

    Parameters:

    • from (String)

      The name of the local file to copy.

    • to (String)

      The file name to write the string to.

    • on (Array) (defaults to: hosts)

      The hosts to operate on. Defaults to the context's current host list.

    • as (#to_s) (defaults to: user)

      The remote user to operate as. Defaults to the context's current user if that is not nil, and the host's configured user otherwise.

    • quiet (Boolean) (defaults to: false)

      Whether to suppress log messages.

    • once (String)

      If non-nil, only perform the operation if it hasn't been done before as per #once.


181
182
183
184
185
186
187
188
189
190
191
# File 'lib/blower/context.rb', line 181

def cp (from, to, as: user, on: hosts, quiet: false, once: nil, delete: false)
  self.once once, quiet: quiet do
    log.info "cp: #{from} -> #{to}", quiet: quiet do
      Dir.chdir File.dirname(file) do
        hash_map(hosts) do |host|
          host.cp from, to, as: as, quiet: quiet, delete: delete
        end
      end
    end
  end
end

#get(name, default = nil) ⇒ Object

Return a context variable.

Parameters:

  • name

    The name of the variable.

  • default (defaults to: nil)

    The value to return if the variable is not set.


58
59
60
# File 'lib/blower/context.rb', line 58

def get (name, default = nil)
  @data.fetch(name, default)
end

#on(*hosts, quiet: false) ⇒ Object

Yield with a temporary host list

Parameters:

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.


95
96
97
98
99
100
101
# File 'lib/blower/context.rb', line 95

def on (*hosts, quiet: false)
  let :@hosts => hosts.flatten do
    log.info "on #{@hosts.map(&:name).join(", ")}", quiet: quiet do
      yield *hosts
    end
  end
end

#once(key, store: "/var/cache/blower.json", quiet: false) ⇒ Object

Execute a block only once per host. It is usually preferable to make tasks idempotent, but when that isn't possible, once will only execute the block on hosts where a block with the same key hasn't previously been successfully executed.

Parameters:

  • key (String)

    Uniquely identifies the block.

  • store (String) (defaults to: "/var/cache/blower.json")

    File to store once's state in.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.


278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/blower/context.rb', line 278

def once (key, store: "/var/cache/blower.json", quiet: false)
  return yield unless key
  log.info "once: #{key}", quiet: quiet do
    hash_map(hosts) do |host|
      done = begin
        JSON.parse(host.read(store, quiet: true))
      rescue => e
        {}
      end
      unless done[key]
        on [host] do
          yield
        end
        done[key] = true
        host.write(done.to_json, store, quiet: true)
      end
    end
  end
end

#ping(on: hosts, quiet: false) ⇒ Object

Ping each host by trying to connect to port 22

Parameters:

  • on (Array) (defaults to: hosts)

    The hosts to operate on. Defaults to the context's current host list.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.


263
264
265
266
267
268
269
# File 'lib/blower/context.rb', line 263

def ping (on: hosts, quiet: false)
  log.info "ping", quiet: quiet do
    hash_map(hosts) do |host|
      host.ping
    end
  end
end

#read(filename, as: user, on: hosts, quiet: false) ⇒ Hash

Reads a remote file from each host.

Parameters:

  • filename (String)

    The file to read.

  • on (Array) (defaults to: hosts)

    The hosts to operate on. Defaults to the context's current host list.

  • as (#to_s) (defaults to: user)

    The remote user to operate as. Defaults to the context's current user if that is not nil, and the host's configured user otherwise.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.

Returns:

  • (Hash)

    A hash of Host objects to Strings of the file contents.


199
200
201
202
203
204
205
# File 'lib/blower/context.rb', line 199

def read (filename, as: user, on: hosts, quiet: false)
  log.info "read: #{filename}", quiet: quiet do
    hash_map(hosts) do |host|
      host.read filename, as: as
    end
  end
end

#render(from, to, as: user, on: hosts, quiet: false, once: nil) ⇒ Object

Renders and installs files from ERB templates. Files are under from in ERB format. from/foo/bar.conf.erb is rendered and written to to/foo/bar.conf. Non-ERB files are ignored.

Parameters:

  • from (String)

    The directory to search for .erb files.

  • to (String)

    The remote directory to put files in.

  • on (Array) (defaults to: hosts)

    The hosts to operate on. Defaults to the context's current host list.

  • as (#to_s) (defaults to: user)

    The remote user to operate as. Defaults to the context's current user if that is not nil, and the host's configured user otherwise.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.

  • once (String) (defaults to: nil)

    If non-nil, only perform the operation if it hasn't been done before as per #once.


232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/blower/context.rb', line 232

def render (from, to, as: user, on: hosts, quiet: false, once: nil)
  self.once once, quiet: quiet do
    Dir.chdir File.dirname(file) do
      Find.find(from).each do |path|
        if File.directory?(path)
          to_path = to + path[from.length..-1]
          sh "mkdir -p #{to_path.shellescape}"
        elsif path =~ /\.erb$/
          template = ERB.new(File.read(path))
          to_path = to + path[from.length..-5]
          log.info "render: #{path} -> #{to_path}", quiet: quiet do
            hash_map(hosts) do |host|
              host.cp StringIO.new(template.result(binding)), to_path, as: as, quiet: quiet
            end
          end
        else
          to_path = to + path[from.length..-1]
          log.info "copy: #{path} -> #{to_path}", quiet: quiet do
            hash_map(hosts) do |host|
              host.cp File.open(path), to_path, as: as, quiet: quiet
            end
          end
        end
      end
    end
  end
end

#run(task, optional: false, quiet: false, once: nil) ⇒ Object

Find and execute a task. For each entry in the search path, it checks for path/task.rb, path/task/blow.rb, and finally for bare path/task. The search stops at the first match. If found, the task is executed within the context, with @file bound to the task's file name.

Parameters:

  • task (String)

    The name of the task.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.

  • once (String) (defaults to: nil)

    If non-nil, only perform the operation if it hasn't been done before as per #once.

Returns:

  • The result of evaluating the task file.

Raises:

  • (TaskNotFound)

    If no task is found.

  • Whatever the task itself raises.


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/blower/context.rb', line 122

def run (task, optional: false, quiet: false, once: nil)
  @run_cache ||= {}
  once once, quiet: quiet do
    log.info "run #{task}", quiet: quiet do
      file = find_task(task)
      if @run_cache.has_key? file
        log.info "*cached*"
        @run_cache[file]
      else
        @run_cache[file] = begin
          code = File.read(file)
          let :@file => file do
            instance_eval(code, file)
          end
        end
      end
    end
  end
rescue TaskNotFound => e
  return if optional
  raise e
end

#set(hash) ⇒ Object

Merge the hash into the context variables.

Parameters:

  • hash (Hash)

    The values to merge into the context variables.


72
73
74
# File 'lib/blower/context.rb', line 72

def set (hash)
  @data.merge! hash
end

#sh(command, as: user, on: hosts, quiet: false, once: nil) ⇒ Object

Execute a shell command on each host

Parameters:

  • on (Array) (defaults to: hosts)

    The hosts to operate on. Defaults to the context's current host list.

  • as (#to_s) (defaults to: user)

    The remote user to operate as. Defaults to the context's current user if that is not nil, and the host's configured user otherwise.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.

  • once (String) (defaults to: nil)

    If non-nil, only perform the operation if it hasn't been done before as per #once.


156
157
158
159
160
161
162
163
164
# File 'lib/blower/context.rb', line 156

def sh (command, as: user, on: hosts, quiet: false, once: nil)
  self.once once, quiet: quiet do
    log.info "sh: #{command}", quiet: quiet do
      hash_map(hosts) do |host|
        host.sh command, as: as, quiet: quiet
      end
    end
  end
end

#sh?(command, as: user, on: hosts, quiet: false, once: nil) ⇒ Boolean

Returns:

  • (Boolean)

145
146
147
148
149
# File 'lib/blower/context.rb', line 145

def sh? (command, as: user, on: hosts, quiet: false, once: nil)
  sh command, as: as, on: on, quiet: quiet, once: once
rescue
  nil
end

#unset(*names) ⇒ Object

Remove context variables

Parameters:

  • names

    The names to remove from the variables.


64
65
66
67
68
# File 'lib/blower/context.rb', line 64

def unset (*names)
  names.each do |name|
    @data.delete name
  end
end

#with(hash, quiet: false) ⇒ Object

Yield with the hash temporary merged into the context variables. Only variables specifically named in hash will be reset when the yield returns.

Parameters:

  • hash (Hash)

    The values to merge into the context variables.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.

Returns:

  • Whatever the yielded-to block returns.


81
82
83
84
85
86
87
88
89
90
91
# File 'lib/blower/context.rb', line 81

def with (hash, quiet: false)
  old_values = data.values_at(hash.keys)
  log.debug "with #{hash}", quiet: quiet do
    set hash
    yield
  end
ensure
  hash.keys.each.with_index do |key, i|
    @data[key] = old_values[i]
  end
end

#write(string, to, as: user, on: hosts, quiet: false, once: nil) ⇒ Object

Writes a string to a file on the host filesystems.

Parameters:

  • string (String)

    The string to write.

  • to (String)

    The file name to write the string to.

  • on (Array) (defaults to: hosts)

    The hosts to operate on. Defaults to the context's current host list.

  • as (#to_s) (defaults to: user)

    The remote user to operate as. Defaults to the context's current user if that is not nil, and the host's configured user otherwise.

  • quiet (Boolean) (defaults to: false)

    Whether to suppress log messages.

  • once (String) (defaults to: nil)

    If non-nil, only perform the operation if it hasn't been done before as per #once.


214
215
216
217
218
219
220
221
222
# File 'lib/blower/context.rb', line 214

def write (string, to, as: user, on: hosts, quiet: false, once: nil)
  self.once once, quiet: quiet do
    log.info "write: #{string.bytesize} bytes -> #{to}", quiet: quiet do
      hash_map(hosts) do |host|
        host.write string, to, as: as, quiet: quiet
      end
    end
  end
end