lib/fulmar/infrastructure/service/ssh_config_service.rb in fulmar-2.0.2 vs lib/fulmar/infrastructure/service/ssh_config_service.rb in fulmar-2.1.0
- old
+ new
@@ -1,47 +1,83 @@
require 'fulmar/shell'
+require 'pp'
+require 'diffy'
+require 'active_support/core_ext/object/blank'
module Fulmar
module Infrastructure
module Service
# Adds entries to the ssh config and checks for existing ones
class SSHConfigService
- CONFIG_FILE = "#{ENV['HOME']}/.ssh/config"
- KNOWN_HOST_FILE = "#{ENV['HOME']}/.ssh/known_hosts"
+ DEFAULT_CONFIG_FILE = "#{ENV['HOME']}/.ssh/config"
+ DEFAULT_KNOWN_HOSTS_FILE = "#{ENV['HOME']}/.ssh/known_hosts"
- def initialize(config)
+ attr_accessor :config_file, :known_host_file, :quiet
+
+ def initialize(config, config_file = DEFAULT_CONFIG_FILE, known_hosts_file = DEFAULT_KNOWN_HOSTS_FILE)
@config = config
+ @config_file = config_file
+ @known_hosts_file = known_hosts_file
+ @quiet = false
end
+ def changed?
+ File.read(@config_file) != File.read("#{@config_file}.bak")
+ end
+
+ def diff(type = :color)
+ before = File.read("#{@config_file}.bak")
+ after = File.read(@config_file)
+ Diffy::Diff.new(before, after, context: 3).to_s(type)
+ end
+
+ def show_diff
+ return if @quiet || !changed?
+ puts 'You ssh host configuration changed: '
+ puts '--------------- DIFF ---------------'
+ puts diff
+ puts '--------------- /DIFF --------------'
+ puts 'You can revert these changes by running "fulmar revert:ssh_config"'
+ end
+
def add_hosts
+ backup_file
@config.hosts.values.each do |data|
unless config_valid?(data)
puts "Skipping #{data[:hostname]}, config not sufficient." if @config[:debug]
next
end
- if host_exists?(data[:hostname])
- puts "Host #{data[:hostname]} exists, skipping..." if @config[:debug]
- else
- add_host(data[:hostname], data[:ssh_config])
- end
+ edit_host(data[:hostname], data[:ssh_config])
end
+ show_diff
end
def remove_known_host(hostname)
- input_file = File.open(KNOWN_HOST_FILE, 'r')
- output_file = File.open(KNOWN_HOST_FILE + '.temp', 'w')
+ input_file = File.open(@known_hosts_file, 'r')
+ output_file = File.open(@known_hosts_file + '.temp', 'w')
while (line = input_file.gets)
output_file.puts(line) unless /^\[?#{hostname.gsub('.', '\\.')}(?:\]:\d+)?[ ,]/.match(line)
end
input_file.close
output_file.close
- FileUtils.mv(KNOWN_HOST_FILE + '.temp', KNOWN_HOST_FILE)
+ FileUtils.mv(@known_hosts_file + '.temp', @known_hosts_file)
end
+ def edit_host(hostname, ssh_config)
+ data = read_file
+ new_data = block_before(data.clone, hostname) +
+ host_entry(hostname, ssh_config) +
+ block_after(data, hostname)
+
+ File.open(@config_file, 'w') do |file|
+ file.puts new_data.join("\n")
+ end
+ end
+
# Parses the users ssh config for an existing hostname
def host_exists?(hostname)
- config_file = File.open(CONFIG_FILE, 'r')
+ config_file = File.open(@config_file, 'r')
while (line = config_file.gets)
if /\s*Host #{hostname.gsub('.', '\\.')}\s*$/.match(line)
config_file.close
return true
end
@@ -51,31 +87,110 @@
end
# Adds a host to the ssh config file
def add_host(hostname, ssh_config = {})
puts "Adding host #{hostname}..." if @config[:debug]
- config_file = File.open(CONFIG_FILE, 'a')
+ config_file = File.open(@config_file, 'a')
unless ssh_config[:IdentityFile].blank? or ssh_config[:IdentityFile][0, 1] == '/'
ssh_config[:IdentityFile] = @config.base_path + '/' + ssh_config[:IdentityFile]
end
- config_file.puts "\n" # Add some space between this and the second last entry
- config_file.puts "# Automatically generated by fulmar for project '#{@config.project.description}'"
- config_file.puts "Host #{hostname}"
- ssh_config.keys.each do |key|
- value = ssh_config[key].to_s
- value = "\"#{value.gsub('"', '\\"')}\"" if value.include?(' ')
- config_file.puts " #{key} #{value}"
- end
+ config_file.puts host_entry(hostname, ssh_config)
config_file.close
end
- private
+ def backup_file
+ backup_filename = "#{@config_file}.bak"
+ FileUtils.cp @config_file, backup_filename
+ end
+ protected
+
+ def host_entry(hostname, ssh_config = {})
+ unless ssh_config[:IdentityFile].blank? or ssh_config[:IdentityFile][0, 1] == '/'
+ ssh_config[:IdentityFile] = @config.base_path + '/' + ssh_config[:IdentityFile]
+ end
+
+ entry = [
+ '', # Add some space between this and the second last entry
+ "# Automatically generated by fulmar for project '#{@config.project.description}'",
+ "Host #{hostname}"
+ ]
+ ssh_config.keys.each { |key| entry << " #{key} #{escape_value(ssh_config[key])}" }
+ entry << ''
+ entry
+ end
+
+ def escape_value(value)
+ value = value.to_s
+ value = "\"#{value.gsub('"', '\\"')}\"" if value.include?(' ')
+ value
+ end
+
+ def read_file
+ config_file_data = []
+ File.open(@config_file, 'r') do |file|
+ until file.eof?
+ config_file_data << file.gets.chomp
+ end
+ end
+ config_file_data
+ end
+
def config_valid?(host_config)
(!host_config[:hostname].blank? && !host_config[:ssh_config].nil?)
+ end
+
+ def remove_trailing_newlines(data)
+ while !data.empty? && data.last.strip.empty?
+ data.pop
+ end
+ data
+ end
+
+ def block_before(data, hostname)
+ cache = []
+ before = []
+ data.each do |line|
+ if line.strip[0] == '#'
+ cache << line
+ else
+ if /^Host\s#{hostname}$/.match line.strip
+ return remove_trailing_newlines(before)
+ end
+ before += cache
+ cache = []
+ before << line
+ end
+ end
+ remove_trailing_newlines(before)
+ end
+
+ def block_after(data, hostname)
+ data = data.drop_while { |i| !/^Host\s#{hostname}$/.match(i.strip) }
+ return [] if data.empty?
+ data.shift
+
+ after = []
+ cache = []
+ write = false
+ data.each do |line|
+ if line.strip[0] == '#'
+ cache << line
+ else
+ if /^Host\s/.match line.strip
+ write = true
+ end
+ if write
+ after += cache
+ after << line
+ end
+ cache = []
+ end
+ end
+ remove_trailing_newlines(after)
end
end
end
end
end