chef-workflow 0.1.1 → 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/CHANGELOG.md +19 -0
- data/LICENSE.txt +2 -2
- data/README.md +31 -142
- data/bin/chef-workflow-bootstrap +6 -0
- data/chef-workflow.gemspec +4 -1
- data/lib/chef-workflow.rb +41 -39
- data/lib/chef-workflow/support/attr.rb +28 -26
- data/lib/chef-workflow/support/db.rb +26 -0
- data/lib/chef-workflow/support/db/basic.rb +225 -0
- data/lib/chef-workflow/support/db/group.rb +72 -0
- data/lib/chef-workflow/support/debug.rb +47 -45
- data/lib/chef-workflow/support/ec2.rb +136 -134
- data/lib/chef-workflow/support/general.rb +46 -54
- data/lib/chef-workflow/support/generic.rb +27 -23
- data/lib/chef-workflow/support/ip.rb +89 -103
- data/lib/chef-workflow/support/knife-plugin.rb +26 -24
- data/lib/chef-workflow/support/knife.rb +76 -102
- data/lib/chef-workflow/support/scheduler.rb +319 -324
- data/lib/chef-workflow/support/ssh.rb +100 -0
- data/lib/chef-workflow/support/vagrant.rb +34 -30
- data/lib/chef-workflow/support/vm.rb +25 -54
- data/lib/chef-workflow/support/vm/chef_server.rb +28 -19
- data/lib/chef-workflow/support/vm/ec2.rb +135 -106
- data/lib/chef-workflow/support/vm/helpers/knife.rb +26 -0
- data/lib/chef-workflow/support/vm/knife.rb +218 -189
- data/lib/chef-workflow/support/vm/vagrant.rb +90 -74
- data/lib/chef-workflow/version.rb +3 -5
- metadata +57 -4
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'chef-workflow/support/vm/knife'
|
2
|
+
|
3
|
+
module ChefWorkflow
|
4
|
+
module KnifeProvisionHelper
|
5
|
+
def build_knife_provisioner
|
6
|
+
kp = ChefWorkflow::VM::KnifeProvisioner.new
|
7
|
+
kp.username = ChefWorkflow::KnifeSupport.ssh_user
|
8
|
+
kp.password = ChefWorkflow::KnifeSupport.ssh_password
|
9
|
+
kp.use_sudo = ChefWorkflow::KnifeSupport.use_sudo
|
10
|
+
kp.ssh_key = ChefWorkflow::KnifeSupport.ssh_identity_file
|
11
|
+
kp.environment = ChefWorkflow::KnifeSupport.test_environment
|
12
|
+
|
13
|
+
return kp
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if defined? Rake::DSL
|
19
|
+
module Rake::DSL
|
20
|
+
include ChefWorkflow::KnifeProvisionHelper
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << eval('self', TOPLEVEL_BINDING)
|
25
|
+
include ChefWorkflow::KnifeProvisionHelper
|
26
|
+
end
|
@@ -1,217 +1,246 @@
|
|
1
1
|
require 'chef-workflow/support/debug'
|
2
|
-
require 'chef-workflow/support/knife'
|
3
2
|
require 'chef-workflow/support/knife-plugin'
|
4
|
-
require 'chef/node'
|
5
|
-
require 'chef/search/query'
|
6
|
-
require 'chef/knife/bootstrap'
|
7
|
-
require 'chef/knife/client_delete'
|
8
|
-
require 'chef/knife/node_delete'
|
9
|
-
require 'timeout'
|
10
|
-
|
11
|
-
class VM
|
12
|
-
#
|
13
|
-
# The Knife Provisioner does three major things:
|
14
|
-
#
|
15
|
-
# * Bootstraps a series of machines living on IP addresses supplied to it
|
16
|
-
# * Ensures that they converged successfully (if not, raises and displays output)
|
17
|
-
# * Waits until chef has indexed their metadata
|
18
|
-
#
|
19
|
-
# On deprovision, it deletes the nodes and clients related to this server group.
|
20
|
-
#
|
21
|
-
# Machines are named as such: $server_group-$number, where $number starts at 0
|
22
|
-
# and increases with the number of servers requested. Your node names will be
|
23
|
-
# named this as well as the clients associated with them.
|
24
|
-
#
|
25
|
-
# It does as much of this as it can in parallel, but stalls the current thread
|
26
|
-
# until the subthreads complete. This allows is to work as quickly as possible
|
27
|
-
# in a 'serial' scheduling scenario as we know bootstrapping can always occur
|
28
|
-
# in parallel for the group.
|
29
|
-
#
|
30
|
-
class KnifeProvisioner
|
31
|
-
|
32
|
-
include DebugSupport
|
33
|
-
include KnifePluginSupport
|
34
|
-
|
35
|
-
# the username for SSH.
|
36
|
-
attr_accessor :username
|
37
|
-
# the password for SSH.
|
38
|
-
attr_accessor :password
|
39
|
-
# drive knife bootstrap's sudo functionality.
|
40
|
-
attr_accessor :use_sudo
|
41
|
-
# the ssh key to be used for SSH
|
42
|
-
attr_accessor :ssh_key
|
43
|
-
# the bootstrap template to be used.
|
44
|
-
attr_accessor :template_file
|
45
|
-
# the chef environment to be used.
|
46
|
-
attr_accessor :environment
|
47
|
-
# the port to contact for SSH
|
48
|
-
attr_accessor :port
|
49
|
-
# the list of IPs to provision.
|
50
|
-
attr_accessor :ips
|
51
|
-
# the run list of this server group.
|
52
|
-
attr_accessor :run_list
|
53
|
-
# the name of this server group.
|
54
|
-
attr_accessor :name
|
55
|
-
# perform the solr check to ensure the instance has converged and its
|
56
|
-
# metadata is ready for searching.
|
57
|
-
attr_accessor :solr_check
|
58
|
-
|
59
|
-
# constructor.
|
60
|
-
def initialize
|
61
|
-
@ips = []
|
62
|
-
@username = nil
|
63
|
-
@password = nil
|
64
|
-
@ssh_key = nil
|
65
|
-
@port = nil
|
66
|
-
@use_sudo = nil
|
67
|
-
@run_list = nil
|
68
|
-
@template_file = nil
|
69
|
-
@environment = nil
|
70
|
-
@node_names = []
|
71
|
-
@solr_check = true
|
72
|
-
end
|
73
3
|
|
4
|
+
module ChefWorkflow
|
5
|
+
class VM
|
6
|
+
#
|
7
|
+
# The Knife Provisioner does three major things:
|
8
|
+
#
|
9
|
+
# * Bootstraps a series of machines living on IP addresses supplied to it
|
10
|
+
# * Ensures that they converged successfully (if not, raises and displays output)
|
11
|
+
# * Waits until chef has indexed their metadata
|
74
12
|
#
|
75
|
-
#
|
76
|
-
# argument, intended to be provided by provisioners that ran before it as
|
77
|
-
# their return value.
|
13
|
+
# On deprovision, it deletes the nodes and clients related to this server group.
|
78
14
|
#
|
79
|
-
#
|
80
|
-
#
|
15
|
+
# Machines are named as such: $server_group-$number, where $number starts at 0
|
16
|
+
# and increases with the number of servers requested. Your node names will be
|
17
|
+
# named this as well as the clients associated with them.
|
81
18
|
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
19
|
+
# It does as much of this as it can in parallel, but stalls the current thread
|
20
|
+
# until the subthreads complete. This allows is to work as quickly as possible
|
21
|
+
# in a 'serial' scheduling scenario as we know bootstrapping can always occur
|
22
|
+
# in parallel for the group.
|
23
|
+
#
|
24
|
+
class KnifeProvisioner
|
25
|
+
|
26
|
+
include ChefWorkflow::DebugSupport
|
27
|
+
include ChefWorkflow::KnifePluginSupport
|
28
|
+
|
29
|
+
# the username for SSH.
|
30
|
+
attr_accessor :username
|
31
|
+
# the password for SSH.
|
32
|
+
attr_accessor :password
|
33
|
+
# drive knife bootstrap's sudo functionality.
|
34
|
+
attr_accessor :use_sudo
|
35
|
+
# the ssh key to be used for SSH
|
36
|
+
attr_accessor :ssh_key
|
37
|
+
# the bootstrap template to be used.
|
38
|
+
attr_accessor :template_file
|
39
|
+
# the chef environment to be used.
|
40
|
+
attr_accessor :environment
|
41
|
+
# the port to contact for SSH
|
42
|
+
attr_accessor :port
|
43
|
+
# the list of IPs to provision.
|
44
|
+
attr_accessor :ips
|
45
|
+
# the run list of this server group.
|
46
|
+
attr_accessor :run_list
|
47
|
+
# the name of this server group.
|
48
|
+
attr_accessor :name
|
49
|
+
# perform the solr check to ensure the instance has converged and its
|
50
|
+
# metadata is ready for searching.
|
51
|
+
attr_accessor :solr_check
|
52
|
+
|
53
|
+
# constructor.
|
54
|
+
def initialize
|
55
|
+
require 'chef/node'
|
56
|
+
require 'chef/search/query'
|
57
|
+
require 'chef/knife/ssh'
|
58
|
+
require 'chef/knife/bootstrap'
|
59
|
+
require 'chef-workflow/support/knife'
|
60
|
+
require 'timeout'
|
61
|
+
|
62
|
+
@ips = []
|
63
|
+
@username = nil
|
64
|
+
@password = nil
|
65
|
+
@ssh_key = nil
|
66
|
+
@port = nil
|
67
|
+
@use_sudo = nil
|
68
|
+
@run_list = nil
|
69
|
+
@template_file = nil
|
70
|
+
@environment = nil
|
71
|
+
@solr_check = true
|
94
72
|
end
|
95
73
|
|
96
|
-
|
74
|
+
def init_nodes_db
|
75
|
+
@node_names = ChefWorkflow::DatabaseSupport::Set.new('nodes', name)
|
76
|
+
end
|
97
77
|
|
98
|
-
|
99
|
-
|
78
|
+
#
|
79
|
+
# Runs the provisioner. Accepts an array of IP addresses as its first
|
80
|
+
# argument, intended to be provided by provisioners that ran before it as
|
81
|
+
# their return value.
|
82
|
+
#
|
83
|
+
# Will raise if the IPs are not supplied or the provisioner is not named with
|
84
|
+
# a server group.
|
85
|
+
#
|
86
|
+
def startup(*args)
|
87
|
+
@ips = args.first #argh
|
88
|
+
raise "This provisioner is unnamed, cannot continue" unless name
|
89
|
+
raise "This provisioner requires ip addresses which were not supplied" unless ips
|
90
|
+
|
91
|
+
init_nodes_db
|
92
|
+
|
93
|
+
@run_list ||= ["role[#{name}]"]
|
94
|
+
|
95
|
+
t = []
|
96
|
+
ips.each_with_index do |ip, index|
|
97
|
+
node_name = "#{name}-#{index}"
|
98
|
+
@node_names.add(node_name)
|
99
|
+
t.push bootstrap(node_name, ip)
|
100
|
+
end
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
def shutdown
|
106
|
-
t = []
|
107
|
-
|
108
|
-
@node_names.each do |node_name|
|
109
|
-
t.push(
|
110
|
-
Thread.new do
|
111
|
-
client_delete(node_name)
|
112
|
-
node_delete(node_name)
|
113
|
-
end
|
114
|
-
)
|
102
|
+
t.each(&:join)
|
103
|
+
|
104
|
+
return solr_check ? check_nodes : true
|
115
105
|
end
|
116
106
|
|
117
|
-
|
107
|
+
#
|
108
|
+
# Deprovisions the server group. Runs node delete and client delete on all
|
109
|
+
# nodes that were created by this provisioner.
|
110
|
+
#
|
111
|
+
def shutdown
|
112
|
+
t = []
|
113
|
+
|
114
|
+
init_nodes_db
|
115
|
+
|
116
|
+
@node_names.each do |node_name|
|
117
|
+
t.push(
|
118
|
+
Thread.new do
|
119
|
+
client_delete(node_name) rescue nil
|
120
|
+
node_delete(node_name) rescue nil
|
121
|
+
end
|
122
|
+
)
|
123
|
+
end
|
118
124
|
|
119
|
-
|
120
|
-
end
|
125
|
+
t.each(&:join)
|
121
126
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
127
|
+
@node_names.clear
|
128
|
+
|
129
|
+
return true
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Checks that the nodes have made it into the search index. Will block until
|
134
|
+
# all nodes in this server group are found, or a 60 second timeout is
|
135
|
+
# reached, at which point it will raise.
|
136
|
+
#
|
137
|
+
def check_nodes
|
138
|
+
q = Chef::Search::Query.new
|
139
|
+
unchecked_node_names = @node_names.to_a
|
140
|
+
|
141
|
+
# this dirty hack turns 'role[foo]' into 'roles:foo', but also works on
|
142
|
+
# recipe[] too. Then joins the whole thing with AND
|
143
|
+
search_query = run_list.
|
144
|
+
map { |s| s.gsub(/\[/, 's:"').gsub(/\]/, '"') }.
|
145
|
+
join(" AND ")
|
146
|
+
|
147
|
+
Timeout.timeout(ChefWorkflow::KnifeSupport.search_index_wait) do
|
148
|
+
until unchecked_node_names.empty?
|
149
|
+
node_name = unchecked_node_names.shift
|
150
|
+
if_debug(3) do
|
151
|
+
$stderr.puts "Checking search validity for node #{node_name}"
|
152
|
+
end
|
153
|
+
|
154
|
+
result = q.search(
|
155
|
+
:node,
|
156
|
+
search_query + %Q[ AND name:"#{node_name}"]
|
157
|
+
).first
|
158
|
+
|
159
|
+
unless result and result.count == 1 and result.first.name == node_name
|
160
|
+
unchecked_node_names << node_name
|
161
|
+
end
|
162
|
+
|
163
|
+
# unfortunately if this isn't here you might as well issue kill -9 to
|
164
|
+
# the rake process
|
165
|
+
sleep 0.3
|
142
166
|
end
|
167
|
+
end
|
143
168
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
169
|
+
return true
|
170
|
+
rescue Timeout::Error
|
171
|
+
raise "Bootstrapped nodes for #{name} did not appear in Chef search index after 60 seconds."
|
172
|
+
end
|
148
173
|
|
149
|
-
|
150
|
-
|
174
|
+
#
|
175
|
+
# Bootstraps a single node. Validates bootstrap by checking the node metadata
|
176
|
+
# directly and ensuring it made it into the chef server.
|
177
|
+
#
|
178
|
+
def bootstrap(node_name, ip)
|
179
|
+
args = []
|
180
|
+
|
181
|
+
args += %W[-x #{username}] if username
|
182
|
+
args += %W[-P #{password}] if password
|
183
|
+
args += %w[--sudo] if use_sudo
|
184
|
+
args += %W[-i #{ssh_key}] if ssh_key
|
185
|
+
args += %W[--template-file #{template_file}] if template_file
|
186
|
+
args += %W[-p #{port}] if port
|
187
|
+
args += %W[-E #{environment}] if environment
|
188
|
+
|
189
|
+
args += %W[-r #{run_list.join(",")}]
|
190
|
+
args += %W[-N '#{node_name}']
|
191
|
+
args += [ip]
|
192
|
+
|
193
|
+
bootstrap_cli = init_knife_plugin(Chef::Knife::Bootstrap, args)
|
194
|
+
|
195
|
+
Thread.new do
|
196
|
+
begin
|
197
|
+
bootstrap_cli.run
|
198
|
+
rescue SystemExit => e
|
199
|
+
# welp, looks like they finally fixed it.
|
200
|
+
# can't rely on it for compat reasons, but at least we're not
|
201
|
+
# dropping to a prompt when a bootstrap fails.
|
202
|
+
end
|
203
|
+
# knife bootstrap is the honey badger when it comes to exit status.
|
204
|
+
# We can't rely on it, so we examine the run_list of the node instead
|
205
|
+
# to ensure it converged.
|
206
|
+
run_list_size = Chef::Node.load(node_name).run_list.to_a.size rescue 0
|
207
|
+
unless run_list_size > 0
|
208
|
+
puts bootstrap_cli.ui.stdout.string
|
209
|
+
puts bootstrap_cli.ui.stderr.string
|
210
|
+
raise "bootstrap for #{node_name}/#{ip} wasn't successful."
|
211
|
+
end
|
212
|
+
if_debug(2) do
|
213
|
+
puts bootstrap_cli.ui.stdout.string
|
214
|
+
puts bootstrap_cli.ui.stderr.string
|
151
215
|
end
|
152
|
-
|
153
|
-
# unfortunately if this isn't here you might as well issue kill -9 to
|
154
|
-
# the rake process
|
155
|
-
sleep 0.3
|
156
216
|
end
|
157
217
|
end
|
158
218
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
219
|
+
#
|
220
|
+
# Deletes a chef client.
|
221
|
+
#
|
222
|
+
def client_delete(node_name)
|
223
|
+
require 'chef/knife/client_delete'
|
224
|
+
init_knife_plugin(Chef::Knife::ClientDelete, [node_name, '-y']).run
|
225
|
+
end
|
163
226
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
args += %W[-x #{username}] if username
|
172
|
-
args += %W[-P #{password}] if password
|
173
|
-
args += %w[--sudo] if use_sudo
|
174
|
-
args += %W[-i #{ssh_key}] if ssh_key
|
175
|
-
args += %W[--template-file #{template_file}] if template_file
|
176
|
-
args += %W[-p #{port}] if port
|
177
|
-
args += %W[-E #{environment}] if environment
|
178
|
-
|
179
|
-
args += %W[-r #{run_list.join(",")}]
|
180
|
-
args += %W[-N '#{node_name}']
|
181
|
-
args += [ip]
|
182
|
-
|
183
|
-
bootstrap_cli = init_knife_plugin(Chef::Knife::Bootstrap, args)
|
184
|
-
|
185
|
-
Thread.new do
|
186
|
-
bootstrap_cli.run
|
187
|
-
# knife bootstrap is the honey badger when it comes to exit status.
|
188
|
-
# We can't rely on it, so we examine the run_list of the node instead
|
189
|
-
# to ensure it converged.
|
190
|
-
run_list_size = Chef::Node.load(node_name).run_list.to_a.size
|
191
|
-
unless run_list_size > 0
|
192
|
-
puts bootstrap_cli.ui.stdout.string
|
193
|
-
puts bootstrap_cli.ui.stderr.string
|
194
|
-
raise "bootstrap for #{node_name}/#{ip} wasn't successful."
|
195
|
-
end
|
196
|
-
if_debug(2) do
|
197
|
-
puts bootstrap_cli.ui.stdout.string
|
198
|
-
puts bootstrap_cli.ui.stderr.string
|
199
|
-
end
|
227
|
+
#
|
228
|
+
# Deletes a chef node.
|
229
|
+
#
|
230
|
+
def node_delete(node_name)
|
231
|
+
require 'chef/knife/node_delete'
|
232
|
+
init_knife_plugin(Chef::Knife::NodeDelete, [node_name, '-y']).run
|
200
233
|
end
|
201
|
-
end
|
202
234
|
|
203
|
-
|
204
|
-
|
205
|
-
#
|
206
|
-
def client_delete(node_name)
|
207
|
-
init_knife_plugin(Chef::Knife::ClientDelete, [node_name, '-y']).run
|
208
|
-
end
|
235
|
+
def report
|
236
|
+
res = ["nodes:"]
|
209
237
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
238
|
+
ChefWorkflow::IPSupport.get_role_ips(name).each_with_index do |ip, i|
|
239
|
+
res += ["#{name}-#{i}: #{ip}"]
|
240
|
+
end
|
241
|
+
|
242
|
+
return res
|
243
|
+
end
|
215
244
|
end
|
216
245
|
end
|
217
246
|
end
|