fulmar 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.gitignore +2 -0
 - data/fulmar.gemspec +1 -0
 - data/lib/fulmar/domain/model/configuration.rb +1 -14
 - data/lib/fulmar/domain/service/config_tests/misc.rb +21 -0
 - data/lib/fulmar/domain/task/configuration.rake +14 -0
 - data/lib/fulmar/infrastructure/model/transfer/rsync_with_versions.rb +2 -0
 - data/lib/fulmar/infrastructure/service/ssh_config_service.rb +137 -22
 - data/lib/fulmar/version.rb +1 -1
 - data/spec/fixtures/ssh_config/default_config.txt +16 -0
 - data/spec/fixtures/ssh_config/default_known_hosts.txt +0 -0
 - data/spec/lib/fulmar/service/ssh_config_service_spec.rb +122 -0
 - metadata +23 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: b5773cc97e0e9d8615d47d72ee31a9d9b0438e6b
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 9f2702193748a1561335ef13eacad2ba07acd6d6
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: eff45b616da762576e948fe24d2c0ebc201a8f3cea1e134c687c5e6d5d77db2ee4e5bd7a224c82720d737eaf046f6caef7b463deafa4a330f8d0554661f9ad54
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 0aeedda8a394b79420f082ebc3a91c2ad08ab4e20e2c9217ba32e3a0597bab662d2f243afb8ce4986690ffcfcef220e4f175edc1531e831fef60d9d5e67bafb4
         
     | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/fulmar.gemspec
    CHANGED
    
    
| 
         @@ -94,22 +94,9 @@ module Fulmar 
     | 
|
| 
       94 
94 
     | 
    
         
             
                      @data[:hosts]
         
     | 
| 
       95 
95 
     | 
    
         
             
                    end
         
     | 
| 
       96 
96 
     | 
    
         | 
| 
       97 
     | 
    
         
            -
                    # Check for a feature
         
     | 
| 
       98 
     | 
    
         
            -
                    # @todo Do we still need this? Maybe replace this with a "plugin?" method?
         
     | 
| 
       99 
     | 
    
         
            -
                    def feature?(feature)
         
     | 
| 
       100 
     | 
    
         
            -
                      return @data[:features].include? feature.to_s unless @data[:features].nil?
         
     | 
| 
       101 
     | 
    
         
            -
                      case feature
         
     | 
| 
       102 
     | 
    
         
            -
                      when :maria
         
     | 
| 
       103 
     | 
    
         
            -
                        key? :maria
         
     | 
| 
       104 
     | 
    
         
            -
                      else
         
     | 
| 
       105 
     | 
    
         
            -
                        false
         
     | 
| 
       106 
     | 
    
         
            -
                      end
         
     | 
| 
       107 
     | 
    
         
            -
                    end
         
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
97 
     | 
    
         
             
                    # Checks if a configuration key exists in one of the targets
         
     | 
| 
       110 
98 
     | 
    
         
             
                    def key?(key)
         
     | 
| 
       111 
     | 
    
         
            -
                       
     | 
| 
       112 
     | 
    
         
            -
                      false
         
     | 
| 
      
 99 
     | 
    
         
            +
                      ready? ? @data[:environments][@environment][@target].key?(key) : false
         
     | 
| 
       113 
100 
     | 
    
         
             
                    end
         
     | 
| 
       114 
101 
     | 
    
         | 
| 
       115 
102 
     | 
    
         
             
                    # Merge another configuration into the currently active one
         
     | 
| 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            relevant_options = [:rsync, :local_path, :remote_path, :host, :hostname, :shared, :templates,
         
     | 
| 
      
 2 
     | 
    
         
            +
              :ssh_config, :type]
         
     | 
| 
      
 3 
     | 
    
         
            +
            relevant_options.each do |option|
         
     | 
| 
      
 4 
     | 
    
         
            +
              target_test "empty configuration settings #{option}" do |config|
         
     | 
| 
      
 5 
     | 
    
         
            +
                if config.key?(option) && config[option].nil?
         
     | 
| 
      
 6 
     | 
    
         
            +
                  next {
         
     | 
| 
      
 7 
     | 
    
         
            +
                    severity: :warning,
         
     | 
| 
      
 8 
     | 
    
         
            +
                    message: "config setting '#{option}' is set to nil/null, this might overwrite default settings"
         
     | 
| 
      
 9 
     | 
    
         
            +
                  }
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
            end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            target_test 'test if at least one shared directory exists' do |config|
         
     | 
| 
      
 15 
     | 
    
         
            +
              if config[:type] == 'rsync_with_versions' && (config[:shared].nil? || config[:shared].empty?)
         
     | 
