Module: Boucher

Defined in:
lib/boucher/io.rb,
lib/boucher/env.rb,
lib/boucher/util.rb,
lib/boucher/meals.rb,
lib/boucher/compute.rb,
lib/boucher/servers.rb,
lib/boucher/storage.rb,
lib/boucher/volumes.rb,
lib/boucher/addresses.rb,
lib/boucher/provision.rb,
lib/boucher/snapshots.rb

Defined Under Namespace

Modules: IO, Servers, Snapshots, Storage, Volumes

Constant Summary collapse

Config =
{
        :branch => ENV["BRANCH"] || "master"
}
EC2_CONFIG =
{
        :provider => 'AWS',
        :aws_secret_access_key => Boucher::Config[:aws_secret_access_key],
        :aws_access_key_id => Boucher::Config[:aws_access_key_id],
        :region => Boucher::Config[:aws_region]
}
SERVER_TABLE_FORMAT =
"%-12s  %-12s  %-10s  %-10s  %-10s  %-15s  %-15s %-10s\n"
S3_CONFIG =
{
        :provider => 'AWS',
        :aws_secret_access_key => Boucher::Config[:aws_secret_access_key],
        :aws_access_key_id => Boucher::Config[:aws_access_key_id]
}
FILE_TABLE_FORMAT =
"%-4s  %-60s  %-10s  %-25s  %-32s\n"
VOLUME_TABLE_FORMAT =
"%-12s  %-6s  %-10s  %-12s %-10s  %-13s\n"
ADDRESS_TABLE_FORMAT =
"%-15s  %-12s\n"
ADDRESS_OVERVIEW_TABLE_FORMAT =
"%12s  %-15s  %-12s\n"
SNAPSHOT_TABLE_FORMAT =
"%-13s  %-12s  %-18s  %-80s\n"

Class Method Summary collapse

Class Method Details

.address_overviewObject



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/boucher/addresses.rb', line 35

def self.address_overview
  ips = {}
  Boucher.compute.addresses.each do |ip|
    ips[ip.public_ip] = {ip: ip.public_ip, server_id: ip.server_id}
  end
  Boucher.meals.each do |name, meal|
    (meal[:elastic_ips] || []).each do |ip|
      if ip.nil? || ip.size == 0
        # skip
      elsif ips[ip]
        ips[ip][:meal] = name
      else
        ips[ip] = {meal: name, ip: ip}
      end
    end
  end
  ips
end

.assert_env!Object



37
38
39
40
41
# File 'lib/boucher/env.rb', line 37

def self.assert_env!
  unless ENV['BENV']
    raise 'BENV must be set before running this command'
  end
end

.associate_addresses_for(meal, server) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/boucher/addresses.rb', line 54

def self.associate_addresses_for(meal, server)
  ips = meal[:elastic_ips]
  if ips.nil? || ips.empty?
    puts "No Elastic IPs to associate for meal #{meal[:name]}."
    return
  end
  ips.each do |ip|
    address = Boucher.compute.addresses.get(ip)
    if address
      if address.server_id == server.id
        puts "#{ip} already associated with #{meal[:name]}:#{server.id}"
      else
        puts "Associating #{ip} with #{meal[:name]}:#{server.id}"
        address.server = server
      end
    else
      puts "Elastic IP (#{ip}) not found. Skipping."
    end
  end
end

.associate_all_addressesObject



75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/boucher/addresses.rb', line 75

def self.associate_all_addresses
  meals = Boucher.meals
  meals.each do |name, meal|
    ips = meal[:elastic_ips]
    if ips && ips.size > 0
      begin
        server = Boucher::Servers[name]
        associate_addresses_for(meal, server)
      rescue Boucher::Servers::NotFound => e
        puts "Can't associate address to '#{name}' server because it can't be found."
      end
    end
  end
end

.change_servers_state(servers, command, new_state) ⇒ Object



128
129
130
131
132
133
134
135
136
# File 'lib/boucher/servers.rb', line 128

