vagrant-ssh-config-manager 0.8.3 → 1.0.0.alpha

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.bundle/config +1 -1
  3. data/.gitignore +2 -1
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +62 -0
  6. data/Gemfile +11 -9
  7. data/README.md +2 -2
  8. data/Rakefile +10 -8
  9. data/TESTING.md +82 -0
  10. data/lib/vagrant/ssh/config/manager.rb +5 -0
  11. data/lib/vagrant_ssh_config_manager/action/destroy.rb +82 -0
  12. data/lib/vagrant_ssh_config_manager/action/halt.rb +66 -0
  13. data/lib/vagrant_ssh_config_manager/action/provision.rb +81 -0
  14. data/lib/vagrant_ssh_config_manager/action/reload.rb +105 -0
  15. data/lib/vagrant_ssh_config_manager/action/up.rb +98 -0
  16. data/lib/{vagrant-ssh-config-manager → vagrant_ssh_config_manager}/config.rb +45 -49
  17. data/lib/{vagrant-ssh-config-manager → vagrant_ssh_config_manager}/file_locker.rb +35 -37
  18. data/lib/{vagrant-ssh-config-manager → vagrant_ssh_config_manager}/file_manager.rb +90 -80
  19. data/lib/{vagrant-ssh-config-manager → vagrant_ssh_config_manager}/include_manager.rb +54 -53
  20. data/lib/{vagrant-ssh-config-manager → vagrant_ssh_config_manager}/plugin.rb +15 -13
  21. data/lib/vagrant_ssh_config_manager/ssh_config_manager.rb +1152 -0
  22. data/lib/{vagrant-ssh-config-manager → vagrant_ssh_config_manager}/ssh_info_extractor.rb +129 -141
  23. data/lib/vagrant_ssh_config_manager/version.rb +7 -0
  24. data/lib/{vagrant-ssh-config-manager.rb → vagrant_ssh_config_manager.rb} +15 -12
  25. data/test-all.sh +11 -0
  26. data/test-integration.sh +4 -0
  27. data/test-unit.sh +4 -0
  28. data/vagrant-ssh-config-manager.gemspec +25 -21
  29. metadata +28 -18
  30. data/lib/vagrant-ssh-config-manager/action/destroy.rb +0 -84
  31. data/lib/vagrant-ssh-config-manager/action/halt.rb +0 -68
  32. data/lib/vagrant-ssh-config-manager/action/provision.rb +0 -82
  33. data/lib/vagrant-ssh-config-manager/action/reload.rb +0 -106
  34. data/lib/vagrant-ssh-config-manager/action/up.rb +0 -99
  35. data/lib/vagrant-ssh-config-manager/ssh_config_manager.rb +0 -2150
  36. data/lib/vagrant-ssh-config-manager/version.rb +0 -5
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VagrantPlugins
4
+ module SshConfigManager
5
+ module Action
6
+ class Up
7
+ def initialize(app, env)
8
+ @app = app
9
+ @env = env
10
+ @logger = Log4r::Logger.new('vagrant::plugins::ssh_config_manager::action::up')
11
+ end
12
+
13
+ def call(env)
14
+ # Call the next middleware first
15
+ @app.call(env)
16
+
17
+ # Only proceed if the machine is running and SSH is ready
18
+ machine = env[:machine]
19
+ return unless machine
20
+ return unless machine.state.id == :running
21
+
22
+ # Check if plugin is enabled
23
+ config = machine.config.sshconfigmanager
24
+ return unless config&.enabled
25
+
26
+ @logger.info("SSH Config Manager: Creating SSH config file for machine: #{machine.name}")
27
+
28
+ # Handle SSH config file creation
29
+ handle_ssh_config_creation(machine, config)
30
+ rescue StandardError => e
31
+ @logger.error("SSH Config Manager: Error in Up action: #{e.message}")
32
+ @logger.debug("Backtrace: #{e.backtrace.join("\n")}")
33
+ end
34
+
35
+ private
36
+
37
+ def handle_ssh_config_creation(machine, config)
38
+ @logger.info("Creating SSH config file for machine: #{machine.name}")
39
+
40
+ # Lazy load required classes with error handling
41
+ begin
42
+ require 'vagrant_ssh_config_manager/ssh_info_extractor'
43
+ require 'vagrant_ssh_config_manager/file_manager'
44
+ require 'vagrant_ssh_config_manager/include_manager'
45
+ rescue LoadError => e
46
+ @logger.error("Failed to load required classes: #{e.message}")
47
+ machine.ui.warn('SSH config manager: Failed to load required components, skipping SSH config creation')
48
+ return
49
+ end
50
+
51
+ # Extract SSH information
52
+ extractor = SshInfoExtractor.new(machine)
53
+
54
+ # Check if machine supports SSH
55
+ unless extractor.ssh_capable?
56
+ @logger.debug("Machine #{machine.name} does not support SSH, skipping")
57
+ return
58
+ end
59
+
60
+ # Create file manager and include manager
61
+ file_manager = FileManager.new(config)
62
+ include_manager = IncludeManager.new(config)
63
+
64
+ # Write SSH config file
65
+ @logger.info("Attempting to create SSH config file for #{machine.name}")
66
+
67
+ if file_manager.write_ssh_config_file(machine)
68
+ host_name = file_manager.send(:generate_host_name, machine)
69
+ machine.ui.info("SSH config file created for machine '#{machine.name}' as '#{host_name}'")
70
+ machine.ui.info("You can now connect with: ssh #{host_name}")
71
+ @logger.info("Successfully created SSH config file for #{machine.name}")
72
+
73
+ # Manage Include directive after file creation
74
+ include_manager.manage_include_directive
75
+ else
76
+ machine.ui.warn("Failed to create SSH config file for machine: #{machine.name}")
77
+ @logger.warn("Failed to create SSH config file for #{machine.name}")
78
+ end
79
+ rescue Errno::EACCES => e
80
+ @logger.error("Permission denied accessing SSH config for #{machine.name}: #{e.message}")
81
+ machine.ui.warn('SSH config manager: Permission denied. Check file permissions.')
82
+ rescue Errno::ENOSPC => e
83
+ @logger.error("No space left on device for #{machine.name}: #{e.message}")
84
+ machine.ui.warn('SSH config manager: No space left on device.')
85
+ rescue Errno::EIO => e
86
+ @logger.error("I/O error for #{machine.name}: #{e.message}")
87
+ machine.ui.warn('SSH config manager: I/O error accessing SSH config files.')
88
+ rescue StandardError => e
89
+ @logger.error("Error creating SSH config for #{machine.name}: #{e.message}")
90
+ @logger.debug("Backtrace: #{e.backtrace.join("\n")}")
91
+
92
+ # Don't fail the vagrant up process, just warn
93
+ machine.ui.warn("SSH config manager encountered an error: #{e.message}")
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,26 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
  require 'vagrant'
