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.
@@ -0,0 +1,443 @@
1
+ module VagrantPlugins
2
+ module SshConfigManager
3
+ class SshInfoExtractor
4
+ def initialize(machine)
5
+ @machine = machine
6
+ @logger = Log4r::Logger.new("vagrant::plugins::ssh_config_manager::ssh_info_extractor")
7
+ end
8
+
9
+ # Extract SSH information from Vagrant's internal APIs
10
+ # This replicates what 'vagrant ssh-config' does but using internal methods
11
+ def extract_ssh_info
12
+ begin
13
+ ssh_info = @machine.ssh_info
14
+ return nil if ssh_info.nil?
15
+
16
+ # Additional validation for SSH info completeness
17
+ return nil unless valid_ssh_info?(ssh_info)
18
+
19
+ # Get the SSH configuration similar to what vagrant ssh-config provides
20
+ config = build_ssh_config(ssh_info)
21
+
22
+ @logger.info("Extracted SSH info for machine: #{@machine.name}") if @logger
23
+ config
24
+ rescue Vagrant::Errors::SSHNotReady => e
25
+ @logger.debug("SSH not ready for machine #{@machine.name}: #{e.message}") if @logger
26
+ nil
27
+ rescue Vagrant::Errors::SSHUnavailable => e
28
+ @logger.debug("SSH unavailable for machine #{@machine.name}: #{e.message}") if @logger
29
+ nil
30
+ rescue => e
31
+ @logger.warn("Failed to extract SSH info for machine #{@machine.name}: #{e.message}") if @logger
32
+ nil
33
+ end
34
+ end
35
+
36
+ # Check if the machine supports SSH with comprehensive validation
37
+ def ssh_capable?
38
+ # Simplified check - if machine is running and has SSH info, assume SSH is available
39
+ @machine &&
40
+ @machine.state &&
41
+ @machine.state.id == :running &&
42
+ @machine.ssh_info &&
43
+ ssh_communicator?
44
+ rescue => e
45
+ @logger.debug("SSH capability check failed: #{e.message}") if @logger
46
+ false
47
+ end
48
+
49
+ # Comprehensive check for SSH support
50
+ def machine_supports_ssh?
51
+ # Basic checks
52
+ return false unless @machine
53
+ return false unless @machine.communicate_ready?
54
+
55
+ # Check if machine state supports SSH
56
+ return false unless machine_state_supports_ssh?
57
+
58
+ # Check if communicator is SSH-based
59
+ return false unless ssh_communicator?
60
+
61
+ # Check if provider supports SSH
62
+ return false unless provider_supports_ssh?
63
+
64
+ true
65
+ rescue => e
66
+ @logger.debug("Machine SSH support check failed: #{e.message}")
67
+ false
68
+ end
69
+
70
+ private
71
+
72
+ # Check if machine state allows SSH connections
73
+ def machine_state_supports_ssh?
74
+ state = @machine.state
75
+ return false unless state
76
+
77
+ # Common states that support SSH
78
+ ssh_ready_states = [:running, :up, :active, :created]
79
+ ssh_ready_states.include?(state.id)
80
+ rescue
81
+ false
82
+ end
83
+
84
+ # Check if the communicator is SSH-based
85
+ def ssh_communicator?
86
+ communicator = @machine.config.vm.communicator
87
+ communicator.nil? || communicator == :ssh
88
+ rescue
89
+ # Default to assuming SSH if we can't determine
90
+ true
91
+ end
92
+
93
+ # Check if the provider supports SSH
94
+ def provider_supports_ssh?
95
+ provider_name = @machine.provider_name
96
+ return true unless provider_name
97
+
98
+ # List of providers known to support SSH
99
+ ssh_providers = [
100
+ :virtualbox, :vmware_desktop, :vmware_fusion, :vmware_workstation,
101
+ :libvirt, :kvm, :qemu, :parallels, :hyper_v, :lxc, :docker,
102
+ :aws, :azure, :google, :digitalocean, :linode, :vultr
103
+ ]
104
+
105
+ # Providers known to NOT support SSH
106
+ non_ssh_providers = [:winrm]
107
+
108
+ return false if non_ssh_providers.include?(provider_name)
109
+
110
+ # If it's a known SSH provider or unknown (assume SSH), return true
111
+ ssh_providers.include?(provider_name) || !non_ssh_providers.include?(provider_name)
112
+ rescue
113
+ # Default to assuming SSH support if we can't determine
114
+ true
115
+ end
116
+
117
+ # Validate that SSH info contains required fields
118
+ def valid_ssh_info?(ssh_info)
119
+ return false if ssh_info.nil?
120
+ return false if ssh_info[:host].nil? || ssh_info[:host].to_s.strip.empty?
121
+ return false if ssh_info[:port].nil? || ssh_info[:port].to_i <= 0
122
+
123
+ # Username is not strictly required (can default to 'vagrant')
124
+ # but if present, it shouldn't be empty
125
+ if ssh_info[:username]
126
+ return false if ssh_info[:username].to_s.strip.empty?
127
+ end
128
+
129
+ true
130
+ rescue
131
+ false
132
+ end
133
+
134
+ # Enhanced SSH info extraction with edge case handling
135
+ def extract_ssh_info_safe
136
+ return nil unless machine_supports_ssh?
137
+
138
+ retries = 0
139
+ max_retries = 3
140
+ retry_delay = 1
141
+
142
+ begin
143
+ ssh_info = @machine.ssh_info
144
+ return nil unless valid_ssh_info?(ssh_info)
145
+
146
+ build_ssh_config(ssh_info)
147
+ rescue Vagrant::Errors::SSHNotReady => e
148
+ retries += 1
149
+ if retries <= max_retries
150
+ @logger.debug("SSH not ready, retrying in #{retry_delay}s (attempt #{retries}/#{max_retries})")
151
+ sleep(retry_delay)
152
+ retry_delay *= 2 # Exponential backoff
153
+ retry
154
+ else
155
+ @logger.debug("SSH still not ready after #{max_retries} attempts")
156
+ nil
157
+ end
158
+ rescue => e
159
+ @logger.warn("SSH info extraction failed: #{e.message}")
160
+ nil
161
+ end
162
+ end
163
+
164
+ # Safe host name generation with fallbacks
165
+ def generate_host_name
166
+ begin
167
+ # Generate a unique host name based on project directory and machine name
168
+ project_name = File.basename(@machine.env.root_path)
169
+ machine_name = @machine.name.to_s
170
+
171
+ # Sanitize names for SSH config
172
+ project_name = sanitize_name(project_name)
173
+ machine_name = sanitize_name(machine_name)
174
+
175
+ host_name = "#{project_name}-#{machine_name}"
176
+
177
+ # Ensure the host name is not empty after sanitization
178
+ if host_name.strip.empty? || host_name == '-'
179
+ host_name = "vagrant-#{@machine.id || 'unknown'}"
180
+ end
181
+
182
+ host_name
183
+ rescue => e
184
+ @logger.debug("Host name generation failed: #{e.message}")
185
+ "vagrant-#{@machine.name || 'unknown'}"
186
+ end
187
+ end
188
+
189
+ # Normalize SSH config data to ensure consistency
190
+ def normalize_ssh_config(config)
191
+ normalized = {}
192
+
193
+ config.each do |key, value|
194
+ # Normalize key names to proper SSH config format
195
+ normalized_key = normalize_config_key(key)
196
+ normalized_value = normalize_config_value(key, value)
197
+
198
+ normalized[normalized_key] = normalized_value if normalized_value
199
+ end
200
+
201
+ normalized
202
+ end
203
+
204
+ # Parse SSH config entries back from file format
205
+ def parse_ssh_config_entry(entry_text)
206
+ config = {}
207
+ current_host = nil
208
+
209
+ entry_text.split("\n").each do |line|
210
+ line = line.strip
211
+ next if line.empty? || line.start_with?('#')
212
+
213
+ if line.start_with?('Host ')
214
+ current_host = line.sub(/^Host\s+/, '').strip
215
+ config['Host'] = current_host
216
+ elsif current_host && line.include?(' ')
217
+ key, value = line.split(' ', 2)
218
+ config[key.strip] = value.strip
219
+ end
220
+ end
221
+
222
+ config
223
+ end
224
+
225
+ # Convert normalized config back to SSH config file format
226
+ def to_ssh_config_format(config)
227
+ lines = []
228
+
229
+ # Host entry always comes first
230
+ if config['Host']
231
+ lines << "Host #{config['Host']}"
232
+
233
+ # Add other entries in a logical order
234
+ ssh_option_order = %w[
235
+ HostName User Port IdentityFile IdentitiesOnly
236
+ StrictHostKeyChecking UserKnownHostsFile PasswordAuthentication
237
+ LogLevel ProxyCommand Compression CompressionLevel
238
+ ConnectTimeout ForwardAgent ForwardX11
239
+ ]
240
+
241
+ ssh_option_order.each do |key|
242
+ if config[key]
243
+ lines << " #{key} #{config[key]}"
244
+ end
245
+ end
246
+
247
+ # Add any remaining options not in the predefined order
248
+ config.each do |key, value|
249
+ next if key == 'Host' || ssh_option_order.include?(key)
250
+ lines << " #{key} #{value}"
251
+ end
252
+ end
253
+
254
+ lines.join("\n")
255
+ end
256
+
257
+ private
258
+
259
+ def build_ssh_config(ssh_info)
260
+ config = {
261
+ 'Host' => generate_host_name,
262
+ 'HostName' => ssh_info[:host],
263
+ 'User' => ssh_info[:username] || 'vagrant',
264
+ 'Port' => ssh_info[:port] || 22
265
+ }
266
+
267
+ # Add SSH key information
268
+ if ssh_info[:private_key_path] && !ssh_info[:private_key_path].empty?
269
+ # Use the first private key if multiple are provided
270
+ key_path = ssh_info[:private_key_path].is_a?(Array) ?
271
+ ssh_info[:private_key_path].first :
272
+ ssh_info[:private_key_path]
273
+ config['IdentityFile'] = key_path
274
+ config['IdentitiesOnly'] = 'yes'
275
+ end
276
+
277
+ # Add common SSH options for Vagrant VMs
278
+ config['StrictHostKeyChecking'] = 'no'
279
+ config['UserKnownHostsFile'] = '/dev/null'
280
+ config['PasswordAuthentication'] = 'no'
281
+ config['LogLevel'] = 'FATAL'
282
+
283
+ # Add proxy command if using a proxy
284
+ if ssh_info[:proxy_command]
285
+ config['ProxyCommand'] = ssh_info[:proxy_command]
286
+ end
287
+
288
+ # Add comprehensive SSH options from the machine config
289
+ add_comprehensive_ssh_options(config, ssh_info)
290
+
291
+ # Normalize the final configuration
292
+ normalize_ssh_config(config)
293
+ end
294
+
295
+ # Add comprehensive SSH options support
296
+ def add_comprehensive_ssh_options(config, ssh_info)
297
+ return unless ssh_info[:config] && ssh_info[:config].ssh
298
+
299
+ ssh_config = ssh_info[:config].ssh
300
+
301
+ # Connection options
302
+ config['Compression'] = ssh_config.compression ? 'yes' : 'no' if ssh_config.compression != nil
303
+ config['CompressionLevel'] = ssh_config.compression_level.to_s if ssh_config.compression_level
304
+ config['ConnectTimeout'] = ssh_config.connect_timeout.to_s if ssh_config.connect_timeout
305
+ config['ConnectionAttempts'] = ssh_config.connection_attempts.to_s if ssh_config.connection_attempts
306
+ config['ServerAliveInterval'] = ssh_config.server_alive_interval.to_s if ssh_config.server_alive_interval
307
+ config['ServerAliveCountMax'] = ssh_config.server_alive_count_max.to_s if ssh_config.server_alive_count_max
308
+
309
+ # Authentication options
310
+ config['ForwardAgent'] = ssh_config.forward_agent ? 'yes' : 'no' if ssh_config.forward_agent != nil
311
+ config['PubkeyAuthentication'] = ssh_config.pubkey_authentication ? 'yes' : 'no' if ssh_config.pubkey_authentication != nil
312
+ config['PreferredAuthentications'] = ssh_config.preferred_authentications if ssh_config.preferred_authentications
313
+
314
+ # Forwarding options
315
+ config['ForwardX11'] = ssh_config.forward_x11 ? 'yes' : 'no' if ssh_config.forward_x11 != nil
316
+ config['ForwardX11Trusted'] = ssh_config.forward_x11_trusted ? 'yes' : 'no' if ssh_config.forward_x11_trusted != nil
317
+
318
+ # Security options
319
+ config['StrictHostKeyChecking'] = ssh_config.verify_host_key ? 'yes' : 'no' if ssh_config.verify_host_key != nil
320
+ config['CheckHostIP'] = ssh_config.check_host_ip ? 'yes' : 'no' if ssh_config.check_host_ip != nil
321
+
322
+ # Protocol options
323
+ config['Protocol'] = ssh_config.protocol if ssh_config.protocol
324
+ config['Ciphers'] = ssh_config.ciphers.join(',') if ssh_config.ciphers && !ssh_config.ciphers.empty?
325
+ config['MACs'] = ssh_config.macs.join(',') if ssh_config.macs && !ssh_config.macs.empty?
326
+ config['KexAlgorithms'] = ssh_config.kex_algorithms.join(',') if ssh_config.kex_algorithms && !ssh_config.kex_algorithms.empty?
327
+
328
+ # Terminal options
329
+ config['RequestTTY'] = ssh_config.pty ? 'yes' : 'no' if ssh_config.pty != nil
330
+ config['RemoteCommand'] = ssh_config.remote_command if ssh_config.remote_command
331
+
332
+ # File and directory options
333
+ config['ControlMaster'] = ssh_config.control_master if ssh_config.control_master
334
+ config['ControlPath'] = ssh_config.control_path if ssh_config.control_path
335
+ config['ControlPersist'] = ssh_config.control_persist.to_s if ssh_config.control_persist
336
+
337
+ # Logging options
338
+ config['LogLevel'] = ssh_config.log_level.to_s.upcase if ssh_config.log_level
339
+ config['SyslogFacility'] = ssh_config.syslog_facility if ssh_config.syslog_facility
340
+
341
+ # Banner and environment
342
+ config['Banner'] = ssh_config.banner if ssh_config.banner
343
+ config['SendEnv'] = ssh_config.send_env.join(' ') if ssh_config.send_env && !ssh_config.send_env.empty?
344
+ config['SetEnv'] = ssh_config.set_env.map { |k, v| "#{k}=#{v}" }.join(' ') if ssh_config.set_env && !ssh_config.set_env.empty?
345
+
346
+ # Keep alive options
347
+ config['TCPKeepAlive'] = ssh_config.tcp_keep_alive ? 'yes' : 'no' if ssh_config.tcp_keep_alive != nil
348
+
349
+ # Escape character
350
+ config['EscapeChar'] = ssh_config.escape_char if ssh_config.escape_char
351
+
352
+ # Gateway options
353
+ config['ProxyJump'] = ssh_config.proxy_jump if ssh_config.proxy_jump
354
+
355
+ # Add any custom options that might be defined
356
+ if ssh_config.respond_to?(:extra_options) && ssh_config.extra_options
357
+ ssh_config.extra_options.each do |key, value|
358
+ config[key.to_s] = value.to_s
359
+ end
360
+ end
361
+ end
362
+
363
+ def generate_host_name
364
+ # Generate a unique host name based on project directory and machine name
365
+ project_name = File.basename(@machine.env.root_path)
366
+ machine_name = @machine.name.to_s
367
+
368
+ # Sanitize names for SSH config
369
+ project_name = sanitize_name(project_name)
370
+ machine_name = sanitize_name(machine_name)
371
+
372
+ "#{project_name}-#{machine_name}"
373
+ end
374
+
375
+ def sanitize_name(name)
376
+ # Remove or replace characters that might cause issues in SSH config
377
+ name.gsub(/[^a-zA-Z0-9\-_]/, '-').gsub(/-+/, '-').gsub(/^-|-$/, '')
378
+ end
379
+
380
+ def normalize_config_key(key)
381
+ # Convert various key formats to standard SSH config format
382
+ key_mappings = {
383
+ 'hostname' => 'HostName',
384
+ 'host_name' => 'HostName',
385
+ 'user' => 'User',
386
+ 'username' => 'User',
387
+ 'port' => 'Port',
388
+ 'identity_file' => 'IdentityFile',
389
+ 'identityfile' => 'IdentityFile',
390
+ 'identities_only' => 'IdentitiesOnly',
391
+ 'identitiesonly' => 'IdentitiesOnly',
392
+ 'strict_host_key_checking' => 'StrictHostKeyChecking',
393
+ 'stricthostkeychecking' => 'StrictHostKeyChecking',
394
+ 'user_known_hosts_file' => 'UserKnownHostsFile',
395
+ 'userknownhostsfile' => 'UserKnownHostsFile',
396
+ 'password_authentication' => 'PasswordAuthentication',
397
+ 'passwordauthentication' => 'PasswordAuthentication',
398
+ 'log_level' => 'LogLevel',
399
+ 'loglevel' => 'LogLevel',
400
+ 'proxy_command' => 'ProxyCommand',
401
+ 'proxycommand' => 'ProxyCommand',
402
+ 'compression' => 'Compression',
403
+ 'compression_level' => 'CompressionLevel',
404
+ 'compressionlevel' => 'CompressionLevel',
405
+ 'connect_timeout' => 'ConnectTimeout',
406
+ 'connecttimeout' => 'ConnectTimeout',
407
+ 'forward_agent' => 'ForwardAgent',
408
+ 'forwardagent' => 'ForwardAgent',
409
+ 'forward_x11' => 'ForwardX11',
410
+ 'forwardx11' => 'ForwardX11'
411
+ }
412
+
413
+ key_str = key.to_s.downcase
414
+ key_mappings[key_str] || key.to_s
415
+ end
416
+
417
+ def normalize_config_value(key, value)
418
+ return nil if value.nil? || value.to_s.strip.empty?
419
+
420
+ case key.to_s.downcase
421
+ when 'port'
422
+ value.to_i.to_s
423
+ when 'compression', 'identitiesonly', 'stricthostkeychecking',
424
+ 'passwordauthentication', 'forwardagent', 'forwardx11'
425
+ # Normalize boolean-like values
426
+ case value.to_s.downcase
427
+ when 'true', 'yes', '1'
428
+ 'yes'
429
+ when 'false', 'no', '0'
430
+ 'no'
431
+ else
432
+ value.to_s
433
+ end
434
+ when 'identityfile'
435
+ # Expand tilde in file paths
436
+ File.expand_path(value.to_s)
437
+ else
438
+ value.to_s.strip
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end
@@ -0,0 +1,5 @@
1
+ module VagrantPlugins
2
+ module SshConfigManager
3
+ VERSION = "0.8.2"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ require "vagrant-ssh-config-manager/version"
2
+ require "vagrant-ssh-config-manager/plugin"
3
+
4
+ module VagrantPlugins
5
+ module SshConfigManager
6
+ # Main plugin entry point
7
+ # This file is loaded when the plugin is activated
8
+
9
+ # Lazy load other components only when needed
10
+ def self.require_file_manager
11
+ require "vagrant-ssh-config-manager/file_manager" unless defined?(FileManager)
12
+ end
13
+
14
+ def self.require_include_manager
15
+ require "vagrant-ssh-config-manager/include_manager" unless defined?(IncludeManager)
16
+ end
17
+
18
+ def self.require_ssh_info_extractor
19
+ require "vagrant-ssh-config-manager/ssh_info_extractor" unless defined?(SshInfoExtractor)
20
+ end
21
+
22
+ def self.require_file_locker
23
+ require "vagrant-ssh-config-manager/file_locker" unless defined?(FileLocker)
24
+ end
25
+
26
+ def self.require_config
27
+ require "vagrant-ssh-config-manager/config" unless defined?(Config)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "vagrant-ssh-config-manager/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "vagrant-ssh-config-manager"
7
+ spec.version = VagrantPlugins::SshConfigManager::VERSION
8
+ spec.authors = ["Marek Ruzicka"]
9
+ spec.email = ["marek.ruzicka@glide.sk"]
10
+
11
+ spec.summary = "Vagrant plugin that automatically manages SSH configurations"
12
+ spec.description = "A Vagrant plugin that automatically manages SSH configurations. Creates and maintains SSH config entries when VMs are started and cleans them up when VMs are destroyed, with environment isolation and file locking support."
13
+ spec.homepage = "https://github.com/marekruzicka/vagrant-ssh-config-manager.git"
14
+ spec.license = "MIT"
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGems gemspec, but not Git submodules.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|tasks|.github|.ai)/}) }
20
+ end
21
+ spec.require_paths = ["lib"]
22
+
23
+ # Development dependencies
24
+ spec.add_development_dependency "bundler", "~> 2.0"
25
+ spec.add_development_dependency "rake", "~> 13.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency "rspec-mocks", "~> 3.0"
28
+ spec.add_development_dependency "simplecov", "~> 0.21"
29
+
30
+ # Ensure compatibility with supported Ruby versions
31
+ spec.required_ruby_version = ">= 2.6.0"
32
+
33
+ # Plugin metadata for Vagrant
34
+ spec.metadata["vagrant_plugin"] = "true"
35
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-ssh-config-manager
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.2
5
+ platform: ruby
6
+ authors:
7
+ - Marek Ruzicka
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bundler
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '13.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec-mocks
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: simplecov
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.21'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.21'
82
+ description: A Vagrant plugin that automatically manages SSH configurations. Creates
83
+ and maintains SSH config entries when VMs are started and cleans them up when VMs
84
+ are destroyed, with environment isolation and file locking support.
85
+ email:
86
+ - marek.ruzicka@glide.sk
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".bundle/config"
92
+ - ".gitignore"
93
+ - Gemfile
94
+ - README.md
95
+ - Rakefile
96
+ - lib/vagrant-ssh-config-manager.rb
97
+ - lib/vagrant-ssh-config-manager/action/destroy.rb
98
+ - lib/vagrant-ssh-config-manager/action/halt.rb
99
+ - lib/vagrant-ssh-config-manager/action/provision.rb
100
+ - lib/vagrant-ssh-config-manager/action/reload.rb
101
+ - lib/vagrant-ssh-config-manager/action/up.rb
102
+ - lib/vagrant-ssh-config-manager/config.rb
103
+ - lib/vagrant-ssh-config-manager/file_locker.rb
104
+ - lib/vagrant-ssh-config-manager/file_manager.rb
105
+ - lib/vagrant-ssh-config-manager/include_manager.rb
106
+ - lib/vagrant-ssh-config-manager/plugin.rb
107
+ - lib/vagrant-ssh-config-manager/ssh_config_manager.rb
108
+ - lib/vagrant-ssh-config-manager/ssh_info_extractor.rb
109
+ - lib/vagrant-ssh-config-manager/version.rb
110
+ - vagrant-ssh-config-manager.gemspec
111
+ homepage: https://github.com/marekruzicka/vagrant-ssh-config-manager.git
112
+ licenses:
113
+ - MIT
114
+ metadata:
115
+ vagrant_plugin: 'true'
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 2.6.0
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubygems_version: 3.6.7
131
+ specification_version: 4
132
+ summary: Vagrant plugin that automatically manages SSH configurations
133
+ test_files: []