| 
      
 16 
     | 
    
         
            +
                next {
         
     | 
| 
      
 17 
     | 
    
         
            +
                  severity: warning,
         
     | 
| 
      
 18 
     | 
    
         
            +
                  message: 'config does not contain a shared directory for versioning'
         
     | 
| 
      
 19 
     | 
    
         
            +
                }
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -43,3 +43,17 @@ namespace :test do 
     | 
|
| 
       43 
43 
     | 
    
         
             
                info "Feelin' fine." if error_count == 0
         
     | 
| 
       44 
44 
     | 
    
         
             
              end
         
     | 
| 
       45 
45 
     | 
    
         
             
            end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            if File.exist?("#{Fulmar::Infrastructure::Service::SSHConfigService::DEFAULT_CONFIG_FILE}.bak")
         
     | 
| 
      
 48 
     | 
    
         
            +
              namespace :revert do
         
     | 
| 
      
 49 
     | 
    
         
            +
                task :ssh_config do
         
     | 
| 
      
 50 
     | 
    
         
            +
                  config = Fulmar::Infrastructure::Service::SSHConfigService::DEFAULT_CONFIG_FILE
         
     | 
| 
      
 51 
     | 
    
         
            +
                  backup = "#{config}.bak"
         
     | 
| 
      
 52 
     | 
    
         
            +
                  temp = "#{config}.tmp"
         
     | 
| 
      
 53 
     | 
    
         
            +
                  FileUtils.cp config, temp
         
     | 
| 
      
 54 
     | 
    
         
            +
                  FileUtils.cp backup, config
         
     | 
| 
      
 55 
     | 
    
         
            +
                  FileUtils.mv temp, backup
         
     | 
| 
      
 56 
     | 
    
         
            +
                  Fulmar::Infrastructure::Service::SSHConfigService.new(config).show_diff
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -190,6 +190,8 @@ module Fulmar 
     | 
|
| 
       190 
190 
     | 
    
         
             
                      # @return [true, false] success
         
     | 
| 
       191 
191 
     | 
    
         
             
                      def add_shared
         
     | 
| 
       192 
192 
     | 
    
         
             
                        commands = [] # Collect all remote commands first, then execute them in one step to avoid reconnecting very often
         
     | 
| 
      
 193 
     | 
    
         
            +
                        return true if @config[:shared].nil? || @config[:shared].empty?
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
       193 
195 
     | 
    
         
             
                        @config[:shared].each do |path|
         
     | 
| 
       194 
196 
     | 
    
         
             
                          commands << "mkdir -p \"#{release_dir}/#{File.dirname(path)}\""
         
     | 
| 
       195 
197 
     | 
    
         | 
| 
         @@ -1,45 +1,81 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'fulmar/shell'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'pp'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'diffy'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'active_support/core_ext/object/blank'
         
     | 
| 
       2 
5 
     | 
    
         | 
| 
       3 
6 
     | 
    
         
             
            module Fulmar
         
     | 
| 
       4 
7 
     | 
    
         
             
              module Infrastructure
         
     | 
| 
       5 
8 
     | 
    
         
             
                module Service
         
     | 
| 
       6 
9 
     | 
    
         
             
                  # Adds entries to the ssh config and checks for existing ones
         
     | 
| 
       7 
10 
     | 
    
         
             
                  class SSHConfigService
         
     | 
| 
       8 
     | 
    
         
            -
                     
     | 
| 
       9 
     | 
    
         
            -
                     
     | 
| 
      
 11 
     | 
    
         
            +
                    DEFAULT_CONFIG_FILE = "#{ENV['HOME']}/.ssh/config"
         
     | 
| 
      
 12 
     | 
    
         
            +
                    DEFAULT_KNOWN_HOSTS_FILE = "#{ENV['HOME']}/.ssh/known_hosts"
         
     | 
| 
       10 
13 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                     
     | 
| 
      
 14 
     | 
    
         
            +
                    attr_accessor :config_file, :known_host_file, :quiet
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    def initialize(config, config_file = DEFAULT_CONFIG_FILE, known_hosts_file = DEFAULT_KNOWN_HOSTS_FILE)
         
     | 
| 
       12 
17 
     | 
    
         
             
                      @config = config
         
     | 
| 
      
 18 
     | 
    
         
            +
                      @config_file = config_file
         
     | 
| 
      
 19 
     | 
    
         
            +
                      @known_hosts_file = known_hosts_file
         
     | 
| 
      
 20 
     | 
    
         
            +
                      @quiet = false
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    def changed?
         
     | 
