lib/stack.rb in stack-kicker-0.0.18 vs lib/stack.rb in stack-kicker-0.0.19
- old
+ new
@@ -28,19 +28,21 @@
#
# This really needs to be converted into a class....
#
module Stack
+ include Methadone::SH
# Shadow the global constant Logger with Stack::Logger
# (if you want access to the global constant, use ::Logger from inside the Stack module)
Logger = Logger.new(STDOUT)
Logger.level = ::Logger::INFO
Logger.datetime_format = "%Y-%m-%d %H:%M:%S"
Logger.formatter = proc do |severity, datetime, progname, msg|
"#{datetime} #{severity}: #{msg}\n"
end
+ set_sh_logger(Logger)
# location of gem, where config[:gemhome]/lib contains our default cloud-init templates
@@gemhome = File.absolute_path(File.realpath(File.dirname(File.expand_path(__FILE__)) + '/..'))
# Methadone::CLILogger is a Class, Stack is still a module, so we can't include it
@@ -140,11 +142,11 @@
if config[:provisioner] == 'chef'
# check that we have semi-sensible Chef setup
# at a bare minimum, we need the directory where we're going to download
# validation.pem to to exist
- dot_chef_abs = File.absolute_path(config[:stackhome] + '/' + config[:dot_chef])
+ dot_chef_abs = File.absolute_path(File.join(config[:stackhome], config[:dot_chef]))
if !File.directory?(dot_chef_abs)
Logger.warn "#{dot_chef_abs} doesn't exist"
end
# Check we have a #{dot_chef_abs}/.chef/knife.rb
@@ -164,11 +166,11 @@
# check that the ssh-key is loaded, otherwise most post-install scripts will fail
# this lazily assumes that the :key_pair name matches the file the keys were loaded
# from
if (0 == 1)
- ssh_keys_loaded = `ssh-add -L`
+ ssh_keys_loaded = Stack.shellout(config, "ssh-add -L")
Logger.debug "ssh_keys_loaded: #{ssh_keys_loaded}"
Logger.debug "Looking for #{config[:key_pair]}"
if ssh_keys_loaded.include?(config[:key_pair])
Logger.info "Found #{config[:key_pair]} in the ssh-agent key list"
else
@@ -240,31 +242,31 @@
end
end
end
# CWD shoud be chef-repo/bootstrap, so the project .chef directory should be
- dot_chef_abs = File.absolute_path(config[:stackhome] + '/' + config[:dot_chef])
+ dot_chef_abs = File.absolute_path(File.join(config[:stackhome],config[:dot_chef]))
if !File.directory?(dot_chef_abs)
Logger.warn "#{dot_chef_abs} doesn't exist, creating it..."
Dir.mkdir(dot_chef_abs)
end
- client_key = dot_chef_abs + '/' + config[:name] + '-' + ENV['USER'] + '.pem'
- validation_key = dot_chef_abs + '/' + config[:name] + '-' + 'validation.pem'
+ client_key = File.join(dot_chef_abs, config[:name] + '-' + ENV['USER'] + '.pem')
+ validation_key = File.join(dot_chef_abs, config[:name] + '-' + 'validation.pem')
Logger.debug "stackhome: #{config[:stackhome]}"
Logger.debug "Current user client key: #{client_key}"
Logger.debug "New Host Validation key: #{validation_key}"
knife_rb_template = %q{
log_level :info
log_location STDOUT
node_name '<%=ENV['USER']%>'
-client_key '<%=dot_chef_abs%>/'+ ENV['USER'] + '.pem'
+client_key '<%=client_key%>'
validation_client_name 'chef-validator'
-validation_key '<%=dot_chef_abs%>/validation.pem'
+validation_key '<%=validation_key%>'
chef_server_url '<%=config[:chef_server_public]%>'
cache_type 'BasicFile'
cache_options( :path => '<%=dot_chef_abs%>/checksums' )
cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
}
@@ -304,10 +306,14 @@
def Stack.populate_config(config)
# config[:role_details] contains built out role details with defaults filled in from stack defaults
# config[:node_details] contains node details built out from role_details
+ if config[:find_file_paths].nil?
+ config[:find_file_paths] = Array.new
+ end
+
# set some sensible defaults to the stack-wide defaults if they haven't been set in the Stackfile.
if config[:provisioner].nil?
Logger.warn { "Defaulting to chef for config[:provisioner] "}
config[:provisioner] = 'chef'
end
@@ -324,10 +330,11 @@
if config[:chef_validation_pem].nil?
Logger.warn { "Defaulting to .chef/validation.pem for config[:chef_validation_pem]" }
config[:chef_validation_pem] = '.chef/validation.pem'
end
+ config[:chef_validation_pem] = Stack.find_file(config, config[:chef_validation_pem])
if config[:name_template].nil?
Logger.warn { "Defaulting to '%s-%s-%s%04d' for config[:name_template]" }
config[:name_template] = '%s-%s-%s%04d'
end
@@ -344,14 +351,10 @@
if config[:metadata].nil?
config[:metadata] = Hash.new
end
- if config[:find_file_paths].nil?
- config[:find_file_paths] = Array.new
- end
-
if config[:node_details].nil?
Logger.debug { "Initializing config[:node_details] and config[:azs]" }
config[:node_details] = Hash.new
config[:azs] = Array.new
@@ -529,11 +532,11 @@
if hostname.nil?
Logger.debug { "request to SSH to all hosts" }
servers.each do |host, details|
public_ip = Stack.get_public_ip(config, host)
Logger.info { "#{host} #{public_ip}" }
- cmd_output = `ssh -oStrictHostKeyChecking=no -l #{user} #{public_ip} "#{command}"`
+ cmd_output = Stack.shellout(config, %Q[ssh -oStrictHostKeyChecking=no -l #{user} #{public_ip} "#{command}"])
Logger.info { "#{host} #{public_ip} #{cmd_output}" }
end
else
Logger.debug { "request to SSH to #{servers[hostname]}" }
end
@@ -706,28 +709,28 @@
if (role_details[:skip_chef_prereg] == true || role_details[:chef_server])
Logger.debug "Skipping Chef pre-reg for #{hostname}"
else
# Prepare Chef
# 1) delete the client if it exists
- knife_client_list = `knife client list | grep #{hostname}`
+ knife_client_list = Stack.shellout(config, "knife client list | grep #{hostname}")
knife_client_list.sub!(/\s/,'')
if knife_client_list.length() > 0
# we should delete the client to make way for this new machine
- Logger.info `knife client delete --yes #{hostname}`
+ Stack.shellout(config, "knife client delete --yes #{hostname}")
end
# knife node create -d --environment $CHEF_ENVIRONMENT $SERVER_NAME
# knife node run_list add -d --environment $CHEF_ENVIRONMENT $SERVER_NAME "role[${ROLE}]"
# this relies on .chef matching the stacks config (TODO: poke the Chef API directly?)
cmd = "EDITOR=\"perl -p -i -e 's/_default/#{config[:chef_environment]}/'\" knife node create --server-url #{config[:chef_server_public]} #{hostname}"
Logger.debug cmd
- knife_node_create = `#{cmd}`
+ knife_node_create = Stack.shellout(config, cmd)
Logger.info "Priming Chef Server: #{knife_node_create}"
cmd = "knife node run_list add -d --environment #{config[:chef_environment]} #{hostname} \"role[#{role}]\""
Logger.info cmd
- knife_node_run_list = `#{cmd}`
+ knife_node_run_list = Stack.shellout(config, cmd)
Logger.info "Priming Chef Server: #{knife_node_run_list}"
end
# build the user-data content for this host
# (we have a local copy of https://github.com/lovelysystems/cloud-init/blob/master/tools/write-mime-multipart)
@@ -744,22 +747,29 @@
Logger.debug { "Replacing %HOSTNAME% with #{hostname} in multipart" }
multipart.gsub!(%q!%HOSTNAME%!, hostname)
if config[:chef_server_hostname].nil?
Logger.info "config[:chef_server_hostname] is nil, skipping chef server substitution"
+ elsif (role_details[:chef_server])
+ Logger.info "This is the Chef Server - setting up to talk to ourselves"
+ multipart.gsub!(%q!%CHEF_SERVER%!, 'http://127.0.0.1:4000/')
+ multipart.gsub!(%q!%CHEF_ENVIRONMENT%!, config[:chef_environment])
else
Logger.info "Chef server is #{config[:chef_server_hostname]}, which is in #{config[:node_details][config[:chef_server_hostname]][:region]}"
Logger.info "#{hostname}'s region is #{config[:node_details][hostname][:region]}"
+
# if this host is in the same region/az, use the private URL, if not, use the public url
if (config[:node_details][hostname][:region] == config[:node_details][config[:chef_server_hostname]][:region]) && !config[:chef_server_private].nil?
multipart.gsub!(%q!%CHEF_SERVER%!, config[:chef_server_private])
elsif !config[:chef_server_public].nil?
multipart.gsub!(%q!%CHEF_SERVER%!, config[:chef_server_public])
else
Logger.warn { "Not setting the chef url for #{hostname} as neither chef_server_private or chef_server_public are valid yet" }
end
+
multipart.gsub!(%q!%CHEF_ENVIRONMENT%!, config[:chef_environment])
+
if File.exists?(config[:chef_validation_pem])
multipart.gsub!(%q!%CHEF_VALIDATION_PEM%!, File.read(config[:chef_validation_pem]))
else
Logger.warn "Skipping #{config[:chef_validation_pem]} substitution in user-data"
end
@@ -781,10 +791,11 @@
multipart_complete = multipart_template.result(binding)
Logger.debug { "multipart_complete after erb => #{multipart_complete} " }
multipart = multipart_complete
end
+ Logger.debug { "multipart = #{multipart}" }
Logger.info "Creating #{hostname} in #{node_details[hostname][:az]} with role #{role}"
# this will get put in /meta.js - should look like this:
# {"region": "az-2.region-a.geo-1", "area": "aw2", "az": "az2", "continent": "dev"}
@@ -845,11 +856,11 @@
# run any post-install scripts, these are run from the current host, not the nodes
if role_details[:post_install_script]
Logger.debug { "This role has a post-install script (#{role_details[:post_install_script]}), preparing to run" }
# convert when we got passed to an absolute path
- post_install_script_abs = File.realpath(config[:stackhome] + '/' + role_details[:post_install_script])
+ post_install_script_abs = Stack.find_file(config, role_details[:post_install_script])
post_install_cwd_abs = File.realpath(config[:stackhome] + '/' + role_details[:post_install_cwd])
# replace any tokens in the argument
public_ip = Stack.get_public_ip(config, hostname)
post_install_args = role_details[:post_install_args].sub(%q!%PUBLIC_IP%!, public_ip)
@@ -869,20 +880,26 @@
# find a file, using the standard path precedence
# 1) cwd
# 2) stackhome
# 2) stackhome + find_file_paths
# 3) gemhome/lib
- dirs = [ './' ]
+
+ if filename.nil? || filename.empty?
+ raise ArgumentError
+ end
+
+ dirs = [ '.' ] # current directory
dirs.push(config[:stackhome])
config[:find_file_paths].each { |fp| dirs.push(File.join(config[:stackhome], fp)) }
dirs.push(File.join(@@gemhome, 'lib'))
+ dirs.push('') # find absolute paths
dirs.flatten!
Logger.debug "find_file, looking for #{filename} in #{dirs}"
filename_fqp = ''
dirs.each do |dir|
- fqp = dir + '/' + filename
+ fqp = File.join(dir, filename)
Logger.debug "find_file: checking #{fqp}"
if File.file?(fqp)
Logger.debug "find_file: found #{fqp}!"
filename_fqp = File.expand_path(fqp)
end
@@ -892,7 +909,24 @@
Logger.warn "couldn't find #{filename} in #{dirs}"
end
filename_fqp
end
+ def Stack.shellout(config, command, cwd=nil)
+ # wrapper to exec things in a knife friendly way
+
+ if cwd.nil?
+ cwd = config[:stackhome]
+ end
+
+ saved_wd = Dir.getwd()
+ output = ""
+ Dir.chdir(cwd)
+ sh command do |stdout, stderr, exitstatus|
+ output = stdout
+ end
+ Dir.chdir(saved_wd)
+
+ return output
+ end
end