def self.change_servers_state(servers, command, new_state)
  print "#{command}-ing servers #{servers.map(&:id).join(", ")}..."
  servers.each { |s| s.send(command.to_sym) }
  servers.each { |s| s.wait_for { print "."; s.state == new_state }}
  puts
  Boucher.print_servers servers
  puts
  puts "The servers have been #{command}-ed."
end

.computeObject



16
17
18
19
# File 'lib/boucher/compute.rb', line 16

def self.compute
  @compute ||= Fog::Compute.new(EC2_CONFIG)
  @compute
end

.cook_meal(server, meal_name) ⇒ Object



46
47
48
49
# File 'lib/boucher/compute.rb', line 46

def self.cook_meal(server, meal_name)
  update_recipes(server)
  ssh server, "cd infrastructure && sudo BENV=#{Boucher::Config[:env]} BRANCH=#{Boucher::Config[:branch]} chef-solo -c config/solo.rb -j config/#{meal_name}.json"
end

.cook_recipe(server, recipe) ⇒ Object



51
52
53
54
55
# File 'lib/boucher/compute.rb', line 51

def self.cook_recipe(server, recipe)
  update_recipes(server)
  ssh server, "echo '{\\\"run_list\\\": [\\\"recipe[#{recipe}]\\\"]}' > /tmp/single_recipe.json"
  ssh server, "cd infrastructure && sudo BENV=#{Boucher::Config[:env]} BRANCH=#{Boucher::Config[:branch]} chef-solo -c config/solo.rb -j /tmp/single_recipe.json"
end

.current_userObject



3
4
5
6
7
# File 'lib/boucher/util.rb', line 3

def self.current_user
  `git config user.name`.strip
rescue
  "unknown"
end

.env_nameObject



7
8
9
# File 'lib/boucher/env.rb', line 7

def self.env_name
  Boucher::Config[:env] || ENV["BENV"] || :dev
end

.establish_server(server, meal_name) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/boucher/provision.rb', line 22

def self.establish_server(server, meal_name)
  meal = Boucher.meal(meal_name)
  if server.nil?
    Boucher.provision(meal)
  elsif server.state == "stopped"
    Boucher::Servers.start([server])
    server.reload
    Boucher.cook_meal_on_server(meal, server)
  else
    Boucher.cook_meal_on_server(meal, server)
  end
end

.find_server(meal, environment) ⇒ Object



18
19
20
# File 'lib/boucher/provision.rb', line 18

def self.find_server(meal, environment)
  get_server(meal, environment, "stopped") || get_server(meal, environment, "running")
end

.force_env!(name) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/boucher/env.rb', line 23

def self.force_env!(name)
  env = name.to_sym
  Boucher::Config[:branch] = ENV["BRANCH"] || "master"
  load File.expand_path(File.dirname(__FILE__) + "/../../config/env/shared.rb") unless defined?(Boucher::NO_LOAD_CONFIG)
  file = File.expand_path(File.dirname(__FILE__) + "/../../config/env/#{env}.rb")
  unless defined?(Boucher::NO_LOAD_CONFIG)
    if File.exist?(file)
      load file
    else
      require_relative "../../config/env/shared"
    end
  end
end

.get_server(meal, environment, state) ⇒ Object



10
11
12
13
14
15
16
# File 'lib/boucher/provision.rb', line 10

def self.get_server(meal, environment, state)
  begin
    Boucher::Servers.find(:env => environment.to_s, :meal => meal, :state => state)
  rescue Boucher::Servers::NotFound
    nil
  end
end

.json_to_meal(json) ⇒ Object



8
9
10
11
12
13
14
# File 'lib/boucher/meals.rb', line 8

def self.json_to_meal(json)
  template = ERB.new(json)
  json = template.result(binding)
  parser = JSON.parser.new(json, :symbolize_names => true)
  config = parser.parse
  config[:boucher] || {}
end

.meal(meal_name) ⇒ Object



28
29
30
31
32
# File 'lib/boucher/meals.rb', line 28

def self.meal(meal_name)
  the_meal = meals[meal_name.to_sym]
  raise "Missing meal: #{meal_name}" unless the_meal
  return the_meal
end

.mealsObject



