259 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
		
		
			
		
	
	
			259 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
|   | require_relative 'tool' | ||
|  | require "lib/ssh/ssh_utils" | ||
|  | require "lib/knife/knife_factory" | ||
|  | require 'lib/executors/server_executor/server_operation_result' | ||
|  | require "db/mongo/models/key" | ||
|  | 
 | ||
|  | module Devops | ||
|  |   module Model | ||
|  |     module ConfigurationManagement | ||
|  |       class Chef < Tool | ||
|  | 
 | ||
|  |         include Devops::Executor::ServerOperationResultCode | ||
|  | 
 | ||
|  |         attr_accessor :bootstrap_template, :chef_env, :use_json_file | ||
|  | 
 | ||
|  |         def initialize hash={} | ||
|  |           @name = 'chef' | ||
|  |           @bootstrap_template = hash['bootstrap_template'] || 'omnibus' | ||
|  |           @chef_env = hash.fetch('chef_env') | ||
|  |           @use_json_file = hash['use_json_file'] || false | ||
|  |         end | ||
|  | 
 | ||
|  |         def to_hash | ||
|  |           super.merge({ | ||
|  |             'bootstrap_template' => self.bootstrap_template, | ||
|  |             'chef_env' => self.chef_env | ||
|  |           }) | ||
|  |         end | ||
|  | 
 | ||
|  |         def knife_instance | ||
|  |           @knife_instance ||= KnifeFactory.instance(self.chef_env) | ||
|  |         end | ||
|  | 
 | ||
|  |         def use_json_file? | ||
|  |           self.use_json_file | ||
|  |         end | ||
|  | 
 | ||
|  |         def check_node_name name | ||
|  |           s = knife_instance.chef_node_list.detect {|n| n == name} | ||
|  |           raise Devops::Exception::ValidationError.new("Chef node with name '#{name}' already exist") unless s.nil? | ||
|  |           s = knife_instance.chef_client_list.detect {|c| c == name} | ||
|  |           raise Devops::Exception::ValidationError.new("Chef client with name '#{name}' already exist") unless s.nil? | ||
|  |         end | ||
|  | 
 | ||
|  |         def prepare options, out | ||
|  |           out << "Prepare bootstrap...\n" | ||
|  |           out << "Done\n" | ||
|  |         end | ||
|  | 
 | ||
|  |         # returns bootstrap options | ||
|  |         # raises Devops::Exception::BootstrapError | ||
|  |         def bootstrap_instance server, attributes, out, options = {} | ||
|  |           prepare(options, out) | ||
|  |           provider = server.provider_instance | ||
|  |           options[:run_list] = provider.run_list if options[:run_list].nil? | ||
|  |           server.cm_name = options[:cm_name] || server.name | ||
|  |           bootstrap_opts = bootstrap(server, attributes, options, out) | ||
|  | 
 | ||
|  |           if check_server_name(server) | ||
|  |             out.puts "Server '#{server.id}' has been bootstraped with name '#{server.cm_name}'" | ||
|  |           else | ||
|  |             raise Devops::Exception::BootstrapError.new("Can not find client or node on chef-server") | ||
|  |           end | ||
|  |           return {options: bootstrap_opts, info: attributes} | ||
|  |         rescue Devops::Exception::BootstrapError => e | ||
|  |           raise e | ||
|  |         rescue => e | ||
|  |           msg = "Bootstrap operation, unexpected error: #{e.message}\n#{e.backtrace.join("\n")}" | ||
|  |           out.puts msg | ||
|  |           out.flush | ||
|  |           DevopsLogger.logger.error(msg) | ||
|  |           raise Devops::Exception::BootstrapError.new(msg) | ||
|  |         end | ||
|  | 
 | ||
|  |         def bootstrap_options server, attributes, options | ||
|  |           bootstrap_options = [ | ||
|  |             "--no-color", | ||
|  |             "-x #{server.remote_user}", | ||
|  |             "-i #{options[:cert_path]}", | ||
|  |             "--json-attributes '#{attributes.to_json}'" | ||
|  |           ] | ||
|  |           bootstrap_options.push "-N #{server.cm_name}" if server.cm_name | ||
|  |           bootstrap_options.push "--sudo" unless server.remote_user == "root" | ||
|  |           bootstrap_options.push "-t #{options[:bootstrap_template]}" if options[:bootstrap_template] | ||
|  |           bootstrap_options.push "-E #{options[:environment]}" if options[:environment] | ||
|  |           rl = options[:run_list] | ||
|  |           bootstrap_options.push "-r #{rl.join(",")}" unless rl.nil? or rl.empty? | ||
|  |           bootstrap_options.push "-c #{options[:config]}" if options[:config] | ||
|  |           bootstrap_options | ||
|  |         end | ||
|  | 
 | ||