| 
      
 24 
     | 
    
         
            +
                      File.read(@config_file) != File.read("#{@config_file}.bak")
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    def diff(type = :color)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      before = File.read("#{@config_file}.bak")
         
     | 
| 
      
 29 
     | 
    
         
            +
                      after = File.read(@config_file)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      Diffy::Diff.new(before, after, context: 3).to_s(type)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    def show_diff
         
     | 
| 
      
 34 
     | 
    
         
            +
                      return if @quiet || !changed?
         
     | 
| 
      
 35 
     | 
    
         
            +
                      puts 'You ssh host configuration changed: '
         
     | 
| 
      
 36 
     | 
    
         
            +
                      puts '--------------- DIFF ---------------'
         
     | 
| 
      
 37 
     | 
    
         
            +
                      puts diff
         
     | 
| 
      
 38 
     | 
    
         
            +
                      puts '--------------- /DIFF --------------'
         
     | 
| 
      
 39 
     | 
    
         
            +
                      puts 'You can revert these changes by running "fulmar revert:ssh_config"'
         
     | 
| 
       13 
40 
     | 
    
         
             
                    end
         
     | 
| 
       14 
41 
     | 
    
         | 
| 
       15 
42 
     | 
    
         
             
                    def add_hosts
         
     | 
| 
      
 43 
     | 
    
         
            +
                      backup_file
         
     | 
| 
       16 
44 
     | 
    
         
             
                      @config.hosts.values.each do |data|
         
     | 
| 
       17 
45 
     | 
    
         
             
                        unless config_valid?(data)
         
     | 
| 
       18 
46 
     | 
    
         
             
                          puts "Skipping #{data[:hostname]}, config not sufficient." if @config[:debug]
         
     | 
| 
       19 
47 
     | 
    
         
             
                          next
         
     | 
| 
       20 
48 
     | 
    
         
             
                        end
         
     | 
| 
       21 
     | 
    
         
            -
                         
     | 
| 
       22 
     | 
    
         
            -
                          puts "Host #{data[:hostname]} exists, skipping..." if @config[:debug]
         
     | 
| 
       23 
     | 
    
         
            -
                        else
         
     | 
| 
       24 
     | 
    
         
            -
                          add_host(data[:hostname], data[:ssh_config])
         
     | 
| 
       25 
     | 
    
         
            -
                        end
         
     | 
| 
      
 49 
     | 
    
         
            +
                        edit_host(data[:hostname], data[:ssh_config])
         
     | 
| 
       26 
50 
     | 
    
         
             
                      end
         
     | 
| 
      
 51 
     | 
    
         
            +
                      show_diff
         
     | 
| 
       27 
52 
     | 
    
         
             
                    end
         
     | 
| 
       28 
53 
     | 
    
         | 
| 
       29 
54 
     | 
    
         
             
                    def remove_known_host(hostname)
         
     | 
