vagrant-ssh-config-manager 0.8.2
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 +7 -0
- data/.bundle/config +2 -0
- data/.gitignore +5 -0
- data/Gemfile +15 -0
- data/README.md +267 -0
- data/Rakefile +22 -0
- data/lib/vagrant-ssh-config-manager/action/destroy.rb +84 -0
- data/lib/vagrant-ssh-config-manager/action/halt.rb +68 -0
- data/lib/vagrant-ssh-config-manager/action/provision.rb +82 -0
- data/lib/vagrant-ssh-config-manager/action/reload.rb +106 -0
- data/lib/vagrant-ssh-config-manager/action/up.rb +99 -0
- data/lib/vagrant-ssh-config-manager/config.rb +194 -0
- data/lib/vagrant-ssh-config-manager/file_locker.rb +140 -0
- data/lib/vagrant-ssh-config-manager/file_manager.rb +245 -0
- data/lib/vagrant-ssh-config-manager/include_manager.rb +251 -0
- data/lib/vagrant-ssh-config-manager/plugin.rb +56 -0
- data/lib/vagrant-ssh-config-manager/ssh_config_manager.rb +2150 -0
- data/lib/vagrant-ssh-config-manager/ssh_info_extractor.rb +443 -0
- data/lib/vagrant-ssh-config-manager/version.rb +5 -0
- data/lib/vagrant-ssh-config-manager.rb +30 -0
- data/vagrant-ssh-config-manager.gemspec +35 -0
- metadata +133 -0
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'log4r'
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module SshConfigManager
|
7
|
+
class IncludeManager
|
8
|
+
PLUGIN_MARKER_START = "# BEGIN vagrant-ssh-config-manager"
|
9
|
+
PLUGIN_MARKER_END = "# END vagrant-ssh-config-manager"
|
10
|
+
|
11
|
+
# Initialize IncludeManager with configuration
|
12
|
+
def initialize(config)
|
13
|
+
@config = config
|
14
|
+
@logger = Log4r::Logger.new("vagrant::plugins::sshconfigmanager::includemanager")
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the SSH config file path (always ~/.ssh/config)
|
18
|
+
def ssh_config_file
|
19
|
+
@ssh_config_file ||= File.expand_path("~/.ssh/config")
|
20
|
+
end
|
21
|
+
|
22
|
+
# Check if Include directive exists in main SSH config
|
23
|
+
def include_directive_exists?
|
24
|
+
return false unless File.exist?(ssh_config_file)
|
25
|
+
|
26
|
+
content = File.read(ssh_config_file)
|
27
|
+
include_pattern = /^Include\s+#{Regexp.escape(@config.ssh_config_dir)}/
|
28
|
+
content.match?(include_pattern)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add Include directive to main SSH config file
|
32
|
+
def add_include_directive
|
33
|
+
return false unless @config.manage_includes
|
34
|
+
return true if include_directive_exists?
|
35
|
+
|
36
|
+
begin
|
37
|
+
# Create backup before modifying
|
38
|
+
create_backup
|
39
|
+
|
40
|
+
# Add Include directive at the beginning of file
|
41
|
+
add_include_to_config
|
42
|
+
|
43
|
+
@logger.info("Added Include directive for #{@config.ssh_config_dir} to ~/.ssh/config")
|
44
|
+
true
|
45
|
+
rescue => e
|
46
|
+
@logger.error("Failed to add Include directive: #{e.message}")
|
47
|
+
restore_backup
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Remove Include directive from main SSH config file
|
53
|
+
def remove_include_directive
|
54
|
+
return false unless @config.manage_includes
|
55
|
+
return true unless include_directive_exists?
|
56
|
+
|
57
|
+
begin
|
58
|
+
# Create backup before modifying
|
59
|
+
create_backup
|
60
|
+
|
61
|
+
# Remove Include directive
|
62
|
+
remove_include_from_config
|
63
|
+
|
64
|
+
@logger.info("Removed Include directive for #{@config.ssh_config_dir} from ~/.ssh/config")
|
65
|
+
true
|
66
|
+
rescue => e
|
67
|
+
@logger.error("Failed to remove Include directive: #{e.message}")
|
68
|
+
restore_backup
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check if Include directive should be removed (no config files exist)
|
74
|
+
def should_remove_include_directive?
|
75
|
+
return false unless @config.cleanup_empty_dir
|
76
|
+
return false unless Dir.exist?(@config.ssh_config_dir)
|
77
|
+
|
78
|
+
# Check if directory is empty (excluding . and ..)
|
79
|
+
entries = Dir.entries(@config.ssh_config_dir) - %w[. ..]
|
80
|
+
config_files = entries.select { |file| file.end_with?('.conf') }
|
81
|
+
config_files.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Manage Include directive based on current state
|
85
|
+
def manage_include_directive
|
86
|
+
return unless @config.manage_includes
|
87
|
+
|
88
|
+
if should_remove_include_directive?
|
89
|
+
remove_include_directive
|
90
|
+
elsif Dir.exist?(@config.ssh_config_dir) && !Dir.entries(@config.ssh_config_dir).select { |f| f.end_with?('.conf') }.empty?
|
91
|
+
add_include_directive
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Parse SSH config file and find optimal location for Include
|
96
|
+
def find_include_location(content)
|
97
|
+
lines = content.lines
|
98
|
+
|
99
|
+
# Look for existing Include directives to place ours with them
|
100
|
+
include_line_index = lines.find_index { |line| line.strip.start_with?('Include ') }
|
101
|
+
|
102
|
+
if include_line_index
|
103
|
+
# Place before other Include directives
|
104
|
+
include_line_index
|
105
|
+
else
|
106
|
+
# Place at the beginning of file, after any initial comments
|
107
|
+
comment_end = 0
|
108
|
+
lines.each_with_index do |line, index|
|
109
|
+
if line.strip.empty? || line.strip.start_with?('#')
|
110
|
+
comment_end = index + 1
|
111
|
+
else
|
112
|
+
break
|
113
|
+
end
|
114
|
+
end
|
115
|
+
comment_end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check if main SSH config file is write-protected
|
120
|
+
def main_config_writable?
|
121
|
+
return false unless File.exist?(ssh_config_file)
|
122
|
+
File.writable?(ssh_config_file)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Handle edge case: empty main config file
|
126
|
+
def handle_empty_main_config
|
127
|
+
unless File.exist?(ssh_config_file)
|
128
|
+
# Create empty SSH config file with proper permissions
|
129
|
+
FileUtils.mkdir_p(File.dirname(ssh_config_file), mode: 0700)
|
130
|
+
File.write(ssh_config_file, "")
|
131
|
+
File.chmod(0600, ssh_config_file)
|
132
|
+
@logger.info("Created empty SSH config file: #{ssh_config_file}")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Validate SSH config file format
|
137
|
+
def validate_main_config
|
138
|
+
return true unless File.exist?(ssh_config_file)
|
139
|
+
|
140
|
+
begin
|
141
|
+
content = File.read(ssh_config_file)
|
142
|
+
# Basic validation - check for severely malformed config
|
143
|
+
lines = content.lines
|
144
|
+
lines.each_with_index do |line, index|
|
145
|
+
stripped = line.strip
|
146
|
+
next if stripped.empty? || stripped.start_with?('#')
|
147
|
+
|
148
|
+
# Check for basic SSH config format issues
|
149
|
+
if stripped.include?("\t") && stripped.start_with?("Host ")
|
150
|
+
@logger.warn("Potential SSH config format issue at line #{index + 1}: tabs in Host directive")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
true
|
154
|
+
rescue => e
|
155
|
+
@logger.error("SSH config validation failed: #{e.message}")
|
156
|
+
false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
# Create backup of main SSH config file
|
163
|
+
def create_backup
|
164
|
+
backup_path = "#{ssh_config_file}.vagrant-ssh-config-manager.backup"
|
165
|
+
FileUtils.cp(ssh_config_file, backup_path) if File.exist?(ssh_config_file)
|
166
|
+
@backup_path = backup_path
|
167
|
+
end
|
168
|
+
|
169
|
+
# Restore backup of main SSH config file
|
170
|
+
def restore_backup
|
171
|
+
if @backup_path && File.exist?(@backup_path)
|
172
|
+
FileUtils.cp(@backup_path, ssh_config_file)
|
173
|
+
File.delete(@backup_path)
|
174
|
+
@logger.info("Restored SSH config from backup")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Add Include directive to SSH config
|
179
|
+
def add_include_to_config
|
180
|
+
handle_empty_main_config
|
181
|
+
|
182
|
+
content = File.exist?(ssh_config_file) ? File.read(ssh_config_file) : ""
|
183
|
+
lines = content.lines
|
184
|
+
|
185
|
+
# Find optimal location for Include
|
186
|
+
insert_index = find_include_location(content)
|
187
|
+
|
188
|
+
# Create Include directive with plugin markers
|
189
|
+
include_lines = [
|
190
|
+
"#{PLUGIN_MARKER_START}\n",
|
191
|
+
"Include #{@config.ssh_config_dir}/*.conf\n",
|
192
|
+
"#{PLUGIN_MARKER_END}\n",
|
193
|
+
"\n"
|
194
|
+
]
|
195
|
+
|
196
|
+
# Insert Include directive
|
197
|
+
lines.insert(insert_index, *include_lines)
|
198
|
+
|
199
|
+
# Write back to file atomically
|
200
|
+
write_config_atomically(lines.join)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Remove Include directive from SSH config
|
204
|
+
def remove_include_from_config
|
205
|
+
return unless File.exist?(ssh_config_file)
|
206
|
+
|
207
|
+
content = File.read(ssh_config_file)
|
208
|
+
lines = content.lines
|
209
|
+
|
210
|
+
# Find and remove plugin-managed Include directive
|
211
|
+
start_index = lines.find_index { |line| line.strip == PLUGIN_MARKER_START.strip }
|
212
|
+
end_index = lines.find_index { |line| line.strip == PLUGIN_MARKER_END.strip }
|
213
|
+
|
214
|
+
if start_index && end_index && end_index > start_index
|
215
|
+
# Remove lines between markers (inclusive)
|
216
|
+
lines.slice!(start_index..end_index)
|
217
|
+
|
218
|
+
# Remove trailing empty line if it exists
|
219
|
+
if lines[start_index] && lines[start_index].strip.empty?
|
220
|
+
lines.delete_at(start_index)
|
221
|
+
end
|
222
|
+
else
|
223
|
+
# Fallback: remove any Include directive for our directory
|
224
|
+
lines.reject! do |line|
|
225
|
+
line.strip.match?(/^Include\s+#{Regexp.escape(@config.ssh_config_dir)}/)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Write back to file atomically
|
230
|
+
write_config_atomically(lines.join)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Write SSH config file atomically
|
234
|
+
def write_config_atomically(content)
|
235
|
+
temp_file = Tempfile.new(File.basename(ssh_config_file), File.dirname(ssh_config_file))
|
236
|
+
begin
|
237
|
+
temp_file.write(content)
|
238
|
+
temp_file.close
|
239
|
+
|
240
|
+
# Set proper permissions before moving
|
241
|
+
File.chmod(0600, temp_file.path)
|
242
|
+
|
243
|
+
# Atomic move
|
244
|
+
FileUtils.mv(temp_file.path, ssh_config_file)
|
245
|
+
ensure
|
246
|
+
temp_file.unlink if temp_file && File.exist?(temp_file.path)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "vagrant"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module SshConfigManager
|
5
|
+
class Plugin < Vagrant.plugin("2")
|
6
|
+
name "SSH Config Manager"
|
7
|
+
description <<-DESC
|
8
|
+
This plugin automatically manages SSH configurations by leveraging Vagrant's
|
9
|
+
internal SSH knowledge. It creates and maintains SSH config entries when VMs
|
10
|
+
are started and cleans them up when VMs are destroyed.
|
11
|
+
DESC
|
12
|
+
|
13
|
+
# Register the configuration class
|
14
|
+
config :sshconfigmanager do
|
15
|
+
require "vagrant-ssh-config-manager/config"
|
16
|
+
Config
|
17
|
+
end
|
18
|
+
|
19
|
+
# Hook into various Vagrant actions
|
20
|
+
action_hook(:ssh_config_manager, :machine_action_up) do |hook|
|
21
|
+
require "vagrant-ssh-config-manager/action/up"
|
22
|
+
hook.after(Vagrant::Action::Builtin::WaitForCommunicator, Action::Up)
|
23
|
+
end
|
24
|
+
|
25
|
+
action_hook(:ssh_config_manager, :machine_action_destroy) do |hook|
|
26
|
+
require "vagrant-ssh-config-manager/action/destroy"
|
27
|
+
hook.before(Vagrant::Action::Builtin::DestroyConfirm, Action::Destroy)
|
28
|
+
end
|
29
|
+
|
30
|
+
action_hook(:ssh_config_manager, :machine_action_reload) do |hook|
|
31
|
+
require "vagrant-ssh-config-manager/action/reload"
|
32
|
+
hook.after(Vagrant::Action::Builtin::WaitForCommunicator, Action::Reload)
|
33
|
+
end
|
34
|
+
|
35
|
+
action_hook(:ssh_config_manager, :machine_action_halt) do |hook|
|
36
|
+
require "vagrant-ssh-config-manager/action/halt"
|
37
|
+
hook.before(Vagrant::Action::Builtin::GracefulHalt, Action::Halt)
|
38
|
+
end
|
39
|
+
|
40
|
+
action_hook(:ssh_config_manager, :machine_action_suspend) do |hook|
|
41
|
+
require "vagrant-ssh-config-manager/action/halt"
|
42
|
+
hook.before(Vagrant::Action::Builtin::Suspend, Action::Halt)
|
43
|
+
end
|
44
|
+
|
45
|
+
action_hook(:ssh_config_manager, :machine_action_resume) do |hook|
|
46
|
+
require "vagrant-ssh-config-manager/action/up"
|
47
|
+
hook.after(Vagrant::Action::Builtin::Resume, Action::Up)
|
48
|
+
end
|
49
|
+
|
50
|
+
action_hook(:ssh_config_manager, :machine_action_provision) do |hook|
|
51
|
+
require "vagrant-ssh-config-manager/action/provision"
|
52
|
+
hook.after(Vagrant::Action::Builtin::Provision, Action::Provision)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|