require 'rake' require 'dotenv' require 'open3' require_relative '../../../../../lib/shiplane/safe_yaml_loading' desc "Provision host" task :provision, [:role, :username, :keypath] => ['provision:default'] namespace :provision do task :default, [:role, :username, :keypath] => [ :prepare_files, 'cookbooks.tar.gz', :set_host_options, :prepare_host, :evaluate_erb_files, 'upload-cookbooks', :rsync_chef, :rsync_custom_configuration, :fix_file_permissions, ] do |task, args| errors = {} on roles(fetch(:host_options).role) do |host| begin sudo 'chef-solo', '-c', '/var/chef/solo.rb' rescue => e error_line_regexp = /================================================================================/ message_lines = e.message.split("\n").reverse error_message_start = message_lines.index(message_lines.find{|line| line =~ error_line_regexp }) || message_lines.size - 1 error_message_end = message_lines.index(message_lines.find{|line| line =~ /ERROR: Exception handlers complete/ || line =~ /FATAL: Chef::Exceptions::ChildConvergeError/ }) || 0 error_lines = message_lines[error_message_end..error_message_start].reverse errors["#{host}"] = error_lines end end unless errors.empty? puts "#{errors.keys.size} Errors encountered:" errors.each do |host, trace| puts "~" * 80 puts "Server: #{host}" puts *trace puts "~" * 80 end end end task :set_host_options, :role, :username, :keypath do |task, args| set :host_options, HostOptions.new(**args.to_h) roles(args['role']).each do |role| role.ssh_options = fetch(:host_options).ssh_options #unless role.user == fetch(:host_options).ssh_options[:user] role.user = fetch(:host_options).username role.keys = fetch(:host_options).keys end end task :prepare_host, :role, :username, :keypath do |task, args| on roles(fetch(:host_options).role) do |host| #preparation_started = test("[ -f #{File.join(chef_path, '.preparation-started')} ]") preparation_finished = test("[ -f #{File.join(chef_path, '.prepared')} ]") #if preparation_started && !preparation_finished #sudo :dpkg, '--configure', '-a' #sudo :dpkg, "--remove", "--force-remove-reinstreq", *packages #end unless preparation_finished sudo :mkdir, '-m', '2777', '-p', chef_path sudo :touch, File.join(chef_path, '.preparation-started') sudo 'apt-get', 'update' sudo 'apt-get', 'install', '-y', *packages execute :wget, chef_package_url sudo :dpkg, '-i', chef_package_name sudo :ls, '-al', chef_path sudo :touch, File.join(chef_path, '.prepared') end end end task :rsync_chef, :role, :username, :keypath do |task, args| on roles(fetch(:host_options).role) do |host| rsync_arguments = [ '-r', '-e', "ssh -p #{host.port || 22} -i #{host.netssh_options.fetch(:keys)}", "#{chef_folder_path}/", "#{host.netssh_options.fetch(:user)}@#{host}:/var/chef" ] Kernel.system 'rsync', *rsync_arguments end end task :rsync_custom_configuration, :role, :username, :keypath do |task, args| on roles(fetch(:host_options).role) do |host| rsync_arguments = [ '-r', '-e', "ssh -p #{host.port || 22} -i #{host.netssh_options.fetch(:keys)}", "#{boostrap_config_path}/", "#{host.netssh_options.fetch(:user)}@#{host}:/var/chef/cookbooks/barebones-docker/files" ] stdout, stderr, status = Open3.capture3("rsync" *rsync_arguments) binding.pry end end desc "Uploads the tarballed cookbooks" task :'upload-cookbooks', :role, :username, :keypath do |task, args| on roles(args['role']) do |host| sudo :rm, '-Rf', File.join(chef_path, 'cookbooks.tar.gz') upload!('cookbooks.tar.gz', chef_path) sudo :rm, '-Rf', File.join(chef_path, 'cookbooks') execute :tar, '-xzf', File.join(chef_path, 'cookbooks.tar.gz'), '-C', chef_path end end task :fix_file_permissions, :role, :username, :keypath do |task, args| on roles(args['role']) do |host| sudo :chmod, '-R', '777', chef_path sudo :chown, '-R', '$USER', chef_path end end task :evaluate_erb_files, :role, :username, :keypath do |task, args| Dotenv.load ".env.#{ENV['RAILS_ENV']}" on roles(args['role']) do |host| Dir["#{chef_folder_path}/**/*.erb"].map do |filename| compiled_template = ERB.new(File.read(filename)).result(binding) compiled_file_name = filename.match(/.*\/chef\/(.*)\.erb/)[1] sudo :mkdir, '-m', '777', '-p', File.join(chef_path, File.dirname(compiled_file_name)) upload! StringIO.new(compiled_template), File.join(chef_path, compiled_file_name) end end end task :prepare_files do |task| rm 'cookbooks.tar.gz', force: true rm_r 'cookbooks', force: true end file "cookbooks.tar.gz" => :cookbooks do |task| sh "tar -cvzf #{task.name} cookbooks" end file cookbooks: :prepare_files do |task| cp_r 'berks-cookbooks/.', "cookbooks" cp_r 'site-cookbooks/.', "cookbooks" end end def packages %w(ruby ruby2.3-dev build-essential wget) end def chef_package_url "https://packages.chef.io/files/stable/chefdk/3.3.23/ubuntu/16.04/#{chef_package_name}" end def chef_package_name 'chefdk_3.3.23-1_amd64.deb' end def chef_folder_path File.expand_path("../../../chef", __FILE__) end def boostrap_config_path @boostrap_config_path ||= File.join(Dir.pwd, '.shiplane', 'bootstrap_config') end def sudo(*args) execute :sudo, *args, interaction_handler: { "[sudo] password for #{fetch(:host_options).username}: " => "#{fetch(:host_options).password}\n" } end def chef_path File.join("/var","chef") end class HostOptions attr_accessor :role, :keypath, :username, :password def initialize(username: nil, password: nil, keypath: nil, role: nil) @username = username @password = password @keypath = keypath @role = role end def username @username ||= config['username'] || 'deploy' end def password @password ||= config['password'] end def keypath @keypath ||= config['keypath'] || "#{Dir.home}/.ssh/id_rsa" end def keys @keys ||= keypath.split('/') end def config @config ||= Shiplane::SafeYamlLoading.load_file(config_file_path)['capistrano'] end def ssh_options @ssh_options ||= { user: username, keys: File.join(keys), forward_agent: true, auth_methods: %w(publickey), } end def config_file_path File.expand_path("../../../../config/config.yml", __FILE__) end end