3
5
 
4
6
  module VagrantPlugins
5
7
  module SshConfigManager
6
- class Config < Vagrant.plugin("2", :config)
8
+ # Configuration for SSH Config Manager
9
+ class Config < Vagrant.plugin('2', :config)
7
10
  # Plugin enabled/disabled flag
8
11
  attr_accessor :enabled
9
12
 
10
13
  # SSH config directory configuration
11
14
  attr_accessor :ssh_config_dir
12
- attr_accessor :manage_includes
13
- attr_accessor :auto_create_dir
14
- attr_accessor :cleanup_empty_dir
15
+ attr_accessor :manage_includes, :auto_create_dir, :cleanup_empty_dir, :update_on_reload, :refresh_on_provision,
16
+ :keep_config_on_halt, :project_isolation
15
17
 
16
18
  # Additional configuration options
17
19
  attr_accessor :auto_remove_on_destroy
18
- attr_accessor :update_on_reload
19
- attr_accessor :refresh_on_provision
20
- attr_accessor :keep_config_on_halt
21
- attr_accessor :project_isolation
22
20
 
21
+ # Set initial state, call parent constructor
23
22
  def initialize
23
+ super
24
24
  @enabled = Vagrant::Plugin::V2::Config::UNSET_VALUE
25
25
  @ssh_config_dir = Vagrant::Plugin::V2::Config::UNSET_VALUE
