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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e1bd8cef21dfadbeaed3204b702d00203c2b30581a6ad27c370d5679a661e594
4
+ data.tar.gz: 2ce4b4be6a99bdfc0c978c3fda2af091e37f80cebca628bb317cc52ac19266a9
5
+ SHA512:
6
+ metadata.gz: c314ed1a2dfcfb02dee0c09e4cabb3b36f8f6423685388f34632dd707545c8d9408178db0c63617cd789dd7115cd115d37fefed57b8b619500fd55ded0e35ccf
7
+ data.tar.gz: 62de37425bece9d76e6f756ccdf71ac52c7c03a46258222412a3993151297d24ae4d8eb7929b33a1407c3b7f0fbc53aaa9608262fee4d7ea22169a39f967ac9b
data/.bundle/config ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_PATH: "vendor/bundle"
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ vendor
2
+ .github/instructions
3
+ test_file
4
+ vagrant-ssh-config-manager-*.gem
5
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vagrant-ssh-config-manager.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem "bundler", "~> 2.0"
8
+ gem "rake", "~> 13.0"
9
+ gem "rspec", "~> 3.0"
10
+ gem "rspec-mocks", "~> 3.0"
11
+ gem "simplecov", "~> 0.21"
12
+ gem "rubocop", "~> 1.0"
13
+ gem "rubocop-rspec", "~> 2.0"
14
+ gem "vagrant", git: "https://github.com/hashicorp/vagrant.git", tag: "v2.4.1"
15
+ end
data/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # Vagrant SSH Config Manager
2
+
3
+ A Vagrant plugin that automatically manages SSH configuration entries for Vagrant-managed VMs, providing seamless SSH access without the need to remember ports, IPs, or key locations.
4
+
5
+ ## Features
6
+
7
+ - **Automatic SSH Config Management**: Creates and maintains SSH config files when VMs are started
8
+ - **Project Isolation**: Each Vagrant project gets its own SSH config namespace to avoid conflicts
9
+ - **Separate File Management**: Creates individual SSH config files for each VM in a dedicated directory
10
+ - **Include Directive Management**: Automatically manages Include directives in your main SSH config
11
+ - **Lifecycle Integration**: Hooks into all Vagrant VM lifecycle events (up, destroy, reload, halt, provision)
12
+ - **Configurable Behavior**: Extensive configuration options for different workflows
13
+ - **Robust Error Handling**: Graceful handling of file permission issues and concurrent access
14
+ - **File Locking**: Prevents SSH config corruption during concurrent operations
15
+ - **Backup Support**: Creates backups before modifying SSH config files
16
+
17
+ ## Installation
18
+
19
+ ### Install from RubyGems (Recommended)
20
+
21
+ ```bash
22
+ vagrant plugin install vagrant-ssh-config-manager
23
+ ```
24
+
25
+ ### Install from Source
26
+
27
+ ```bash
28
+ git clone https://github.com/your-username/vagrant-ssh-config-manager.git
29
+ cd vagrant-ssh-config-manager
30
+ gem build vagrant-ssh-config-manager.gemspec
31
+ vagrant plugin install vagrant-ssh-config-manager-*.gem
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ 1. Install the plugin
37
+ 2. Add configuration to your `Vagrantfile` (optional)
38
+ 3. Run `vagrant up`
39
+ 4. Connect to your VM with: `ssh <project>-<machine-name>`
40
+
41
+ ### Example
42
+
43
+ ```ruby
44
+ # Vagrantfile
45
+ Vagrant.configure("2") do |config|
46
+ config.vm.box = "ubuntu/focal64"
47
+
48
+ # Optional: Configure the plugin (uses sensible defaults)
49
+ config.sshconfigmanager.enabled = true
50
+ config.sshconfigmanager.ssh_config_dir = "~/.ssh/config.d/vagrant"
51
+ config.sshconfigmanager.manage_includes = true
52
+
53
+ config.vm.define "web" do |web|
54
+ web.vm.network "private_network", ip: "192.168.33.10"
55
+ end
56
+ end
57
+ ```
58
+
59
+ After `vagrant up`, you can connect with:
60
+ ```bash
61
+ ssh vagrant-web # Project + machine name
62
+ ```
63
+
64
+ ## Configuration
65
+
66
+ All configuration options are available in your `Vagrantfile`:
67
+
68
+ ```ruby
69
+ Vagrant.configure("2") do |config|
70
+ config.sshconfigmanager.enabled = true # Enable/disable plugin (default: true)
71
+ config.sshconfigmanager.ssh_config_dir = "~/.ssh/config.d/vagrant" # Directory for individual SSH config files (default)
72
+ config.sshconfigmanager.manage_includes = false # Manage Include directive in main SSH config (default: false)
73
+ config.sshconfigmanager.auto_create_dir = true # Auto-create SSH config dir if missing (default: true)
74
+ config.sshconfigmanager.cleanup_empty_dir = true # Remove empty SSH config dir when no VMs remain (default: true)
75
+ config.sshconfigmanager.auto_remove_on_destroy = true # Remove SSH entries on VM destroy (default: true)
76
+ config.sshconfigmanager.update_on_reload = true # Update SSH entries on VM reload (default: true)
77
+ config.sshconfigmanager.refresh_on_provision = true # Refresh SSH entries on VM provision (default: true)
78
+ config.sshconfigmanager.keep_config_on_halt = true # Keep SSH entries when VM is halted (default: true)
79
+ config.sshconfigmanager.project_isolation = true # Use project-specific naming (default: true)
80
+ end
81
+ ```
82
+
83
+ ### Configuration Options
84
+
85
+ | Option | Type | Default | Description |
86
+ |--------|------|---------|-------------|
87
+ | `enabled` | Boolean | `true` | Enable or disable the plugin globally |
88
+ | `ssh_config_dir` | String | `~/.ssh/config.d/vagrant` | Directory for individual SSH config files |
89
+ | `manage_includes` | Boolean | `false` | Automatically manage Include directive in main SSH config |
90
+ | `auto_create_dir` | Boolean | `true` | Automatically create SSH config directory if it doesn't exist |
91
+ | `cleanup_empty_dir` | Boolean | `true` | Remove empty SSH config directory when no VMs remain |
92
+ | `auto_remove_on_destroy` | Boolean | `true` | Remove SSH entries when VM is destroyed |
93
+ | `update_on_reload` | Boolean | `true` | Update SSH entries when VM is reloaded |
94
+ | `refresh_on_provision` | Boolean | `true` | Refresh SSH entries when VM is provisioned |
95
+ | `keep_config_on_halt` | Boolean | `true` | Keep SSH entries when VM is halted/suspended |
96
+ | `project_isolation` | Boolean | `true` | Use project-specific naming for SSH entries.<br>Set to `false` if using together with `vagrant-hostmanager`.<br>**Be mindful of hostname collisions!** |
97
+
98
+ ## How It Works
99
+
100
+ ### Separate File Architecture (Default)
101
+
102
+ The plugin creates individual SSH config files for each VM in a dedicated directory (default: `~/.ssh/config.d/vagrant/`). This approach:
103
+
104
+ - **Prevents conflicts**: Each VM has its own config file
105
+ - **Enables easier cleanup**: Destroying a VM removes only its config file
106
+ - **Supports concurrent operations**: Multiple VMs can be managed simultaneously
107
+ - **Maintains cleaner config**: Main SSH config stays uncluttered
108
+
109
+ ### SSH Config Structure
110
+
111
+ ```
112
+ ~/.ssh/config # Your main SSH config
113
+ ~/.ssh/config.d/vagrant/ # Plugin-managed directory
114
+ ├── a1b2c3d4-web.conf # Individual VM config files
115
+ ├── a1b2c3d4-db.conf
116
+ └── f5e6d7c8-api.conf # From different projects
117
+ ```
118
+
119
+ ### Include Directive Management
120
+
121
+ When `manage_includes` is enabled (default: false), the plugin automatically adds:
122
+
123
+ ```
124
+ # ~/.ssh/config
125
+ # BEGIN vagrant-ssh-config-manager
126
+ Include ~/.ssh/config.d/vagrant/*.conf
127
+ # END vagrant-ssh-config-manager
128
+
129
+ # Your existing SSH configuration continues here...
130
+ ```
131
+
132
+ ### Project Isolation
133
+
134
+ Each Vagrant project gets a unique identifier based on the project path:
135
+ - Project path: `/home/user/my-app`
136
+ - Project hash: `a1b2c3d4` (first 8 chars of MD5)
137
+ - SSH host names: `my-app-web`, `my-app-db`
138
+ - Config files: `a1b2c3d4-web.conf`, `a1b2c3d4-db.conf`
139
+
140
+ ### Individual Config File Example
141
+
142
+ ```
143
+ # ~/.ssh/config.d/vagrant/a1b2c3d4-web.conf
144
+ # Managed by vagrant-ssh-config-manager plugin
145
+ # Project: my-app
146
+ # VM: web
147
+ # Generated: 2025-01-01 12:00:00
148
+
149
+ Host my-app-web
150
+ HostName 192.168.33.10
151
+ Port 22
152
+ User vagrant
153
+ IdentityFile /home/user/.vagrant.d/insecure_private_key
154
+ IdentitiesOnly yes
155
+ UserKnownHostsFile /dev/null
156
+ StrictHostKeyChecking no
157
+ PasswordAuthentication no
158
+ LogLevel FATAL
159
+ ```
160
+
161
+ ### Main SSH Config Integration
162
+
163
+ When `manage_includes` is enabled, the plugin automatically adds an Include directive:
164
+
165
+ ```
166
+ # ~/.ssh/config
167
+ # BEGIN vagrant-ssh-config-manager
168
+ Include ~/.ssh/config.d/vagrant/*.conf
169
+ # END vagrant-ssh-config-manager
170
+
171
+ # Your existing SSH configuration...
172
+ ```
173
+
174
+ ## Usage Examples
175
+
176
+ ### Basic Multi-Machine Setup
177
+
178
+ ```ruby
179
+ Vagrant.configure("2") do |config|
180
+ config.vm.box = "generic/debian12"
181
+
182
+ config.vm.define "web" do |web|
183
+ web.vm.network "private_network", ip: "192.168.33.10"
184
+ end
185
+
186
+ config.vm.define "db" do |db|
187
+ db.vm.network "private_network", ip: "192.168.33.11"
188
+ end
189
+ end
190
+ ```
191
+
192
+ Connect to machines:
193
+ ```bash
194
+ vagrant up
195
+ ssh vagrant-web
196
+ ssh vagrant-db
197
+ ```
198
+
199
+ ### Disable for Specific Environments
200
+
201
+ ```ruby
202
+ Vagrant.configure("2") do |config|
203
+ # Disable in production environment
204
+ config.sshconfigmanager.enabled = ENV['VAGRANT_ENV'] != 'production'
205
+ # ... rest of config
206
+ end
207
+ ```
208
+
209
+ ## Troubleshooting
210
+
211
+ ### Common Issues
212
+
213
+ #### Permission Denied
214
+ ```
215
+ SSH config manager: Permission denied. Check file permissions.
216
+ ```
217
+ **Solution**: Ensure SSH config file and directory are writable:
218
+ ```bash
219
+ chmod 600 ~/.ssh/config
220
+ chmod 700 ~/.ssh
221
+ ```
222
+
223
+ #### File Not Found
224
+ ```
225
+ SSH config manager: SSH config file cannot be created
226
+ ```
227
+ **Solution**: Ensure SSH directory exists:
228
+ ```bash
229
+ mkdir -p ~/.ssh
230
+ chmod 700 ~/.ssh
231
+ ```
232
+
233
+ #### Concurrent Access Issues
234
+ The plugin uses separate ssh config file for each of the servers... there should be no concurrency issues by design.
235
+
236
+
237
+ ### Debugging
238
+
239
+ Enable Vagrant debug output:
240
+ ```bash
241
+ VAGRANT_LOG=debug vagrant up
242
+ ```
243
+
244
+ Check plugin logs in the output for SSH config manager operations.
245
+
246
+ ### Manual Cleanup
247
+
248
+ If you need to manually clean up SSH config entries:
249
+
250
+ ```bash
251
+ # Remove all Vagrant-managed entries
252
+ rm ~/.ssh/config.d/vagrant/*
253
+
254
+ # Remove include directive from main config
255
+ sed -i '/# BEGIN vagrant-ssh-config-manager/,/# END vagrant-ssh-config-manager/d' ~/.ssh/config
256
+ ```
257
+
258
+ ## Compatibility
259
+
260
+ - **Vagrant**: 2.0+
261
+ - **Ruby**: 2.4+
262
+ - **Platforms**: Linux, macOS, Windows (with WSL)
263
+ - **Providers**: VirtualBox, VMware, Libvirt, Hyper-V, Docker, AWS, and others
264
+
265
+ ## License
266
+
267
+ MIT License - see [LICENSE](LICENSE) file for details.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ desc "Run RuboCop"
9
+ task :rubocop do
10
+ sh "bundle exec rubocop"
11
+ end
12
+
13
+ desc "Run RuboCop with auto-correct"
14
+ task :rubocop_fix do
15
+ sh "bundle exec rubocop -a"
16
+ end
17
+
18
+ desc "Run tests with coverage"
19
+ task :coverage do
20
+ ENV['COVERAGE'] = 'true'
21
+ Rake::Task[:spec].invoke
22
+ end
@@ -0,0 +1,84 @@
1
+ module VagrantPlugins
2
+ module SshConfigManager
3
+ module Action
4
+ class Destroy
5
+ def initialize(app, env)
6
+ @app = app
7
+ @env = env
8
+ @logger = Log4r::Logger.new("vagrant::plugins::ssh_config_manager::action::destroy")
9
+ end
10
+
11
+ def call(env)
12
+ machine = env[:machine]
13
+
14
+ # Handle SSH config removal before destroying the machine
15
+ if machine
16
+ config = machine.config.sshconfigmanager
17
+ if config && config.enabled && config.auto_remove_on_destroy
18
+ handle_ssh_config_removal(machine, config)
19
+ end
20
+ end
21
+
22
+ # Call the next middleware (actual destroy)
23
+ @app.call(env)
24
+ end
25
+
26
+ private
27
+
28
+ def handle_ssh_config_removal(machine, config)
29
+ begin
30
+ @logger.info("Removing SSH config file for machine: #{machine.name}")
31
+
32
+ # Lazy load required classes with error handling
33
+ begin
34
+ require 'vagrant-ssh-config-manager/file_manager'
35
+ require 'vagrant-ssh-config-manager/include_manager'
36
+ rescue LoadError => e
37
+ @logger.error("Failed to load required classes: #{e.message}")
38
+ machine.ui.warn("SSH config manager: Failed to load required components, skipping SSH config removal")
39
+ return
40
+ end
41
+
42
+ # Create file manager and include manager
43
+ file_manager = FileManager.new(config)
44
+ include_manager = IncludeManager.new(config)
45
+
46
+ # Check if SSH config file exists
47
+ unless file_manager.ssh_config_file_exists?(machine)
48
+ @logger.debug("No SSH config file found for machine: #{machine.name}")
49
+ machine.ui.info("No SSH config file found to remove for machine: #{machine.name}")
50
+ return
51
+ end
52
+
53
+ # Remove SSH config file with logging
54
+ @logger.info("Attempting to remove SSH config file for #{machine.name}")
55
+
56
+ if file_manager.remove_ssh_config_file(machine)
57
+ machine.ui.info("SSH config file removed for machine '#{machine.name}'")
58
+ @logger.info("Successfully removed SSH config file for #{machine.name}")
59
+
60
+ # Manage Include directive after file removal
61
+ include_manager.manage_include_directive
62
+ else
63
+ machine.ui.warn("Failed to remove SSH config file for machine: #{machine.name}")
64
+ @logger.warn("Failed to remove SSH config file for #{machine.name}")
65
+ end
66
+
67
+ rescue Errno::EACCES => e
68
+ @logger.error("Permission denied accessing SSH config for #{machine.name}: #{e.message}")
69
+ machine.ui.warn("SSH config manager: Permission denied. Check file permissions.")
70
+ rescue Errno::EIO => e
71
+ @logger.error("I/O error for #{machine.name}: #{e.message}")
72
+ machine.ui.warn("SSH config manager: I/O error accessing SSH config files.")
73
+ rescue => e
74
+ @logger.error("Error removing SSH config for #{machine.name}: #{e.message}")
75
+ @logger.debug("Backtrace: #{e.backtrace.join("\n")}")
76
+
77
+ # Don't fail the vagrant destroy process, just warn
78
+ machine.ui.warn("SSH config manager encountered an error during cleanup: #{e.message}")
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,68 @@
1
+ module VagrantPlugins
2
+ module SshConfigManager
3
+ module Action
4
+ class Halt
5
+ def initialize(app, env)
6
+ @app = app
7
+ @env = env
8
+ @logger = Log4r::Logger.new("vagrant::plugins::ssh_config_manager::action::halt")
9
+ end
10
+
11
+ def call(env)
12
+ machine = env[:machine]
13
+
14
+ # Handle SSH config for halt/suspend operations
15
+ if machine
16
+ config = machine.config.sshconfigmanager
17
+ if config && config.enabled && config.keep_config_on_halt
18
+ handle_ssh_config_for_halt(machine, config, env)
19
+ end
20
+ end
21
+
22
+ # Call the next middleware (actual halt/suspend)
23
+ @app.call(env)
24
+ end
25
+
26
+ private
27
+
28
+ def handle_ssh_config_for_halt(machine, config, env)
29
+ begin
30
+ # Determine the type of operation
31
+ operation = determine_operation_type(env)
32
+ @logger.info("Handling SSH config for #{operation} operation on machine: #{machine.name}")
33
+
34
+ # With separate files, we keep the SSH config file during halt/suspend
35
+ # This allows users to quickly resume and reconnect
36
+ case operation
37
+ when :halt, :suspend, :poweroff, :force_halt
38
+ machine.ui.info("Machine #{operation}ed - SSH config file retained")
39
+ @logger.debug("Keeping SSH config file for #{operation}ed machine: #{machine.name}")
40
+ else
41
+ @logger.debug("No SSH config action needed for operation: #{operation}")
42
+ end
43
+
44
+ rescue => e
45
+ @logger.error("Error handling SSH config for halt/suspend #{machine.name}: #{e.message}")
46
+ @logger.debug("Backtrace: #{e.backtrace.join("\n")}")
47
+
48
+ # Don't fail the halt/suspend process
49
+ machine.ui.warn("SSH config manager encountered an error: #{e.message}")
50
+ end
51
+ end
52
+
53
+ def determine_operation_type(env)
54
+ # Try to determine what type of halt operation this is
55
+ if env[:force_halt]
56
+ :force_halt
57
+ elsif env[:suspend]
58
+ :suspend
59
+ elsif env[:graceful_halt] == false
60
+ :poweroff
61
+ else
62
+ :halt
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,82 @@
1
+ module VagrantPlugins
2
+ module SshConfigManager
3
+ module Action
4
+ class Provision
5
+ def initialize(app, env)
6
+ @app = app
7
+ @env = env
8
+ @logger = Log4r::Logger.new("vagrant::plugins::ssh_config_manager::action::provision")
9
+ end
10
+
11
+ def call(env)
12
+ # Call the next middleware first (actual provisioning)
13
+ @app.call(env)
14
+
15
+ # Only proceed if the machine is running and SSH is ready
16
+ machine = env[:machine]
17
+ return unless machine && machine.state.id == :running
18
+
19
+ # Check if plugin is enabled
20
+ config = machine.config.sshconfigmanager
21
+ return unless config && config.enabled && config.refresh_on_provision
22
+
23
+ # Handle SSH config file refresh after provisioning
24
+ handle_ssh_config_refresh(machine, config)
25
+ end
26
+
27
+ private
28
+
29
+ def handle_ssh_config_refresh(machine, config)
30
+ begin
31
+ @logger.info("Refreshing SSH config file after provisioning for machine: #{machine.name}")
32
+
33
+ # Lazy load required classes with error handling
34
+ begin
35
+ require 'vagrant-ssh-config-manager/ssh_info_extractor'
36
+ require 'vagrant-ssh-config-manager/file_manager'
37
+ require 'vagrant-ssh-config-manager/include_manager'
38
+ rescue LoadError => e
39
+ @logger.error("Failed to load required classes: #{e.message}")
40
+ machine.ui.warn("SSH config manager: Failed to load required components, skipping SSH config refresh")
41
+ return
42
+ end
43
+
44
+ # Extract SSH information
45
+ extractor = SshInfoExtractor.new(machine)
46
+
47
+ # Check if machine supports SSH
48
+ unless extractor.ssh_capable?
49
+ @logger.debug("Machine #{machine.name} does not support SSH, skipping")
50
+ return
51
+ end
52
+
53
+ # Create file manager and include manager
54
+ file_manager = FileManager.new(config)
55
+ include_manager = IncludeManager.new(config)
56
+
57
+ # Refresh SSH config file (regenerate it)
58
+ # Provisioning might have changed SSH configuration
59
+ if file_manager.write_ssh_config_file(machine)
60
+ host_name = file_manager.send(:generate_host_name, machine)
61
+ machine.ui.info("SSH config file refreshed for machine '#{machine.name}' after provisioning")
62
+ @logger.info("SSH config file refreshed due to provisioning")
63
+
64
+ # Ensure Include directive is managed
65
+ include_manager.manage_include_directive
66
+ else
67
+ machine.ui.warn("Failed to refresh SSH config file for machine: #{machine.name}")
68
+ @logger.warn("Failed to refresh SSH config file for #{machine.name}")
69
+ end
70
+
71
+ rescue => e
72
+ @logger.error("Error refreshing SSH config for #{machine.name}: #{e.message}")
73
+ @logger.debug("Backtrace: #{e.backtrace.join("\n")}")
74
+
75
+ # Don't fail the vagrant provision process, just warn
76
+ machine.ui.warn("SSH config manager encountered an error during refresh: #{e.message}")
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,106 @@
1
+ module VagrantPlugins
2
+ module SshConfigManager
3
+ module Action
4
+ class Reload
5
+ def initialize(app, env)
6
+ @app = app
7
+ @env = env
8
+ @logger = Log4r::Logger.new("vagrant::plugins::ssh_config_manager::action::reload")
9
+ end
10
+
11
+ def call(env)
12
+ # Call the next middleware first
13
+ @app.call(env)
14
+
15
+ # Only proceed if the machine is running and SSH is ready
16
+ machine = env[:machine]
17
+ return unless machine && machine.state.id == :running
18
+
19
+ # Check if plugin is enabled
20
+ config = machine.config.sshconfigmanager
21
+ return unless config && config.enabled
22
+
23
+ # Handle SSH config update
24
+ handle_ssh_config_update(machine, config)
25
+ end
26
+
27
+ private
28
+
29
+ def handle_ssh_config_update(machine, config)
30
+ begin
31
+ @logger.info("Updating SSH config entry for machine: #{machine.name}")
32
+
33
+ # Lazy load required classes with error handling
34
+ begin
35
+ require 'vagrant-ssh-config-manager/ssh_info_extractor'
36
+ require 'vagrant-ssh-config-manager/file_manager'
37
+ require 'vagrant-ssh-config-manager/include_manager'
38
+ rescue LoadError => e
39
+ @logger.error("Failed to load required classes: #{e.message}")
40
+ machine.ui.warn("SSH config manager: Failed to load required components, skipping SSH config update")
41
+ return
42
+ end
43
+
44
+ # Extract SSH information
45
+ extractor = SshInfoExtractor.new(machine)
46
+
47
+ # Check if machine supports SSH
48
+ unless extractor.ssh_capable?
49
+ @logger.debug("Machine #{machine.name} does not support SSH, skipping")
50
+ machine.ui.info("Machine #{machine.name} does not support SSH, skipping SSH config update")
51
+ return
52
+ end
53
+
54
+ # Create file manager and include manager
55
+ file_manager = FileManager.new(config)
56
+ include_manager = IncludeManager.new(config)
57
+
58
+ # Check if file exists and compare content
59
+ if file_manager.ssh_config_file_exists?(machine)
60
+ # Update SSH config file (always regenerate for simplicity)
61
+ @logger.info("SSH config file exists, updating for #{machine.name}")
62
+ if file_manager.write_ssh_config_file(machine)
63
+ host_name = file_manager.send(:generate_host_name, machine)
64
+ machine.ui.info("SSH config file updated for machine '#{machine.name}' (#{host_name})")
65
+ @logger.info("SSH config file updated due to reload")
66
+
67
+ # Ensure Include directive is managed
68
+ include_manager.manage_include_directive
69
+ else
70
+ machine.ui.warn("Failed to update SSH config file for machine: #{machine.name}")
71
+ @logger.warn("Failed to update SSH config file for #{machine.name}")
72
+ end
73
+ else
74
+ # Add new SSH config file (machine might have been added during reload)
75
+ @logger.info("No existing SSH config file found, creating new file for #{machine.name}")
76
+ if file_manager.write_ssh_config_file(machine)
77
+ host_name = file_manager.send(:generate_host_name, machine)
78
+ machine.ui.info("SSH config file created for machine '#{machine.name}' as '#{host_name}'")
79
+ @logger.info("Successfully created new SSH config file for #{machine.name}")
80
+
81
+ # Manage Include directive after file creation
82
+ include_manager.manage_include_directive
83
+ else
84
+ machine.ui.warn("Failed to create SSH config file for machine: #{machine.name}")
85
+ @logger.warn("Failed to create SSH config file for #{machine.name}")
86
+ end
87
+ end
88
+
89
+ rescue Errno::EACCES => e
90
+ @logger.error("Permission denied accessing SSH config for #{machine.name}: #{e.message}")
91
+ machine.ui.warn("SSH config manager: Permission denied. Check file permissions.")
92
+ rescue Errno::EIO => e
93
+ @logger.error("I/O error for #{machine.name}: #{e.message}")
94
+ machine.ui.warn("SSH config manager: I/O error accessing SSH config files.")
95
+ rescue => e
96
+ @logger.error("Error updating SSH config for #{machine.name}: #{e.message}")
97
+ @logger.debug("Backtrace: #{e.backtrace.join("\n")}")
98
+
99
+ # Don't fail the vagrant reload process, just warn
100
+ machine.ui.warn("SSH config manager encountered an error: #{e.message}")
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end