|  |         def bootstrap server, attributes, options, out | ||
|  |           out << "\n\nBootstrap...\n" | ||
|  |           out.flush | ||
|  |           k = Devops::Model::Key.find(server.ssh_key) | ||
|  |           options[:cert_path] = k.path | ||
|  |           out << "\nBefore bootstrap hooks...\n" | ||
|  |           res = self.run_hook(:before_bootstrap, out) | ||
|  |           out << "Done\n" | ||
|  |           if server.private_ip.nil? | ||
|  |             out.puts "Private IP is unset" | ||
|  |             out.flush | ||
|  |             raise Devops::Exception::BootstrapError.new("Error: Private IP is unset") | ||
|  |           end | ||
|  |           bootstrap_attrs = attributes.merge({ | ||
|  |             :provider => server.provider, | ||
|  |             :provider_account => server.provider_account, | ||
|  |             :devops_host => `hostname`.strip | ||
|  |           }) | ||
|  |           ip = server.private_ip | ||
|  |           unless server.public_ip.nil? | ||
|  |             ip = server.public_ip | ||
|  |             out << "\nPublic IP is present\n" | ||
|  |           end | ||
|  |           out.flush | ||
|  | 
 | ||
|  |           bopts = bootstrap_options(server, bootstrap_attrs, options) | ||
|  |           r = knife_instance.knife_bootstrap(out, ip, bopts) | ||
|  |           if r == 0
 | ||
|  |             out.puts "Chef node name: #{server.cm_name}\nAfter bootstrap hooks...\n" | ||
|  |             res = self.run_hook(:after_bootstrap, out) | ||
|  |             msg = "Server with id '#{server.id}' is bootstraped" | ||
|  |             out.puts "Done\n#{msg}" | ||
|  |             out.flush | ||
|  |             DevopsLogger.logger.info msg | ||
|  |             return bopts | ||
|  |           else | ||
|  |             raise Devops::Exception::BootstrapError.new("Can not bootstrap node '#{server.id}', error code: #{r}") | ||
|  |           end | ||
|  |         end | ||
|  | 
 | ||
|  |         def delete_instance server, out | ||
|  |           msg = "Removing instance '#{server.cm_name}' from chef server" | ||
|  |           out.puts msg | ||
|  |           if server.cm_name.nil? | ||
|  |             out.puts "There is no specified configuration manager name" | ||
|  |             return | ||
|  |           end | ||
|  |           DevopsLogger.logger.info msg | ||
|  |           res = { | ||
|  |             :chef_node => knife_instance.chef_node_delete(server.cm_name), | ||
|  |             :chef_client => knife_instance.chef_client_delete(server.cm_name) | ||
|  |           } | ||
|  |           msg = res.values.join("\n") | ||
|  |           out.puts msg | ||
|  |           DevopsLogger.logger.info msg | ||
|  |           server.cm_name = nil | ||
|  |           server.update | ||
|  |           DevopsLogger.logger.info "Server cm_name has been removed" | ||
|  |           k = Devops::Model::Key.find(server.ssh_key) | ||
|  |           cert_path = k.path | ||
|  |           i = 0
 | ||
|  |           begin | ||
|  |             new_name = "/etc/chef.backup_#{Time.now.strftime("%d-%m-%Y_%H.%M.%S")}" | ||
|  |             exit_code = Devops::SSH::Utils.run_command_out("/bin/sh -c 'if [[ -d /etc/chef ]]; then mv /etc/chef #{new_name}; else exit 22; fi'", server.public_ip || server.private_ip, server.remote_user, cert_path, out) | ||
|  |             if exit_code == 0
 | ||
|  |               out.puts "'/etc/chef' renamed to '#{new_name}'" | ||
|  |             elsif exit_code == 22
 | ||
|  |               out.puts "Directory '/etc/chef' does not exists" | ||
|  |             else | ||
|  |               out.puts "Can not rename /etc/chef directory" | ||
|  |             end | ||
|  |           rescue Net::SSH::ConnectionTimeout => e | ||
|  |             DevopsLogger.logger.error "Unbootstrap error: connection timeout" + e.message | ||
|  |           rescue => e | ||
|  |             DevopsLogger.logger.error "Unbootstrap error: " + e.message | ||
|  |             i += 1
 | ||
|  |             sleep(1) | ||
|  |             retry unless i == 5
 | ||
|  |           end | ||
|  |           res | ||
|  |         end | ||
|  | 
 | ||
