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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1b386982a3ab26556fd6efa59581e8431d768234
4
- data.tar.gz: 8c06a406efb3477836ceba7c40ed3d418949ae7f
3
+ metadata.gz: b5773cc97e0e9d8615d47d72ee31a9d9b0438e6b
4
+ data.tar.gz: 9f2702193748a1561335ef13eacad2ba07acd6d6
5
5
  SHA512:
6
- metadata.gz: 4d4fd4a803b9c83b4a74dd3042501567ce731c57ee6b414094205f8ebe6d5ae83c62395e4d788d680c54ed9218181ee73570f15c79cda5faa24513fed811f3b8
7
- data.tar.gz: cd186eb2339d4b36ea5dc171c849201b34f4cd7308454bb8ddf55de431d7e2614676d9413582d3344260fe5dc2dbcf9e2d13955e2575df88794c8da9215fbe66
6
+ metadata.gz: eff45b616da762576e948fe24d2c0ebc201a8f3cea1e134c687c5e6d5d77db2ee4e5bd7a224c82720d737eaf046f6caef7b463deafa4a330f8d0554661f9ad54
7
+ data.tar.gz: 0aeedda8a394b79420f082ebc3a91c2ad08ab4e20e2c9217ba32e3a0597bab662d2f243afb8ce4986690ffcfcef220e4f175edc1531e831fef60d9d5e67bafb4
data/.gitignore CHANGED
@@ -14,6 +14,8 @@
14
14
  mkmf.log
15
15
  /.idea/
16
16
  *.gem
17
+ *.bak
18
+ spec/fixtures/ssh_config/temp_*
17
19
  /.vagrant
18
20
  /Vagrantfile
19
21
  /vendor
data/fulmar.gemspec CHANGED
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
  spec.add_runtime_dependency 'fulmar-shell', '~>1', '>=1.7.0'
25
25
  spec.add_runtime_dependency 'colorize', '~>0'
26
26
  spec.add_runtime_dependency 'activesupport', '~> 5.0'
27
+ spec.add_runtime_dependency 'diffy', '~> 3.2'
27
28
  end
@@ -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
- each { |_env, _target, data| return true unless data[key].nil? }
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
- CONFIG_FILE = "#{ENV['HOME']}/.ssh/config"
9
- KNOWN_HOST_FILE = "#{ENV['HOME']}/.ssh/known_hosts"
11
+ DEFAULT_CONFIG_FILE = "#{ENV['HOME']}/.ssh/config"
12
+ DEFAULT_KNOWN_HOSTS_FILE = "#{ENV['HOME']}/.ssh/known_hosts"
10
13
 
11
- def initialize(config)
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
- if host_exists?(data[:hostname])
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(KNOWN_HOST_FILE, 'r')
31
- output_file = File.open(KNOWN_HOST_FILE + '.temp', 'w')
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(KNOWN_HOST_FILE + '.temp', KNOWN_HOST_FILE)
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(CONFIG_FILE, 'r')
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(CONFIG_FILE, 'a')
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 "\n" # Add some space between this and the second last entry
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
- private
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
@@ -1,4 +1,4 @@
1
1
  # Provides a global version number
2
2
  module Fulmar
3
- VERSION = '2.0.2'
3
+ VERSION = '2.1.0'
4
4
  end
@@ -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.2
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-05-31 00:00:00.000000000 Z
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