boucher 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.gitmodules +3 -0
- data/.rvmrc +2 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +9 -0
- data/README.md +82 -0
- data/Rakefile +10 -0
- data/boucher.gemspec +21 -0
- data/lib/boucher/ci.rb +18 -0
- data/lib/boucher/classes.rb +39 -0
- data/lib/boucher/compute.rb +93 -0
- data/lib/boucher/env.rb +46 -0
- data/lib/boucher/io.rb +108 -0
- data/lib/boucher/nagios.rb +50 -0
- data/lib/boucher/provision.rb +121 -0
- data/lib/boucher/servers.rb +92 -0
- data/lib/boucher/storage.rb +43 -0
- data/lib/boucher/tasks/console.rake +21 -0
- data/lib/boucher/tasks/env.rake +42 -0
- data/lib/boucher/tasks/nagios.rake +74 -0
- data/lib/boucher/tasks/servers.rake +127 -0
- data/lib/boucher/tasks/storage.rake +32 -0
- data/lib/boucher/tasks/volumes.rake +14 -0
- data/lib/boucher/tasks.rb +6 -0
- data/lib/boucher/util.rb +9 -0
- data/lib/boucher/volumes.rb +41 -0
- data/spec/boucher/classes_spec.rb +35 -0
- data/spec/boucher/compute_spec.rb +75 -0
- data/spec/boucher/env_spec.rb +47 -0
- data/spec/boucher/io_spec.rb +68 -0
- data/spec/boucher/provision_spec.rb +77 -0
- data/spec/boucher/servers_spec.rb +79 -0
- data/spec/boucher/storage_spec.rb +47 -0
- data/spec/spec_helper.rb +36 -0
- metadata +152 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'boucher/compute'
|
2
|
+
require 'boucher/io'
|
3
|
+
require 'boucher/servers'
|
4
|
+
require 'boucher/volumes'
|
5
|
+
require 'boucher/nagios'
|
6
|
+
require 'retryable'
|
7
|
+
|
8
|
+
module Boucher
|
9
|
+
def self.each_required_server(&block)
|
10
|
+
Boucher::Config[:servers].each do |server_class|
|
11
|
+
server = find_server(server_class, Boucher::Config[:env])
|
12
|
+
block.yield(server, server_class)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.get_server(server_class, environment, state)
|
17
|
+
begin
|
18
|
+
Boucher::Servers.find(:env => environment.to_s, :class => server_class, :state => state)
|
19
|
+
rescue Boucher::Servers::NotFound
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.find_server(server_class, environment)
|
25
|
+
get_server(server_class, environment, "stopped") || get_server(server_class, environment, "running")
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.establish_all_servers
|
29
|
+
Boucher.each_required_server do |server, server_class|
|
30
|
+
# Retries after 2, 4, 8, 16, 32, and 64 seconds
|
31
|
+
retryable(:tries => 6, :sleep => lambda { |n| 2**n }) do
|
32
|
+
# A RuntimeError will sometimes be thrown here, with a message of:
|
33
|
+
# "command failed with code 255"
|
34
|
+
Boucher.establish_server(server, server_class)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.establish_server(server, server_class)
|
40
|
+
if server.nil?
|
41
|
+
Boucher.provision(server_class, Boucher.server_classes[server_class.to_sym])
|
42
|
+
elsif server.state == "stopped"
|
43
|
+
Boucher::Servers.start(server.id)
|
44
|
+
server.reload
|
45
|
+
Boucher.cook_meals_on_server(server_class, Boucher.server_classes[server_class.to_sym], server)
|
46
|
+
else
|
47
|
+
Boucher.cook_meals_on_server(server_class, Boucher.server_classes[server_class.to_sym], server)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.provision(class_name, class_map)
|
52
|
+
puts "Provisioning new #{class_name} server..."
|
53
|
+
server = create_classified_server(class_map)
|
54
|
+
wait_for_server_to_boot(server)
|
55
|
+
wait_for_server_to_accept_ssh(server)
|
56
|
+
volumes = create_volumes(class_map, server)
|
57
|
+
attach_volumes(volumes, server)
|
58
|
+
cook_meals_on_server(class_name, class_map, server)
|
59
|
+
puts "\nThe new #{class_name} server has been provisioned! id: #{server.id}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.attach_elastic_ips(class_name, server)
|
63
|
+
puts "Attaching elastic IPs..."
|
64
|
+
return unless Boucher::Config[:elastic_ips] && Boucher::Config[:elastic_ips][class_name]
|
65
|
+
|
66
|
+
puts "Associating #{server.id} with #{Boucher::Config[:elastic_ips][class_name]}"
|
67
|
+
compute.associate_address(server.id, Boucher::Config[:elastic_ips][class_name])
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def self.cook_meals_on_server(class_name, class_map, server)
|
73
|
+
return unless class_map[:meals]
|
74
|
+
|
75
|
+
class_map[:meals].each do |meal|
|
76
|
+
meal = meal.call if meal.is_a?(Proc)
|
77
|
+
Boucher.cook_meal(server, meal)
|
78
|
+
end
|
79
|
+
|
80
|
+
attach_elastic_ips(class_name, server)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.wait_for_server_to_accept_ssh(server)
|
84
|
+
puts "Waiting for server's SSH port to open..."
|
85
|
+
Fog.wait_for { ssh_open?(server) }
|
86
|
+
puts
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.wait_for_server_to_boot(server)
|
90
|
+
print "Waiting for server to boot..."
|
91
|
+
server.wait_for { print "."; server.ready? }
|
92
|
+
puts
|
93
|
+
Boucher.print_servers([server])
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.create_classified_server(class_map)
|
97
|
+
server = compute.servers.new(:tags => {})
|
98
|
+
Boucher.classify(server, class_map)
|
99
|
+
server.save
|
100
|
+
Boucher.print_servers([server])
|
101
|
+
server
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.create_volumes(class_map, server)
|
105
|
+
Array(class_map[:volumes]).map do |volume_name|
|
106
|
+
attributes = Boucher.volume_configs[volume_name]
|
107
|
+
snapshot = snapshots.get(attributes[:snapshot])
|
108
|
+
puts "Creating volume from snapshot #{snapshot.id}..."
|
109
|
+
Boucher::Volumes.create(server.availability_zone, snapshot, attributes[:device])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.attach_volumes(volumes, server)
|
114
|
+
volumes.each do |volume|
|
115
|
+
print "Attaching volume #{volume.id} to #{server.id}..."
|
116
|
+
Boucher::Volumes.attach(volume, server)
|
117
|
+
volume.wait_for { print "."; state == "in-use" }
|
118
|
+
puts
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'boucher/compute'
|
2
|
+
|
3
|
+
module Boucher
|
4
|
+
module Servers
|
5
|
+
NotFound = Class.new(StandardError)
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def instance
|
9
|
+
reload if !@instance
|
10
|
+
@instance
|
11
|
+
end
|
12
|
+
|
13
|
+
def reload
|
14
|
+
@instance = Boucher.compute.servers
|
15
|
+
@instance.each {} # Wake up you lazy list!
|
16
|
+
cultivate(@instance)
|
17
|
+
end
|
18
|
+
|
19
|
+
%w{all of_class in_env in_state search find [] with_id}.each do |m|
|
20
|
+
module_eval "def #{m}(*args); instance.#{m}(*args); end"
|
21
|
+
end
|
22
|
+
|
23
|
+
def cultivate(thing)
|
24
|
+
thing.extend(Boucher::Servers) if thing
|
25
|
+
thing
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def all
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def search(options={})
|
34
|
+
servers = self
|
35
|
+
servers = servers.of_class(options[:class]) if options[:class]
|
36
|
+
servers = servers.in_env(options[:env]) if options[:env]
|
37
|
+
servers = servers.in_state(options[:state]) if options[:state]
|
38
|
+
servers
|
39
|
+
end
|
40
|
+
|
41
|
+
def find(options={})
|
42
|
+
servers = search(options)
|
43
|
+
first = servers.first
|
44
|
+
if first.nil?
|
45
|
+
raise Boucher::Servers::NotFound.new("No server matches criteria: #{options.inspect}")
|
46
|
+
end
|
47
|
+
first
|
48
|
+
end
|
49
|
+
|
50
|
+
def in_env(env)
|
51
|
+
Servers.cultivate(self.find_all {|s| s.tags["Env"] == env.to_s })
|
52
|
+
end
|
53
|
+
|
54
|
+
def in_state(state)
|
55
|
+
Servers.cultivate(self.find_all {|s| s.state == state.to_s })
|
56
|
+
end
|
57
|
+
|
58
|
+
def of_class(klass)
|
59
|
+
Servers.cultivate(self.find_all {|s| s.tags["Class"] == klass.to_s })
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.start(server_id)
|
63
|
+
Boucher.change_server_state(server_id, :start, "running")
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.stop(server_id)
|
67
|
+
Boucher.change_server_state(server_id, :stop, "stopped")
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.terminate(server)
|
71
|
+
Boucher::Nagios.remove_host(server)
|
72
|
+
volumes = server.volumes
|
73
|
+
volumes_to_destroy = volumes.select {|v| !v.delete_on_termination}
|
74
|
+
|
75
|
+
Boucher.change_server_state server.id, :destroy, "terminated"
|
76
|
+
|
77
|
+
volumes_to_destroy.each do |volume|
|
78
|
+
volume.wait_for { volume.state == 'available' }
|
79
|
+
puts "Destroying volume #{volume.id}..."
|
80
|
+
Boucher::Volumes.destroy(volume)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def with_id(server_id)
|
85
|
+
Servers.cultivate(self.find_all {|s| s.id == server_id}).first
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](klass)
|
89
|
+
find(:env => Boucher::Config[:env], :class => klass, :state => "running")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'boucher/env'
|
2
|
+
require 'boucher/classes'
|
3
|
+
require 'fog'
|
4
|
+
|
5
|
+
module Boucher
|
6
|
+
|
7
|
+
S3_CONFIG = {
|
8
|
+
:provider => 'AWS',
|
9
|
+
:aws_secret_access_key => Boucher::Config[:aws_secret_access_key],
|
10
|
+
:aws_access_key_id => Boucher::Config[:aws_access_key_id]
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.storage
|
14
|
+
@store ||= Fog::Storage.new(S3_CONFIG)
|
15
|
+
@store
|
16
|
+
end
|
17
|
+
|
18
|
+
module Storage
|
19
|
+
|
20
|
+
def self.list(dir_name)
|
21
|
+
dir = Boucher.storage.directories.get(dir_name)
|
22
|
+
result = dir.files.select {|f| f.key[-1] != "/" }.to_a
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.put(dir_name, key, filename)
|
27
|
+
dir = Boucher.storage.directories.get(dir_name)
|
28
|
+
body = File.open(filename)
|
29
|
+
file = dir.files.new(:key => key, :body => body)
|
30
|
+
file.save
|
31
|
+
body.close
|
32
|
+
file
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.get(dir_name, key, filename)
|
36
|
+
dir = Boucher.storage.directories.get(dir_name)
|
37
|
+
url = dir.files.get_https_url(key, Time.now + 3600)
|
38
|
+
Kernel.system("curl", url, "-o", filename)
|
39
|
+
dir.files.detect{|f| f.key == key}
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'boucher/compute'
|
2
|
+
require 'boucher/env'
|
3
|
+
require 'boucher/io'
|
4
|
+
require 'boucher/classes'
|
5
|
+
require 'boucher/provision'
|
6
|
+
require 'boucher/servers'
|
7
|
+
require 'boucher/volumes'
|
8
|
+
require 'boucher/nagios'
|
9
|
+
|
10
|
+
desc "Starts a console with the Boucher modules loaded"
|
11
|
+
task :console do
|
12
|
+
require 'pry'
|
13
|
+
|
14
|
+
Dir[File.expand_path("../../lib/boucher/**/*.rb", __FILE__)].each do |filename|
|
15
|
+
require filename
|
16
|
+
end
|
17
|
+
|
18
|
+
include Boucher
|
19
|
+
|
20
|
+
pry
|
21
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'boucher/env'
|
2
|
+
require 'boucher/provision'
|
3
|
+
require 'retryable'
|
4
|
+
|
5
|
+
namespace :env do
|
6
|
+
|
7
|
+
desc "Attaches elastic IPs"
|
8
|
+
task :elastic_ips, [:env] do |t, args|
|
9
|
+
Boucher.force_env!(args.env)
|
10
|
+
Boucher.each_required_server do |server, server_class|
|
11
|
+
Boucher.attach_elastic_ips(server_class, server)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Starts and deploys all the servers for the specified environment"
|
16
|
+
task :start, [:env] do |t, args|
|
17
|
+
Boucher.force_env!(args.env)
|
18
|
+
Boucher.assert_env!
|
19
|
+
Boucher.establish_all_servers
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Stops all the servers for the specified environment"
|
23
|
+
task :stop, [:env] do |t, args|
|
24
|
+
Boucher.force_env!(args.env)
|
25
|
+
|
26
|
+
Boucher.each_required_server do |server, server_class|
|
27
|
+
if server
|
28
|
+
Boucher::Servers.stop(server.id)
|
29
|
+
else
|
30
|
+
puts "No #{server_class} server found for #{Boucher::Config[:env]} environment."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Terminates all servers for the specified environment"
|
36
|
+
task :terminate, [:env] do |t, args|
|
37
|
+
Boucher.force_env!(args.env)
|
38
|
+
Boucher.each_required_server do |server, server_class|
|
39
|
+
Boucher::Servers.terminate(server) if server
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
def make_config_path(host_name)
|
5
|
+
"/etc/nagios3/conf.d/#{host_name}.cfg"
|
6
|
+
end
|
7
|
+
|
8
|
+
def host_entry(host_name, ip)
|
9
|
+
<<-HOST
|
10
|
+
define host {
|
11
|
+
host_name #{host_name}
|
12
|
+
alias #{host_name}
|
13
|
+
address #{ip}
|
14
|
+
check_command check_ssh
|
15
|
+
notification_interval 0
|
16
|
+
notification_period 24x7
|
17
|
+
max_check_attempts 10
|
18
|
+
notification_options d,u,r
|
19
|
+
notifications_enabled 1
|
20
|
+
}
|
21
|
+
HOST
|
22
|
+
end
|
23
|
+
|
24
|
+
def service_entry(host_name, ip, plugin)
|
25
|
+
<<-SERVICE
|
26
|
+
define command {
|
27
|
+
command_name #{host_name}-#{plugin}
|
28
|
+
command_line /usr/lib/nagios/plugins/check_nrpe -H #{ip} -c "#{plugin}"
|
29
|
+
}
|
30
|
+
|
31
|
+
define service {
|
32
|
+
use generic-service
|
33
|
+
host_name #{host_name}
|
34
|
+
check_command #{host_name}-#{plugin}
|
35
|
+
service_description #{plugin}
|
36
|
+
normal_check_interval 5
|
37
|
+
check_period 24x7
|
38
|
+
notifications_enabled 1
|
39
|
+
}
|
40
|
+
SERVICE
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :nagios do
|
44
|
+
desc "Remove a nagios host from this machine"
|
45
|
+
task :remove_host, [:name] do |t, args|
|
46
|
+
FileUtils.rm_f make_config_path(args.name)
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Adds a nagios host to be monitored by this machine"
|
50
|
+
task :add_host, [:name, :ip, :klass] do |t, args|
|
51
|
+
checks = Boucher::Nagios.checks_for(args.klass)
|
52
|
+
return if checks.empty?
|
53
|
+
|
54
|
+
File.open(make_config_path(args.name), "w") do |file|
|
55
|
+
host_name = "#{args.klass}-#{args.name}"
|
56
|
+
|
57
|
+
file.puts host_entry(host_name, args.ip)
|
58
|
+
|
59
|
+
checks.each do |plugin, command|
|
60
|
+
file.puts
|
61
|
+
file.puts service_entry(host_name, args.ip, plugin)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
desc "Opens the nagios web console"
|
67
|
+
task :open do
|
68
|
+
server = Boucher::Servers["nagios_server"]
|
69
|
+
url = "http://#{server.public_ip_address}/nagios3"
|
70
|
+
puts "Nagios lives at #{url}"
|
71
|
+
puts "Login using nagiosadmin / 'we are many lonely souls'"
|
72
|
+
`open #{url}`
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'boucher/compute'
|
2
|
+
require 'boucher/env'
|
3
|
+
require 'boucher/io'
|
4
|
+
require 'boucher/classes'
|
5
|
+
require 'boucher/provision'
|
6
|
+
require 'boucher/servers'
|
7
|
+
require 'boucher/volumes'
|
8
|
+
require 'boucher/nagios'
|
9
|
+
require 'retryable'
|
10
|
+
|
11
|
+
def meals
|
12
|
+
config_files = Dir.glob("config/*.json")
|
13
|
+
configs = config_files.map { |config_file| File.basename(config_file) }
|
14
|
+
configs.map { |config| config.gsub(".json", "") }
|
15
|
+
end
|
16
|
+
|
17
|
+
def server_listing(description, servers)
|
18
|
+
puts "Listing all AWS server #{description}..."
|
19
|
+
Boucher.print_servers servers
|
20
|
+
puts
|
21
|
+
puts "#{servers.size} server(s)"
|
22
|
+
end
|
23
|
+
|
24
|
+
namespace :servers do
|
25
|
+
desc "List all volumes for a given server"
|
26
|
+
task :volumes, [:server_id] do |t, args|
|
27
|
+
volumes = Boucher::Servers.with_id(args.server_id).volumes
|
28
|
+
Boucher.print_volumes(volumes)
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "List ALL AWS servers, with optional [env] param."
|
32
|
+
task :list, [:env] do |t, args|
|
33
|
+
servers = Boucher::Servers.all
|
34
|
+
servers = args.env ? servers.in_env(args.env) : servers
|
35
|
+
server_listing("", servers)
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "List AWS servers in specified environment"
|
39
|
+
task :in_env, [:env] do |t, args|
|
40
|
+
server_listing("in the '#{args.env}' environment", Boucher::Servers.in_env(args.env))
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "List AWS servers in specified class"
|
44
|
+
task :of_class, [:klass] do |t, args|
|
45
|
+
server_listing("of class '#{args.klass}'", Boucher::Servers.of_class(args.klass))
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Terminates the specified server"
|
49
|
+
task :terminate, [:server_id] do |t, args|
|
50
|
+
server = Boucher::Servers.with_id(args.server_id)
|
51
|
+
|
52
|
+
if !server
|
53
|
+
puts "Server #{args.server_id} does not exist"
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
|
57
|
+
begin
|
58
|
+
Boucher::Servers.terminate(server)
|
59
|
+
rescue => e
|
60
|
+
puts "\nTermination failed. This may be due to termination protection. If
|
61
|
+
you're sure you wish to disable this protection, select the instance in the AWS
|
62
|
+
web console and click Instance Actions -> Change Termination Protection -> Yes."
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Stops the specified server"
|
68
|
+
task :stop, [:server_id] do |t, args|
|
69
|
+
server = Boucher.compute.servers.get(args.server_id)
|
70
|
+
Boucher::Nagios.remove_host(server)
|
71
|
+
Boucher::Servers.stop(args.server_id)
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Starts the specified server"
|
75
|
+
task :start, [:server_id] do |t, args|
|
76
|
+
Boucher::Servers.start(args.server_id)
|
77
|
+
server = Boucher.compute.servers.get(args.server_id)
|
78
|
+
Boucher::Nagios.add_host(server)
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "Open an SSH session with the specified server"
|
82
|
+
task :ssh, [:server_id] do |t, args|
|
83
|
+
puts "Opening SSH session to #{args.server_id}"
|
84
|
+
server = Boucher.compute.servers.get(args.server_id)
|
85
|
+
Boucher.ssh server
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Download a file from the server"
|
89
|
+
task :download, [:server_id, :filepath] do |t, args|
|
90
|
+
puts "Downloading #{args.filepath}"
|
91
|
+
|
92
|
+
server = Boucher.compute.servers.get(args.server_id)
|
93
|
+
remote_path = args.filepath
|
94
|
+
local_path = File.expand_path(File.join("..", "..", File.basename(args.filepath)), __FILE__)
|
95
|
+
|
96
|
+
Boucher.download(server, remote_path, local_path)
|
97
|
+
end
|
98
|
+
|
99
|
+
desc "Fix permissions on key files"
|
100
|
+
task :key_fix do
|
101
|
+
system "chmod 0600 *.pem"
|
102
|
+
end
|
103
|
+
|
104
|
+
desc "Provision new server [#{Boucher.server_classes.keys.sort.join(', ')}]"
|
105
|
+
task :provision, [:klass] do |t, args|
|
106
|
+
class_map = Boucher.server_classes[args.klass.to_sym]
|
107
|
+
Boucher.provision(args.klass, class_map)
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Provision new, or chef existing server of the specified class"
|
111
|
+
task :establish, [:klass] do |t, args|
|
112
|
+
Boucher.assert_env!
|
113
|
+
server = Boucher.find_server(args.klass, ENV['BUTCHER_ENV'])
|
114
|
+
Boucher.establish_server(server, args.klass)
|
115
|
+
end
|
116
|
+
|
117
|
+
namespace :chef do
|
118
|
+
meals.each do |s_class|
|
119
|
+
desc "Cook #{s_class} meal"
|
120
|
+
task s_class, [:server_id] do |t, args|
|
121
|
+
Boucher.assert_env!
|
122
|
+
server = Boucher.compute.servers.get(args.server_id)
|
123
|
+
Boucher.cook_meal(server, s_class)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'boucher/storage'
|
2
|
+
require 'boucher/io'
|
3
|
+
|
4
|
+
namespace :storage do
|
5
|
+
|
6
|
+
desc "Lists all the files in the infrastructure bucket on S3"
|
7
|
+
task :list do
|
8
|
+
files = Boucher::Storage.list("infrastructure")
|
9
|
+
Boucher.print_files files
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Puts a file in the infrastructure bucket"
|
13
|
+
task :put, [:file] do |t, args|
|
14
|
+
filename = args.file
|
15
|
+
key = File.basename(filename)
|
16
|
+
puts "Storing file #{filename} as #{key}"
|
17
|
+
file = Boucher::Storage.put("infrastructure", key, filename)
|
18
|
+
Boucher.print_files [file]
|
19
|
+
puts "File uploaded as #{key}."
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Gets a file from the infrastructure bucket. The file arg is the key on AWS."
|
23
|
+
task :get, [:file] do |t, args|
|
24
|
+
key = args.file
|
25
|
+
filename = File.basename(key)
|
26
|
+
puts "Getting file #{key} and saving to #{filename}"
|
27
|
+
file = Boucher::Storage.get("infrastructure", key, filename)
|
28
|
+
Boucher.print_files [file]
|
29
|
+
puts "File saved locally as #{filename}."
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'boucher/volumes'
|
2
|
+
require 'boucher/io'
|
3
|
+
|
4
|
+
namespace :volumes do
|
5
|
+
desc "List provisioned volumes"
|
6
|
+
task :list do
|
7
|
+
Boucher.print_volumes(Boucher::Volumes.all)
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Destroy the specified volume"
|
11
|
+
task :destroy, [:volume_id] do |t, args|
|
12
|
+
Boucher.destroy_volume(args.volume_id)
|
13
|
+
end
|
14
|
+
end
|
data/lib/boucher/util.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'boucher/compute'
|
2
|
+
|
3
|
+
module Boucher
|
4
|
+
def self.destroy_volume(volume_id)
|
5
|
+
volume = Boucher::Volumes.with_id(volume_id)
|
6
|
+
Volumes.destroy(volume)
|
7
|
+
end
|
8
|
+
|
9
|
+
module Volumes
|
10
|
+
def self.all
|
11
|
+
@volumes ||= Boucher.compute.volumes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.destroy(volumes)
|
15
|
+
Array(volumes).each do |volume|
|
16
|
+
volume.reload
|
17
|
+
volume.destroy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.with_id(volume_id)
|
22
|
+
all.find {|volume| volume.id == volume_id}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.create(zone, snapshot, device)
|
26
|
+
response = Boucher.compute.create_volume(zone, snapshot.volume_size.to_i, snapshot.id)
|
27
|
+
volume_id = response.body["volumeId"]
|
28
|
+
volume = Boucher.compute.volumes.get(volume_id)
|
29
|
+
|
30
|
+
volume.wait_for { ready? }
|
31
|
+
volume.device = device
|
32
|
+
volume
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.attach(volumes, server)
|
36
|
+
Array(volumes).each do |volume|
|
37
|
+
Boucher.compute.attach_volume(server.id, volume.id, volume.device)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
require 'boucher/classes'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
describe "Boucher Server Classes" do
|
6
|
+
|
7
|
+
before do
|
8
|
+
Boucher.stub(:current_user).and_return("joe")
|
9
|
+
@server = OpenStruct.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "pull classification from json" do
|
13
|
+
json = "{\"boucher\": {\"foo\": 1,\n \"bar\": 2}}"
|
14
|
+
Boucher.json_to_class(json).should == {:foo => 1, :bar => 2}
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can classify base server" do
|
18
|
+
some_class = {:class_name => "base",
|
19
|
+
:meals => ["base"]}
|
20
|
+
Boucher::Config[:default_instance_flavor_id] = 'm1.small'
|
21
|
+
Boucher::Config[:default_instance_groups] = ["SSH"]
|
22
|
+
Boucher.classify(@server, some_class)
|
23
|
+
|
24
|
+
@server.image_id.should == Boucher::Config[:base_image_id]
|
25
|
+
@server.flavor_id.should == 'm1.small'
|
26
|
+
@server.groups.should == ["SSH"]
|
27
|
+
@server.key_name.should == "test_key"
|
28
|
+
@server.tags["Class"].should == "base"
|
29
|
+
@server.tags["Name"].should_not == nil
|
30
|
+
@server.tags["Creator"].should == "joe"
|
31
|
+
@server.tags["Env"].should == Boucher::Config[:env]
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|