26
26
  @manage_includes = Vagrant::Plugin::V2::Config::UNSET_VALUE
@@ -36,7 +36,9 @@ module VagrantPlugins
36
36
  def finalize!
37
37
  # Set default values for unset configuration options
38
38
  @enabled = true if @enabled == Vagrant::Plugin::V2::Config::UNSET_VALUE
39
- @ssh_config_dir = File.expand_path("~/.ssh/config.d/vagrant") if @ssh_config_dir == Vagrant::Plugin::V2::Config::UNSET_VALUE
39
+ if @ssh_config_dir == Vagrant::Plugin::V2::Config::UNSET_VALUE
40
+ @ssh_config_dir = File.expand_path('~/.ssh/config.d/vagrant')
41
+ end
40
42
  @manage_includes = false if @manage_includes == Vagrant::Plugin::V2::Config::UNSET_VALUE
41
43
  @auto_create_dir = true if @auto_create_dir == Vagrant::Plugin::V2::Config::UNSET_VALUE
42
44
  @cleanup_empty_dir = true if @cleanup_empty_dir == Vagrant::Plugin::V2::Config::UNSET_VALUE
@@ -48,48 +50,44 @@ module VagrantPlugins
48
50
 
49
51
  # Expand and validate file paths
50
52
  @ssh_config_dir = File.expand_path(@ssh_config_dir) if @ssh_config_dir.is_a?(String)
51
-
53
+
52
54
  # Ensure SSH config directory exists if auto_create_dir is enabled
53
55
  ensure_ssh_config_directory if @auto_create_dir && @ssh_config_dir
54
56
  end
55
57
 
56
- def validate(machine)
58
+ def validate(_machine)
57
59
  errors = _detected_errors
58
60
 
59
61
  # Validate enabled flag
60
- unless [true, false].include?(@enabled)
61
- errors << "sshconfigmanager.enabled must be true or false"
62
- end
62
+ errors << 'sshconfigmanager.enabled must be true or false' unless [true, false].include?(@enabled)
63
63
 
64
64
  # Validate SSH config directory
65
65
  if @ssh_config_dir
66
- unless @ssh_config_dir.is_a?(String)
67
- errors << "sshconfigmanager.ssh_config_dir must be a string path"
68
- else
66
+ if @ssh_config_dir.is_a?(String)
69
67
  # Validate directory path format
70
68
  expanded_path = File.expand_path(@ssh_config_dir)
71
- if expanded_path.include?("..") || expanded_path.include?("//")
69
+ if expanded_path.include?('..') || expanded_path.include?('//')
72
70
  errors << "sshconfigmanager.ssh_config_dir contains invalid path components: #{@ssh_config_dir}"
73
71
  end
74
72
 
75
73
  # Check if the directory exists or can be created
76
- unless File.directory?(@ssh_config_dir)
77
- if @auto_create_dir
78
- begin
79
- # Try to create the directory to validate the path
80
- FileUtils.mkdir_p(@ssh_config_dir, mode: 0700)
81
- rescue => e
82
- errors << "sshconfigmanager.ssh_config_dir cannot be created: #{e.message}"
83
- end
84
- else
85
- errors << "sshconfigmanager.ssh_config_dir does not exist and auto_create_dir is disabled: #{@ssh_config_dir}"
86
- end
87
- else
74
+ if File.directory?(@ssh_config_dir)
88
75
  # Check directory permissions