| 
       30 
     | 
    
         
            -
                      input_file = File.open( 
     | 
| 
       31 
     | 
    
         
            -
                      output_file = File.open( 
     | 
| 
      
 55 
     | 
    
         
            +
                      input_file = File.open(@known_hosts_file, 'r')
         
     | 
| 
      
 56 
     | 
    
         
            +
                      output_file = File.open(@known_hosts_file + '.temp', 'w')
         
     | 
| 
       32 
57 
     | 
    
         
             
                      while (line = input_file.gets)
         
     | 
| 
       33 
58 
     | 
    
         
             
                        output_file.puts(line) unless /^\[?#{hostname.gsub('.', '\\.')}(?:\]:\d+)?[ ,]/.match(line)
         
     | 
| 
       34 
59 
     | 
    
         
             
                      end
         
     | 
| 
       35 
60 
     | 
    
         
             
                      input_file.close
         
     | 
| 
       36 
61 
     | 
    
         
             
                      output_file.close
         
     | 
| 
       37 
     | 
    
         
            -
                      FileUtils.mv( 
     | 
| 
      
 62 
     | 
    
         
            +
                      FileUtils.mv(@known_hosts_file + '.temp', @known_hosts_file)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    def edit_host(hostname, ssh_config)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      data = read_file
         
     | 
| 
      
 67 
     | 
    
         
            +
                      new_data = block_before(data.clone, hostname) +
         
     | 
| 
      
 68 
     | 
    
         
            +
                                 host_entry(hostname, ssh_config) +
         
     | 
| 
      
 69 
     | 
    
         
            +
                                 block_after(data, hostname)
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                      File.open(@config_file, 'w') do |file|
         
     | 
| 
      
 72 
     | 
    
         
            +
                        file.puts new_data.join("\n")
         
     | 
| 
      
 73 
     | 
    
         
            +
                      end
         
     | 
| 
       38 
74 
     | 
    
         
             
                    end
         
     | 
| 
       39 
75 
     | 
    
         | 
| 
       40 
76 
     | 
    
         
             
                    # Parses the users ssh config for an existing hostname
         
     | 
| 
       41 
77 
     | 
    
         
             
                    def host_exists?(hostname)
         
     | 
| 
       42 
     | 
    
         
            -
                      config_file = File.open( 
     | 
| 
      
 78 
     | 
    
         
            +
                      config_file = File.open(@config_file, 'r')
         
     | 
| 
       43 
79 
     | 
    
         
             
                      while (line = config_file.gets)
         
     | 
| 
       44 
80 
     | 
    
         
             
                        if /\s*Host #{hostname.gsub('.', '\\.')}\s*$/.match(line)
         
     | 
| 
       45 
81 
     | 
    
         
             
                          config_file.close
         
     | 
| 
         @@ -53,29 +89,108 @@ module Fulmar 
     | 
|
| 
       53 
89 
     | 
    
         
             
                    # Adds a host to the ssh config file
         
     | 
| 
       54 
90 
     | 
    
         
             
                    def add_host(hostname, ssh_config = {})
         
     | 
| 
       55 
91 
     | 
    
         
             
                      puts "Adding host #{hostname}..." if @config[:debug]
         
     | 
| 
       56 
     | 
    
         
            -
                      config_file = File.open( 
     | 
| 
      
 92 
     | 
    
         
            +
                      config_file = File.open(@config_file, 'a')
         
     | 
| 
       57 
93 
     | 
    
         | 
| 
       58 
94 
     | 
    
         
             
                      unless ssh_config[:IdentityFile].blank? or ssh_config[:IdentityFile][0, 1] == '/'
         
     | 
| 
       59 
95 
     | 
    
         
             
                        ssh_config[:IdentityFile] = @config.base_path + '/' + ssh_config[:IdentityFile]
         
     | 
| 
       60 
96 
     | 
    
         
             
                      end
         
     | 
| 
       61 
97 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                      config_file.puts  
     | 
| 
       63 
     | 
    
         
            -
                      config_file.puts "# Automatically generated by fulmar for project '#{@config.project.description}'"
         
     | 
| 
       64 
     | 
    
         
            -
                      config_file.puts "Host #{hostname}"
         
     | 
| 
       65 
     | 
    
         
            -
                      ssh_config.keys.each do |key|
         
     | 
| 
       66 
     | 
    
         
            -
                        value = ssh_config[key].to_s
         
     | 
| 
       67 
     | 
    
         
            -
                        value = "\"#{value.gsub('"', '\\"')}\"" if value.include?(' ')
         
     | 
| 
       68 
     | 
    
         
            -
                        config_file.puts "    #{key} #{value}"
         
     | 
| 
       69 
     | 
    
         
            -
                      end
         
     | 
| 
      
 98 
     | 
    
         
            +
                      config_file.puts host_entry(hostname, ssh_config)
         
     | 
| 
       70 
99 
     | 
    
         | 
| 
       71 
100 
     | 
    
         
             
                      config_file.close
         
     | 
| 
       72 
101 
     | 
    
         
             
                    end
         
     | 
| 
       73 
102 
     | 
    
         | 
| 
       74 
     | 
    
         
            -
                     
     | 
| 
      
 103 
     | 
    
         
            +
                    def backup_file
         
     | 
| 
      
 104 
     | 
    
         
            +
                      backup_filename = "#{@config_file}.bak"
         
     | 
| 
      
 105 
     | 
    
         
            +
                      FileUtils.cp @config_file, backup_filename
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                    protected
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                    def host_entry(hostname, ssh_config = {})
         
     | 
| 
      
 111 
     | 
    
         
            +
                      unless ssh_config[:IdentityFile].blank? or ssh_config[:IdentityFile][0, 1] == '/'
         
     | 
| 
      
 112 
     | 
    
         
            +
                        ssh_config[:IdentityFile] = @config.base_path + '/' + ssh_config[:IdentityFile]
         
     | 
| 
      
 113 
     | 
    
         
            +
                      end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                      entry = [
         
     | 
| 
      
 116 
     | 
    
         
            +
                        '', # Add some space between this and the second last entry
         
     | 
| 
      
 117 
     | 
    
         
            +
                        "# Automatically generated by fulmar for project '#{@config.project.description}'",
         
     | 
| 
      
 118 
     | 
    
         
            +
                        "Host #{hostname}"
         
     | 
| 
      
 119 
     | 
    
         
            +
                      ]
         
     | 
| 
      
 120 
     | 
    
         
            +
                      ssh_config.keys.each { |key| entry << "    #{key} #{escape_value(ssh_config[key])}" }
         
     | 
| 
      
 121 
     | 
    
         
            +
                      entry << ''
         
     | 
| 
      
 122 
     | 
    
         
            +
                      entry
         
     | 
| 
      
 123 
     | 
    
         
            +
                    end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    def escape_value(value)
         
     | 
| 
      
 126 
     | 
    
         
            +
                      value = value.to_s
         
     | 
| 
      
 127 
     | 
    
         
            +
                      value = "\"#{value.gsub('"', '\\"')}\"" if value.include?(' ')
         
     | 
| 
      
 128 
     | 
    
         
            +
                      value
         
     | 
| 
      
 129 
     | 
    
         
            +
                    end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                    def read_file
         
     | 
| 
      
 132 
     | 
    
         
            +
                      config_file_data = []
         
     | 
| 
      
 133 
     | 
    
         
            +
                      File.open(@config_file, 'r') do |file|
         
     | 
| 
      
 134 
     | 
    
         
            +
                        until file.eof?
         
     | 
| 
      
 135 
     | 
    
         
            +
                          config_file_data << file.gets.chomp
         
     | 
| 
      
 136 
     | 
    
         
            +
                        end
         
     | 
| 
      
 137 
     | 
    
         
            +
                      end
         
     | 
| 
      
 138 
     | 
    
         
            +
                      config_file_data
         
     | 
| 
      
 139 
     | 
    
         
            +
                    end
         
     | 
| 
       75 
140 
     | 
    
         | 
| 
       76 
141 
     | 
    
         
             
                    def config_valid?(host_config)
         
     | 
| 
       77 
142 
     | 
    
         
             
                      (!host_config[:hostname].blank? && !host_config[:ssh_config].nil?)
         
     | 
| 
       78 
143 
     | 
    
         
             
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                    def remove_trailing_newlines(data)
         
     | 
| 
      
 146 
     | 
    
         
            +
                      while !data.empty? && data.last.strip.empty?
         
     | 
| 
      
 147 
     | 
    
         
            +
                        data.pop
         
     | 
| 
      
 148 
     | 
    
         
            +
                      end
         
     | 
| 
      
 149 
     | 
    
         
            +
                      data
         
     | 
| 
      
 150 
     | 
    
         
            +
                    end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                    def block_before(data, hostname)
         
     | 
| 
      
 153 
     | 
    
         
            +
                      cache = []
         
     | 
| 
      
 154 
     | 
    
         
            +
                      before = []
         
     | 
| 
      
 155 
     | 
    
         
            +
                      data.each do |line|
         
     | 
| 
      
 156 
     | 
    
         
            +
                        if line.strip[0] == '#'
         
     | 
| 
      
 157 
     | 
    
         
            +
                          cache << line
         
     | 
| 
      
 158 
     | 
    
         
            +
                        else
         
     | 
| 
      
 159 
     | 
    
         
            +
                          if /^Host\s#{hostname}$/.match line.strip
         
     | 
| 
      
 160 
     | 
    
         
            +
                            return remove_trailing_newlines(before)
         
     | 
| 
      
 161 
     | 
    
         
            +
                          end
         
     | 
| 
      
 162 
     | 
    
         
            +
                          before += cache
         
     | 
| 
      
 163 
     | 
    
         
            +
                          cache = []
         
     | 
| 
      
 164 
     | 
    
         
            +
                          before << line
         
     | 
| 
      
 165 
     | 
    
         
            +
                        end
         
     | 
| 
      
 166 
     | 
    
         
            +
                      end
         
     | 
| 
      
 167 
     | 
    
         
            +
                      remove_trailing_newlines(before)
         
     | 
| 
      
 168 
     | 
    
         
            +
                    end
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                    def block_after(data, hostname)
         
     | 
| 
      
 171 
     | 
    
         
            +
                      data = data.drop_while { |i| !/^Host\s#{hostname}$/.match(i.strip) }
         
     | 
| 
      
 172 
     | 
    
         
            +
                      return [] if data.empty?
         
     | 
| 
      
 173 
     | 
    
         
            +
                      data.shift
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                      after = []
         
     | 
| 
      
 176 
     | 
    
         
            +
                      cache = []
         
     | 
| 
      
 177 
     | 
    
         
            +
                      write = false
         
     | 
| 
      
 178 
     | 
    
         
            +
                      data.each do |line|
         
     | 
| 
      
 179 
     | 
    
         
            +
                        if line.strip[0] == '#'
         
     | 
| 
      
 180 
     | 
    
         
            +
                          cache << line
         
     | 
| 
      
 181 
     | 
    
         
            +
                        else
         
     | 
| 
      
 182 
     | 
    
         
            +
                          if /^Host\s/.match line.strip
         
     | 
| 
      
 183 
     | 
    
         
            +
                            write = true
         
     | 
| 
      
 184 
     | 
    
         
            +
                          end
         
     | 
| 
      
 185 
     | 
    
         
            +
                          if write
         
     | 
| 
      
 186 
     | 
    
         
            +
                            after += cache
         
     | 
| 
      
 187 
     | 
    
         
            +
                            after << line
         
     | 
| 
      
 188 
     | 
    
         
            +
                          end
         
     | 
| 
      
 189 
     | 
    
         
            +
                          cache = []
         
     | 
| 
      
 190 
     | 
    
         
            +
                        end
         
     | 
| 
      
 191 
     | 
    
         
            +
                      end
         
     | 
| 
      
 192 
     | 
    
         
            +
                      remove_trailing_newlines(after)
         
     | 
| 
      
 193 
     | 
    
         
            +
                    end
         
     | 
| 
       79 
194 
     | 
    
         
             
                  end
         
     | 
| 
       80 
195 
     | 
    
         
             
                end
         
     | 
| 
       81 
196 
     | 
    
         
             
              end
         
     | 
    
        data/lib/fulmar/version.rb
    CHANGED
    
    
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Host testhost examplehost foo-host
         
     | 
| 
      
 2 
     | 
    
         
            +
                SendEnv FOO
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            Host testhost
         
     | 
| 
      
 5 
     | 
    
         
            +
                Hostname testhost.example.om
         
     | 
| 
      
 6 
     | 
    
         
            +
                Port 1234
         
     | 
| 
      
 7 
     | 
    
         
            +
                User nobody
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            # trailing comment 4711
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            # host comment 0815
         
     | 
| 
      
 12 
     | 
    
         
            +
            Host examplehost
         
     | 
| 
      
 13 
     | 
    
         
            +
                Hostname example.com
         
     | 
| 
      
 14 
     | 
    
         
            +
                IdentityFile /tmp/private.key
         
     | 
| 
      
 15 
     | 
    
         
            +
                # ProxyCommand whoop whoop whoop
         
     | 
| 
      
 16 
     | 
    
         
            +
                Port 4321
         
     | 
| 
         
            File without changes
         
     | 
| 
         @@ -0,0 +1,122 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fulmar/infrastructure/service/ssh_config_service'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            DEFAULT_CONFIG_FILE = "#{File.dirname(__FILE__)}/../../../fixtures/ssh_config/default_config.txt"
         
     | 
| 
      
 4 
     | 
    
         
            +
            DEFAULT_KNOWN_HOSTS_FILE = "#{File.dirname(__FILE__)}/../../../fixtures/ssh_config/default_known_hosts.txt"
         
     | 
| 
      
 5 
     | 
    
         
            +
            CONFIG_TEMP_FILE = "#{File.dirname(__FILE__)}/../../../fixtures/ssh_config/temp_config.txt"
         
     | 
| 
      
 6 
     | 
    
         
            +
            KNOWN_HOSTS_TEMP_FILE = "#{File.dirname(__FILE__)}/../../../fixtures/ssh_config/temp_known_hosts.txt"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            class MockProject
         
     | 
| 
      
 9 
     | 
    
         
            +
              attr_accessor :name, :description
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            class MockConfig
         
     | 
| 
      
 13 
     | 
    
         
            +
              def hosts
         
     | 
| 
      
 14 
     | 
    
         
            +
                []
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def project
         
     | 
| 
      
 18 
     | 
    
         
            +
                MockProject.new
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            def new_service(config_file = DEFAULT_CONFIG_FILE, known_hosts_file = DEFAULT_KNOWN_HOSTS_FILE)
         
     | 
| 
      
 23 
     | 
    
         
            +
              FileUtils.cp config_file, CONFIG_TEMP_FILE
         
     | 
| 
      
 24 
     | 
    
         
            +
              FileUtils.cp known_hosts_file, KNOWN_HOSTS_TEMP_FILE
         
     | 
| 
      
 25 
     | 
    
         
            +
              service = Fulmar::Infrastructure::Service::SSHConfigService.new(MockConfig.new, CONFIG_TEMP_FILE, KNOWN_HOSTS_TEMP_FILE)
         
     | 
| 
      
 26 
     | 
    
         
            +
              service.backup_file
         
     | 
| 
      
 27 
     | 
    
         
            +
              service
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            describe Fulmar::Infrastructure::Service::SSHConfigService do
         
     | 
| 
      
 31 
     | 
    
         
            +
              after :each do
         
     | 
| 
      
 32 
     | 
    
         
            +
                FileUtils.rm "#{CONFIG_TEMP_FILE}.bak"
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              describe '#edit_host' do
         
     | 
| 
      
 36 
     | 
    
         
            +
                it 'should create a backup file' do
         
     | 
| 
      
 37 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 38 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 39 
     | 
    
         
            +
                  expect(File.exist?("#{CONFIG_TEMP_FILE}.bak")).to be true
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                it 'should add a host' do
         
     | 
| 
      
 43 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 44 
     | 
    
         
            +
                  service.edit_host('test_new', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 45 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE)).to include('testhost')
         
     | 
| 
      
 46 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE)).to include('test_new')
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                it 'should replace an existing host' do
         
     | 
| 
      
 50 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 51 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 52 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE)).not_to include('1234')
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                it 'should keep a trailing comment above the edited host entry' do
         
     | 
| 
      
 56 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 57 
     | 
    
         
            +
                  service.edit_host('examplehost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 58 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE)).to include('trailing comment 4711')
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                it 'should keep replace the host comment above the edited host entry' do
         
     | 
| 
      
 62 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 63 
     | 
    
         
            +
                  service.edit_host('examplehost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 64 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE)).not_to include('host comment 0815')
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                it 'should keep the number of hosts when replacing one' do
         
     | 
| 
      
 68 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 69 
     | 
    
         
            +
                  service.edit_host('examplehost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 70 
     | 
    
         
            +
                  before = File.read("#{CONFIG_TEMP_FILE}.bak").scan(/Host /).size
         
     | 
| 
      
 71 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE).scan(/Host /).size).to eql(before)
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                it 'should increase the number of hosts when adding one' do
         
     | 
| 
      
 75 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 76 
     | 
    
         
            +
                  service.edit_host('examplehost2', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 77 
     | 
    
         
            +
                  before = File.read("#{CONFIG_TEMP_FILE}.bak").scan(/Host /).size
         
     | 
| 
      
 78 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE).scan(/Host /).size).to eql(before + 1)
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                it 'should keep comments within unaffected host blocks' do
         
     | 
| 
      
 82 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 83 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 84 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE)).to include('whoop whoop whoop')
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                it 'should not add more new lines with every call' do
         
     | 
