class Hula::BoshDirector

Attributes

certificate_path[R]
command_runner[R]
env_login[R]
logger[R]
password[R]
target_url[R]
username[R]

Public Class Methods

new( target_url:, username:, password:, manifest_path: nil, command_runner: CommandRunner.new, logger: default_logger, certificate_path: nil, env_login: false ) click to toggle source
# File lib/hula/bosh_director.rb, line 24
def initialize(
  target_url:,
  username:,
  password:,
  manifest_path: nil,
  command_runner: CommandRunner.new,
  logger: default_logger,
  certificate_path: nil,
  env_login: false
)
  @target_url            = target_url
  @username              = username
  @password              = password
  @default_manifest_path = manifest_path
  @command_runner        = command_runner
  @logger                = logger
  @certificate_path      = certificate_path
  @env_login             = env_login

  target_and_login
end

Public Instance Methods

delete_deployment(deployment_name, force: false) click to toggle source
# File lib/hula/bosh_director.rb, line 56
def delete_deployment(deployment_name, force: false)
  cmd = ["delete deployment #{deployment_name}"]
  cmd << '-f' if force
  run_bosh(cmd.join(' '))
end
deploy(manifest_path = default_manifest_path) click to toggle source
# File lib/hula/bosh_director.rb, line 52
def deploy(manifest_path = default_manifest_path)
  run_bosh("--deployment #{manifest_path} deploy")
end
deployment_names() click to toggle source
# File lib/hula/bosh_director.rb, line 125
def deployment_names
  deployments = run_bosh('deployments')
  # [\n\r]+ a new line,
  # \s* maybe followed by whitespace,
  # \| followed by a pipe,
  # \s+ followed by whitespace,
  # ([^\s]+) followed some characters (ie, not whitespace, or a pipe) — this is the match
  first_column = deployments.scan(/[\n\r]+\s*\|\s+([^\s\|]+)/).flatten

  first_column.drop(1) # without header
end
download_manifest(deployment_name) click to toggle source
# File lib/hula/bosh_director.rb, line 176
def download_manifest(deployment_name)
  Tempfile.open 'manifest' do |file|
    run_bosh("download manifest #{deployment_name} #{file.path}")
    return YAML.load_file("#{file.path}")
  end
end
has_logfiles?(job_name, logfile_names) click to toggle source
# File lib/hula/bosh_director.rb, line 117
def has_logfiles?(job_name, logfile_names)
  logs = job_logfiles(job_name)
  logfile_names.each do |logfile_name|
    return false unless logs.include?(logfile_name)
  end
  true
end
ips_for_job(job, deployment_name = nil) click to toggle source

Parses output of `bosh vms` like below, getting an array of IPs for a job name ------------------------------------———---------------————–+ | Job/index | State | Resource Pool | IPs | ------------------------------------———---------------————–+ | api_z1/0 | running | large_z1 | 10.244.0.138 | …

Also matches output from 1.3184 bosh_cli e.g.

------------------------------------------------———----------------————–+ | VM | State | AZ | VM Type | IPs | ------------------------------------------------———----------------————–+ | api_z1/0 (fe04916e-afd0-42a3-aaf5-52a8b163f1ab)| running | n/a | large_z1 | 10.244.0.138 | …

# File lib/hula/bosh_director.rb, line 152
def ips_for_job(job, deployment_name = nil)
  output = run_bosh("vms #{deployment_name}")
  deployments = output.split(/^Deployment/)

  job_ip_map = {}

  deployments.each do |deployment|
    rows = deployment.split("\n")
    row_cols = rows.map { |row| row.split('|') }
    job_cols = row_cols.  select { |cols| cols.length == 5 || cols.length == 6 } # match job boxes
    job_ip_pairs = job_cols.map { |cols| [cols[1].strip.split(' ')[0], cols.last.strip] }
    jobs_with_real_ips = job_ip_pairs.select { |pairs| pairs.last =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ }
    # converts eg   cf-redis-broker/2  to cf-redis-broker
    jobs_without_instance_numbers = jobs_with_real_ips.map { |pair| [pair.first.gsub(/\/.*/, ''), pair.last] }
    jobs_without_instance_numbers.each do |job|
      name, ip = job
      job_ip_map[name] ||= []
      job_ip_map[name] << ip
    end
  end

  job_ip_map.fetch(job, [])
end
job_logfiles(job_name) click to toggle source
# File lib/hula/bosh_director.rb, line 106
def job_logfiles(job_name)
  tmpdir = Dir.tmpdir
  run_bosh("logs #{job_name} 0 --job --dir #{tmpdir}")
  tarball = Dir[File.join(tmpdir, job_name.to_s + '*.tgz')].last
  output = command_runner.run("tar tf #{tarball}")
  lines = output.split(/\n+/)
  filepaths = lines.map { |f| Pathname.new(f) }
  logpaths = filepaths.select { |f| f.extname == '.log' }
  logpaths.map(&:basename).map(&:to_s)
end
lite?() click to toggle source

Should rely on `bosh status` and CPI, but currently Bosh Lite is reporting 'vsphere' instead of 'warden'.

# File lib/hula/bosh_director.rb, line 48
def lite?
  target_url.include? '192.168.50.4'