89
76
  unless File.readable?(@ssh_config_dir) && File.writable?(@ssh_config_dir)
90
77
  errors << "sshconfigmanager.ssh_config_dir is not readable/writable: #{@ssh_config_dir}"
91
78
  end
79
+ elsif @auto_create_dir
80
+ begin
81
+ # Try to create the directory to validate the path
82
+ FileUtils.mkdir_p(@ssh_config_dir, mode: 0o700)
83
+ rescue StandardError => e
84
+ errors << "sshconfigmanager.ssh_config_dir cannot be created: #{e.message}"
85
+ end
86
+ else
87
+ errors << "sshconfigmanager.ssh_config_dir does not exist and auto_create_dir is disabled: #{@ssh_config_dir}"
92
88
  end
89
+ else
90
+ errors << 'sshconfigmanager.ssh_config_dir must be a string path'
93
91
  end
94
92
  end
95
93
 
@@ -106,13 +104,11 @@ module VagrantPlugins
106
104
  }
107
105
 
108
106
  boolean_options.each do |option_name, value|
109
- unless [true, false].include?(value)
110
- errors << "sshconfigmanager.#{option_name} must be true or false"
111
- end
107
+ errors << "sshconfigmanager.#{option_name} must be true or false" unless [true, false].include?(value)
112
108
  end
113
109
 
114
110
  # Return validation results
115
- { "SSH Config Manager" => errors }
111
+ { 'SSH Config Manager' => errors }
116
112
  end
117
113
 
118
114
  # Get configuration summary for debugging
@@ -156,16 +152,16 @@ module VagrantPlugins
156
152
  result = self.class.new
157
153
 
158
154
  # Merge each attribute, preferring the other config's values if set
159
- result.enabled = other.enabled != UNSET_VALUE ? other.enabled : @enabled
160
- result.ssh_config_dir = other.ssh_config_dir != UNSET_VALUE ? other.ssh_config_dir : @ssh_config_dir
161
- result.manage_includes = other.manage_includes != UNSET_VALUE ? other.manage_includes : @manage_includes
162
- result.auto_create_dir = other.auto_create_dir != UNSET_VALUE ? other.auto_create_dir : @auto_create_dir
163
- result.cleanup_empty_dir = other.cleanup_empty_dir != UNSET_VALUE ? other.cleanup_empty_dir : @cleanup_empty_dir
164
- result.auto_remove_on_destroy = other.auto_remove_on_destroy != UNSET_VALUE ? other.auto_remove_on_destroy : @auto_remove_on_destroy
165
- result.update_on_reload = other.update_on_reload != UNSET_VALUE ? other.update_on_reload : @update_on_reload
166
- result.refresh_on_provision = other.refresh_on_provision != UNSET_VALUE ? other.refresh_on_provision : @refresh_on_provision
167
- result.keep_config_on_halt = other.keep_config_on_halt != UNSET_VALUE ? other.keep_config_on_halt : @keep_config_on_halt
168
- result.project_isolation = other.project_isolation != UNSET_VALUE ? other.project_isolation : @project_isolation
155
+ result.enabled = other.enabled == UNSET_VALUE ? @enabled : other.enabled
156
+ result.ssh_config_dir = other.ssh_config_dir == UNSET_VALUE ? @ssh_config_dir : other.ssh_config_dir
157
+ result.manage_includes = other.manage_includes == UNSET_VALUE ? @manage_includes : other.manage_includes
158
+ result.auto_create_dir = other.auto_create_dir == UNSET_VALUE ? @auto_create_dir : other.auto_create_dir
159
+ result.cleanup_empty_dir = other.cleanup_empty_dir == UNSET_VALUE ? @cleanup_empty_dir : other.cleanup_empty_dir
160
+ result.auto_remove_on_destroy = other.auto_remove_on_destroy == UNSET_VALUE ? @auto_remove_on_destroy : other.auto_remove_on_destroy
161
+ result.update_on_reload = other.update_on_reload == UNSET_VALUE ? @update_on_reload : other.update_on_reload
162
+ result.refresh_on_provision = other.refresh_on_provision == UNSET_VALUE ? @refresh_on_provision : other.refresh_on_provision
163
+ result.keep_config_on_halt = other.keep_config_on_halt == UNSET_VALUE ? @keep_config_on_halt : other.keep_config_on_halt
164
+ result.project_isolation = other.project_isolation == UNSET_VALUE ? @project_isolation : other.project_isolation
169
165
 
