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.
- checksums.yaml +7 -0
- data/README.md +115 -0
- data/Rakefile +42 -0
- data/bin/stackbuilder_debug +0 -0
- data/lib/chef/knife/stack_build.rb +114 -0
- data/lib/chef/knife/stack_delete.rb +50 -0
- data/lib/chef/knife/stack_initialize_repo.rb +62 -0
- data/lib/chef/knife/stack_upload_certificates.rb +38 -0
- data/lib/chef/knife/stack_upload_cookbooks.rb +49 -0
- data/lib/chef/knife/stack_upload_data_bags.rb +36 -0
- data/lib/chef/knife/stack_upload_environments.rb +31 -0
- data/lib/chef/knife/stack_upload_repo.rb +39 -0
- data/lib/chef/knife/stack_upload_roles.rb +33 -0
- data/lib/chef/knife/stackbuilder_base.rb +32 -0
- data/lib/stackbuilder/chef/repo.rb +442 -0
- data/lib/stackbuilder/chef/stack_generic_node.rb +67 -0
- data/lib/stackbuilder/chef/stack_node_manager.rb +251 -0
- data/lib/stackbuilder/chef/stack_provider.rb +77 -0
- data/lib/stackbuilder/chef/stack_vagrant_node.rb +72 -0
- data/lib/stackbuilder/common/config.rb +44 -0
- data/lib/stackbuilder/common/errors.rb +18 -0
- data/lib/stackbuilder/common/helpers.rb +379 -0
- data/lib/stackbuilder/common/semaphore.rb +54 -0
- data/lib/stackbuilder/common/teeio.rb +29 -0
- data/lib/stackbuilder/stack/node_manager.rb +38 -0
- data/lib/stackbuilder/stack/node_provider.rb +21 -0
- data/lib/stackbuilder/stack/node_task.rb +424 -0
- data/lib/stackbuilder/stack/stack.rb +224 -0
- data/lib/stackbuilder/version.rb +8 -0
- data/lib/stackbuilder.rb +53 -0
- metadata +100 -0
@@ -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
|