| 
      
 88 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 89 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 90 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 91 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 92 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 93 
     | 
    
         
            +
                  expect(File.read(CONFIG_TEMP_FILE)).not_to include("\n\n\n")
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                it 'should not change the file on a second run' do
         
     | 
| 
      
 97 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 98 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 99 
     | 
    
         
            +
                  service.backup_file
         
     | 
| 
      
 100 
     | 
    
         
            +
                  service.edit_host('testhost', {'Hostname' => '123.example.com'})
         
     | 
| 
      
 101 
     | 
    
         
            +
                  before = File.read("#{CONFIG_TEMP_FILE}.bak")
         
     | 
| 
      
 102 
     | 
    
         
            +
                  after = File.read(CONFIG_TEMP_FILE)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  expect(after).to eql(before)
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
              describe '#remove_trailing_newlines' do
         
     | 
| 
      
 108 
     | 
    
         
            +
                it 'should remove multiple newlines from an array' do
         
     | 
| 
      
 109 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 110 
     | 
    
         
            +
                  test_array = ['test', '', '', '']
         
     | 
| 
      
 111 
     | 
    
         
            +
                  result = service.send(:remove_trailing_newlines, test_array)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  expect(result).to eql(['test'])
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                it 'should not touch non-empty lines' do
         
     | 