170
166
  result
171
167
  end
@@ -176,19 +172,19 @@ module VagrantPlugins
176
172
  return true if File.directory?(@ssh_config_dir)
177
173
 
178
174
  begin
179
- FileUtils.mkdir_p(@ssh_config_dir, mode: 0700)
175
+ FileUtils.mkdir_p(@ssh_config_dir, mode: 0o700)
180
176
  true
181
- rescue => e
177
+ rescue StandardError
182
178
  false
183
179
  end
184
180
  end
185
181
 
186
- # Get the appropriate manager instance
187
- def get_ssh_manager_instance(machine)
188
- # Use separate file approach with FileManager
182
+ # Retrieve the SSH config manager for a given machine
183
+ def ssh_manager_instance(_machine)
189
184
  require_relative 'file_manager'
190
185
  FileManager.new(self)
191
186
  end
187
+ alias get_ssh_manager_instance ssh_manager_instance
192
188
  end
193
189
  end
194
190
  end
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fcntl'
2
4
  require 'timeout'
3
5
 
4
6
  module VagrantPlugins
5
7
  module SshConfigManager
8
+ # Handles file locking for SSH config files to prevent concurrent conflicts
6
9
  class FileLocker
7
10
  # Default timeout for acquiring locks (in seconds)
8
11
  DEFAULT_TIMEOUT = 30
@@ -14,29 +17,25 @@ module VagrantPlugins
14
17
 
15
18
  def initialize(file_path, logger = nil)
16
19
  @file_path = file_path
17
- @logger = logger || Log4r::Logger.new("vagrant::plugins::ssh_config_manager::file_locker")
20
+ @logger = logger || Log4r::Logger.new('vagrant::plugins::ssh_config_manager::file_locker')
18
21
  @lock_file = nil
19
22
  @locked = false
20
23
  end
21
24
 
22
25
  # Acquire an exclusive lock on the file
23
- def with_exclusive_lock(timeout: DEFAULT_TIMEOUT)
24
- with_lock(LOCK_EXCLUSIVE, timeout: timeout) do
25
- yield
26
- end
26
+ def with_exclusive_lock(timeout: DEFAULT_TIMEOUT, &block)
27
+ with_lock(LOCK_EXCLUSIVE, timeout: timeout, &block)
27
28
  end
28
29
 
29
30
  # Acquire a shared lock on the file
30
- def with_shared_lock(timeout: DEFAULT_TIMEOUT)
31
- with_lock(LOCK_SHARED, timeout: timeout) do
32
- yield
33
- end
31
+ def with_shared_lock(timeout: DEFAULT_TIMEOUT, &block)
32
+ with_lock(LOCK_SHARED, timeout: timeout, &block)
34
33
  end
35
34
 
36
35
  # Check if file is currently locked by another process
37
36
  def locked?
38
37
  return false unless File.exist?(@file_path)
39
-
38
+
40
39
  begin
41
40
  File.open(@file_path, 'r') do |file|
42
41
  # Try to acquire a non-blocking exclusive lock
@@ -45,7 +44,7 @@ module VagrantPlugins
45
44
  end
46
45
  rescue Errno::EAGAIN, Errno::EACCES
47
46
  true # File is locked
48
- rescue => e
47
+ rescue StandardError => e
49
48
  @logger.debug("Error checking lock status: #{e.message}")
50
49
  false
51
50
  end
