boucher 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|