class Berkflow::Cli

Constants

LATEST

Public Class Methods

new(*args) click to toggle source
Calls superclass method
# File lib/berkflow/cli.rb, line 17
def initialize(*args)
  super(*args)

  if @options[:verbose]
    Ridley.logger.level = ::Logger::INFO
  end

  if @options[:debug]
    Ridley.logger.level = ::Logger::DEBUG
  end
end

Public Instance Methods

exec(environment, command) click to toggle source
# File lib/berkflow/cli.rb, line 155
def exec(environment, command)
  env = find_environment!(environment)

  say "Discovering nodes in #{environment}..."
  nodes = find_nodes(environment)

  if nodes.empty?
    say "No nodes in #{environment}. Done."
    exit(0)
  end

  say "Executing command on #{nodes.length} nodes..."
  success, failures, out = handle_results nodes.pmap { |node|
    ridley.node.run connect_address(node, options), command
  }

  unless success.empty?
    say "Successfully executed command on #{success.length} nodes"
  end

  unless failures.empty?
    error "Failed to execute command on #{failures.length} nodes"
  end

  say "Done. See #{out} for logs."
  failures.empty? ? exit(0) : exit(1)
end
install(url) click to toggle source
# File lib/berkflow/cli.rb, line 69
def install(url)
  require 'uri'
  require 'open-uri'
  require 'zlib'
  require 'rubygems/package'

  if is_url?(url)
    tempfile = Tempfile.new("berkflow")
    tempfile.binmode
    begin
      open(url) { |remote_file| tempfile.write(remote_file.read) }
    rescue OpenURI::HTTPError => ex
      error "Error retrieving remote package: #{ex.message}."
      exit(1)
    end
    url = tempfile.path
  end

  unless File.exist?(url)
    error "Package not found: #{url}."
    exit(1)
  end

  tmpdir = Dir.mktmpdir("berkflow")

  begin
    zlib   = Zlib::GzipReader.new(File.open(url, "rb"))
    io     = StringIO.new(zlib.read)
    zlib.close

    Gem::Package::TarReader.new io do |tar|
      tar.each do |tarfile|
        destination_file = File.join(tmpdir, tarfile.full_name)

        if tarfile.directory?
          FileUtils.mkdir_p(destination_file)
        else
          destination_directory = File.dirname(destination_file)
          FileUtils.mkdir_p(destination_directory) unless File.directory?(destination_directory)
          File.open(destination_file, "wb") { |f| f.print tarfile.read }
        end
      end
    end
  rescue Zlib::GzipFile::Error => ex
    error "Error extracting package: #{ex.message}"
    exit(1)
  end

  cookbooks_dir = File.join(tmpdir, "cookbooks")

  unless File.exist?(cookbooks_dir)
    error "Package did not contain a 'cookbooks' directory."
    exit(1)
  end

  uploaded = Dir.entries(cookbooks_dir).collect do |path|
    path = File.join(cookbooks_dir, path)
    next unless File.cookbook?(path)
    begin
      cookbook = Ridley::Chef::Cookbook.from_path(path)
      say "Uploading #{cookbook.cookbook_name} (#{cookbook.version})"
      ridley.cookbook.upload(path, freeze: true, force: options[:force])
      true
    rescue Ridley::Errors::FrozenCookbook
      true
    end
  end.compact

  say "Uploaded #{uploaded.length} cookbooks."
  say "Done."
ensure
  tempfile.close(true) if tempfile
  FileUtils.rm_rf(tmpdir) if tmpdir
end
run_chef(environment) click to toggle source
# File lib/berkflow/cli.rb, line 190
def run_chef(environment)
  env = find_environment!(environment)

  say "Discovering nodes in #{environment}..."
  nodes = find_nodes(environment)

  if nodes.empty?
    say "No nodes in #{environment}. Done."
    exit(0)
  end

  say "Running Chef Client on #{nodes.length} nodes..."
  success, failures, out = handle_results nodes.pmap { |node|
    ridley.node.chef_run connect_address(node, options)
  }

  unless success.empty?
    say "Successfully ran Chef Client on #{success.length} nodes"
  end

  unless failures.empty?
    error "Failed to run Chef Client on #{failures.length} nodes"
  end

  say "Done. See #{out} for logs."
  failures.empty? ? exit(0) : exit(1)
