class Bootscript::Script

Main functional class. Models and builds a self-extracting Bash/TAR file.

Attributes

data_map[RW]

A Hash of data sources to be written onto the boot target’s filesystem. Each (String) key is a path to the desired file on the boot target. Each value can be a String (treated as ERB), or Object with a read method. Any Ruby File objects with extension “.ERB” are also processed as ERB.

log[R]

Standard Ruby Logger, overridden by passing :logger to {#initialize}

Public Class Methods

new(logger = nil) click to toggle source

constructor - configures the AWS S3 connection and logging @param logger [::Logger] - a standard Ruby logger

# File lib/bootscript/script.rb, line 25
def initialize(logger = nil)
  @log      ||= logger || Bootscript.default_logger
  @data_map = Hash.new
  @vars     = Hash.new
end

Public Instance Methods

generate(erb_vars = {}, destination = nil) click to toggle source

Generates the BootScript contents by interpreting the @data_map based on erb_vars. If destination has a write() method, the data is streamed there line-by-line, and the number of bytes written is returned. Otherwise, the BootScript contents are returned as a String. In the case of streaming output, the destination must be already opened. @param erb_vars [Hash] Ruby variables to interpolate into all templates @param destination [IO] a Ruby object that responds to write(String) @return [Fixnum] the number of bytes written to the destination, or @return [String] the text of the rendered script, if destination is nil

# File lib/bootscript/script.rb, line 40
def generate(erb_vars = {}, destination = nil)
  # Set state / instance variables, used by publish() and helper methods
  @vars           = Bootscript.merge_platform_defaults(erb_vars)
  output          = destination || StringIO.open(@script_data = "")
  @bytes_written  = 0
  if Bootscript.windows?(@vars)
    @bytes_written += output.write(render_erb_text(File.read(
      "#{File.dirname(__FILE__)}/../templates/windows_header.bat.erb"
    )))
  end
  write_bootscript(output)          # streams the script part line-by-line
  write_uuencoded_archive(output)   # streams the archive line-by-line
  if Bootscript.windows?(@vars)
    @bytes_written += output.write(render_erb_text(File.read(
      "#{File.dirname(__FILE__)}/../templates/windows_footer.bat.erb"
    )))
  end
  output.close unless destination   # (close StringIO if it was opened)
  return (destination ? @bytes_written : @script_data)
end

Private Instance Methods

full_data_map() click to toggle source

merges the @data_map with the Chef built-ins, as-needed

# File lib/bootscript/script.rb, line 158
def full_data_map
  Bootscript::Chef.included?(@vars) ?
    @data_map.merge(Bootscript::Chef.files(@vars)) : @data_map
end
render_data_map_into(archive) click to toggle source

renders each data map item into an ‘archive’, which must be either an Archive::Tar::Minitar::Writer (if unix), or a Zip::OutputStream (windows)

# File lib/bootscript/script.rb, line 124
def render_data_map_into(archive)
  full_data_map.each do |remote_path, item|
    if item.is_a? String            # case 1: data item is a String
      @log.debug "Rendering ERB data (#{item[0..16]}...) into archive"
      data  = render_erb_text(item)
      input = StringIO.open(data, 'r')
      size  = data.bytes.count
    elsif item.is_a?(File)          # case 2: data item is an ERB file
      if item.path.upcase.sub(/\A.*\./,'') == 'ERB'
        @log.debug "Rendering ERB file #{item.path} into archive"
        data  = render_erb_text(item.read)
        input = StringIO.open(data, 'r')
        size  = data.bytes.count
      else                          # case 3: data item is a regular File
        @log.debug "Copying data from #{item.inspect} into archive"
        input = item
        size  = File.stat(item).size
      end
    else                            # case 4: Error
      raise ArgumentError.new("cannot process item: #{item}")
    end
    if Bootscript.windows?(@vars)
      archive.put_next_entry remote_path
      archive.write input.read
    else
      opts = {mode: 0600, size: size, mtime: Time.now}
      archive.add_file_simple(remote_path, opts) do |output|
        while data = input.read(512) ; output.write data end
      end
    end
  end
end
render_erb_text(erb_text) click to toggle source

renders erb_text, using @vars

# File lib/bootscript/script.rb, line 164
def render_erb_text(erb_text)
  Erubis::Eruby.new(erb_text).result(@vars)
end
strip_shell_comments(text) click to toggle source

strips all empty lines and lines beginning with # from text does NOT touch the first line of text

# File lib/bootscript/script.rb, line 170
def strip_shell_comments(text)
  lines = text.lines.to_a
  return text if lines.count < 2
  lines.first + lines[1..lines.count].
    reject{|l| (l =~ /^\s*#/) || (l =~ /^\s+$/)}.join('')
end
write_bootscript(destination) click to toggle source

Streams the bootscript to destination and updates @bytes_written

# File lib/bootscript/script.rb, line 64
def write_bootscript(destination)
  # If streaming, send the top-level script line-by-line from memory
  if Bootscript.windows?(@vars)
    template_path = Bootscript::WINDOWS_TEMPLATE
  else
    template_path = Bootscript::UNIX_TEMPLATE
  end
  template = File.read(template_path)
  template = strip_shell_comments(template) if @vars[:strip_comments]
  @log.debug "Rendering boot script to #{destination}..."
  render_erb_text(template).each_line do |ln|
    destination.write ln
    @bytes_written += ln.bytes.count
  end
end
write_unix_archive(destination) click to toggle source

Streams a uuencoded TGZ archive to destination, updating @bytes_written

# File lib/bootscript/script.rb, line 95
def write_unix_archive(destination)
  begin
    uuencode  = UUWriter.new(destination)
    gz        = Zlib::GzipWriter.new(uuencode)
    tar       = Archive::Tar::Minitar::Writer.open(gz)
    render_data_map_into(tar)
  ensure
    tar.close
    gz.close
    @bytes_written += uuencode.bytes_written
  end
end
write_uuencoded_archive(destination) click to toggle source

Streams the uuencoded archive to destination, updating @bytes_written

# File lib/bootscript/script.rb, line 81
def write_uuencoded_archive(destination)
  @log.debug "Writing #{@vars[:platform]} archive to #{destination}..."
  if Bootscript.windows?(@vars)
    @bytes_written += destination.write("$archive = @'\n")
    write_windows_archive(destination)
    @bytes_written += destination.write("'@\n")
  else  # :platform = 'unix'
    @bytes_written += destination.write("begin-base64 0600 bootstrap.tbz\n")
    write_unix_archive(destination)
    @bytes_written += destination.write("====\n") # (base64 footer)
  end
end
write_windows_archive(destination) click to toggle source

Streams a uuencoded ZIP archive to destination, updating @bytes_written

# File lib/bootscript/script.rb, line 109
def write_windows_archive(destination)
  Dir.mktmpdir do |dir|
    zip_path = "#{dir}/archive.zip"
    zipfile = File.open(zip_path, 'wb')
    Zip::OutputStream.open(zipfile) {|zip| render_data_map_into(zip)}
    zipfile.close
    @log.debug "zipfile = #{zip_path}, length = #{File.size zip_path}"
    File.open(zip_path, 'rb') do |zipfile|
      @bytes_written += destination.write([zipfile.read].pack 'm')
    end
  end
end