require 'json' require 'highline' include Rake::DSL if defined? Rake::DSL module Itamae module Mitsurin class ItamaeTask class << self class ::Hash def deep_merge(other) merger = lambda {|key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2} self.merge(other, &merger) end def deep_merge!(other) merger = lambda {|key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2} self.merge!(other, &merger) end end def self.get_roles(node_file) roles = [] JSON.parse(File.read(node_file))['run_list'].each do |role| roles << role.gsub(/role\[(.+)\]/, '\1') if /role\[(.+)\]/ === role end roles end def self.get_recipes(role) recipes = [] JSON.parse(File.read("roles/#{role}.json"))['run_list'].each do |recipe| if /recipe\[(.+)::(.+)\]/ === recipe recipes << {recipe.gsub(/recipe\[(.+)::(.+)\]/, '\1') => recipe.gsub(/recipe\[(.+)::(.+)\]/, '\2')} else recipes << {recipe.gsub(/recipe\[(.+)\]/, '\1') => nil} end end recipes end def self.get_node_recipes(node_file) recipes = [] JSON.parse(File.read(node_file))['run_list'].each do |recipe| if /recipe\[(.+)::(.+)\]/ === recipe recipes << {recipe.gsub(/recipe\[(.+)::(.+)\]/, '\1') => recipe.gsub(/recipe\[(.+)::(.+)\]/, '\2')} else recipes << {recipe.gsub(/recipe\[(.+)\]/, '\1') => nil} unless /role\[(.+)\]/ === recipe end end recipes end def self.jq(*objs) par = nil objs.each {|obj| par = JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)} return par end def self.write_json(filename) File.open "tmp-nodes/#{filename}.json", 'w' do |f| f.flock File::LOCK_EX yield f f.flock File::LOCK_UN end end hl = HighLine.new namespace :itamae do Dir.glob("nodes/**/*.json").each do |node_file| bname = File.basename(node_file, '.json') node_h = JSON.parse(File.read(node_file), symbolize_names: true) desc "Itamae to #{bname}" task node_h[:environments][:hostname].split(".")[0] do begin recipes = [] get_roles(node_file).each do |role| recipes << get_recipes(role) end get_node_recipes(node_file).each do |recipe| recipes << recipe end rescue Exception => e puts e.class.to_s + ", " + e.backtrace[0].to_s puts "nodefile or role error, nodefile:#{node_file} reason:#{e.message}" exit 1 else recipes.flatten! end # get env attr begin env_set = node_h[:environments][:set] env_h = JSON.parse(File.read("environments/#{env_set}.json"), symbolize_names: true) rescue Exception => e puts e.class.to_s + ", " + e.backtrace[0].to_s puts "nodefile or environments error, nodefile:#{node_file} reason:#{e.message}" exit 1 end # get recipes attr recipe_attr_file = [] recipes.each do |recipe_h| if recipe_h["#{recipe_h.keys.join}"].nil? recipe_attr_file.insert 0, Dir.glob("site-cookbooks/**/#{recipe_h.keys.join}/attributes/default.json") else recipe_attr_file << Dir.glob("site-cookbooks/**/#{recipe_h.keys.join}/attributes/#{recipe_h["#{recipe_h.keys.join}"]}.json") end end recipe_attr_file.flatten! # recipes attr other=env recipe_env_h_a = [] recipe_attr_file.each do |file| recipe_h = JSON.parse(File.read(file), symbolize_names: true) recipe_env_h_a << recipe_h.deep_merge(env_h) end # recipe attr other=recipes_env moto = recipe_env_h_a[0] recipe_env_h_a.each {|hash| moto.deep_merge!(hash)} recipe_env_h = moto if recipe_env_h.nil? # env attr other=node node_env_h = env_h.deep_merge(node_h) node_env_j = jq node_env_h write_json(bname) {|file| file.puts node_env_j} else # recipe_env attr other=node recipe_env_node_h = recipe_env_h.deep_merge(node_h) recipe_env_node_j = jq recipe_env_node_h write_json(bname) {|file| file.puts recipe_env_node_j} end recipes << {'_base' => nil} node_property = JSON.parse(File.read("tmp-nodes/#{bname}.json"), symbolize_names: true) node = node_property[:environments][:hostname] ssh_user = node_property[:environments][:ssh_user] ssh_password = node_property[:environments][:ssh_password] sudo_password = node_property[:environments][:sudo_password] ssh_port = node_property[:environments][:ssh_port] ssh_key = node_property[:environments][:ssh_key] ENV['TARGET_HOST'] = node ENV['NODE_FILE'] = node_file ENV['SSH_PASSWORD'] = ssh_password ENV['SUDO_PASSWORD'] = sudo_password command = "bundle exec itamae ssh" command << " -h #{node}" command << " -u #{ssh_user}" command << " -p #{ssh_port}" command << " -i keys/#{ssh_key}" unless ssh_key.nil? command << " -j tmp-nodes/#{bname}.json" command << " --shell=bash" command << " --ask-password" unless ssh_password.nil? command << " --dry-run" if ENV['dry_run'] == "true" command << " -l debug" if ENV['debug'] == "true" # recipe load to_command command_recipe = [] recipes.each do |recipe_h| if recipe_h["#{recipe_h.keys.join}"].nil? command_recipe << " #{Dir.glob("site-cookbooks/**/#{recipe_h.keys.join}/recipes/default.rb").join}" else command_recipe << " #{Dir.glob("site-cookbooks/**/#{recipe_h.keys.join}/recipes/#{recipe_h["#{recipe_h.keys.join}"]}.rb").join}" end end command_recipe.sort_by! {|item| File.dirname(item)} command << command_recipe.join puts hl.color(%!Run Itamae to \"#{bname}\"!, :red) puts hl.color(%!Role List to \"#{get_roles(node_file).join(", ")}\"!, :blue) run_list_noti = [] command_recipe.each {|c_recipe| run_list_noti << c_recipe.split("/") [2]} puts hl.color(%!Run List to \"#{run_list_noti.uniq.join(", ")}\"!, :green) puts hl.color(%!#{command}!, :white) st = system command exit 1 unless st end end end end end end end