end
recreate(name) click to toggle source
# File lib/hula/bosh_director.rb, line 84
def recreate(name)
  properties = job_properties(name)

  instances = properties.fetch('instances')

  instances.times do |instance_index|
    run_bosh("recreate #{name} #{instance_index}")
  end
end
recreate_all(jobs) click to toggle source
# File lib/hula/bosh_director.rb, line 72
def recreate_all(jobs)
  jobs.each do |name|
    recreate(name)
  end
end
recreate_instance(name, index) click to toggle source
# File lib/hula/bosh_director.rb, line 78
def recreate_instance(name, index)
  validate_job_instance_index(name, index)

  run_bosh("recreate #{name} #{index}")
end
run_errand(name, manifest_path: default_manifest_path, keep_alive: false) click to toggle source
# File lib/hula/bosh_director.rb, line 62
def run_errand(name, manifest_path: default_manifest_path, keep_alive: false)
    command = "--deployment #{manifest_path} run errand #{name}"

  if keep_alive
    command << " --keep-alive"
  end

  run_bosh(command)
end
start(name, index) click to toggle source
# File lib/hula/bosh_director.rb, line 100
def start(name, index)
  validate_job_instance_index(name, index)

  run_bosh("start #{name} #{index} --force")
end
stop(name, index) click to toggle source
# File lib/hula/bosh_director.rb, line 94
def stop(name, index)
  validate_job_instance_index(name, index)

  run_bosh("stop #{name} #{index} --force")
end

Private Instance Methods

bosh_config_path() click to toggle source
# File lib/hula/bosh_director.rb, line 255
def bosh_config_path
  # We should keep a reference to the tempfile, otherwise,
  # when the object gets GC'd, the tempfile is deleted.
  @bosh_config_tempfile ||= Tempfile.new('bosh_config')
  @bosh_config_tempfile.path
end
check_deployments!() click to toggle source
# File lib/hula/bosh_director.rb, line 271
def check_deployments!
  http = Net::HTTP.new(target_uri.host, target_uri.port)
  http.use_ssl = target_uri.scheme == 'https'
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = Net::HTTP::Get.new('/deployments')
  request.basic_auth username, password
  response = http.request request

  unless response.is_a? Net::HTTPSuccess
    fail DirectorIsBroken, "Failed to GET /deployments from #{target_uri}. Returned:\n\n#{response.to_hash}\n\n#{response.body}"
  end
end
check_port!() click to toggle source
# File lib/hula/bosh_director.rb, line 285
def check_port!
  socket = TCPSocket.new(target_uri.host, target_uri.port)
  socket.close
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
  raise DirectorPortNotOpen, "Cannot connect to #{target_uri.host}:#{target_uri.port}"
end
default_logger() click to toggle source
# File lib/hula/bosh_director.rb, line 205
def default_logger
  @default_logger ||= begin
    STDOUT.sync = true
    require 'logger'
    Logger.new(STDOUT)
  end
end
default_manifest_path() click to toggle source
# File lib/hula/bosh_director.rb, line 217
def default_manifest_path
  fail NoManifestSpecified unless default_manifest_path?
  @default_manifest_path
end
default_manifest_path?() click to toggle source
# File lib/hula/bosh_director.rb, line 213
def default_manifest_path?
  !!@default_manifest_path
end
health_check!() click to toggle source
# File lib/hula/bosh_director.rb, line 262
def health_check!
  check_port!
  check_deployments!
end
job_properties(job_name) click to toggle source
# File lib/hula/bosh_director.rb, line 188
def job_properties(job_name)
  if manifest.has_key?('instance_groups')
    key = "instance_groups"
  else
    key = "jobs"
  end
  manifest.fetch(key).find { |job| job.fetch('name') == job_name }.tap do |properties|
    fail ArgumentError.new('Job not found in manifest') unless properties
  end
end
manifest() click to toggle source
# File lib/hula/bosh_director.rb, line 222
def manifest
  YAML.load_file(default_manifest_path)
end
run_bosh(cmd) click to toggle source
# File lib/hula/bosh_director.rb, line 244
def run_bosh(cmd)
  command = "bosh -v -n --config '#{bosh_config_path}' #{cmd}"
  logger.info(command)

  command_runner.run(command)
rescue CommandFailedError => e
  logger.error(e.message)
  health_check!
  raise e
end
target_and_login() click to toggle source
# File lib/hula/bosh_director.rb, line 226
def target_and_login
  target_cmd = "target #{target_url}"
  if certificate_path
    target_cmd = "--ca-cert #{certificate_path} #{target_cmd}"
  end

  run_bosh(target_cmd)
  run_bosh("deployment #{default_manifest_path}") if default_manifest_path?

  if not env_login
    run_bosh("login #{username} #{password}")
  else
    if not ENV.has_key? 'BOSH_CLIENT' or not ENV.has_key? 'BOSH_CLIENT_SECRET'
      raise 'BOSH_CLIENT and BOSH_CLIENT_SECRET must both be set in the ENV'
    end
  end
end
target_uri() click to toggle source
# File lib/hula/bosh_director.rb, line 267
def target_uri
  @target_uri ||= URI.parse(target_url)
end
validate_job_instance_index(job_name, index) click to toggle source
# File lib/hula/bosh_director.rb, line 199
def validate_job_instance_index(job_name, index)
  properties = job_properties(job_name)
  instances = properties.fetch('instances')
  fail ArgumentError.new('Index out of range') unless (0...instances).include? index
end