knife-stackbuilder 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,251 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ include StackBuilder::Common::Helpers
4
+
5
+ module StackBuilder::Chef
6
+
7
+ class NodeManager < StackBuilder::Stack::NodeManager
8
+
9
+ include ERB::Util
10
+
11
+ attr_accessor :name
12
+
13
+ attr_accessor :run_list
14
+ attr_accessor :run_on_event
15
+
16
+ def initialize(id, node_config, repo_path, environment)
17
+
18
+ @logger = StackBuilder::Common::Config.logger
19
+
20
+ @id = id
21
+ @name = node_config['node']
22
+ @node_id = @name + '-' + @id
23
+
24
+ @run_list = node_config.has_key?('run_list') ? node_config['run_list'].join(',') : nil
25
+ @run_on_event = node_config['run_on_event']
26
+
27
+ @knife_config = node_config['knife']
28
+ if @knife_config
29
+
30
+ raise ArgumentError, 'An ssh user needs to be provided for bootstrap and knife ssh.' \
31
+ unless @knife_config['options'].has_key?('ssh_user')
32
+
33
+ raise ArgumentError, 'An ssh key file or password must be provided for knife to be able ssh to a node.' \
34
+ unless @knife_config['options'].has_key?('identity_file') ||
35
+ @knife_config['options'].has_key?('ssh_password')
36
+
37
+ @ssh_user = @knife_config['options']['ssh_user']
38
+ @ssh_password = @knife_config['options']['ssh_password']
39
+ @identity_file = @knife_config['options']['identity_file']
40
+
41
+ @env_key_file = "#{repo_path}/secrets/#{environment}"
42
+ @env_key_file = nil unless File.exist?(@env_key_file)
43
+ end
44
+ end
45
+
46
+ def get_name
47
+ @name
48
+ end
49
+
50
+ def get_scale
51
+ get_stack_node_resources
52
+ end
53
+
54
+ def node_attributes
55
+ get_stack_node_resources
56
+ @nodes.collect { |n| n.attributes }
57
+ end
58
+
59
+ def create(index)
60
+
61
+ name = "#{@node_id}-#{index}"
62
+ self.create_vm(name, @knife_config)
63
+
64
+ knife_cmd = KnifeAttribute::Node::NodeAttributeSet.new
65
+ knife_cmd.name_args = [ name, 'stack_id', @id ]
66
+ knife_cmd.config[:type] = 'override'
67
+ run_knife(knife_cmd)
68
+
69
+ knife_cmd = KnifeAttribute::Node::NodeAttributeSet.new
70
+ knife_cmd.name_args = [ name, 'stack_node', @name ]
71
+ knife_cmd.config[:type] = 'override'
72
+ run_knife(knife_cmd)
73
+
74
+ unless @env_key_file.nil?
75
+ env_key = IO.read(@env_key_file)
76
+ knife_ssh(name, "echo '#{env_key}' > /etc/chef/encrypted_data_bag_secret")
77
+ end
78
+
79
+ rescue Exception => msg
80
+ puts("\nError creating node #{name} using knife config: #{msg}\n#{@knife_config.to_yaml}\n")
81
+ @logger.info(msg.backtrace.join("\n\t")) if @logger.debug
82
+
83
+ raise msg
84
+ end
85
+
86
+ def create_vm(name, knife_config)
87
+ raise NotImplemented, 'HostNodeManager.create_vm'
88
+ end
89
+
90
+ def process(index, events, attributes, target = nil)
91
+
92
+ if target.nil?
93
+ self.process_vm(index, events, attributes)
94
+ else
95
+ target.process_vm(index, events, attributes, self)
96
+ end
97
+ end
98
+
99
+ def process_vm(index, events, attributes, node_manager = nil)
100
+
101
+ name = "#{@node_id}-#{index}"
102
+
103
+ if node_manager.nil?
104
+ run_list = @run_list
105
+ run_on_event = @run_on_event
106
+ else
107
+ run_list = node_manager.run_list
108
+ run_on_event = node_manager.run_on_event
109
+ end
110
+
111
+ set_attributes(name, attributes)
112
+
113
+ if (events.include?('configure') || events.include?('update')) && !run_list.nil?
114
+
115
+ log_level = (
116
+ @logger.debug? ? 'debug' :
117
+ @logger.info? ? 'info' :
118
+ @logger.warn? ? 'warn' :
119
+ @logger.error? ? 'error' :
120
+ @logger.fatal? ? 'fatal' : 'error' )
121
+
122
+ knife_cmd = Chef::Knife::NodeRunListSet.new
123
+ knife_cmd.name_args = [ name, run_list ]
124
+ run_knife(knife_cmd)
125
+
126
+ knife_ssh( name,
127
+ "TMPFILE=`mktemp`\n" +
128
+ "echo '#{attributes.to_json}' > $TMPFILE\n" +
129
+ "chef-client -l #{log_level} -j $TMPFILE\n" +
130
+ "result=$?\n" +
131
+ "rm -f $TMPFILE\n" +
132
+ "exit $result" )
133
+ end
134
+
135
+ run_on_event.each_pair { |event, cmd|
136
+ knife_ssh(name, ERB.new(cmd, nil, '-<>').result(binding)) if events.include?(event) } \
137
+ unless run_on_event.nil?
138
+
139
+ rescue Exception => msg
140
+
141
+ puts( "\nError processing node #{name}: #{msg} " +
142
+ "\nEvents => #{events.collect { |e| e } .join(", ")}" +
143
+ "\nknife config =>\n#{@knife_config.to_yaml}" +
144
+ "\nrun list =>\n#{run_list}" +
145
+ "\nevent scripts =>\n#{run_on_event.to_yaml}\n" )
146
+
147
+ @logger.info(msg.backtrace.join("\n\t")) if @logger.debug
148
+
149
+ raise msg
150
+ end
151
+
152
+ def delete(index)
153
+
154
+ name = "#{@node_id}-#{index}"
155
+ self.delete_vm(name, @knife_config)
156
+
157
+ knife_cmd = Chef::Knife::NodeDelete.new
158
+ knife_cmd.name_args = [ name ]
159
+ knife_cmd.config[:yes] = true
160
+ run_knife(knife_cmd)
161
+
162
+ knife_cmd = Chef::Knife::ClientDelete.new
163
+ knife_cmd.name_args = [ name ]
164
+ knife_cmd.config[:yes] = true
165
+ run_knife(knife_cmd)
166
+
167
+ rescue Exception => msg
168
+ puts("\nError deleting node #{name} using knife config: #{msg}\n#{@knife_config.to_yaml}\n")
169
+ @logger.info(msg.backtrace.join("\n\t")) if @logger.debug
170
+
171
+ raise msg
172
+ end
173
+
174
+ def delete_vm(name, knife_config)
175
+ raise NotImplemented, 'HostNodeManager.delete_vm'
176
+ end
177
+
178
+ def config_knife(knife_cmd, options)
179
+
180
+ options.each_pair do |k, v|
181
+
182
+ arg = k.gsub(/-/, '_')
183
+
184
+ # Fix issue where '~/' is not expanded to home dir
185
+ v.gsub!(/~\//, Dir.home + '/') if arg.end_with?('_dir') && v.start_with?('~/')
186
+
187
+ knife_cmd.config[arg.to_sym] = v
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ def get_stack_node_resources
194
+
195
+ query = Chef::Search::Query.new
196
+
197
+ escaped_query = URI.escape(
198
+ "stack_id:#{@id} AND stack_node:#{@name}",
199
+ Regexp.new("[^#{URI::PATTERN::UNRESERVED}]") )
200
+
201
+ results = query.search('node', escaped_query, nil, 0, 999999)
202
+ @nodes = results[0]
203
+
204
+ results[2]
205
+ end
206
+
207
+ def set_attributes(name, attributes, key = nil)
208
+
209
+ attributes.each do |k, v|
210
+
211
+ if v.is_a?(Hash)
212
+ set_attributes(name, v, key.nil? ? k : key + '.' + k)
213
+ else
214
+ knife_cmd = KnifeAttribute::Node::NodeAttributeSet.new
215
+ knife_cmd.name_args = [ name, (key.nil? ? k : key + '.' + k), v.to_s ]
216
+ knife_cmd.config[:type] = 'override'
217
+ run_knife(knife_cmd)
218
+ end
219
+ end
220
+ end
221
+
222
+ def knife_ssh(name, cmd)
223
+
224
+ sudo = @knife_config['options']['sudo'] ? 'sudo -i su -c ' : ''
225
+
226
+ ssh_cmd = "TMPFILE=`mktemp` && " +
227
+ "echo -e \"#{cmd.gsub(/\"/, "\\\"").gsub(/\$/, "\\$").gsub(/\`/, '\\' + '\`')}\" > $TMPFILE && " +
228
+ "chmod 0744 $TMPFILE && " +
229
+ "#{sudo}$TMPFILE && " +
230
+ "rm $TMPFILE"
231
+
232
+ knife_cmd = Chef::Knife::Ssh.new
233
+ knife_cmd.name_args = [ "name:#{name}", ssh_cmd ]
234
+ knife_cmd.config[:attribute] = 'ipaddress'
235
+
236
+ config_knife(knife_cmd, @knife_config['options'] || { })
237
+
238
+ if @logger.info? || @logger.debug?
239
+
240
+ output = StackBuilder::Common::TeeIO.new($stdout)
241
+ error = StackBuilder::Common::TeeIO.new($stderr)
242
+
243
+ @logger.info("Running '#{cmd}' on node 'name:#{name}'.")
244
+ run_knife(knife_cmd, 0, output, error)
245
+ else
246
+ run_knife(knife_cmd)
247
+ end
248
+ end
249
+
250
+ end
251
+ end
@@ -0,0 +1,77 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ include StackBuilder::Common::Helpers
4
+
5
+ module StackBuilder::Chef
6
+
7
+ class NodeProvider < StackBuilder::Stack::NodeProvider
8
+
9
+ def initialize(repo_path, environment)
10
+
11
+ @logger = StackBuilder::Common::Config.logger
12
+
13
+ @repo_path = File.expand_path(repo_path)
14
+ @environment = environment
15
+
16
+ env_file = "#{@repo_path}/etc/#{@environment}.yml"
17
+ if File.exist?(env_file)
18
+
19
+ @logger.debug( "Loading externalized environment variables from " +
20
+ "'#{env_file}' and merging them with the current process environment." )
21
+
22
+ @env_vars = StackBuilder::Common.load_yaml(env_file, ENV)
23
+ else
24
+ @logger.warn( "No environment variable file '#{env_file}' " +
25
+ "found. Continuing with empty environemnt variables")
26
+
27
+ @env_vars = { }
28
+ end
29
+ end
30
+
31
+ def set_stack(stack, id)
32
+
33
+ @stack = stack
34
+ @id = id
35
+
36
+ stack_environment = stack['environment']
37
+ raise ArgmentError, "Stack file is fixed to the environment '#{stack_environment}', " +
38
+ " which it does not match the environment '#{@environment}' provided." \
39
+ unless stack_environment.nil? || stack_environment==@environment
40
+
41
+ Chef::Config[:chef_server_url] = stack['chef_server_url'] if stack.has_key?('chef_server_url')
42
+ Chef::Config[:environment] = @environment
43
+ end
44
+
45
+ def get_env_vars
46
+ @env_vars
47
+ end
48
+
49
+ def get_node_manager(node_config)
50
+
51
+ knife_config = node_config['knife']
52
+ if knife_config.nil?
53
+ return StackBuilder::Chef::NodeManager.new(@id, node_config, @repo_path, @environment)
54
+
55
+ else
56
+ if knife_config.has_key?('plugin')
57
+
58
+ case knife_config['plugin']
59
+ when 'vagrant'
60
+ return StackBuilder::Chef::VagrantNodeManager.new(@id, node_config, @repo_path, @environment)
61
+
62
+ # TODO: Refactor so that managers are pluggable from other gems
63
+
64
+ else
65
+ raise ArgumentError, "Unknown plugin #{knife['plugin']}."
66
+ end
67
+
68
+ elsif knife_config.has_key?('create')
69
+ return StackBuilder::Chef::GenericNodeManager.new(@id, node_config, @repo_path, @environment)
70
+
71
+ else
72
+ return StackBuilder::Chef::NodeManager.new(@id, node_config, @repo_path, @environment)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ include StackBuilder::Common::Helpers
4
+
5
+ module StackBuilder::Chef
6
+
7
+ class VagrantNodeManager < StackBuilder::Chef::NodeManager
8
+
9
+ def create_vm(name, knife_config)
10
+
11
+ knife_cmd = Chef::Knife::VagrantServerCreate.new
12
+
13
+ knife_cmd.config[:chef_node_name] = name
14
+
15
+ # Set the defaults
16
+ knife_cmd.config[:distro] = 'chef-full'
17
+ knife_cmd.config[:template_file] = false
18
+
19
+ knife_cmd.config[:vagrant_dir] = File.join(Dir.home, '/.vagrant')
20
+ knife_cmd.config[:provider] = 'virtualbox'
21
+ knife_cmd.config[:memsize] = 1024
22
+ knife_cmd.config[:subnet] = '192.168.67.0/24'
23
+ knife_cmd.config[:port_forward] = { }
24
+ knife_cmd.config[:share_folders] = [ ]
25
+ knife_cmd.config[:use_cachier] = false
26
+
27
+ knife_cmd.config[:host_key_verify] = false
28
+ knife_cmd.config[:ssh_user] = 'vagrant'
29
+ knife_cmd.config[:ssh_port] = '22'
30
+
31
+ config_knife(knife_cmd, knife_config['options'] || { })
32
+
33
+ ip_address = knife_cmd.config[:ip_address]
34
+ knife_cmd.config[:ip_address] = ip_address[/(\d+\.\d+\.\d+\.)/, 1] + \
35
+ (ip_address[/\.(\d+)\+/, 1].to_i + name[/-(\d+)$/, 1].to_i).to_s \
36
+ unless ip_address.nil? || !ip_address.end_with?('+')
37
+
38
+ @@sync ||= Mutex.new
39
+ @@sync.synchronize {
40
+ run_knife(knife_cmd)
41
+ }
42
+ end
43
+
44
+ def delete_vm(name, knife_config)
45
+
46
+ knife_cmd = Chef::Knife::VagrantServerDelete.new
47
+ knife_cmd.name_args = [ name ]
48
+ knife_cmd.config[:yes] = true
49
+ knife_cmd.config[:vagrant_dir] = File.join(Dir.home, '/.vagrant')
50
+
51
+ @@sync ||= Mutex.new
52
+ @@sync.synchronize {
53
+ run_knife(knife_cmd, 3)
54
+ }
55
+
56
+ rescue Exception => msg
57
+
58
+ if Dir.exist?(knife_cmd.config[:vagrant_dir] + '/' + name)
59
+
60
+ knife_cmd = Chef::Knife::VagrantServerList.new
61
+ knife_cmd.config[:vagrant_dir] = File.join(Dir.home, '/.vagrant')
62
+ server_list = run_knife(knife_cmd)
63
+
64
+ if server_list.lines.keep_if { |l| l=~/test-TEST-0/ }.first.chomp.end_with?('running')
65
+ raise msg
66
+ else
67
+ FileUtils.rm_rf(knife_cmd.config[:vagrant_dir] + '/' + name)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ module StackBuilder::Common
4
+
5
+ class Config
6
+
7
+ CACHE_TIMEOUT = 60
8
+
9
+ class << self
10
+ extend Forwardable
11
+ def_delegators :@delegate, :logger, :timeouts, :enable_caching, :cachedir, :silent
12
+ end
13
+
14
+ def self.configure(config)
15
+
16
+ config.silent = false
17
+
18
+ # Determine timeouts
19
+ config.timeouts = { } if config.timeouts.nil?
20
+ config.timeouts[:CACHE_TIMEOUT] = CACHE_TIMEOUT unless config.timeouts.has_key?(:CACHE_TIMEOUT)
21
+
22
+ # Create cache folder
23
+ config.enable_caching = false if config.enable_caching.nil?
24
+ if config.enable_caching
25
+ config.cachedir = File.expand_path(File.join(Dir.home, ".c2c_cache")) if config.cachedir.nil?
26
+ begin
27
+ FileUtils.mkdir_p(config.cachedir) if !Dir.exists?(config.cachedir)
28
+ rescue
29
+ self.logger.debug("could not create cachedir: #{config.cachedir}")
30
+ config.enable_caching = false
31
+ config.cachdir = nil
32
+ end
33
+ end
34
+
35
+ @delegate = config
36
+
37
+ self.logger.debug("Caching Enabled: #{config.enable_caching}; Cache Dir: #{self.cachedir}; Timeouts: #{self.timeouts}")
38
+ end
39
+
40
+ def self.set_silent
41
+ @delegate.silent = true
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2014 Mevan Samaratunga
2
+
3
+ module StackBuilder::Common
4
+
5
+ class StackBuilderError < StandardError; end
6
+ class InvalidArgs < StackBuilderError; end
7
+ class NotImplemented < StackBuilderError; end
8
+ class NotSupported < StackBuilderError; end
9
+
10
+ class StackCreateError < StackBuilderError; end
11
+ class StackOrchestrateError < StackBuilderError; end
12
+ class StackDeleteError < StackBuilderError; end
13
+
14
+ class CertificateError < StackBuilderError; end
15
+
16
+ class KnifeError < StackBuilderError; end
17
+ class BerksError < StackBuilderError; end
18
+ end