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.
@@ -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