@@ -55,7 +54,7 @@ module VagrantPlugins
55
54
 
56
55
  def with_lock(lock_type, timeout: DEFAULT_TIMEOUT)
57
56
  acquire_lock(lock_type, timeout: timeout)
58
-
57
+
59
58
  begin
60
59
  yield
61
60
  ensure
@@ -65,34 +64,33 @@ module VagrantPlugins
65
64
 
66
65
  def acquire_lock(lock_type, timeout: DEFAULT_TIMEOUT)
67
66
  ensure_directory_exists
68
-
67
+
69
68
  @logger.debug("Acquiring #{lock_type_name(lock_type)} lock on #{@file_path}")
70
-
69
+
71
70
  # Use timeout to prevent infinite waiting
72
71
  Timeout.timeout(timeout) do
73
- @lock_file = File.open(@file_path, File::RDWR | File::CREAT, 0600)
72
+ @lock_file = File.open(@file_path, File::RDWR | File::CREAT, 0o600)
74
73
  @lock_file.flock(lock_type)
75
74
  @locked = true
76
75
  @logger.debug("Successfully acquired lock on #{@file_path}")
77
76
  end
78
-
79
77
  rescue Timeout::Error
80
78
  cleanup_lock_file
81
- raise LockTimeoutError.new("Timeout waiting for lock on #{@file_path} (waited #{timeout}s)")
82
- rescue => e
79
+ raise LockTimeoutError, "Timeout waiting for lock on #{@file_path} (waited #{timeout}s)"
80
+ rescue StandardError => e
83
81
  cleanup_lock_file
84
82
  @logger.error("Failed to acquire lock on #{@file_path}: #{e.message}")
85
- raise LockAcquisitionError.new("Could not acquire lock: #{e.message}")
83
+ raise LockAcquisitionError, "Could not acquire lock: #{e.message}"
86
84
  end
87
85
 
88
86
  def release_lock
89
87
  return unless @locked && @lock_file
90
88
 
91
89
  @logger.debug("Releasing lock on #{@file_path}")
92
-
90
+
93
91
  begin
94
92
  @lock_file.flock(File::LOCK_UN)
95
- rescue => e
93
+ rescue StandardError => e
96
94
  @logger.warn("Error releasing lock: #{e.message}")
97
95
  ensure
98
96
  cleanup_lock_file
@@ -100,34 +98,34 @@ module VagrantPlugins
100
98
  end
101
99
 
102
100
  def cleanup_lock_file
103
- if @lock_file
104
- begin
105
- @lock_file.close unless @lock_file.closed?
106
- rescue => e
107
- @logger.debug("Error closing lock file: #{e.message}")
108
- ensure
109
- @lock_file = nil
110
- @locked = false
111
- end
101
+ return unless @lock_file
102
+
103
+ begin
104
+ @lock_file.close unless @lock_file.closed?
105
+ rescue StandardError => e
106
+ @logger.debug("Error closing lock file: #{e.message}")
107
+ ensure
108
+ @lock_file = nil
109
+ @locked = false
112
110
  end
113
111
  end
114
112
 
115
113
  def ensure_directory_exists
116
114
  dir = File.dirname(@file_path)
117
- unless File.directory?(dir)
118
- FileUtils.mkdir_p(dir, mode: 0700)
119
- @logger.debug("Created directory: #{dir}")
120
- end
115
+ return if File.directory?(dir)
116
+
117
+ FileUtils.mkdir_p(dir, mode: 0o700)
118
+ @logger.debug("Created directory: #{dir}")
121
119
  end
122
120
 
123
121
  def lock_type_name(lock_type)
124
122
  case lock_type
125
123
  when LOCK_SHARED
126
- "shared"
124
+ 'shared'
127
125
  when LOCK_EXCLUSIVE
128
- "exclusive"
126
+ 'exclusive'
129
127
  else
130
- "unknown"
128
+ 'unknown'
131
129
  end
132
130
  end
133
131
  end