| 
      
 116 
     | 
    
         
            +
                  service = new_service
         
     | 
| 
      
 117 
     | 
    
         
            +
                  test_array = %w[test foo bar]
         
     | 
| 
      
 118 
     | 
    
         
            +
                  result = service.send(:remove_trailing_newlines, test_array)
         
     | 
| 
      
 119 
     | 
    
         
            +
                  expect(result).to eql(test_array)
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: fulmar
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 2.0 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 2.1.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Jonas Siegl
         
     | 
| 
         @@ -9,7 +9,7 @@ authors: 
     | 
|
| 
       9 
9 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       10 
10 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       11 
11 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       12 
     | 
    
         
            -
            date: 2017- 
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2017-07-25 00:00:00.000000000 Z
         
     | 
| 
       13 
13 
     | 
    
         
             
            dependencies:
         
     | 
| 
       14 
14 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       15 
15 
     | 
    
         
             
              name: bundler
         
     | 
| 
         @@ -87,6 +87,20 @@ dependencies: 
     | 
|
| 
       87 
87 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       88 
88 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       89 
89 
     | 
    
         
             
                    version: '5.0'
         
     | 
| 
      
 90 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 91 
     | 
    
         
            +
              name: diffy
         
     | 
| 
      
 92 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 93 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 94 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 95 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 96 
     | 
    
         
            +
                    version: '3.2'
         
     | 
