boucher 0.1.8 → 0.2.0
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/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
|