|  |         def deploy_instance server, deploy_info, out | ||
|  |           old_tags_str = nil | ||
|  |           new_tags_str = nil | ||
|  |           if deploy_info.key(:tags) | ||
|  |             old_tags_str = knife_instance.tags_list(server.name).join(" ") | ||
|  |             out << "Server tags: #{old_tags_str}\n" | ||
|  |             knife_instance.tags_delete(server.name, old_tags_str) | ||
|  | 
 | ||
|  |             new_tags_str = tags.join(" ") | ||
|  |             out << "Server new tags: #{new_tags_str}\n" | ||
|  |             cmd = knife_instance.tags_create(server.name, new_tags_str) | ||
|  |             unless cmd[1] | ||
|  |               m = "Error: Cannot add tags '#{new_tags_str}' to server '#{server.name}'" | ||
|  |               DevopsLogger.logger.error(m) | ||
|  |               out.puts m | ||
|  |               return 3
 | ||
|  |             end | ||
|  |             DevopsLogger.logger.info("New temporary tags for '#{server.name}': #{new_tags_str}") | ||
|  |           end | ||
|  | 
 | ||
|  |           out << "\nBefore deploy hooks...\n" | ||
|  |           res = self.run_hook(:before_deploy, out, deploy_info) | ||
|  |           out << "Done\n" | ||
|  |           out << "\nRun chef-client on '#{server.name}'\n" | ||
|  |           cmd = server.remote_user == "root" ? "chef-client --no-color" : "sudo chef-client --no-color" | ||
|  | 
 | ||
|  |           ip = if server.public_ip.nil? | ||
|  |             server.private_ip | ||
|  |           else | ||
|  |             out << "Public IP detected\n" | ||
|  |             server.public_ip | ||
|  |           end | ||
|  | 
 | ||
|  |           k = Devops::Model::Key.find(server.ssh_key) | ||
|  |           json = {} | ||
|  |           if deploy_info["use_json_file"] | ||
|  |             deploy_info.delete("use_json_file") | ||
|  |             dir = DevopsConfig.config[:project_info_dir] | ||
|  |             file = deploy_info.delete("json_file") || "#{server.project}_#{server.deploy_env}_#{server.category}_#{Time.new.to_i}" | ||
|  |             path = File.join(dir, file) | ||
|  |             if File.exists?(path) | ||
|  |               json = File.read(path) | ||
|  |             else | ||
|  |               json = JSON.pretty_generate(deploy_info) | ||
|  |               File.open(File.join(dir, file), "w") do |f| | ||
|  |                 f.write json | ||
|  |               end | ||
|  |             end | ||
|  |           else | ||
|  |             cmd << " -r #{deploy_info[:run_list].join(",")}" unless server.stack.nil? | ||
|  |           end | ||
|  |           out << "Deploy Input Parameters:\n" | ||
|  |           out.puts json | ||
|  |           out.flush | ||
|  |           path = Devops::SSH::Utils.copy_deploy_info json, ip, server.remote_user, k.path, out | ||
|  |           cmd << " -j #{path}" | ||
|  |           out.flush | ||
|  |           out << "Deploy command: #{cmd}\n" | ||
|  |           status = Devops::SSH::Utils.run_command_out cmd, ip, server.remote_user, k.path, out | ||
|  |           if status == 0
 | ||
|  |             out << "\nAfter deploy hooks...\n" | ||
|  |             res = self.run_hook(:after_deploy, out, deploy_info) | ||
|  |             out << "Done\n" | ||
|  |           else | ||
|  |             raise Devops::Exception::DeployError.new("Chef client exit status code: '#{status}'") | ||
|  |           end | ||
|  |         ensure | ||
|  |           unless old_tags_str.nil? | ||
|  |             out << "Restore tags\n" | ||
|  |             cmd = knife_instance.tags_delete(server.name, new_tags_str) | ||
|  |             DevopsLogger.logger.info("Deleted tags for #{server.name}: #{new_tags_str}") | ||
|  |             cmd = knife_instance.tags_create(server.name, old_tags_str) | ||
|  |             DevopsLogger.logger.info("Set tags for #{server.name}: #{old_tags_str}") | ||
|  |           end | ||
|  |         end | ||
|  | 
 | ||
|  |         private | ||
|  |         def check_server_name server | ||
|  |           knife_instance.chef_node_list.include?(server.cm_name) and knife_instance.chef_client_list.include?(server.cm_name) | ||
|  |         end | ||
|  | 
 | ||
|  |       end | ||
|  |     end | ||
|  |   end | ||
|  | end |