16
17
18
19
20
21
22
23
24
25
26
# File 'lib/boucher/meals.rb', line 16

def self.meals
  if @meals.nil?
    @meals = {}
    Dir.glob(File.join("config", "*.json")).each do |file|
      spec = json_to_meal(::IO.read(file))
      meal_name = File.basename(file)[0...-5].to_sym
      @meals[meal_name] = spec.merge(:name => meal_name)
    end
  end
  @meals
end


22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/boucher/addresses.rb', line 22

def self.print_address_overview(addresses)
  puts
  printf ADDRESS_OVERVIEW_TABLE_FORMAT, "Meal", "Public IP", "Server ID"
  puts ("-" * 43)

  addresses.values.each do |address|
    printf ADDRESS_OVERVIEW_TABLE_FORMAT,
           address[:meal],
           address[:ip],
           address[:server_id]
  end
end


8
9
10
11
12
13
14
15
16
17
18
# File 'lib/boucher/addresses.rb', line 8

def self.print_addresses(addresses)
  puts
  printf ADDRESS_TABLE_FORMAT, "Public IP", "Server ID"
  puts ("-" * 29)

  addresses.each do |address|
    printf ADDRESS_TABLE_FORMAT,
           address.public_ip,
           address.server_id
  end
end


35
36
37
38
39
40
41
42
# File 'lib/boucher/storage.rb', line 35

def self.print_directory(directory)
  printf FILE_TABLE_FORMAT,
         "dir",
         directory.key,
         "",
         directory.creation_date,
         ""
end


26
27
28
29
30
31
32
33
# File 'lib/boucher/storage.rb', line 26

def self.print_file(file)
  printf FILE_TABLE_FORMAT,
         "file",
         file.key,
         file.content_length,
         file.last_modified,
         file.etag
end


20
21
22
23
24
# File 'lib/boucher/storage.rb', line 20

def self.print_file_table_header
  puts
  printf FILE_TABLE_FORMAT, "Type", "Key", "Size", "Last Modified", "etag"
  puts ("-" * 156)
end


44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/boucher/storage.rb', line 44

def self.print_files(files)
  print_file_table_header
  files.each do |file|
    if file
      if file.class.name =~ /Directory/
        print_directory(file)
      else
        print_file(file)
      end
    end
  end
  puts
end


13
14
15
16
17
18
19
20
21
22
23
# File 'lib/boucher/servers.rb', line 13

def self.print_server(server)
  printf SERVER_TABLE_FORMAT,
         server.id,
         (server.tags["Env"] || "???")[0...12],
         (server.tags["Meal"] || "???")[0...10],
         (server.tags["Creator"] || "???")[0...10],
         server.state,
         server.public_ip_address,
         server.private_ip_address,
         server.flavor_id
end


7
8
9
10
11
# File 'lib/boucher/servers.rb', line 7

def self.print_server_table_header
  puts
  printf SERVER_TABLE_FORMAT, "ID", "Environment", "Meal", "Creator", "State", "Public IP", "Private IP", "Inst. Size"
  puts ("-" * 107)
end


25
26
27
28
29
30
31
32
33
# File 'lib/boucher/servers.rb', line 25

def self.print_servers(servers)
  print_server_table_header
  sorted_servers = servers.sort_by { |s| [s.tags["Env"] || "?",
                                          s.tags["Meal"] || "?"] }
  sorted_servers.each do |server|
    print_server(server) if server
  end
  puts
end


7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/boucher/snapshots.rb', line 7

def self.print_snapshots(volumes)
  puts
  printf SNAPSHOT_TABLE_FORMAT, "ID", "Volume ID", "Created At", "Description"
  puts ("-" * 120)

  volumes.each do |snapshot|
    printf SNAPSHOT_TABLE_FORMAT,
           snapshot.id,
           snapshot.volume_id,
           snapshot.created_at.strftime("%b %d %Y %H:%M"),
           snapshot.description[0...80]
  end
end


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/boucher/volumes.rb', line 8