| 
      
 97 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 98 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 99 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 100 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 101 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 102 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 103 
     | 
    
         
            +
                    version: '3.2'
         
     | 
| 
       90 
104 
     | 
    
         
             
            description: Fulmar is a task manager for deployments.
         
     | 
| 
       91 
105 
     | 
    
         
             
            email:
         
     | 
| 
       92 
106 
     | 
    
         
             
            - j.siegl@core4.de
         
     | 
| 
         @@ -113,6 +127,7 @@ files: 
     | 
|
| 
       113 
127 
     | 
    
         
             
            - lib/fulmar/domain/service/config_test_service.rb
         
     | 
| 
       114 
128 
     | 
    
         
             
            - lib/fulmar/domain/service/config_tests/compatibility.rb
         
     | 
| 
       115 
129 
     | 
    
         
             
            - lib/fulmar/domain/service/config_tests/hosts.rb
         
     | 
| 
      
 130 
     | 
    
         
            +
            - lib/fulmar/domain/service/config_tests/misc.rb
         
     | 
| 
       116 
131 
     | 
    
         
             
            - lib/fulmar/domain/service/config_tests/paths.rb
         
     | 
| 
       117 
132 
     | 
    
         
             
            - lib/fulmar/domain/service/config_tests/project.rb
         
     | 
