class Comfy::Creator

Creates given virtual machine images.

Constants

NEEDLE_REPLACEMENTS
REPLACEMENT_REGEX
TIMESTAMP_FORMAT

Attributes

data[RW]

Public Class Methods

new(options) click to toggle source

Creates a creator instance.

@param options [Hashie::Mash] hash-like structure with options

# File lib/comfy/creator.rb, line 25
def initialize(options)
  @data = options.clone
end

Public Instance Methods

clean() click to toggle source

Cleans everything from temporary directory

# File lib/comfy/creator.rb, line 55
def clean
  if data[:server_dir]
    logger.debug("Cleaning temporary directory #{data[:server_dir]}.")
    FileUtils.remove_dir(data[:server_dir])
  end
end
create() click to toggle source

Method representing the whole creation process. Prepares enviroment, prepare files and starts packer job.

# File lib/comfy/creator.rb, line 31
def create
  logger.info('Preparing for image creation...')

  data[:server_dir] = Dir.mktmpdir('comfy')
  logger.debug("Server root directory: #{data[:server_dir]}")

  prepare_data
  logger.debug("Prepared data: #{data}")

  templater = Comfy::Templater.new data
  templater.prepare_files

  packer_file = "#{data[:server_dir]}/#{data[:distribution]}.packer"
  run_packer(packer_file)

  # let's create cloud appliance descriptor files
  if data[:description]
    data[:formats].each do |format|
      File.write(File.join(data[:'output-dir'], "#{data[:identifier]}.json"), description(format))
    end
  end
end

Private Instance Methods

choose_version() click to toggle source

Choose_version is a method that selects required version from available versions.

# File lib/comfy/creator.rb, line 110
def choose_version
  version = data[:version]

  available_versions = []
  data[:distro]['versions'].each do |v|
    available_versions << { major: v['major_version'].to_i, minor: v['minor_version'].to_i, patch: v['patch_version'].to_i, version: v }
  end
  available_versions.sort_by! { |v| [v[:major], v[:minor], v[:patch]] }.reverse!

  return available_versions.first[:version] if version == :newest

  version_parts = version.split('.')
  fail Comfy::Errors::InvalidDistributionVersionError, "Version '#{version}' is not a valid distribution version" if version_parts.size > 3

  version_parts.map! do |part|
    fail Comfy::Errors::InvalidDistributionVersionError, "Version '#{version}' is not a valid distribution version" unless part =~ /\A\d+\z/

    part.to_i
  end

  selected = available_versions.select { |v| v[:major] == version_parts[0] }
  if version_parts.size > 1
    selected = selected.select { |v| v[:minor] == version_parts[1] }

    if version_parts.size > 2
      selected = selected.select { |v| v[:patch] == version_parts[2] }
    end
  end

  fail Comfy::Errors::NoSuchDistributionVersionError, "No version '#{version}' available for distribution '#{data[:distribution]}'" if selected.empty?

  selected.sort_by { |v| [v[:major], v[:minor], v[:patch]] }.reverse.first[:version]
end
description(builder) click to toggle source

Description returns cloud appliance descriptor JSON. It uses information gathered from command line arguments and the config file.

@param builder [Symbol] builder used in the description of the cloud appliance descriptor

@return [Json] appliance descriptor in Json format

# File lib/comfy/creator.rb, line 158
def description(builder)
  # FIXME? mapping platforms/builders to formats is hardcoded for now, nothing else is supported
  formats = { 'virtualbox' => 'ova', 'qemu' => 'qcow2' }
  vm_dir = "comfy_#{data[:distribution]}-#{data[:distro][:version]['major_version']}.#{data[:distro][:version]['minor_version']}_#{builder}"
  vm_name = "#{vm_dir}.#{formats[builder]}"
  disk_path = File.join(data[:'output-dir'],vm_dir,vm_name)

  os = Cloud::Appliance::Descriptor::Os.new distribution: data[:distribution], version: version_string
  disk = Cloud::Appliance::Descriptor::Disk.new type: :os, format: formats[builder], path: disk_path

  appliance = Cloud::Appliance::Descriptor::Appliance.new action: :registration, os: os, disks: [disk]
  appliance.title = data[:distro]['name']
  appliance.identifier = data[:identifier]
  appliance.version = Time.new.strftime(TIMESTAMP_FORMAT)
  appliance.groups = data[:groups]

  appliance.to_json
end
password() click to toggle source

Method generating a temporary random password used while creating image.

@return [String] password

# File lib/comfy/creator.rb, line 147
def password
  o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
  (0...100).map { o[rand(o.length)] }.join
end
prepare_data() click to toggle source

Preparation of various data. Method prepares description file for packer and distribution preseed / kickstart file.

# File lib/comfy/creator.rb, line 88
def prepare_data
  description_file = "#{data[:'template-dir']}/#{data[:distribution]}/#{data[:distribution]}.description"
  JSON::Validator.validate!(Comfy::DESCRIPTION_SCHEMA_FILE, description_file)

  description = File.read(description_file)
  data[:distro] = JSON.parse(description)
  logger.debug("Data from description file: #{data[:distro]}")

  data[:distro][:version] = choose_version
  logger.debug("Version selected for build: #{data[:distro][:version]}")

  data[:provisioners] = {}
  data[:provisioners][:scripts] = Dir.glob(File.join(data[:'template-dir'], data[:distribution], 'scripts', '*'))
  data[:provisioners][:files] = Dir.glob(File.join(data[:'template-dir'], data[:distribution], 'files', '*'))

  data[:password] = password
  logger.debug("Temporary password: '#{data[:password]}'")

  data[:identifier] = replace_needles(data[:identifier])
end
replace_needles(format_string) click to toggle source

Replace needles in the argument. Replacements are picked from NEEDLE_REPLACEMENTS constant.

@param [String] format_string string with needles to be replaced

@return [String] format_string with all needles replaced

# File lib/comfy/creator.rb, line 183
def replace_needles(format_string)
  format_string.gsub(REPLACEMENT_REGEX) do |match|
    NEEDLE_REPLACEMENTS.key?(match.to_sym) ? NEEDLE_REPLACEMENTS[match.to_sym].call(self) : match
  end
end
run_packer(packer_file) click to toggle source

Method wrapping usage of packer tool.

@param packer_file descriptor file with info for packer processing.

# File lib/comfy/creator.rb, line 67
def run_packer(packer_file)
  logger.info("Calling Packer - building distribution: '#{data[:distribution]}'.")

  packer_binary = data[:'packer-binary'] || 'packer'

  packer = Mixlib::ShellOut.new(packer_binary, "validate", packer_file)
  packer.run_command

  fail Comfy::Errors::PackerValidationError, "Packer validation failed for distribution '#{data[:distribution]}': #{packer.stdout}" if packer.error?

  packer = Mixlib::ShellOut.new(packer_binary, "build", "-parallel=false", packer_file, timeout: 5400)
  packer.live_stream = logger
  packer.run_command

  fail Comfy::Errors::PackerExecutionError, "Packer finished with error for distribution '#{data[:distribution]}': #{packer.stderr}" if packer.error?

  logger.info("Packer finished successfully for distribution '#{data[:distribution]}'")
end
version_string() click to toggle source

Simple method used to return the version string

@return [String] string which contains major, minor, and patch version (if possible).

# File lib/comfy/creator.rb, line 192
def version_string
  result = []
  result << data[:distro][:version]['major_version']
  result << data[:distro][:version]['minor_version']
  result << data[:distro][:version]['patch_version']
  result.compact.join('.')
end