end
upgrade(environment, application, version = LATEST) click to toggle source
# File lib/berkflow/cli.rb, line 239
def upgrade(environment, application, version = LATEST)
  version  = sanitize_version(version)
  env      = find_environment!(environment)
  cookbook = find_cookbook!(application, version)

  unless options[:force]
    if locked = env.cookbook_versions[application]
      if Semverse::Constraint.new(locked).version.to_s == cookbook.version
        say "Environment already at #{cookbook.version}."
        say "Done."
        exit(0)
      end
    end
  end

  file = Tempfile.new("berkflow")
  unless contents = cookbook.download_file(:root_file, Berkshelf::Lockfile::DEFAULT_FILENAME, file.path)
    error "#{application} (#{version}) did not contain a Berksfile.lock"
    exit(1)
  end

  say "Upgrading #{environment} to #{cookbook.version}"
  say "Applying cookbook locks to #{environment}..."
  lockfile = Berkshelf::Lockfile.from_file(file.path)
  unless lockfile.apply(environment)
    error "Failed to apply Berksfile.lock to #{environment}."
    exit(1)
  end

  run_chef(environment) unless options[:skip_chef_run]
ensure
  file.close(true) if file
end
version() click to toggle source
# File lib/berkflow/cli.rb, line 274
def version
  say Berkflow::VERSION
end

Private Instance Methods

config() click to toggle source
# File lib/berkflow/cli.rb, line 290
def config
  Berkshelf::Config.instance
end
connect_address(node, options) click to toggle source
# File lib/berkflow/cli.rb, line 294
def connect_address(node, options)
  address = case options[:connect_attribute].downcase
  when "ipv4"
    node.public_ipv4
  when "hostname"
    node.public_hostname
  else
    node.public_hostname
  end
end
find_cookbook!(application, version) click to toggle source
# File lib/berkflow/cli.rb, line 335
def find_cookbook!(application, version)
  if version == LATEST
    unless version = ridley.cookbook.latest_version(application)
      error "No versions of Cookbook found: #{application}."
      exit(1)
    end
  end

  unless cookbook = ridley.cookbook.find(application, version)
    error "Cookbook not found: #{application} (#{version})."
    exit(1)
  end
  cookbook
end
find_environment!(environment) click to toggle source
# File lib/berkflow/cli.rb, line 350
def find_environment!(environment)
  unless env = ridley.environment.find(environment)
    error "Environment not found: #{environment}"
    exit(1)
  end
  env
end
find_nodes(environment) click to toggle source
# File lib/berkflow/cli.rb, line 358
def find_nodes(environment)
  ridley.search(:node, "chef_environment:#{environment}")
end
handle_results(result_set) click to toggle source
# File lib/berkflow/cli.rb, line 305
def handle_results(result_set)
  failure,  success = result_set.partition { |result| result.error? }
  log_dir           = log_results(success, failure)
  [success, failure, log_dir]
end
is_url?(string) click to toggle source
# File lib/berkflow/cli.rb, line 331
def is_url?(string)
  string =~ /^#{URI::regexp}$/
end
log_results(success, failure) click to toggle source
# File lib/berkflow/cli.rb, line 311
def log_results(success, failure)
  out_dir     = File.join("berkflow_out", Time.now.strftime("%Y%m%d%H%M%S"))
  success_dir = File.join(out_dir, "success")
  failure_dir = File.join(out_dir, "failure")

  [success_dir, failure_dir].each { |dir| FileUtils.mkdir_p(dir) }
  success.each { |result| write_logs(result, success_dir) }
  failure.each { |result| write_logs(result, failure_dir) }
  out_dir
end
ridley() click to toggle source
# File lib/berkflow/cli.rb, line 280
def ridley
  @ridley ||= Ridley.new(server_url: config.chef.chef_server_url, client_name: config.chef.node_name,
    client_key: config.chef.client_key, ssh: {
      user: @options[:ssh_user], password: @options[:ssh_password], keys: @options[:ssh_key],
      sudo: use_sudo?
  }, ssl: {
    verify: config.ssl.verify
  })
end
sanitize_version(version) click to toggle source
# File lib/berkflow/cli.rb, line 322
def sanitize_version(version)
  return version if version == LATEST

  Semverse::Version.new(version).to_s
rescue Semverse::InvalidVersionFormat
  error "Invalid version: #{version}. Provide a valid SemVer version string. (i.e. 1.2.3)."
  exit(1)
end
use_sudo?() click to toggle source
# File lib/berkflow/cli.rb, line 362
def use_sudo?
  @options[:sudo].nil? ? true : @options[:sudo]
end
write_logs(result, dir) click to toggle source
# File lib/berkflow/cli.rb, line 366
def write_logs(result, dir)
  write_stdout(result, dir)
  write_stderr(result, dir)
end
write_stderr(result, dir) click to toggle source
# File lib/berkflow/cli.rb, line 375
def write_stderr(result, dir)
  File.open(File.join(dir, "#{result.host}.stderr"), "w") { |file| file.write(result.stderr) }
end
write_stdout(result, dir) click to toggle source
# File lib/berkflow/cli.rb, line 371
def write_stdout(result, dir)
  File.open(File.join(dir, "#{result.host}.stdout"), "w") { |file| file.write(result.stdout) }
end