| 
       118 
133 
     | 
    
         
             
            - lib/fulmar/domain/service/configuration_service.rb
         
     | 
| 
         @@ -139,9 +154,12 @@ files: 
     | 
|
| 
       139 
154 
     | 
    
         
             
            - lib/fulmar/service/helper_service.rb
         
     | 
| 
       140 
155 
     | 
    
         
             
            - lib/fulmar/task_manager.rb
         
     | 
| 
       141 
156 
     | 
    
         
             
            - lib/fulmar/version.rb
         
     | 
| 
      
 157 
     | 
    
         
            +
            - spec/fixtures/ssh_config/default_config.txt
         
     | 
| 
      
 158 
     | 
    
         
            +
            - spec/fixtures/ssh_config/default_known_hosts.txt
         
     | 
| 
       142 
159 
     | 
    
         
             
            - spec/lib/fulmar/model/configuration_spec.rb
         
     | 
| 
       143 
160 
     | 
    
         
             
            - spec/lib/fulmar/service/helper_service_spec.rb
         
     | 
| 
       144 
161 
     | 
    
         
             
            - spec/lib/fulmar/service/plugin_service_spec.rb
         
     | 
| 
      
 162 
     | 
    
         
            +
            - spec/lib/fulmar/service/ssh_config_service_spec.rb
         
     | 
| 
       145 
163 
     | 
    
         
             
            - spec/spec_helper.rb
         
     | 
| 
       146 
164 
     | 
    
         
             
            homepage: https://github.com/CORE4/fulmar
         
     | 
| 
       147 
165 
     | 
    
         
             
            licenses:
         
     | 
| 
         @@ -168,7 +186,10 @@ signing_key: 
     | 
|
| 
       168 
186 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       169 
187 
     | 
    
         
             
            summary: A deployment task manager.
         
     | 
| 
       170 
188 
     | 
    
         
             
            test_files:
         
     | 
| 
      
 189 
     | 
    
         
            +
            - spec/fixtures/ssh_config/default_config.txt
         
     | 
| 
      
 190 
     | 
    
         
            +
            - spec/fixtures/ssh_config/default_known_hosts.txt
         
     | 
| 
       171 
191 
     | 
    
         
             
            - spec/lib/fulmar/model/configuration_spec.rb
         
     | 
| 
       172 
192 
     | 
    
         
             
            - spec/lib/fulmar/service/helper_service_spec.rb
         
     | 
| 
       173 
193 
     | 
    
         
             
            - spec/lib/fulmar/service/plugin_service_spec.rb
         
     | 
| 
      
 194 
     | 
    
         
            +
            - spec/lib/fulmar/service/ssh_config_service_spec.rb
         
     | 
| 
       174 
195 
     | 
    
         
             
            - spec/spec_helper.rb
         
     |