knife-stackbuilder 0.5.2

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,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