chef-workflow 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Runs the provisioner. Accepts an array of IP addresses as its first
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
- # Will raise if the IPs are not supplied or the provisioner is not named with
80
- # a server group.
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
- def startup(*args)
83
- @ips = args.first #argh
84
- raise "This provisioner is unnamed, cannot continue" unless name
85
- raise "This provisioner requires ip addresses which were not supplied" unless ips
86
-
87
- @run_list ||= ["role[#{name}]"]
88
-
89
- t = []
90
- ips.each_with_index do |ip, index|
91
- node_name = "#{name}-#{index}"
92
- @node_names.push(node_name)
93
- t.push bootstrap(node_name, ip)
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
- t.each(&:join)
74
+ def init_nodes_db
75
+ @node_names = ChefWorkflow::DatabaseSupport::Set.new('nodes', name)
76
+ end
97
77
 
98
- return solr_check ? check_nodes : true
99
- end
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
- # Deprovisions the server group. Runs node delete and client delete on all
103
- # nodes that were created by this provisioner.
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
- t.each(&:join)
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
- return true
120
- end
125
+ t.each(&:join)
121
126
 
122
- #
123
- # Checks that the nodes have made it into the search index. Will block until
124
- # all nodes in this server group are found, or a 60 second timeout is
125
- # reached, at which point it will raise.
126
- #
127
- def check_nodes
128
- q = Chef::Search::Query.new
129
- unchecked_node_names = @node_names.dup
130
-
131
- # this dirty hack turns 'role[foo]' into 'roles:foo', but also works on
132
- # recipe[] too. Then joins the whole thing with AND
133
- search_query = run_list.
134
- map { |s| s.gsub(/\[/, 's:"').gsub(/\]/, '"') }.
135
- join(" AND ")
136
-
137
- Timeout.timeout(KnifeSupport.singleton.search_index_wait) do
138
- until unchecked_node_names.empty?
139
- node_name = unchecked_node_names.shift
140
- if_debug(3) do
141
- $stderr.puts "Checking search validity for node #{node_name}"
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
- result = q.search(
145
- :node,
146
- search_query + %Q[ AND name:"#{node_name}"]
147
- ).first
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
- unless result and result.count == 1 and result.first.name == node_name
150
- unchecked_node_names << node_name
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
- return true
160
- rescue Timeout::Error
161
- raise "Bootstrapped nodes for #{name} did not appear in Chef search index after 60 seconds."
162
- end
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
- # Bootstraps a single node. Validates bootstrap by checking the node metadata
166
- # directly and ensuring it made it into the chef server.
167
- #
168
- def bootstrap(node_name, ip)
169
- args = []
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
- # Deletes a chef client.
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
- # Deletes a chef node.
212
- #
213
- def node_delete(node_name)
214
- init_knife_plugin(Chef::Knife::NodeDelete, [node_name, '-y']).run
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