boucher 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +26 -2
- data/TODO.md +2 -0
- data/boucher.gemspec +1 -1
- data/lib/boucher/addresses.rb +90 -0
- data/lib/boucher/compute.rb +0 -15
- data/lib/boucher/env.rb +1 -1
- data/lib/boucher/io.rb +0 -83
- data/lib/boucher/provision.rb +23 -26
- data/lib/boucher/servers.rb +76 -18
- data/lib/boucher/storage.rb +26 -2
- data/lib/boucher/tasks/addresses.rake +42 -0
- data/lib/boucher/tasks/servers.rake +20 -26
- data/lib/boucher/tasks/volumes.rake +7 -1
- data/lib/boucher/volumes.rb +32 -16
- data/spec/boucher/addresses_spec.rb +73 -0
- data/spec/boucher/provision_spec.rb +66 -15
- data/spec/boucher/servers_spec.rb +10 -2
- data/spec/boucher/util_spec.rb +6 -0
- data/spec/boucher/volumes_spec.rb +50 -0
- data/spec/spec_helper.rb +15 -14
- metadata +11 -5
data/README.md
CHANGED
@@ -119,11 +119,13 @@ allows you too add extra configuration information under the "Boucher": key. Fo
|
|
119
119
|
"groups": ["SSH"], // overides :default_groups config
|
120
120
|
"key_name": ["some_key"], // overides :aws_key_filename config
|
121
121
|
"elastic_ips": ["1.2.3.4"], // a list of elastic IPs that'll be attached to the server. Elastic IP's acquired via AWS management console.
|
122
|
-
"volumes":
|
122
|
+
"volumes": {"/dev/sda2": <volume spec>} // See Volume Specs below
|
123
123
|
}
|
124
124
|
}
|
125
125
|
|
126
|
-
|
126
|
+
### ERB in config
|
127
|
+
|
128
|
+
Meal .json files may contain ERB in the "boucher" section. However, the file get's parsed by chef-solo so it has to remain a valid JSON file. But you can do things like this:
|
127
129
|
|
128
130
|
{
|
129
131
|
"run_list": ...
|
@@ -133,6 +135,28 @@ ERB: The "boucher": content can contain ERB. So you can use config params like
|
|
133
135
|
}
|
134
136
|
}
|
135
137
|
|
138
|
+
Also keep in mind that you can use ERB in recipes' template files.
|
139
|
+
|
140
|
+
### Volume Specs
|
141
|
+
|
142
|
+
Volumes may be specified in the config for a given meal. The "volumes": entry must be a hash where keys are the device name (mount point) and the values
|
143
|
+
are a hash describing the volume. There are really three variations:
|
144
|
+
|
145
|
+
1) Mounting an existing volume by using the volume_id:
|
146
|
+
|
147
|
+
"volumes": {"/dev/sda3" => {"volume_id": "volume-abc123"}}
|
148
|
+
|
149
|
+
2) Mount a new volume based on an existing snapshot:
|
150
|
+
|
151
|
+
"volumes": {"/dev/sda4" => {"snapshot_id": "snapshot-abc123"}}
|
152
|
+
|
153
|
+
3) Mount a new volume of a given size:
|
154
|
+
|
155
|
+
"volumes": {"/dev/sda5" => {"size": 16}}
|
156
|
+
|
157
|
+
If volumes are not specified, AWS will apply the default volume setup in the management console.
|
158
|
+
|
159
|
+
|
136
160
|
### Environments
|
137
161
|
|
138
162
|
Enviroments are configured in config/env/<env_name>.rb. The project template we checked out earlier only provides one: dev.
|
data/TODO.md
ADDED
data/boucher.gemspec
CHANGED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'boucher/compute'
|
2
|
+
require 'boucher/servers'
|
3
|
+
|
4
|
+
module Boucher
|
5
|
+
|
6
|
+
ADDRESS_TABLE_FORMAT = "%-15s %-12s\n"
|
7
|
+
|
8
|
+
def self.print_addresses(addresses)
|
9
|
+
puts
|
10
|
+
printf ADDRESS_TABLE_FORMAT, "Public IP", "Server ID"
|
11
|
+
puts ("-" * 29)
|
12
|
+
|
13
|
+
addresses.each do |address|
|
14
|
+
printf ADDRESS_TABLE_FORMAT,
|
15
|
+
address.public_ip,
|
16
|
+
address.server_id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ADDRESS_OVERVIEW_TABLE_FORMAT = "%12s %-15s %-12s\n"
|
21
|
+
|
22
|
+
def self.print_address_overview(addresses)
|
23
|
+
puts
|
24
|
+
printf ADDRESS_OVERVIEW_TABLE_FORMAT, "Meal", "Public IP", "Server ID"
|
25
|
+
puts ("-" * 43)
|
26
|
+
|
27
|
+
addresses.values.each do |address|
|
28
|
+
printf ADDRESS_OVERVIEW_TABLE_FORMAT,
|
29
|
+
address[:meal],
|
30
|
+
address[:ip],
|
31
|
+
address[:server_id]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.address_overview
|
36
|
+
ips = {}
|
37
|
+
Boucher.compute.addresses.each do |ip|
|
38
|
+
ips[ip.public_ip] = {ip: ip.public_ip, server_id: ip.server_id}
|
39
|
+
end
|
40
|
+
Boucher.meals.each do |name, meal|
|
41
|
+
(meal[:elastic_ips] || []).each do |ip|
|
42
|
+
if ip.nil? || ip.size == 0
|
43
|
+
# skip
|
44
|
+
elsif ips[ip]
|
45
|
+
ips[ip][:meal] = name
|
46
|
+
else
|
47
|
+
ips[ip] = {meal: name, ip: ip}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
ips
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.associate_addresses_for(meal, server)
|
55
|
+
ips = meal[:elastic_ips]
|
56
|
+
if ips.nil? || ips.empty?
|
57
|
+
puts "No Elastic IPs to associate for meal #{meal[:name]}."
|
58
|
+
return
|
59
|
+
end
|
60
|
+
ips.each do |ip|
|
61
|
+
address = Boucher.compute.addresses.get(ip)
|
62
|
+
if address
|
63
|
+
if address.server_id == server.id
|
64
|
+
puts "#{ip} already associated with #{meal[:name]}:#{server.id}"
|
65
|
+
else
|
66
|
+
puts "Associating #{ip} with #{meal[:name]}:#{server.id}"
|
67
|
+
address.server = server
|
68
|
+
end
|
69
|
+
else
|
70
|
+
puts "Elastic IP (#{ip}) not found. Skipping."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.associate_all_addresses
|
76
|
+
meals = Boucher.meals
|
77
|
+
meals.each do |name, meal|
|
78
|
+
ips = meal[:elastic_ips]
|
79
|
+
if ips && ips.size > 0
|
80
|
+
begin
|
81
|
+
server = Boucher::Servers.find(meal: name, env: Boucher::Config[:env])
|
82
|
+
associate_addresses_for(meal, server)
|
83
|
+
rescue Boucher::Servers::NotFound => e
|
84
|
+
puts "Can't associate address to '#{name}' server because it can't be found."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
data/lib/boucher/compute.rb
CHANGED
@@ -54,19 +54,4 @@ module Boucher
|
|
54
54
|
rescue Exception => e
|
55
55
|
false
|
56
56
|
end
|
57
|
-
|
58
|
-
def self.change_server_state(server_id, command, new_state)
|
59
|
-
print "#{command}-ing server #{server_id}..."
|
60
|
-
server = compute.servers.get(server_id)
|
61
|
-
server.send(command.to_sym)
|
62
|
-
server.wait_for { print "."; state == new_state }
|
63
|
-
puts
|
64
|
-
Boucher.print_servers [server]
|
65
|
-
puts
|
66
|
-
puts "The server has been #{command}-ed."
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.find_servers
|
70
|
-
compute.servers
|
71
|
-
end
|
72
57
|
end
|
data/lib/boucher/env.rb
CHANGED
data/lib/boucher/io.rb
CHANGED
@@ -22,87 +22,4 @@ module Boucher
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
SERVER_TABLE_FORMAT = "%-12s %-12s %-10s %-10s %-10s %-15s %-15s %-10s\n"
|
26
|
-
|
27
|
-
def self.print_server_table_header
|
28
|
-
puts
|
29
|
-
printf SERVER_TABLE_FORMAT, "ID", "Environment", "Meal", "Creator", "State", "Public IP", "Private IP", "Inst. Size"
|
30
|
-
puts ("-" * 120)
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.print_server(server)
|
34
|
-
printf SERVER_TABLE_FORMAT,
|
35
|
-
server.id,
|
36
|
-
(server.tags["Env"] || "???")[0...12],
|
37
|
-
(server.tags["Meal"] || "???")[0...10],
|
38
|
-
(server.tags["Creator"] || "???")[0...10],
|
39
|
-
server.state,
|
40
|
-
server.public_ip_address,
|
41
|
-
server.private_ip_address,
|
42
|
-
server.flavor_id
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.print_servers(servers)
|
46
|
-
print_server_table_header
|
47
|
-
sorted_servers = servers.sort_by{|s| [s.tags["Env"] || "?",
|
48
|
-
s.tags["Meal"] || "?"]}
|
49
|
-
sorted_servers.each do |server|
|
50
|
-
print_server(server) if server
|
51
|
-
end
|
52
|
-
puts
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.print_volumes(volumes)
|
56
|
-
id_sizes = []
|
57
|
-
size_sizes = []
|
58
|
-
state_sizes = []
|
59
|
-
zone_sizes = []
|
60
|
-
snapshot_sizes = []
|
61
|
-
|
62
|
-
Array(volumes).each do |volume|
|
63
|
-
id_sizes << volume.id.length
|
64
|
-
size_sizes << volume.size.to_s.length
|
65
|
-
state_sizes << volume.state.length
|
66
|
-
zone_sizes << volume.availability_zone.length
|
67
|
-
snapshot_sizes << volume.snapshot_id.to_s.length
|
68
|
-
end
|
69
|
-
|
70
|
-
id_length = id_sizes.max + 5
|
71
|
-
size_length = size_sizes.max + 5
|
72
|
-
state_length = state_sizes.max + 5
|
73
|
-
zone_length = zone_sizes.max + 5
|
74
|
-
snapshot_length = snapshot_sizes.max
|
75
|
-
|
76
|
-
puts "ID#{" "*(id_length - 2)}Size#{" "*(size_length - 4)}State#{" "*(state_length - 5)}Zone#{" "*(zone_length - 4)}Snapshot"
|
77
|
-
puts "-"*(id_length + size_length + state_length + zone_length + snapshot_length)
|
78
|
-
|
79
|
-
Array(volumes).each do |volume|
|
80
|
-
puts "#{volume.id}#{" "*(id_length - volume.id.length)}#{volume.size}GB#{" "*(size_length - volume.size.to_s.length - 2)}#{volume.state}#{" "*(state_length - volume.state.length)}#{volume.availability_zone}#{" "*(zone_length - volume.availability_zone.length)}#{volume.snapshot_id}#{" "*(snapshot_length - volume.snapshot_id.to_s.length)}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
FILE_TABLE_FORMAT = "%-60s %-10s %-25s %-32s\n"
|
85
|
-
|
86
|
-
def self.print_file_table_header
|
87
|
-
puts
|
88
|
-
printf FILE_TABLE_FORMAT, "Key", "Size", "Last Modified", "etag"
|
89
|
-
puts ("-" * 150)
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.print_file(file)
|
93
|
-
printf FILE_TABLE_FORMAT,
|
94
|
-
file.key,
|
95
|
-
file.content_length,
|
96
|
-
file.last_modified,
|
97
|
-
file.etag
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.print_files(files)
|
101
|
-
print_file_table_header
|
102
|
-
files.each do |file|
|
103
|
-
print_file(file) if file
|
104
|
-
end
|
105
|
-
puts
|
106
|
-
end
|
107
|
-
|
108
25
|
end
|
data/lib/boucher/provision.rb
CHANGED
@@ -2,6 +2,7 @@ require 'boucher/compute'
|
|
2
2
|
require 'boucher/io'
|
3
3
|
require 'boucher/servers'
|
4
4
|
require 'boucher/volumes'
|
5
|
+
require 'boucher/addresses'
|
5
6
|
require 'retryable'
|
6
7
|
|
7
8
|
module Boucher
|
@@ -23,7 +24,7 @@ module Boucher
|
|
23
24
|
if server.nil?
|
24
25
|
Boucher.provision(meal)
|
25
26
|
elsif server.state == "stopped"
|
26
|
-
Boucher::Servers.start(server
|
27
|
+
Boucher::Servers.start([server])
|
27
28
|
server.reload
|
28
29
|
Boucher.cook_meal_on_server(meal, server)
|
29
30
|
else
|
@@ -36,28 +37,17 @@ module Boucher
|
|
36
37
|
server = create_meal_server(meal)
|
37
38
|
wait_for_server_to_boot(server)
|
38
39
|
wait_for_server_to_accept_ssh(server)
|
39
|
-
|
40
|
-
attach_volumes(volumes, server)
|
40
|
+
attach_volumes(meal, server)
|
41
41
|
cook_meal_on_server(meal, server)
|
42
42
|
puts "\nThe new #{meal[:name]} server has been provisioned! id: #{server.id}"
|
43
43
|
end
|
44
44
|
|
45
|
-
def self.attach_elastic_ips(meal, server)
|
46
|
-
puts "Attaching elastic IPs..."
|
47
|
-
ips = meal[:elastic_ips] || []
|
48
|
-
|
49
|
-
ips.each do |ip|
|
50
|
-
puts "Associating #{server.id} with #{ip}"
|
51
|
-
compute.associate_address(server.id, ip)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
45
|
private
|
56
46
|
|
57
47
|
def self.cook_meal_on_server(meal, server)
|
58
48
|
puts "Cooking meal '#{meal[:name]}' on server: #{server}"
|
59
49
|
Boucher.cook_meal(server, meal[:name])
|
60
|
-
|
50
|
+
associate_addresses_for(meal, server)
|
61
51
|
end
|
62
52
|
|
63
53
|
def self.wait_for_server_to_accept_ssh(server)
|
@@ -81,21 +71,28 @@ module Boucher
|
|
81
71
|
server
|
82
72
|
end
|
83
73
|
|
84
|
-
def self.
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
74
|
+
def self.attach_volumes(meal, server)
|
75
|
+
volumes = meal[:volumes]
|
76
|
+
return unless volumes && volumes.size > 0
|
77
|
+
puts "Attaching volumes..."
|
78
|
+
volumes.each do |device, spec|
|
79
|
+
volume = acquire_volume(spec, server)
|
80
|
+
print "Attaching volume #{volume.id} to #{server.id}..."
|
81
|
+
Boucher.compute.attach_volume(server.id, volume.id, device)
|
82
|
+
volume.wait_for { print "."; volume.state == "in-use" }
|
83
|
+
puts
|
90
84
|
end
|
91
85
|
end
|
92
86
|
|
93
|
-
def self.
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
87
|
+
def self.acquire_volume(spec, server)
|
88
|
+
if spec[:volume_id]
|
89
|
+
Boucher.compute.volumes.get(spec[:volume_id])
|
90
|
+
elsif spec[:snapshot_id]
|
91
|
+
puts "Creating volume based on snapshot: #{spec[:snapshot_id]}"
|
92
|
+
Boucher::Volumes.create(:snapshot_id => spec[:snapshot_id], :availability_zone => server.availability_zone)
|
93
|
+
else
|
94
|
+
puts "Creating new volume of size: #{spec[:size]}GB"
|
95
|
+
Boucher::Volumes.create(:size => spec[:size].to_i, :availability_zone => server.availability_zone)
|
99
96
|
end
|
100
97
|
end
|
101
98
|
end
|
data/lib/boucher/servers.rb
CHANGED
@@ -1,10 +1,45 @@
|
|
1
1
|
require 'boucher/compute'
|
2
2
|
|
3
3
|
module Boucher
|
4
|
+
|
5
|
+
SERVER_TABLE_FORMAT = "%-12s %-12s %-10s %-10s %-10s %-15s %-15s %-10s\n"
|
6
|
+
|
7
|
+
def self.print_server_table_header
|
8
|
+
puts
|
9
|
+
printf SERVER_TABLE_FORMAT, "ID", "Environment", "Meal", "Creator", "State", "Public IP", "Private IP", "Inst. Size"
|
10
|
+
puts ("-" * 107)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.print_server(server)
|
14
|
+
printf SERVER_TABLE_FORMAT,
|
15
|
+
server.id,
|
16
|
+
(server.tags["Env"] || "???")[0...12],
|
17
|
+
(server.tags["Meal"] || "???")[0...10],
|
18
|
+
(server.tags["Creator"] || "???")[0...10],
|
19
|
+
server.state,
|
20
|
+
server.public_ip_address,
|
21
|
+
server.private_ip_address,
|
22
|
+
server.flavor_id
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.print_servers(servers)
|
26
|
+
print_server_table_header
|
27
|
+
sorted_servers = servers.sort_by { |s| [s.tags["Env"] || "?",
|
28
|
+
s.tags["Meal"] || "?"] }
|
29
|
+
sorted_servers.each do |server|
|
30
|
+
print_server(server) if server
|
31
|
+
end
|
32
|
+
puts
|
33
|
+
end
|
34
|
+
|
4
35
|
module Servers
|
5
36
|
NotFound = Class.new(StandardError)
|
6
37
|
|
7
38
|
class << self
|
39
|
+
def clear
|
40
|
+
@instance = nil
|
41
|
+
end
|
42
|
+
|
8
43
|
def instance
|
9
44
|
reload if !@instance
|
10
45
|
@instance
|
@@ -48,44 +83,67 @@ module Boucher
|
|
48
83
|
end
|
49
84
|
|
50
85
|
def in_env(env)
|
51
|
-
Servers.cultivate(self.find_all {|s| s.tags["Env"] == env.to_s })
|
86
|
+
Servers.cultivate(self.find_all { |s| s.tags["Env"] == env.to_s })
|
52
87
|
end
|
53
88
|
|
54
89
|
def in_state(state)
|
55
|
-
|
90
|
+
if state[0] == "!"
|
91
|
+
state = state[1..-1]
|
92
|
+
Servers.cultivate(self.find_all { |s| s.state != state.to_s })
|
93
|
+
else
|
94
|
+
Servers.cultivate(self.find_all { |s| s.state == state.to_s })
|
95
|
+
end
|
56
96
|
end
|
57
97
|
|
58
98
|
def of_meal(meal)
|
59
|
-
Servers.cultivate(self.find_all {|s| s.tags["Meal"] == meal.to_s })
|
99
|
+
Servers.cultivate(self.find_all { |s| s.tags["Meal"] == meal.to_s })
|
60
100
|
end
|
61
101
|
|
62
|
-
def self.start(
|
63
|
-
Boucher.
|
102
|
+
def self.start(servers)
|
103
|
+
Boucher.change_servers_state(servers, :start, "running")
|
64
104
|
end
|
65
105
|
|
66
|
-
def self.stop(
|
67
|
-
Boucher.
|
106
|
+
def self.stop(servers)
|
107
|
+
Boucher.change_servers_state(servers, :stop, "stopped")
|
68
108
|
end
|
69
109
|
|
70
|
-
def self.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
Boucher.change_server_state server.id, :destroy, "terminated"
|
110
|
+
def self.restart(servers)
|
111
|
+
Boucher.change_servers_state(servers, :stop, "stopped")
|
112
|
+
Boucher.change_servers_state(servers, :start, "running")
|
113
|
+
end
|
75
114
|
|
76
|
-
|
77
|
-
|
78
|
-
puts "Destroying volume #{volume.id}..."
|
79
|
-
Boucher::Volumes.destroy(volume)
|
80
|
-
end
|
115
|
+
def self.terminate(servers)
|
116
|
+
Boucher.change_servers_state(servers, :destroy, "terminated")
|
81
117
|
end
|
82
118
|
|
83
119
|
def with_id(server_id)
|
84
|
-
Servers.cultivate(self.find_all {|s| s.id == server_id}).first
|
120
|
+
Servers.cultivate(self.find_all { |s| s.id == server_id }).first
|
85
121
|
end
|
86
122
|
|
87
123
|
def [](meal)
|
88
124
|
find(:env => Boucher::Config[:env], :meal => meal, :state => "running")
|
89
125
|
end
|
90
126
|
end
|
127
|
+
|
128
|
+
def self.change_servers_state(servers, command, new_state)
|
129
|
+
print "#{command}-ing servers #{servers.map(&:id).join(", ")}..."
|
130
|
+
servers.each { |s| s.send(command.to_sym) }
|
131
|
+
servers.each { |s| s.wait_for { print "."; s.state == new_state }}
|
132
|
+
puts
|
133
|
+
Boucher.print_servers servers
|
134
|
+
puts
|
135
|
+
puts "The servers have been #{command}-ed."
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.resolve_servers(id_or_meal)
|
139
|
+
if id_or_meal[0..1] == "i-"
|
140
|
+
puts "Retrieving server with id #{id_or_meal}..."
|
141
|
+
[Boucher::Servers.with_id(id_or_meal)]
|
142
|
+
else
|
143
|
+
puts "Searching for running #{id_or_meal} servers in #{Boucher.env_name} environment..."
|
144
|
+
servers = Boucher::Servers.search(:meal => id_or_meal, :env => Boucher.env_name, :state => "!terminated")
|
145
|
+
Boucher::print_servers(servers)
|
146
|
+
servers
|
147
|
+
end
|
148
|
+
end
|
91
149
|
end
|
data/lib/boucher/storage.rb
CHANGED
@@ -15,11 +15,35 @@ module Boucher
|
|
15
15
|
@store
|
16
16
|
end
|
17
17
|
|
18
|
+
FILE_TABLE_FORMAT = "%-60s %-10s %-25s %-32s\n"
|
19
|
+
|
20
|
+
def self.print_file_table_header
|
21
|
+
puts
|
22
|
+
printf FILE_TABLE_FORMAT, "Key", "Size", "Last Modified", "etag"
|
23
|
+
puts ("-" * 150)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.print_file(file)
|
27
|
+
printf FILE_TABLE_FORMAT,
|
28
|
+
file.key,
|
29
|
+
file.content_length,
|
30
|
+
file.last_modified,
|
31
|
+
file.etag
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.print_files(files)
|
35
|
+
print_file_table_header
|
36
|
+
files.each do |file|
|
37
|
+
print_file(file) if file
|
38
|
+
end
|
39
|
+
puts
|
40
|
+
end
|
41
|
+
|
18
42
|
module Storage
|
19
43
|
|
20
44
|
def self.list(dir_name)
|
21
45
|
dir = Boucher.storage.directories.get(dir_name)
|
22
|
-
result = dir.files.select {|f| f.key[-1] != "/" }.to_a
|
46
|
+
result = dir.files.select { |f| f.key[-1] != "/" }.to_a
|
23
47
|
result
|
24
48
|
end
|
25
49
|
|
@@ -36,7 +60,7 @@ module Boucher
|
|
36
60
|
dir = Boucher.storage.directories.get(dir_name)
|
37
61
|
url = dir.files.get_https_url(key, Time.now + 3600)
|
38
62
|
Kernel.system("curl", url, "-o", filename)
|
39
|
-
dir.files.detect{|f| f.key == key}
|
63
|
+
dir.files.detect { |f| f.key == key }
|
40
64
|
end
|
41
65
|
|
42
66
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'boucher/addresses'
|
2
|
+
|
3
|
+
namespace :addresses do
|
4
|
+
|
5
|
+
desc "Prints a list of allocated Elastic IP addresses"
|
6
|
+
task :list do
|
7
|
+
Boucher.print_address_overview(Boucher.address_overview)
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Allocates a new Elastic IP address"
|
11
|
+
task :allocate do
|
12
|
+
puts "Allocation a new Elastic IP address..."
|
13
|
+
address = Boucher.compute.addresses.create
|
14
|
+
Boucher.print_addresses([address])
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Releases an Elastic IP address"
|
18
|
+
task :deallocate, [:ip] do |t, args|
|
19
|
+
puts "Deallocating Elastic IP address: #{args.ip} ..."
|
20
|
+
address = Boucher.compute.addresses.get(args.ip)
|
21
|
+
raise "Elastic IP address not found: #{args.ip}" unless address
|
22
|
+
address.destroy
|
23
|
+
puts "Done."
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Associates an Elastic IP with a specific server"
|
27
|
+
task :associate, [:ip, :server_id] do |t, args|
|
28
|
+
server = Boucher.compute.servers.get(args.server_id)
|
29
|
+
raise "Server not found!" unless server
|
30
|
+
address = Boucher.compute.addresses.get(args.ip)
|
31
|
+
raise "Elastic IP not found!" unless address
|
32
|
+
address.server = server
|
33
|
+
Boucher.print_addresses [address]
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Associates all unbound Elastic IP addresses configured for all meals"
|
37
|
+
task :push do
|
38
|
+
Boucher.associate_all_addresses
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end
|
@@ -44,17 +44,12 @@ namespace :servers do
|
|
44
44
|
server_listing("of meal '#{args.meal}'", Boucher::Servers.of_meal(args.meal))
|
45
45
|
end
|
46
46
|
|
47
|
-
desc "Terminates the specified server"
|
48
|
-
task :terminate, [:
|
49
|
-
|
50
|
-
|
51
|
-
if !server
|
52
|
-
puts "Server #{args.server_id} does not exist"
|
53
|
-
exit 1
|
54
|
-
end
|
47
|
+
desc "Terminates the specified server(s)"
|
48
|
+
task :terminate, [:id_or_meal] do |t, args|
|
49
|
+
servers = Boucher.resolve_servers(args.id_or_meal)
|
55
50
|
|
56
51
|
begin
|
57
|
-
Boucher::Servers.terminate(
|
52
|
+
Boucher::Servers.terminate(servers) if !servers.empty?
|
58
53
|
rescue => e
|
59
54
|
puts "\nTermination failed. This may be due to termination protection. If
|
60
55
|
you're sure you wish to disable this protection, select the instance in the AWS
|
@@ -63,16 +58,22 @@ web console and click Instance Actions -> Change Termination Protection -> Yes."
|
|
63
58
|
end
|
64
59
|
end
|
65
60
|
|
66
|
-
desc "Stops the specified server"
|
67
|
-
task :stop, [:
|
68
|
-
|
69
|
-
Boucher::Servers.stop(
|
61
|
+
desc "Stops the specified server(s)"
|
62
|
+
task :stop, [:id_or_meal] do |t, args|
|
63
|
+
servers = Boucher.resolve_servers(args.id_or_meal)
|
64
|
+
Boucher::Servers.stop(servers) if !servers.empty?
|
70
65
|
end
|
71
66
|
|
72
|
-
desc "Starts the specified server"
|
73
|
-
task :start, [:
|
74
|
-
Boucher
|
75
|
-
|
67
|
+
desc "Starts the specified server(s)"
|
68
|
+
task :start, [:id_or_meal] do |t, args|
|
69
|
+
servers = Boucher.resolve_servers(args.id_or_meal)
|
70
|
+
Boucher::Servers.start(servers) if !servers.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "Restarts the specified server(s)"
|
74
|
+
task :restart, [:id_or_meal] do |t, args|
|
75
|
+
servers = Boucher.resolve_servers(args.id_or_meal)
|
76
|
+
Boucher::Servers.restart(servers) if !servers.empty?
|
76
77
|
end
|
77
78
|
|
78
79
|
desc "Open an SSH session with the specified server"
|
@@ -110,17 +111,10 @@ web console and click Instance Actions -> Change Termination Protection -> Yes."
|
|
110
111
|
Boucher.establish_server(server, args.meal)
|
111
112
|
end
|
112
113
|
|
113
|
-
desc "Cook the specified meal on the instance specified
|
114
|
+
desc "Cook the specified meal on the instance(s) specified by the given id or meal"
|
114
115
|
task :chef, [:meal, :server_id] do |t, args|
|
115
116
|
Boucher.assert_env!
|
116
|
-
servers =
|
117
|
-
if(args.server_id)
|
118
|
-
servers = [Boucher.compute.servers.get(args.server_id)]
|
119
|
-
else
|
120
|
-
puts "Searching for running #{args.meal} servers in #{Boucher.env_name} environment..."
|
121
|
-
servers = Boucher::Servers.search(:meal => args.meal, :env => Boucher.env_name, :state => "running")
|
122
|
-
puts "Found #{servers.size}."
|
123
|
-
end
|
117
|
+
servers = resolve_servers(args.server_id || args.meal)
|
124
118
|
servers.each do |server|
|
125
119
|
Boucher.cook_meal(server, args.meal)
|
126
120
|
end
|
@@ -9,6 +9,12 @@ namespace :volumes do
|
|
9
9
|
|
10
10
|
desc "Destroy the specified volume"
|
11
11
|
task :destroy, [:volume_id] do |t, args|
|
12
|
-
Boucher.
|
12
|
+
volume = Boucher::Volumes.with_id(args.volume_id)
|
13
|
+
if volume
|
14
|
+
puts "Destroying volume:"
|
15
|
+
print_volumes [volume]
|
16
|
+
else
|
17
|
+
raise "Volume not found: #{args.volume_id}"
|
18
|
+
end
|
13
19
|
end
|
14
20
|
end
|
data/lib/boucher/volumes.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
1
|
require 'boucher/compute'
|
2
2
|
|
3
3
|
module Boucher
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
|
5
|
+
VOLUME_TABLE_FORMAT = "%-12s %-15s %-6s %-10s %-10s %-13s\n"
|
6
|
+
|
7
|
+
def self.print_volumes(volumes)
|
8
|
+
puts
|
9
|
+
printf VOLUME_TABLE_FORMAT, "ID", "Name", "Size", "Server", "State", "Snapshot"
|
10
|
+
puts ("-" * 76)
|
11
|
+
|
12
|
+
volumes.each do |volume|
|
13
|
+
printf VOLUME_TABLE_FORMAT,
|
14
|
+
volume.id,
|
15
|
+
(volume.tags["Name"] || "")[0...15],
|
16
|
+
volume.size.to_s + "GB",
|
17
|
+
volume.server_id,
|
18
|
+
volume.state,
|
19
|
+
volume.snapshot_id
|
20
|
+
end
|
7
21
|
end
|
8
22
|
|
9
23
|
module Volumes
|
@@ -19,23 +33,25 @@ module Boucher
|
|
19
33
|
end
|
20
34
|
|
21
35
|
def self.with_id(volume_id)
|
22
|
-
all.find {|volume| volume.id == volume_id}
|
36
|
+
all.find { |volume| volume.id == volume_id }
|
23
37
|
end
|
24
38
|
|
25
|
-
def self.create(
|
26
|
-
|
39
|
+
def self.create(options)
|
40
|
+
zone = options[:availability_zone]
|
41
|
+
raise ":availability_zone is required to create a volume." unless zone
|
42
|
+
size = options[:size]
|
43
|
+
snapshot_id = options[:snapshot_id]
|
44
|
+
response = if snapshot_id
|
45
|
+
snapshot = Boucher::compute.snapshots.get(snapshot_id)
|
46
|
+
size = snapshot.volume_size.to_i
|
47
|
+
Boucher.compute.create_volume(zone, size, "SnapshotId" => snapshot_id)
|
48
|
+
else
|
49
|
+
Boucher.compute.create_volume(zone, size)
|
50
|
+
end
|
27
51
|
volume_id = response.body["volumeId"]
|
28
|
-
volume
|
29
|
-
|
30
|
-
volume.wait_for { ready? }
|
31
|
-
volume.device = device
|
52
|
+
volume = Boucher.compute.volumes.get(volume_id)
|
53
|
+
volume.wait_for { volume.ready? }
|
32
54
|
volume
|
33
55
|
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
56
|
end
|
41
57
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
require 'boucher/addresses'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
describe "Boucher Addresses" do
|
6
|
+
|
7
|
+
before do
|
8
|
+
Boucher::Servers.clear
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
Boucher::Servers.all.each { |s| s.destroy }
|
13
|
+
Boucher::Config[:env] = "test"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "does nothing for meal with no elastic ips" do
|
17
|
+
server = Boucher.compute.servers.create(tags: {"Meal" => "some_meal"})
|
18
|
+
|
19
|
+
Boucher.associate_addresses_for({name: "some_meal"}, server)
|
20
|
+
|
21
|
+
server.reload
|
22
|
+
server.addresses.should == []
|
23
|
+
end
|
24
|
+
|
25
|
+
it "associates ips for server" do
|
26
|
+
server = Boucher.compute.servers.create(tags: {"Meal" => "some_meal"})
|
27
|
+
ip = Boucher.compute.addresses.create
|
28
|
+
|
29
|
+
meal = {name: "some_meal", elastic_ips: [ip.public_ip]}
|
30
|
+
Boucher.associate_addresses_for(meal, server)
|
31
|
+
|
32
|
+
server.reload
|
33
|
+
server.addresses.count.should == 1
|
34
|
+
server.addresses.first.public_ip.should == ip.public_ip
|
35
|
+
end
|
36
|
+
|
37
|
+
it "associating ips skips missing ips" do
|
38
|
+
server = Boucher.compute.servers.create(tags: {"Meal" => "some_meal"})
|
39
|
+
|
40
|
+
meal = {name: "some_meal", elastic_ips: ["1.2.3.4"]}
|
41
|
+
|
42
|
+
lambda { Boucher.associate_addresses_for(meal, server) }.should_not raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "associates all ips for all meals" do
|
46
|
+
server1 = Boucher.compute.servers.create(tags: {"Meal" => "meal1", "Env" => "test"})
|
47
|
+
server2 = Boucher.compute.servers.create(tags: {"Meal" => "meal2", "Env" => "test"})
|
48
|
+
ip1 = Boucher.compute.addresses.create
|
49
|
+
ip2 = Boucher.compute.addresses.create
|
50
|
+
|
51
|
+
meals = {meal1: {name: "meal1", elastic_ips: [ip1.public_ip]},
|
52
|
+
meal2: {name: "meal2", elastic_ips: [ip2.public_ip]}}
|
53
|
+
Boucher.stub(:meals).and_return meals
|
54
|
+
|
55
|
+
Boucher.associate_all_addresses
|
56
|
+
|
57
|
+
server1.reload
|
58
|
+
server1.addresses.size.should == 1
|
59
|
+
server1.addresses.first.public_ip.should == ip1.public_ip
|
60
|
+
server2.reload
|
61
|
+
server2.addresses.first.public_ip.should == ip2.public_ip
|
62
|
+
end
|
63
|
+
|
64
|
+
it "associate all with missing server doesn't crash" do
|
65
|
+
ip = Boucher.compute.addresses.create
|
66
|
+
|
67
|
+
Boucher.meals[:some_meal] = {name: "some_meal", elastic_ips: [ip.public_ip]}
|
68
|
+
|
69
|
+
lambda { Boucher.associate_all_addresses }.should_not raise_error
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end
|
@@ -4,7 +4,7 @@ require 'boucher/provision'
|
|
4
4
|
describe "Boucher Provisioning" do
|
5
5
|
|
6
6
|
before do
|
7
|
-
Boucher::
|
7
|
+
Boucher::Servers.clear
|
8
8
|
end
|
9
9
|
|
10
10
|
after do
|
@@ -23,23 +23,13 @@ describe "Boucher Provisioning" do
|
|
23
23
|
server = mock(:id => "the id", :state => "stopped")
|
24
24
|
meal = {:name => "some_meal"}
|
25
25
|
Boucher.stub(:meal).and_return(meal)
|
26
|
-
Boucher.should_receive(:
|
26
|
+
Boucher.should_receive(:change_servers_state).with([server], :start, "running")
|
27
27
|
server.should_receive(:reload)
|
28
28
|
Boucher.should_receive(:cook_meal_on_server).with(meal, server)
|
29
29
|
|
30
30
|
Boucher.establish_server server, "some_meal"
|
31
31
|
end
|
32
32
|
|
33
|
-
it "attaches elastic IPs if the server was stopped" do
|
34
|
-
server = mock(:id => "the id", :state => "stopped", :reload => nil)
|
35
|
-
Boucher.stub(:meal).and_return({:name => "some_meal", :elastic_ips => %w(1.2.3.4)})
|
36
|
-
Boucher.stub(:change_server_state)
|
37
|
-
Boucher.stub(:cook_meal)
|
38
|
-
Boucher.compute.should_receive(:associate_address).with(anything, "1.2.3.4")
|
39
|
-
|
40
|
-
Boucher.establish_server server, "meal_name"
|
41
|
-
end
|
42
|
-
|
43
33
|
it "cooks meals on server if it is up and running" do
|
44
34
|
running_server = mock(:id => "the id", :state => "running")
|
45
35
|
meal = {:name => "some_meal"}
|
@@ -60,12 +50,73 @@ describe "Boucher Provisioning" do
|
|
60
50
|
end
|
61
51
|
|
62
52
|
it "provisions a server with elastic IP" do
|
53
|
+
Boucher.compute.key_pairs.create(name: "test_key")
|
54
|
+
ip = Boucher.compute.addresses.create
|
55
|
+
Boucher.stub!(:ssh)
|
56
|
+
Boucher.stub!(:cook_meal)
|
57
|
+
|
58
|
+
Boucher.provision :name => "some_meal", :elastic_ips => [ip.public_ip]
|
59
|
+
|
60
|
+
server = Boucher::Servers["some_meal"]
|
61
|
+
server.reload
|
62
|
+
server.addresses.size.should == 1
|
63
|
+
server.addresses.first.public_ip.should == ip.public_ip
|
64
|
+
end
|
65
|
+
|
66
|
+
it "attaches volumes" do
|
63
67
|
Boucher.stub!(:ssh)
|
64
68
|
Boucher.should_receive(:setup_meal)
|
65
|
-
Boucher.stub(:
|
66
|
-
|
69
|
+
Boucher.stub(:cook_meals_on_server)
|
70
|
+
|
71
|
+
Boucher.should_receive(:attach_volumes)
|
72
|
+
|
73
|
+
Boucher.provision :name => "some_meal", :volumes => {}
|
74
|
+
end
|
75
|
+
end
|
67
76
|
|
68
|
-
|
77
|
+
context "Volumes" do
|
78
|
+
|
79
|
+
let(:server) { server = Boucher.compute.servers.new; server.save; server }
|
80
|
+
|
81
|
+
it "attaches an existing volume" do
|
82
|
+
volume = Boucher::Volumes.create(:size => 12, :availability_zone => "us-east-1c")
|
83
|
+
|
84
|
+
meal_spec = {:volumes => {"/dev/sda2" => {:volume_id => volume.id}}}
|
85
|
+
Boucher.attach_volumes(meal_spec, server)
|
86
|
+
|
87
|
+
server.reload
|
88
|
+
server.volumes.size.should == 1
|
89
|
+
server.volumes.first.device.should == "/dev/sda2"
|
90
|
+
server.volumes.first.availability_zone.should == "us-east-1c"
|
91
|
+
server.volumes.first.size.should == 12
|
92
|
+
end
|
93
|
+
|
94
|
+
it "attaches a new volume based on a snapshot" do
|
95
|
+
old_volume = Boucher::Volumes.create(:size => 12, :availability_zone => "us-east-1c")
|
96
|
+
response = old_volume.snapshot("test")
|
97
|
+
snapshot_id = response.body["snapshotId"]
|
98
|
+
|
99
|
+
meal_spec = {:volumes => {"/dev/sda3" => {:snapshot_id => snapshot_id}}}
|
100
|
+
Boucher.attach_volumes(meal_spec, server)
|
101
|
+
|
102
|
+
server.reload
|
103
|
+
server.volumes.size.should == 1
|
104
|
+
volume = server.volumes.first
|
105
|
+
volume.snapshot_id.should == snapshot_id
|
106
|
+
volume.size.should == 12
|
107
|
+
volume.device.should == "/dev/sda3"
|
108
|
+
end
|
109
|
+
|
110
|
+
it "attaches a new volume with specified size" do
|
111
|
+
meal_spec = {:volumes => {"/dev/sda4" => {:size => 42}}}
|
112
|
+
Boucher.attach_volumes(meal_spec, server)
|
113
|
+
|
114
|
+
server.reload
|
115
|
+
server.volumes.size.should == 1
|
116
|
+
volume = server.volumes.first
|
117
|
+
volume.size.should == 42
|
118
|
+
volume.device.should == "/dev/sda4"
|
69
119
|
end
|
120
|
+
|
70
121
|
end
|
71
122
|
end
|
@@ -44,6 +44,13 @@ describe "Boucher::Servers" do
|
|
44
44
|
Boucher::Servers.in_state("stopped").map(&:id).should == ["s1"]
|
45
45
|
end
|
46
46
|
|
47
|
+
it "finds servers NOT in a given state" do
|
48
|
+
Boucher::Servers.in_state("!running").map(&:id).should == %w(s1 s2 s3)
|
49
|
+
Boucher::Servers.in_state("!terminated").map(&:id).should == %w(s1 s2 s4)
|
50
|
+
Boucher::Servers.in_state("!pending").map(&:id).should == %w(s1 s3 s4)
|
51
|
+
Boucher::Servers.in_state("!stopped").map(&:id).should == %w(s2 s3 s4)
|
52
|
+
end
|
53
|
+
|
47
54
|
it "finds the first matching server" do
|
48
55
|
Boucher::Servers.find.id.should == "s1"
|
49
56
|
Boucher::Servers.find(:meal => "foo").id.should == "s1"
|
@@ -73,7 +80,8 @@ describe "Boucher::Servers" do
|
|
73
80
|
end
|
74
81
|
|
75
82
|
it "stops a server" do
|
76
|
-
|
77
|
-
Boucher
|
83
|
+
server = OpenStruct.new(:id => "the id")
|
84
|
+
Boucher.should_receive(:change_servers_state).with([server], :stop, "stopped")
|
85
|
+
Boucher::Servers.stop([server])
|
78
86
|
end
|
79
87
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
require 'boucher/volumes'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
describe "Boucher::Volumes" do
|
6
|
+
|
7
|
+
context "with mocked volumes" do
|
8
|
+
let(:remote_volumes) {
|
9
|
+
[OpenStruct.new(:id => "v1", :tags => {"Name" => "1", "Meal" => "foo"}, :size => 8),
|
10
|
+
OpenStruct.new(:id => "v2", :tags => {"Name" => "2", "Meal" => "bar"}, :size => 16),
|
11
|
+
OpenStruct.new(:id => "v3", :tags => {"Name" => "3", "Meal" => "foo"}, :size => 32)]
|
12
|
+
}
|
13
|
+
|
14
|
+
before do
|
15
|
+
Boucher.compute.stub(:volumes).and_return(remote_volumes)
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
Boucher::Config[:env] = "test"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "finds all volumes" do
|
23
|
+
Boucher::Volumes.all.size.should == 3
|
24
|
+
Boucher::Volumes.all.should == remote_volumes
|
25
|
+
end
|
26
|
+
|
27
|
+
it "finds volumes by id" do
|
28
|
+
Boucher::Volumes.with_id("v1").should == remote_volumes[0]
|
29
|
+
Boucher::Volumes.with_id("v2").should == remote_volumes[1]
|
30
|
+
Boucher::Volumes.with_id("v3").should == remote_volumes[2]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "creates a volume" do
|
35
|
+
volume = Boucher::Volumes.create(:size => 12, :availability_zone => "us-east-1c")
|
36
|
+
|
37
|
+
volume.availability_zone.should == "us-east-1c"
|
38
|
+
volume.size.should == 12
|
39
|
+
end
|
40
|
+
|
41
|
+
it "creates a volume with snapshot" do
|
42
|
+
volume = Boucher::Volumes.create(:size => 12, :availability_zone => "us-east-1c")
|
43
|
+
response = volume.snapshot("test")
|
44
|
+
|
45
|
+
new_volume = Boucher::Volumes.create(:snapshot_id => response.body["snapshotId"], :availability_zone => "us-east-1c")
|
46
|
+
|
47
|
+
new_volume.size.should == 12
|
48
|
+
new_volume.availability_zone.should == "us-east-1c"
|
49
|
+
end
|
50
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -19,18 +19,19 @@ Boucher::IO.mock!
|
|
19
19
|
|
20
20
|
|
21
21
|
# MDM - Monkey patch wait_for methods so the tests are FASTER!
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
require 'fog/core/model'
|
22
|
+
# Unfortunately, Fog mocks depends on real time delays :.-(
|
23
|
+
#module Fog
|
24
|
+
# def self.wait_for(timeout=Fog.timeout, interval=1)
|
25
|
+
# yield
|
26
|
+
# end
|
27
|
+
#end
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
29
|
+
#require 'fog/core/model'
|
30
|
+
#
|
31
|
+
#module Fog
|
32
|
+
# class Model
|
33
|
+
# def wait_for(timeout=Fog.timeout, interval=1, &block)
|
34
|
+
# yield
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boucher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09
|
12
|
+
date: 2012-10-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -90,7 +90,9 @@ files:
|
|
90
90
|
- LICENSE
|
91
91
|
- README.md
|
92
92
|
- Rakefile
|
93
|
+
- TODO.md
|
93
94
|
- boucher.gemspec
|
95
|
+
- lib/boucher/addresses.rb
|
94
96
|
- lib/boucher/compute.rb
|
95
97
|
- lib/boucher/env.rb
|
96
98
|
- lib/boucher/io.rb
|
@@ -99,12 +101,14 @@ files:
|
|
99
101
|
- lib/boucher/servers.rb
|
100
102
|
- lib/boucher/storage.rb
|
101
103
|
- lib/boucher/tasks.rb
|
104
|
+
- lib/boucher/tasks/addresses.rake
|
102
105
|
- lib/boucher/tasks/console.rake
|
103
106
|
- lib/boucher/tasks/servers.rake
|
104
107
|
- lib/boucher/tasks/storage.rake
|
105
108
|
- lib/boucher/tasks/volumes.rake
|
106
109
|
- lib/boucher/util.rb
|
107
110
|
- lib/boucher/volumes.rb
|
111
|
+
- spec/boucher/addresses_spec.rb
|
108
112
|
- spec/boucher/compute_spec.rb
|
109
113
|
- spec/boucher/env_spec.rb
|
110
114
|
- spec/boucher/io_spec.rb
|
@@ -112,6 +116,8 @@ files:
|
|
112
116
|
- spec/boucher/provision_spec.rb
|
113
117
|
- spec/boucher/servers_spec.rb
|
114
118
|
- spec/boucher/storage_spec.rb
|
119
|
+
- spec/boucher/util_spec.rb
|
120
|
+
- spec/boucher/volumes_spec.rb
|
115
121
|
- spec/spec_helper.rb
|
116
122
|
homepage: http://github.com/8thlight/boucher
|
117
123
|
licenses: []
|
@@ -125,9 +131,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
131
|
- - ! '>='
|
126
132
|
- !ruby/object:Gem::Version
|
127
133
|
version: '0'
|
128
|
-
segments:
|
129
|
-
- 0
|
130
|
-
hash: -3841890267981667096
|
131
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
135
|
none: false
|
133
136
|
requirements:
|
@@ -141,6 +144,7 @@ signing_key:
|
|
141
144
|
specification_version: 3
|
142
145
|
summary: AWS system deployment and management
|
143
146
|
test_files:
|
147
|
+
- spec/boucher/addresses_spec.rb
|
144
148
|
- spec/boucher/compute_spec.rb
|
145
149
|
- spec/boucher/env_spec.rb
|
146
150
|
- spec/boucher/io_spec.rb
|
@@ -148,4 +152,6 @@ test_files:
|
|
148
152
|
- spec/boucher/provision_spec.rb
|
149
153
|
- spec/boucher/servers_spec.rb
|
150
154
|
- spec/boucher/storage_spec.rb
|
155
|
+
- spec/boucher/util_spec.rb
|
156
|
+
- spec/boucher/volumes_spec.rb
|
151
157
|
- spec/spec_helper.rb
|