def self.print_volumes(volumes)
  puts
  printf VOLUME_TABLE_FORMAT, "ID", "Size", "Server", "Device", "State", "Snapshot"
  puts ("-" * 75)

  volumes.each do |volume|
    printf VOLUME_TABLE_FORMAT,
           volume.id,
           volume.size.to_s + "GB",
           volume.server_id,
           volume.device,
           volume.state,
           volume.snapshot_id
  end
end

.provision(meal) ⇒ Object



35
36
37
38
39
40
41
42
43
# File 'lib/boucher/provision.rb', line 35

def self.provision(meal)
  puts "Provisioning new #{meal[:name]} server..."
  server = create_meal_server(meal)
  wait_for_server_to_boot(server)
  wait_for_server_to_accept_ssh(server)
  attach_volumes(meal, server)
  cook_meal_on_server(meal, server)
  puts "\nThe new #{meal[:name]} server has been provisioned! id: #{server.id}"
end

.resolve_servers(id_or_meal) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/boucher/servers.rb', line 138

def self.resolve_servers(id_or_meal)
  if id_or_meal[0..1] == "i-"
    puts "Retrieving server with id #{id_or_meal}..."
    [Boucher::Servers.with_id(id_or_meal)]
  else
    puts "Searching for running #{id_or_meal} servers in #{Boucher.env_name} environment..."
    servers = Boucher::Servers.search(:meal => id_or_meal, :env => Boucher.env_name, :state => "!terminated")
    Boucher::print_servers(servers)
    servers
  end
end

.setup_meal(server, meal) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/boucher/meals.rb', line 34

def self.setup_meal(server, meal)
  server.image_id = meal[:image_id] || Boucher::Config[:default_image_id]
  server.flavor_id = meal[:flavor_id] || Boucher::Config[:default_flavor_id]
  server.groups = meal[:groups] || Boucher::Config[:default_groups]
  server.key_name = meal[:key_name] || Boucher::Config[:aws_key_filename]
  server.tags = {}
  server.tags["Name"] = "#{meal[:name] || "base"} #{Time.new.strftime("%Y%m%d%H%M%S")}"
  server.tags["Meal"] = meal[:name] || "base"
  server.tags["CreatedAt"] = Time.new.strftime("%Y%m%d%H%M%S")
  server.tags["Creator"] = current_user
  server.tags["Env"] = Boucher::Config[:env]
end

.snapshotsObject



21
22
23
# File 'lib/boucher/compute.rb', line 21

def self.snapshots
  @snapshots ||= compute.snapshots
end

.ssh(server, command = nil) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
# File 'lib/boucher/compute.rb', line 25

def self.ssh(server, command=nil)
  command_arg = nil
  if command
    command_arg = "\"#{command}\""
  end

  command = "#{ssh_command} #{Boucher::Config[:username]}@#{server.dns_name} #{command_arg}"
  verbose command
  Kernel.system command
  raise "command failed with code #{$?.exitstatus}" unless $?.success?
end

.ssh_commandObject



37
38
39
# File 'lib/boucher/compute.rb', line 37

def self.ssh_command
  "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i #{Boucher::Config[:aws_key_filename]}.pem"
end

.ssh_open?(server) ⇒ Boolean

Returns:

  • (Boolean)


57
58
59
60
61
62
# File 'lib/boucher/compute.rb', line 57

def self.ssh_open?(server)
  ssh server, "echo 'SSH is open for business!'"
  true
rescue Exception => e
  false
end

.storageObject



13
14
15
16
# File 'lib/boucher/storage.rb', line 13

def self.storage
  @store ||= Fog::Storage.new(S3_CONFIG)
  @store
end

.update_recipes(server) ⇒ Object



41
42
43
44
# File 'lib/boucher/compute.rb', line 41

def self.update_recipes(server)
  puts "Updating recipes on #{server.id}"
  ssh server, "cd infrastructure && git checkout . && git clean -d -f && git pull && bundle"
end

.verbose(*args) ⇒ Object



19
20
21
22
23
# File 'lib/boucher/io.rb', line 19

def self.verbose(*args)
  if ENV["VERBOSE"]
    puts *args
  end
end